From a981003a458f83cb8e58d836c7d8734cdb0ac6e3 Mon Sep 17 00:00:00 2001 From: claude Date: Mon, 8 Jun 2026 19:23:19 +0000 Subject: [PATCH] refactor: make AgentProvisionPlan the source of truth for instance_name, prompt_file, image, dockerfile, guest_home MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drop the parallel fields passed through prepare() → _resolve_plan and read everything from agent_provision instead. The provider plugin now declares its own guest_home (so the backend stops hardcoding "/home/node") and the wrapper that builds the provision plan accepts instance_name and prompt_file, which providers store on the plan. DockerBottlePlan and SmolmachinesBottlePlan expose container_name / machine_name, image / agent_image, dockerfile_path / agent_dockerfile_path, and prompt_file as properties that delegate to agent_provision so existing call sites keep working unchanged. Co-Authored-By: Claude Sonnet 4.6 --- bot_bottle/agent_provider.py | 19 +++++-- bot_bottle/backend/__init__.py | 17 ++---- bot_bottle/backend/docker/backend.py | 8 --- bot_bottle/backend/docker/bottle_plan.py | 27 +++++++--- bot_bottle/backend/docker/resolve_plan.py | 9 ---- bot_bottle/backend/smolmachines/backend.py | 8 --- .../backend/smolmachines/bottle_plan.py | 54 ++++++++++--------- .../backend/smolmachines/resolve_plan.py | 7 --- bot_bottle/contrib/claude/agent_provider.py | 6 ++- bot_bottle/contrib/codex/agent_provider.py | 6 ++- tests/unit/test_agent_provider.py | 30 +++++++---- tests/unit/test_compose.py | 8 +-- tests/unit/test_contrib_claude_provider.py | 25 ++++++--- tests/unit/test_contrib_codex_provider.py | 25 ++++++--- tests/unit/test_docker_launch_teardown.py | 8 ++- tests/unit/test_docker_provision_git_user.py | 6 +-- tests/unit/test_plan_print_parity.py | 17 +++--- tests/unit/test_smolmachines_provision.py | 9 ++-- 18 files changed, 152 insertions(+), 137 deletions(-) diff --git a/bot_bottle/agent_provider.py b/bot_bottle/agent_provider.py index b955618..17f1996 100644 --- a/bot_bottle/agent_provider.py +++ b/bot_bottle/agent_provider.py @@ -104,6 +104,8 @@ class AgentProvisionPlan: image: str dockerfile: str guest_home: str + instance_name: str + prompt_file: Path guest_env: dict[str, str] env_vars: dict[str, str] = field(default_factory=dict) dirs: tuple[AgentProvisionDir, ...] = () @@ -128,6 +130,14 @@ class AgentProvider(ABC): """The static command / image / prompt-mode table for this template.""" + @property + def guest_home(self) -> str: + """In-guest home directory for the agent user. Defaults to + `/home/node` to match the Debian-based bot-bottle-* images + (USER node). Override for plugins whose image runs as a + different user.""" + return "/home/node" + @property def dockerfile(self) -> Path: """Path to the provider's Dockerfile. @@ -143,7 +153,8 @@ class AgentProvider(ABC): *, dockerfile: str, state_dir: Path, - guest_home: str, + instance_name: str, + prompt_file: Path, guest_env: dict[str, str] | None = None, auth_token: str = "", forward_host_credentials: bool = False, @@ -333,7 +344,8 @@ def build_agent_provision_plan( template: str, dockerfile: str, state_dir: Path, - guest_home: str, + instance_name: str, + prompt_file: Path, guest_env: dict[str, str] | None = None, auth_token: str = "", forward_host_credentials: bool = False, @@ -347,7 +359,8 @@ def build_agent_provision_plan( return get_provider(template).provision_plan( dockerfile=dockerfile, state_dir=state_dir, - guest_home=guest_home, + instance_name=instance_name, + prompt_file=prompt_file, guest_env=guest_env, auth_token=auth_token, forward_host_credentials=forward_host_credentials, diff --git a/bot_bottle/backend/__init__.py b/bot_bottle/backend/__init__.py index 8c50193..2915360 100644 --- a/bot_bottle/backend/__init__.py +++ b/bot_bottle/backend/__init__.py @@ -292,7 +292,6 @@ class BottleBackend(ABC, Generic[PlanT, CleanupT]): manifest_bottle = manifest.bottle_for(spec.agent_name) manfiest_agent_provider = manifest_bottle.agent_provider agent_provider = get_provider(manfiest_agent_provider.template) - agent_image = agent_provider.runtime.image resolved_env = resolve_env(manifest, spec.agent_name) slug = mint_slug(spec) @@ -307,7 +306,6 @@ class BottleBackend(ABC, Generic[PlanT, CleanupT]): ) else: agent_dockerfile_path = str(agent_provider.dockerfile) - instance_name = f"bot-bottle-{slug}" agent_dir, prompt_file = prepare_agent_state_dir(slug, spec) @@ -315,7 +313,8 @@ class BottleBackend(ABC, Generic[PlanT, CleanupT]): template=manfiest_agent_provider.template, dockerfile=agent_dockerfile_path, state_dir=agent_dir, - guest_home="/home/node", # FIXME: should be coming from the agent plan + instance_name=f"bot-bottle-{slug}", + prompt_file=prompt_file, guest_env=self._build_guest_env(resolved_env), forward_host_credentials=manfiest_agent_provider.forward_host_credentials, auth_token=manfiest_agent_provider.auth_token, @@ -333,10 +332,6 @@ class BottleBackend(ABC, Generic[PlanT, CleanupT]): spec, slug=slug, resolved_env=resolved_env, - instance_name=instance_name, # FIXME: move to agent provision plan - agent_image=agent_image, # FIXME: move to agent provision plan - prompt_file=prompt_file, # FIXME: move to agent provision plan - agent_dockerfile_path=agent_dockerfile_path, # FIXME: move to agent provision plan agent_provision_plan=agent_provision_plan, egress_plan=egress_plan, supervise_plan=supervise_plan, @@ -408,18 +403,16 @@ class BottleBackend(ABC, Generic[PlanT, CleanupT]): *, slug: str, resolved_env: ResolvedEnv, - instance_name: str, - agent_image: str, - prompt_file: Path, agent_provision_plan: AgentProvisionPlan, - agent_dockerfile_path: str, egress_plan: EgressPlan, git_gate_plan: GitGatePlan, supervise_plan: SupervisePlan | None, stage_dir: Path) -> PlanT: """Backend-specific plan resolution: image/container names, env-file, prompt-file, proxy plan, runtime detection. Called by - `prepare` after `_validate` succeeds.""" + `prepare` after `_validate` succeeds. Instance name, image, + prompt file, Dockerfile path, and guest home all live on + `agent_provision_plan` — the source of truth.""" @abstractmethod def launch(self, plan: PlanT) -> AbstractContextManager[Bottle]: diff --git a/bot_bottle/backend/docker/backend.py b/bot_bottle/backend/docker/backend.py index 63160bf..f91e69f 100644 --- a/bot_bottle/backend/docker/backend.py +++ b/bot_bottle/backend/docker/backend.py @@ -54,11 +54,7 @@ class DockerBottleBackend(BottleBackend["DockerBottlePlan", "DockerBottleCleanup *, slug: str, resolved_env, - instance_name: str, - agent_image: str, - prompt_file: Path, agent_provision_plan, - agent_dockerfile_path: str, egress_plan, git_gate_plan, supervise_plan, @@ -68,10 +64,6 @@ class DockerBottleBackend(BottleBackend["DockerBottlePlan", "DockerBottleCleanup spec, slug=slug, resolved_env=resolved_env, - instance_name=instance_name, - agent_image=agent_image, - agent_dockerfile_path=agent_dockerfile_path, - prompt_file=prompt_file, agent_provision_plan=agent_provision_plan, egress_plan=egress_plan, supervise_plan=supervise_plan, diff --git a/bot_bottle/backend/docker/bottle_plan.py b/bot_bottle/backend/docker/bottle_plan.py index e8aa99f..7ebc917 100644 --- a/bot_bottle/backend/docker/bottle_plan.py +++ b/bot_bottle/backend/docker/bottle_plan.py @@ -22,21 +22,32 @@ class DockerBottlePlan(BottlePlan): `agent_provision` from BottlePlan.""" slug: str - container_name: str - image: str - # Absolute path to the Dockerfile that builds `image`. Empty means - # use the repo's default Dockerfile. Populated to a per-bottle - # state file (~/.bot-bottle/state//Dockerfile) after a - # capability-block remediation (PRD 0016). - dockerfile_path: str # name -> value for vars forwarded into the docker-run child process # via subprocess env (so values never land on argv or in a file). # repr=False keeps secret/interpolated/OAuth values out of any # accidental log of the plan dataclass. forwarded_env: dict[str, str] = field(repr=False) - prompt_file: Path use_runsc: bool + @property + def container_name(self) -> str: + return self.agent_provision.instance_name + + @property + def image(self) -> str: + return self.agent_provision.image + + @property + def dockerfile_path(self) -> str: + """Absolute path to the Dockerfile that builds `image`. Sourced + from the agent provision plan — the manifest may override per + bottle; otherwise the provider plugin's bundled Dockerfile.""" + return self.agent_provision.dockerfile + + @property + def prompt_file(self) -> Path: + return self.agent_provision.prompt_file + @property def agent_command(self) -> str: return self.agent_provision.command diff --git a/bot_bottle/backend/docker/resolve_plan.py b/bot_bottle/backend/docker/resolve_plan.py index 88061d6..e464d5d 100644 --- a/bot_bottle/backend/docker/resolve_plan.py +++ b/bot_bottle/backend/docker/resolve_plan.py @@ -34,10 +34,6 @@ def resolve_plan( spec: BottleSpec, slug: str, resolved_env: ResolvedEnv, - instance_name: str, - agent_image: str, - agent_dockerfile_path: str, - prompt_file: Path, agent_provision_plan: AgentProvisionPlan, egress_plan: EgressPlan, supervise_plan: SupervisePlan, @@ -55,12 +51,7 @@ def resolve_plan( spec=spec, stage_dir=stage_dir, slug=slug, - container_name=instance_name, - # container_name_pinned=container_name_pinned, - image=agent_image, - dockerfile_path=agent_dockerfile_path, forwarded_env=dict(resolved_env.forwarded), - prompt_file=prompt_file, git_gate_plan=git_gate_plan, egress_plan=egress_plan, supervise_plan=supervise_plan, diff --git a/bot_bottle/backend/smolmachines/backend.py b/bot_bottle/backend/smolmachines/backend.py index 222a318..0c389e0 100644 --- a/bot_bottle/backend/smolmachines/backend.py +++ b/bot_bottle/backend/smolmachines/backend.py @@ -47,11 +47,7 @@ class SmolmachinesBottleBackend( *, slug: str, resolved_env, - instance_name: str, - agent_image: str, - prompt_file: Path, agent_provision_plan, - agent_dockerfile_path: str, egress_plan, git_gate_plan, supervise_plan, @@ -61,10 +57,6 @@ class SmolmachinesBottleBackend( spec, slug=slug, resolved_env=resolved_env, - instance_name=instance_name, - agent_image=agent_image, - agent_dockerfile_path=agent_dockerfile_path, - prompt_file=prompt_file, agent_provision_plan=agent_provision_plan, egress_plan=egress_plan, supervise_plan=supervise_plan, diff --git a/bot_bottle/backend/smolmachines/bottle_plan.py b/bot_bottle/backend/smolmachines/bottle_plan.py index c77a1ef..48d6a1f 100644 --- a/bot_bottle/backend/smolmachines/bottle_plan.py +++ b/bot_bottle/backend/smolmachines/bottle_plan.py @@ -29,27 +29,6 @@ class SmolmachinesBottlePlan(BottlePlan): bundle_subnet: str bundle_gateway: str bundle_ip: str - # smolvm machine name + agent image source. machine_create - # boots from a packed `.smolmachine` artifact (pre-baked at - # prepare time via `smolvm pack create`); using `--from` - # instead of `--image` avoids the registry-pull race we hit - # when machine_start tried to fetch on-demand and the libkrun - # agent's network attempt got refused by macOS. - # - # Chunk 2d ships with a public placeholder image (alpine) - # since bot-bottle-claude:latest lives in the operator's local - # docker daemon and smolvm's crane backend can't read from - # there; chunk 4 resolves the agent-image-conversion gap - # (push to a registry first, or smolvm grows a docker-daemon - # transport). - machine_name: str - # Agent image ref (docker tag). `launch` runs the - # build → save → registry push → smolvm pack pipeline against - # this and feeds the resulting `.smolmachine` artifact to - # `machine_create --from`. The pipeline runs at launch time - # (not prepare time) so the docker build output doesn't garble - # the dashboard's preflight modal. - agent_image: str # In-guest env vars (HTTPS_PROXY etc) — IP-literal URLs since # the guest has no DNS resolver inside the TSI allowlist. # Passed to `smolvm machine create` as `-e K=V` flags. @@ -57,11 +36,6 @@ class SmolmachinesBottlePlan(BottlePlan): # `--smolfile` is mutually exclusive with `--from`, and # `--from` is the path that avoids the registry-pull race). guest_env: dict[str, str] - # Path to the agent's prompt file on the host. Always written - # (mode 0o600) so the in-VM path always exists; the file is - # empty when the agent has no prompt — claude-code reads it - # via --append-system-prompt-file only when non-empty. - prompt_file: Path # Inner Plans for the sidecar bundle daemons. The same shape the # docker backend uses — same `.prepare()` calls produced # them — but our launch step doesn't populate the @@ -82,6 +56,34 @@ class SmolmachinesBottlePlan(BottlePlan): agent_git_gate_host: str = "" agent_supervise_url: str = "" + @property + def machine_name(self) -> str: + """smolvm machine name. `machine_create` boots from a packed + `.smolmachine` artifact (pre-baked at prepare time via + `smolvm pack create`); using `--from` instead of `--image` + avoids the registry-pull race we hit when machine_start tried + to fetch on-demand and the libkrun agent's network attempt + got refused by macOS.""" + return self.agent_provision.instance_name + + @property + def agent_image(self) -> str: + """Agent image ref (docker tag). `launch` runs the + build → save → registry push → smolvm pack pipeline against + this and feeds the resulting `.smolmachine` artifact to + `machine_create --from`. The pipeline runs at launch time + (not prepare time) so the docker build output doesn't garble + the dashboard's preflight modal.""" + return self.agent_provision.image + + @property + def prompt_file(self) -> Path: + """Path to the agent's prompt file on the host. Always written + (mode 0o600) so the in-VM path always exists; the file is + empty when the agent has no prompt — claude-code reads it + via --append-system-prompt-file only when non-empty.""" + return self.agent_provision.prompt_file + @property def git_gate_insteadof_host(self) -> str: return self.agent_git_gate_host diff --git a/bot_bottle/backend/smolmachines/resolve_plan.py b/bot_bottle/backend/smolmachines/resolve_plan.py index 23a9b83..1d881e6 100644 --- a/bot_bottle/backend/smolmachines/resolve_plan.py +++ b/bot_bottle/backend/smolmachines/resolve_plan.py @@ -51,10 +51,6 @@ def resolve_plan( spec: BottleSpec, slug: str, resolved_env: ResolvedEnv, - instance_name: str, - agent_image: str, - agent_dockerfile_path: str, - prompt_file: Path, agent_provision_plan: AgentProvisionPlan, egress_plan: EgressPlan, supervise_plan: SupervisePlan, @@ -79,10 +75,7 @@ def resolve_plan( bundle_subnet=subnet, bundle_gateway=gateway, bundle_ip=bundle_ip, - machine_name=instance_name, - agent_image=agent_image, guest_env=agent_provision_plan.guest_env, - prompt_file=prompt_file, git_gate_plan=git_gate_plan, egress_plan=egress_plan, supervise_plan=supervise_plan, diff --git a/bot_bottle/contrib/claude/agent_provider.py b/bot_bottle/contrib/claude/agent_provider.py index 9efc9db..1085176 100644 --- a/bot_bottle/contrib/claude/agent_provider.py +++ b/bot_bottle/contrib/claude/agent_provider.py @@ -59,7 +59,8 @@ class ClaudeAgentProvider(AgentProvider): *, dockerfile: str, state_dir: Path, - guest_home: str, + instance_name: str, + prompt_file: Path, guest_env: dict[str, str] | None = None, auth_token: str = "", forward_host_credentials: bool = False, @@ -70,6 +71,7 @@ class ClaudeAgentProvider(AgentProvider): ) -> AgentProvisionPlan: del forward_host_credentials, host_env # Codex-only knobs resolved_guest_env = dict(guest_env or {}) + guest_home = self.guest_home trusted_path = trusted_project_path or guest_home env_vars: dict[str, str] = { @@ -111,6 +113,8 @@ class ClaudeAgentProvider(AgentProvider): image=_RUNTIME.image, dockerfile=dockerfile, guest_home=guest_home, + instance_name=instance_name, + prompt_file=prompt_file, env_vars=env_vars, guest_env=resolved_guest_env, files=files, diff --git a/bot_bottle/contrib/codex/agent_provider.py b/bot_bottle/contrib/codex/agent_provider.py index fa9143e..4b2c99b 100644 --- a/bot_bottle/contrib/codex/agent_provider.py +++ b/bot_bottle/contrib/codex/agent_provider.py @@ -67,7 +67,8 @@ class CodexAgentProvider(AgentProvider): *, dockerfile: str, state_dir: Path, - guest_home: str, + instance_name: str, + prompt_file: Path, guest_env: dict[str, str] | None = None, auth_token: str = "", forward_host_credentials: bool = False, @@ -78,6 +79,7 @@ class CodexAgentProvider(AgentProvider): ) -> AgentProvisionPlan: del auth_token, label, color # Claude-only knobs resolved_guest_env = dict(guest_env or {}) + guest_home = self.guest_home trusted_path = trusted_project_path or guest_home env_vars: dict[str, str] = { @@ -148,6 +150,8 @@ class CodexAgentProvider(AgentProvider): image=_RUNTIME.image, dockerfile=dockerfile, guest_home=guest_home, + instance_name=instance_name, + prompt_file=prompt_file, env_vars=env_vars, guest_env=resolved_guest_env, dirs=tuple(dirs), diff --git a/tests/unit/test_agent_provider.py b/tests/unit/test_agent_provider.py index 16e307c..a23094b 100644 --- a/tests/unit/test_agent_provider.py +++ b/tests/unit/test_agent_provider.py @@ -26,10 +26,11 @@ class TestAgentProviderRuntime(unittest.TestCase): def test_codex_plan_declares_home_state(self): with tempfile.TemporaryDirectory(prefix="bb-provider.") as tmp: plan = build_agent_provision_plan( - guest_home="/home/node", template="codex", dockerfile="/tmp/Dockerfile.codex", state_dir=Path(tmp), + instance_name="bot-bottle-test", + prompt_file=Path(tmp) / "prompt.txt", ) config = Path(tmp, "codex-config.toml").read_text() self.assertEqual("codex", plan.template) @@ -51,10 +52,11 @@ class TestAgentProviderRuntime(unittest.TestCase): def test_codex_trusts_requested_project_path(self): with tempfile.TemporaryDirectory(prefix="bb-provider.") as tmp: build_agent_provision_plan( - guest_home="/home/node", template="codex", dockerfile="", state_dir=Path(tmp), + instance_name="bot-bottle-test", + prompt_file=Path(tmp) / "prompt.txt", trusted_project_path="/home/node/workspace", ) config = Path(tmp, "codex-config.toml").read_text() @@ -69,10 +71,11 @@ class TestAgentProviderRuntime(unittest.TestCase): "tokens": {"access_token": _jwt(2000000000)}, })) plan = build_agent_provision_plan( - guest_home="/home/node", template="codex", dockerfile="", state_dir=Path(tmp), + instance_name="bot-bottle-test", + prompt_file=Path(tmp) / "prompt.txt", guest_env={"CODEX_HOME": "/run/codex-home"}, forward_host_credentials=True, host_env={"CODEX_HOME": str(home)}, @@ -89,10 +92,11 @@ class TestAgentProviderRuntime(unittest.TestCase): def test_claude_with_auth_token_injects_provider_route_and_placeholder(self): with tempfile.TemporaryDirectory(prefix="bb-provider.") as tmp: plan = build_agent_provision_plan( - guest_home="/home/node", template="claude", dockerfile="/tmp/Dockerfile.claude", state_dir=Path(tmp), + instance_name="bot-bottle-test", + prompt_file=Path(tmp) / "prompt.txt", auth_token="BOT_BOTTLE_CLAUDE_OAUTH_TOKEN", ) claude_config = json.loads(Path(tmp, "claude.json").read_text()) @@ -111,10 +115,11 @@ class TestAgentProviderRuntime(unittest.TestCase): def test_claude_trusts_requested_project_path(self): with tempfile.TemporaryDirectory(prefix="bb-provider.") as tmp: build_agent_provision_plan( - guest_home="/home/node", template="claude", dockerfile="", state_dir=Path(tmp), + instance_name="bot-bottle-test", + prompt_file=Path(tmp) / "prompt.txt", trusted_project_path="/home/node/workspace", ) config = json.loads(Path(tmp, "claude.json").read_text()) @@ -130,10 +135,11 @@ class TestAgentProviderRuntime(unittest.TestCase): "tokens": {"access_token": _jwt(2000000000)}, })) plan = build_agent_provision_plan( - guest_home="/home/node", template="codex", dockerfile="", state_dir=Path(tmp), + instance_name="bot-bottle-test", + prompt_file=Path(tmp) / "prompt.txt", forward_host_credentials=True, host_env={"CODEX_HOME": str(home)}, ) @@ -146,10 +152,11 @@ class TestAgentProviderRuntime(unittest.TestCase): def test_codex_without_forward_host_credentials_has_passthrough_egress_routes(self): with tempfile.TemporaryDirectory(prefix="bb-provider.") as tmp: plan = build_agent_provision_plan( - guest_home="/home/node", template="codex", dockerfile="", state_dir=Path(tmp), + instance_name="bot-bottle-test", + prompt_file=Path(tmp) / "prompt.txt", forward_host_credentials=False, ) self.assertEqual( @@ -163,10 +170,11 @@ class TestAgentProviderRuntime(unittest.TestCase): def test_claude_without_auth_token_has_passthrough_egress_route(self): with tempfile.TemporaryDirectory(prefix="bb-provider.") as tmp: plan = build_agent_provision_plan( - guest_home="/home/node", template="claude", dockerfile="", state_dir=Path(tmp), + instance_name="bot-bottle-test", + prompt_file=Path(tmp) / "prompt.txt", ) self.assertEqual(1, len(plan.egress_routes)) route = plan.egress_routes[0] @@ -186,10 +194,11 @@ class TestAgentProviderRuntime(unittest.TestCase): "tokens": {"access_token": access}, })) plan = build_agent_provision_plan( - guest_home="/home/node", template="codex", dockerfile="", state_dir=Path(tmp), + instance_name="bot-bottle-test", + prompt_file=Path(tmp) / "prompt.txt", forward_host_credentials=True, host_env={"CODEX_HOME": str(home)}, ) @@ -201,10 +210,11 @@ class TestAgentProviderRuntime(unittest.TestCase): def test_codex_without_forward_host_credentials_has_empty_provisioned_env(self): with tempfile.TemporaryDirectory(prefix="bb-provider.") as tmp: plan = build_agent_provision_plan( - guest_home="/home/node", template="codex", dockerfile="", state_dir=Path(tmp), + instance_name="bot-bottle-test", + prompt_file=Path(tmp) / "prompt.txt", forward_host_credentials=False, ) self.assertEqual({}, plan.provisioned_env) diff --git a/tests/unit/test_compose.py b/tests/unit/test_compose.py index f797a99..c661c53 100644 --- a/tests/unit/test_compose.py +++ b/tests/unit/test_compose.py @@ -152,11 +152,7 @@ def _plan( spec=spec, stage_dir=STAGE, slug=SLUG, - container_name=f"bot-bottle-{SLUG}", - image="bot-bottle-claude:latest", - dockerfile_path="", forwarded_env={"CLAUDE_CODE_OAUTH_TOKEN": "x"}, - prompt_file=STAGE / "prompt", git_gate_plan=_git_gate_plan(upstreams), egress_plan=_egress_plan(routes), supervise_plan=_supervise_plan() if supervise else None, @@ -168,6 +164,8 @@ def _plan( image="bot-bottle-claude:latest", dockerfile="", guest_home="/home/node", + instance_name=f"bot-bottle-{SLUG}", + prompt_file=STAGE / "prompt", guest_env={}, ), ) @@ -248,6 +246,8 @@ class TestAgentAlwaysPresent(unittest.TestCase): image="bot-bottle-codex:latest", dockerfile="", guest_home="/home/node", + instance_name=f"bot-bottle-{SLUG}", + prompt_file=STAGE / "prompt", guest_env={"CODEX_HOME": "/home/node/.codex"}, ) plan = type(plan)(**{**vars(plan), "agent_provision": provision}) # type: ignore diff --git a/tests/unit/test_contrib_claude_provider.py b/tests/unit/test_contrib_claude_provider.py index 60b1f91..8e4788d 100644 --- a/tests/unit/test_contrib_claude_provider.py +++ b/tests/unit/test_contrib_claude_provider.py @@ -79,11 +79,7 @@ def _plan( spec=spec, stage_dir=Path("/tmp/stage"), slug="demo-abc12", - container_name="bot-bottle-demo-abc12", - image="bot-bottle-claude:latest", - dockerfile_path="", forwarded_env={}, - prompt_file=Path("/tmp/state/demo-abc12/agent/prompt.txt"), git_gate_plan=GitGatePlan( slug="demo-abc12", entrypoint_script=Path("/tmp/git-gate-entrypoint.sh"), @@ -101,7 +97,11 @@ def _plan( use_runsc=False, agent_provision=agent_provision or AgentProvisionPlan( template="claude", command="claude", prompt_mode="append_file", - image="", dockerfile="", guest_home="/home/node", guest_env={}, + image="bot-bottle-claude:latest", dockerfile="", + guest_home="/home/node", + instance_name="bot-bottle-demo-abc12", + prompt_file=Path("/tmp/state/demo-abc12/agent/prompt.txt"), + guest_env={}, ), ) @@ -205,7 +205,10 @@ class TestClaudeProvision(unittest.TestCase): def test_copies_files_and_chowns(self): provision = AgentProvisionPlan( template="claude", command="claude", prompt_mode="append_file", - image="", dockerfile="", guest_home="/home/node", guest_env={}, + image="", dockerfile="", guest_home="/home/node", + instance_name="bot-bottle-demo-abc12", + prompt_file=Path("/tmp/prompt.txt"), + guest_env={}, files=(AgentProvisionFile( Path("/tmp/claude.json"), "/home/node/.claude.json", ),), @@ -228,7 +231,10 @@ class TestClaudeProvision(unittest.TestCase): def test_dies_when_file_chown_fails(self): provision = AgentProvisionPlan( template="claude", command="claude", prompt_mode="append_file", - image="", dockerfile="", guest_home="/home/node", guest_env={}, + image="", dockerfile="", guest_home="/home/node", + instance_name="bot-bottle-demo-abc12", + prompt_file=Path("/tmp/prompt.txt"), + guest_env={}, files=(AgentProvisionFile( Path("/tmp/claude.json"), "/home/node/.claude.json", ),), @@ -244,7 +250,10 @@ class TestClaudeProvision(unittest.TestCase): def test_runs_verify_commands(self): provision = AgentProvisionPlan( template="claude", command="claude", prompt_mode="append_file", - image="", dockerfile="", guest_home="/home/node", guest_env={}, + image="", dockerfile="", guest_home="/home/node", + instance_name="bot-bottle-demo-abc12", + prompt_file=Path("/tmp/prompt.txt"), + guest_env={}, verify=(AgentProvisionCommand( ("/usr/bin/true",), "verify failed", ),), diff --git a/tests/unit/test_contrib_codex_provider.py b/tests/unit/test_contrib_codex_provider.py index 32274a1..ee77b1c 100644 --- a/tests/unit/test_contrib_codex_provider.py +++ b/tests/unit/test_contrib_codex_provider.py @@ -80,11 +80,7 @@ def _plan( spec=spec, stage_dir=Path("/tmp/stage"), slug="demo-abc12", - container_name="bot-bottle-demo-abc12", - image="bot-bottle-codex:latest", - dockerfile_path="", forwarded_env={}, - prompt_file=Path("/tmp/state/demo-abc12/agent/prompt.txt"), git_gate_plan=GitGatePlan( slug="demo-abc12", entrypoint_script=Path("/tmp/git-gate-entrypoint.sh"), @@ -102,7 +98,11 @@ def _plan( use_runsc=False, agent_provision=agent_provision or AgentProvisionPlan( template="codex", command="codex", prompt_mode="read_prompt_file", - image="", dockerfile="", guest_home="/home/node", guest_env={}, + image="bot-bottle-codex:latest", dockerfile="", + guest_home="/home/node", + instance_name="bot-bottle-demo-abc12", + prompt_file=Path("/tmp/state/demo-abc12/agent/prompt.txt"), + guest_env={}, ), ) @@ -171,7 +171,10 @@ class TestCodexProvision(unittest.TestCase): provision = AgentProvisionPlan( template="codex", command="codex", prompt_mode="read_prompt_file", - image="", dockerfile="", guest_home="/home/node", guest_env={}, + image="", dockerfile="", guest_home="/home/node", + instance_name="bot-bottle-demo-abc12", + prompt_file=Path("/tmp/prompt.txt"), + guest_env={}, dirs=(AgentProvisionDir("/home/node/.codex"),), files=(AgentProvisionFile( Path("/tmp/codex-config.toml"), @@ -195,7 +198,10 @@ class TestCodexProvision(unittest.TestCase): provision = AgentProvisionPlan( template="codex", command="codex", prompt_mode="read_prompt_file", - image="", dockerfile="", guest_home="/home/node", guest_env={}, + image="", dockerfile="", guest_home="/home/node", + instance_name="bot-bottle-demo-abc12", + prompt_file=Path("/tmp/prompt.txt"), + guest_env={}, pre_copy=(AgentProvisionCommand( ("find", "/home/node/.codex", "-name", "*.sqlite", "-delete"), "could not reset runtime db files", @@ -217,7 +223,10 @@ class TestCodexProvision(unittest.TestCase): provision = AgentProvisionPlan( template="codex", command="codex", prompt_mode="read_prompt_file", - image="", dockerfile="", guest_home="/home/node", guest_env={}, + image="", dockerfile="", guest_home="/home/node", + instance_name="bot-bottle-demo-abc12", + prompt_file=Path("/tmp/prompt.txt"), + guest_env={}, dirs=(AgentProvisionDir("/home/node/.codex"),), ) bottle = _make_bottle(exec_result=ExecResult(1, "", "mkdir: nope\n")) diff --git a/tests/unit/test_docker_launch_teardown.py b/tests/unit/test_docker_launch_teardown.py index f0c5993..cff5ccf 100644 --- a/tests/unit/test_docker_launch_teardown.py +++ b/tests/unit/test_docker_launch_teardown.py @@ -63,17 +63,15 @@ def _plan(tmp: str) -> DockerBottlePlan: template="claude", command="claude", prompt_mode="append_file", - image="", + image="bot-bottle-claude:latest", dockerfile="", guest_home="/home/node", + instance_name="bot-bottle-test-teardown-abc", + prompt_file=stage / "prompt.txt", guest_env={}, ), slug="test-teardown-00001", - container_name="bot-bottle-test-teardown-abc", - image="bot-bottle-claude:latest", - dockerfile_path="", forwarded_env={}, - prompt_file=stage / "prompt.txt", use_runsc=False, ) diff --git a/tests/unit/test_docker_provision_git_user.py b/tests/unit/test_docker_provision_git_user.py index 7459ea6..33d6917 100644 --- a/tests/unit/test_docker_provision_git_user.py +++ b/tests/unit/test_docker_provision_git_user.py @@ -64,11 +64,7 @@ def _plan(*, git_user: dict | None = None, # type: ignore spec=spec, stage_dir=stage_dir or Path("/tmp/stage"), slug="demo-abc12", - container_name="bot-bottle-demo-abc12", - image="bot-bottle-claude:latest", - dockerfile_path="", forwarded_env={}, - prompt_file=Path("/tmp/prompt.txt"), git_gate_plan=GitGatePlan( slug="demo-abc12", entrypoint_script=Path("/tmp/git-gate-entrypoint.sh"), @@ -91,6 +87,8 @@ def _plan(*, git_user: dict | None = None, # type: ignore image="bot-bottle-claude:latest", dockerfile="", guest_home="/home/node", + instance_name="bot-bottle-demo-abc12", + prompt_file=Path("/tmp/prompt.txt"), guest_env={}, ), ) diff --git a/tests/unit/test_plan_print_parity.py b/tests/unit/test_plan_print_parity.py index a77b316..9e033f9 100644 --- a/tests/unit/test_plan_print_parity.py +++ b/tests/unit/test_plan_print_parity.py @@ -79,14 +79,16 @@ def _egress_plan(tmp: str) -> EgressPlan: ) -def _agent_provision() -> AgentProvisionPlan: +def _agent_provision(tmp: str) -> AgentProvisionPlan: return AgentProvisionPlan( template="claude", command="claude", prompt_mode="append_file", - image="", + image="bot-bottle-claude:latest", dockerfile="", guest_home="/home/node", + instance_name="bot-bottle-test-00001", + prompt_file=Path(tmp) / "prompt.txt", guest_env={"HTTPS_PROXY": "http://127.0.0.1:9999"}, ) @@ -99,13 +101,9 @@ def _docker_plan(spec: BottleSpec, tmp: str) -> DockerBottlePlan: git_gate_plan=_git_gate_plan(tmp), egress_plan=_egress_plan(tmp), supervise_plan=None, - agent_provision=_agent_provision(), + agent_provision=_agent_provision(tmp), slug="test-00001", - container_name="bot-bottle-test-00001", - image="bot-bottle-claude:latest", - dockerfile_path="", forwarded_env={}, - prompt_file=stage / "prompt.txt", use_runsc=False, ) @@ -118,15 +116,12 @@ def _smolmachines_plan(spec: BottleSpec, tmp: str) -> SmolmachinesBottlePlan: git_gate_plan=_git_gate_plan(tmp), egress_plan=_egress_plan(tmp), supervise_plan=None, - agent_provision=_agent_provision(), + agent_provision=_agent_provision(tmp), slug="test-00001", bundle_subnet="10.99.0.0/24", bundle_gateway="10.99.0.1", bundle_ip="10.99.0.2", - machine_name="bot-bottle-test-00001", - agent_image="bot-bottle-claude:latest", guest_env={"HTTPS_PROXY": "http://127.0.0.1:9999"}, - prompt_file=stage / "prompt.txt", ) diff --git a/tests/unit/test_smolmachines_provision.py b/tests/unit/test_smolmachines_provision.py index d4ce810..6488579 100644 --- a/tests/unit/test_smolmachines_provision.py +++ b/tests/unit/test_smolmachines_provision.py @@ -146,10 +146,7 @@ def _plan( bundle_subnet="192.168.50.0/24", bundle_gateway="192.168.50.1", bundle_ip=bundle_ip, - machine_name="bot-bottle-demo-abc12", - agent_image="bot-bottle-claude:latest", guest_env=dict(guest_env or {}), - prompt_file=Path("/tmp/state/demo-abc12/agent/prompt.txt"), git_gate_plan=GitGatePlan( slug="demo-abc12", entrypoint_script=Path("/tmp/git-gate-entrypoint.sh"), @@ -186,9 +183,11 @@ def _agent_provision( template=template, command=template, prompt_mode="append_file", - image="", + image="bot-bottle-claude:latest", dockerfile="", guest_home="/home/node", + instance_name="bot-bottle-demo-abc12", + prompt_file=Path("/tmp/state/demo-abc12/agent/prompt.txt"), guest_env=dict(guest_env or {}), ) auth_dir = (guest_env or {}).get("CODEX_HOME", "/home/node/.codex") @@ -227,6 +226,8 @@ def _agent_provision( image="bot-bottle-codex:latest", dockerfile="", guest_home="/home/node", + instance_name="bot-bottle-demo-abc12", + prompt_file=Path("/tmp/state/demo-abc12/agent/prompt.txt"), guest_env=dict(guest_env or {}), dirs=(AgentProvisionDir(auth_dir),), files=tuple(files),