fix: persist workspace Python environments
This commit is contained in:
@@ -122,13 +122,21 @@ class DockerExecutionProvider:
|
||||
ref = workspace_ref(user_id)
|
||||
|
||||
def ensure():
|
||||
try:
|
||||
desired_image = self.client.images.get(self.settings.workspace_image)
|
||||
except ImageNotFound as exc:
|
||||
raise RuntimeError(f"Workspace image {self.settings.workspace_image!r} is not installed") from exc
|
||||
try:
|
||||
container = self.client.containers.get(ref.container_name)
|
||||
container.reload()
|
||||
configured_image = str(container.attrs.get("Config", {}).get("Image", ""))
|
||||
running_image_id = str(container.attrs.get("Image", ""))
|
||||
if configured_image != self.settings.workspace_image or running_image_id != desired_image.id:
|
||||
container.remove(force=True)
|
||||
container = None
|
||||
except NotFound:
|
||||
try:
|
||||
self.client.images.get(self.settings.workspace_image)
|
||||
except ImageNotFound as exc:
|
||||
raise RuntimeError(f"Workspace image {self.settings.workspace_image!r} is not installed") from exc
|
||||
container = None
|
||||
if container is None:
|
||||
if self.settings.workspace_network_enabled:
|
||||
try:
|
||||
self.client.networks.get(ref.network_name)
|
||||
@@ -153,6 +161,7 @@ class DockerExecutionProvider:
|
||||
labels={
|
||||
"app.k1412.component": "user-workspace",
|
||||
"app.k1412.workspace-id": ref.workspace_id,
|
||||
"app.k1412.workspace-image": self.settings.workspace_image,
|
||||
},
|
||||
detach=True,
|
||||
read_only=True,
|
||||
@@ -222,8 +231,20 @@ class DockerExecutionProvider:
|
||||
async def exec(self, user_id: str, command: str, cwd: str, timeout_seconds: int) -> ToolResult:
|
||||
normalize_workspace_path(cwd)
|
||||
bounded_timeout = max(1, min(timeout_seconds, 1800))
|
||||
shell = f"timeout --signal=TERM {bounded_timeout}s sh -lc {shlex.quote(command)}"
|
||||
return await self._exec_argv(user_id, ["sh", "-lc", shell], cwd=cwd)
|
||||
return await self._exec_argv(
|
||||
user_id,
|
||||
[
|
||||
"timeout",
|
||||
"--signal=TERM",
|
||||
f"{bounded_timeout}s",
|
||||
"bash",
|
||||
"-o",
|
||||
"pipefail",
|
||||
"-c",
|
||||
command,
|
||||
],
|
||||
cwd=cwd,
|
||||
)
|
||||
|
||||
async def list_files(self, user_id: str, path: str, max_depth: int, limit: int) -> ToolResult:
|
||||
absolute = absolute_workspace_path(path)
|
||||
@@ -233,7 +254,7 @@ class DockerExecutionProvider:
|
||||
"base_depth=root.rstrip('/').count('/')\n"
|
||||
"for current, dirs, files in os.walk(root):\n"
|
||||
" depth=current.rstrip('/').count('/')-base_depth\n"
|
||||
" dirs[:]=sorted(d for d in dirs if d not in {'.git','node_modules','.venv','__pycache__'})\n"
|
||||
" dirs[:]=sorted(d for d in dirs if d not in {'.agent','.git','node_modules','.venv','__pycache__'})\n"
|
||||
" if depth>=max_depth: dirs[:]=[]\n"
|
||||
" rel=os.path.relpath(current,'/workspace')\n"
|
||||
" for name in sorted(dirs): out.append((os.path.join(rel,name) if rel!='.' else name)+'/')\n"
|
||||
@@ -408,19 +429,19 @@ class DockerExecutionProvider:
|
||||
process_id = uuid.uuid4().hex
|
||||
process_dir = absolute_workspace_path(f".agent/processes/{process_id}")
|
||||
worker = (
|
||||
f"sh -lc {shlex.quote(command)}; "
|
||||
f"bash -o pipefail -c {shlex.quote(command)}; "
|
||||
"code=$?; "
|
||||
f'echo "$code" > {shlex.quote(process_dir + "/exit_code")}; '
|
||||
'exit "$code"'
|
||||
)
|
||||
wrapped = (
|
||||
f"mkdir -p {shlex.quote(process_dir)}; "
|
||||
f"setsid sh -lc {shlex.quote(worker)} "
|
||||
f"setsid bash -c {shlex.quote(worker)} "
|
||||
f"> {shlex.quote(process_dir + '/output.log')} 2>&1 & "
|
||||
f'pid=$!; echo "$pid" > {shlex.quote(process_dir + "/pid")}; '
|
||||
f"echo {shlex.quote(json.dumps({'process_id': process_id}))}"
|
||||
)
|
||||
result = await self._exec_argv(user_id, ["sh", "-lc", wrapped], cwd=cwd)
|
||||
result = await self._exec_argv(user_id, ["bash", "-o", "pipefail", "-c", wrapped], cwd=cwd)
|
||||
result.metadata = {"process_id": process_id}
|
||||
return result
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ class SearchFilesRequest(BaseModel):
|
||||
class ExecRequest(BaseModel):
|
||||
command: str = Field(min_length=1, max_length=32_000)
|
||||
cwd: str = Field(default=".", max_length=4096, description="Working directory relative to /workspace.")
|
||||
timeout_seconds: int = Field(default=120, ge=1, le=1800)
|
||||
timeout_seconds: int = Field(default=900, ge=1, le=1800)
|
||||
|
||||
|
||||
class ApplyPatchRequest(BaseModel):
|
||||
|
||||
Reference in New Issue
Block a user