0bf1532557
- Fix launch.py provision callable signature to accept Bottle not str - Rename _prompt_path to prompt_path to make it public (not protected) - Fix PromptMode type handling in bottle.py files - Update WorkspaceSpec protocol to use read-only properties for compatibility with frozen BottleSpec - Fix pty_resize signal handler type annotation - Update local_registry.py contextmanager return type to Generator (not Iterator) These changes fix ~130 pyright errors related to type safety. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
84 lines
2.6 KiB
Python
84 lines
2.6 KiB
Python
"""DockerBottle — concrete Bottle handle yielded by DockerBottleBackend."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import subprocess
|
|
from typing import Callable
|
|
|
|
from ...agent_provider import PromptMode, prompt_args
|
|
from .. import Bottle, ExecResult
|
|
|
|
|
|
class DockerBottle(Bottle):
|
|
"""Concrete Bottle for Docker."""
|
|
|
|
def __init__(
|
|
self,
|
|
container: str,
|
|
teardown: Callable[[], None],
|
|
prompt_path_in_container: str | None,
|
|
*,
|
|
agent_command: str = "claude",
|
|
agent_prompt_mode: PromptMode = "append_file",
|
|
):
|
|
self.name = container
|
|
self._teardown = teardown
|
|
self.prompt_path = prompt_path_in_container
|
|
self._agent_prompt_mode = agent_prompt_mode
|
|
self.agent_command = agent_command
|
|
self.agent_provider_template = (
|
|
"codex" if agent_command == "codex" else "claude"
|
|
)
|
|
self._closed = False
|
|
|
|
def agent_argv(
|
|
self, argv: list[str], *, tty: bool = True,
|
|
) -> list[str]:
|
|
full_argv = list(argv)
|
|
full_argv.extend(
|
|
prompt_args(self._agent_prompt_mode, self.prompt_path, argv=full_argv)
|
|
)
|
|
cmd = ["docker", "exec"]
|
|
if tty:
|
|
cmd.append("-it")
|
|
cmd.extend([self.name, self.agent_command, *full_argv])
|
|
return cmd
|
|
|
|
def exec_agent(self, argv: list[str], *, tty: bool = True) -> int:
|
|
return subprocess.run(
|
|
self.agent_argv(argv, tty=tty), check=False,
|
|
).returncode
|
|
|
|
def exec(self, script: str, *, user: str = "node") -> ExecResult:
|
|
# Pipe via stdin to `sh -s` so the caller never has to worry
|
|
# about quoting; the script source lands inside the container
|
|
# without crossing argv. `-u <user>` overrides the image's
|
|
# default USER — defaults to `node` which is already the
|
|
# image's USER, so the explicit flag is a no-op there but
|
|
# keeps the cross-backend contract uniform.
|
|
result = subprocess.run(
|
|
["docker", "exec", "-u", user, "-i", self.name, "sh", "-s"],
|
|
input=script,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
return ExecResult(
|
|
returncode=result.returncode,
|
|
stdout=result.stdout,
|
|
stderr=result.stderr,
|
|
)
|
|
|
|
def cp_in(self, host_path: str, container_path: str) -> None:
|
|
subprocess.run(
|
|
["docker", "cp", host_path, f"{self.name}:{container_path}"],
|
|
stdout=subprocess.DEVNULL,
|
|
check=True,
|
|
)
|
|
|
|
def close(self) -> None:
|
|
if self._closed:
|
|
return
|
|
self._closed = True
|
|
self._teardown()
|