diff --git a/bot_bottle/agent_provider.py b/bot_bottle/agent_provider.py index da03221..ca2a6ab 100644 --- a/bot_bottle/agent_provider.py +++ b/bot_bottle/agent_provider.py @@ -240,7 +240,7 @@ class AgentProvider(ABC): BottleBackend.provision_workspace against the running bottle.""" from .log import info - manifest_bottle = plan.spec.loaded_manifest.bottle + manifest_bottle = plan.manifest.bottle if manifest_bottle.git: from .git_gate import GIT_GATE_HOSTNAME, git_gate_render_gitconfig gate_host = getattr(plan, "git_gate_insteadof_host", GIT_GATE_HOSTNAME) diff --git a/bot_bottle/backend/__init__.py b/bot_bottle/backend/__init__.py index 6beffc0..36bbb37 100644 --- a/bot_bottle/backend/__init__.py +++ b/bot_bottle/backend/__init__.py @@ -37,7 +37,7 @@ import shlex import sys from abc import ABC, abstractmethod from contextlib import AbstractContextManager -from dataclasses import dataclass, replace +from dataclasses import dataclass from pathlib import Path from typing import Any, Generic, Sequence, TypeVar @@ -61,17 +61,10 @@ class BottleSpec: Resolved values (image names, container name, scratch paths, runsc availability) live on the plan, not the spec.""" - manifest: ManifestIndex | Manifest + manifest: ManifestIndex agent_name: str copy_cwd: bool user_cwd: str - - @property - def loaded_manifest(self) -> Manifest: - assert isinstance(self.manifest, Manifest), ( - "spec.manifest is still a ManifestIndex — call _validate() first" - ) - return self.manifest # PRD 0016 follow-up: when set, the backend's prepare step uses # this identity instead of minting a fresh one — the resume path # (`cli.py resume `) sets this to continue an existing @@ -87,6 +80,7 @@ class BottlePlan(ABC): (e.g. DockerBottlePlan) add backend-specific resolved fields.""" spec: BottleSpec + manifest: Manifest stage_dir: Path git_gate_plan: GitGatePlan @@ -119,7 +113,7 @@ class BottlePlan(ABC): """Render the y/N preflight summary to stderr.""" del remote_control spec = self.spec - manifest = spec.loaded_manifest + manifest = self.manifest agent = manifest.agent bottle = manifest.bottle @@ -296,11 +290,10 @@ class BottleBackend(ABC, Generic[PlanT, CleanupT]): write_launch_metadata, ) - spec = self._validate(spec) + manifest = self._validate(spec) self._preflight() - manifest = spec.loaded_manifest manifest_bottle = manifest.bottle manifest_agent_provider = manifest_bottle.agent_provider agent_provider = get_provider(manifest_agent_provider.template) @@ -320,7 +313,7 @@ class BottleBackend(ABC, Generic[PlanT, CleanupT]): else: agent_dockerfile_path = str(agent_provider.dockerfile) - agent_dir, prompt_file = prepare_agent_state_dir(slug, spec) + agent_dir, prompt_file = prepare_agent_state_dir(slug, manifest) agent_provision_plan = build_agent_provision_plan( template=manifest_agent_provider.template, @@ -344,6 +337,7 @@ class BottleBackend(ABC, Generic[PlanT, CleanupT]): return self._resolve_plan( spec, + manifest=manifest, slug=slug, resolved_env=resolved_env, agent_provision_plan=agent_provision_plan, @@ -362,24 +356,18 @@ class BottleBackend(ABC, Generic[PlanT, CleanupT]): """ pass - def _validate(self, spec: BottleSpec) -> BottleSpec: + def _validate(self, spec: BottleSpec) -> Manifest: """Cross-backend pre-launch checks. Parses the selected agent and its bottle (raising ManifestError on invalid content), confirms skills are present on the host, and every git IdentityFile resolves. - Returns a new BottleSpec whose manifest is fully loaded for the - selected agent. Subclasses with additional preconditions should - override and call `super()._validate(spec)` first, using the - returned spec for further checks.""" - assert isinstance(spec.manifest, ManifestIndex), ( - "_validate() called on a spec whose manifest is already loaded" - ) + Returns the loaded Manifest for the selected agent. Subclasses with + additional preconditions should override and call + `super()._validate(spec)` first.""" manifest = spec.manifest.load_for_agent(spec.agent_name) - spec = replace(spec, manifest=manifest) - agent = manifest.agent - self._validate_skills(agent.skills) - self._validate_agent_provider_dockerfile(spec) - return spec + self._validate_skills(manifest.agent.skills) + self._validate_agent_provider_dockerfile(spec, manifest) + return manifest def _validate_skills(self, skills: Sequence[str]) -> None: """Each named skill must be a directory under the host's @@ -393,8 +381,7 @@ class BottleBackend(ABC, Generic[PlanT, CleanupT]): f"Create it under ~/.claude/skills/, then re-run." ) - def _validate_agent_provider_dockerfile(self, spec: BottleSpec) -> None: - manifest = spec.loaded_manifest + def _validate_agent_provider_dockerfile(self, spec: BottleSpec, manifest: Manifest) -> None: bottle = manifest.bottle dockerfile = bottle.agent_provider.dockerfile if not dockerfile: @@ -412,6 +399,7 @@ class BottleBackend(ABC, Generic[PlanT, CleanupT]): def _resolve_plan(self, spec: BottleSpec, *, + manifest: Manifest, slug: str, resolved_env: ResolvedEnv, agent_provision_plan: AgentProvisionPlan, diff --git a/bot_bottle/backend/docker/backend.py b/bot_bottle/backend/docker/backend.py index 23c7d1e..b3b9e3f 100644 --- a/bot_bottle/backend/docker/backend.py +++ b/bot_bottle/backend/docker/backend.py @@ -30,6 +30,7 @@ from ...egress import EgressPlan from ...env import ResolvedEnv from ...git_gate import GitGatePlan from ...supervise import SupervisePlan +from ...manifest import Manifest from .. import ActiveAgent, BottleBackend, BottleSpec from . import cleanup as _cleanup from . import enumerate as _enumerate @@ -63,6 +64,7 @@ class DockerBottleBackend(BottleBackend["DockerBottlePlan", "DockerBottleCleanup self, spec: BottleSpec, *, + manifest: Manifest, slug: str, resolved_env: ResolvedEnv, agent_provision_plan: AgentProvisionPlan, @@ -73,6 +75,7 @@ class DockerBottleBackend(BottleBackend["DockerBottlePlan", "DockerBottleCleanup ) -> DockerBottlePlan: return _resolve_plan.resolve_plan( spec, + manifest=manifest, slug=slug, resolved_env=resolved_env, agent_provision_plan=agent_provision_plan, diff --git a/bot_bottle/backend/docker/launch.py b/bot_bottle/backend/docker/launch.py index 115db8c..4c1c081 100644 --- a/bot_bottle/backend/docker/launch.py +++ b/bot_bottle/backend/docker/launch.py @@ -75,7 +75,7 @@ def launch( Teardown on exit.""" stack = ExitStack() - _bottle_for_revoke = plan.spec.loaded_manifest.bottle + _bottle_for_revoke = plan.manifest.bottle _git_gate_dir_for_revoke = git_gate_state_dir(plan.slug) def teardown() -> None: diff --git a/bot_bottle/backend/docker/resolve_plan.py b/bot_bottle/backend/docker/resolve_plan.py index f07ecec..c48e3ac 100644 --- a/bot_bottle/backend/docker/resolve_plan.py +++ b/bot_bottle/backend/docker/resolve_plan.py @@ -18,6 +18,7 @@ from .. import BottleSpec from ...env import ResolvedEnv from ...agent_provider import AgentProvisionPlan from ...egress import EgressPlan +from ...manifest import Manifest from ...supervise import SupervisePlan from ...git_gate import GitGatePlan @@ -31,6 +32,7 @@ def build_guest_env(resolved_env: ResolvedEnv) -> dict[str, str]: def resolve_plan( spec: BottleSpec, + manifest: Manifest, slug: str, resolved_env: ResolvedEnv, agent_provision_plan: AgentProvisionPlan, @@ -48,6 +50,7 @@ def resolve_plan( return DockerBottlePlan( spec=spec, + manifest=manifest, stage_dir=stage_dir, slug=slug, forwarded_env=dict(resolved_env.forwarded), diff --git a/bot_bottle/backend/macos_container/backend.py b/bot_bottle/backend/macos_container/backend.py index 14a0496..bd43ed8 100644 --- a/bot_bottle/backend/macos_container/backend.py +++ b/bot_bottle/backend/macos_container/backend.py @@ -11,6 +11,7 @@ from ...egress import EgressPlan from ...env import ResolvedEnv from ...git_gate import GitGatePlan from ...supervise import SupervisePlan +from ...manifest import Manifest from .. import ActiveAgent, BottleBackend, BottleSpec from . import cleanup as _cleanup from . import enumerate as _enumerate @@ -45,6 +46,7 @@ class MacosContainerBottleBackend( self, spec: BottleSpec, *, + manifest: Manifest, slug: str, resolved_env: ResolvedEnv, agent_provision_plan: AgentProvisionPlan, @@ -55,6 +57,7 @@ class MacosContainerBottleBackend( ) -> MacosContainerBottlePlan: return _resolve_plan.resolve_plan( spec, + manifest=manifest, slug=slug, resolved_env=resolved_env, agent_provision_plan=agent_provision_plan, diff --git a/bot_bottle/backend/macos_container/launch.py b/bot_bottle/backend/macos_container/launch.py index fc072c5..fdc9378 100644 --- a/bot_bottle/backend/macos_container/launch.py +++ b/bot_bottle/backend/macos_container/launch.py @@ -68,7 +68,7 @@ def launch( ) -> Generator[MacosContainerBottle, None, None]: """Build, run, provision, and yield an Apple Container bottle.""" stack = ExitStack() - bottle_for_revoke = plan.spec.loaded_manifest.bottle + bottle_for_revoke = plan.manifest.bottle git_gate_dir_for_revoke = git_gate_state_dir(plan.slug) def teardown() -> None: diff --git a/bot_bottle/backend/macos_container/resolve_plan.py b/bot_bottle/backend/macos_container/resolve_plan.py index 021571b..9a9eb28 100644 --- a/bot_bottle/backend/macos_container/resolve_plan.py +++ b/bot_bottle/backend/macos_container/resolve_plan.py @@ -9,6 +9,7 @@ from ...egress import EgressPlan from ...env import ResolvedEnv from ...git_gate import GitGatePlan from ...supervise import SupervisePlan +from ...manifest import Manifest from .. import BottleSpec from . import util as container_mod from .bottle_plan import MacosContainerBottlePlan @@ -24,6 +25,7 @@ def build_guest_env(resolved_env: ResolvedEnv) -> dict[str, str]: def resolve_plan( spec: BottleSpec, + manifest: Manifest, slug: str, resolved_env: ResolvedEnv, agent_provision_plan: AgentProvisionPlan, @@ -34,6 +36,7 @@ def resolve_plan( ) -> MacosContainerBottlePlan: return MacosContainerBottlePlan( spec=spec, + manifest=manifest, stage_dir=stage_dir, slug=slug, forwarded_env=dict(resolved_env.forwarded), diff --git a/bot_bottle/backend/resolve_common.py b/bot_bottle/backend/resolve_common.py index 21b483d..cb9322f 100644 --- a/bot_bottle/backend/resolve_common.py +++ b/bot_bottle/backend/resolve_common.py @@ -26,7 +26,7 @@ from ..bottle_state import ( ) from ..egress import Egress, EgressPlan from ..git_gate import GitGate, GitGatePlan -from ..manifest import ManifestBottle +from ..manifest import Manifest, ManifestBottle from ..supervise import Supervise, SupervisePlan from . import BottleSpec @@ -66,10 +66,10 @@ def write_launch_metadata( )) -def prepare_agent_state_dir(slug: str, spec: BottleSpec) -> tuple[Path, Path]: +def prepare_agent_state_dir(slug: str, manifest: Manifest) -> tuple[Path, Path]: """Create the agent state subdir, write the prompt file. Returns (agent_dir, prompt_file).""" - agent = spec.loaded_manifest.agent + agent = manifest.agent agent_dir = agent_state_dir(slug) agent_dir.mkdir(parents=True, exist_ok=True) prompt_file = agent_dir / "prompt.txt" diff --git a/bot_bottle/backend/smolmachines/backend.py b/bot_bottle/backend/smolmachines/backend.py index 4cd7fef..f24be61 100644 --- a/bot_bottle/backend/smolmachines/backend.py +++ b/bot_bottle/backend/smolmachines/backend.py @@ -18,6 +18,7 @@ from ...egress import EgressPlan from ...env import ResolvedEnv from ...git_gate import GitGatePlan from ...supervise import SupervisePlan +from ...manifest import Manifest from .. import ActiveAgent, BottleBackend, BottleSpec from . import cleanup as _cleanup from . import enumerate as _enumerate @@ -55,6 +56,7 @@ class SmolmachinesBottleBackend( self, spec: BottleSpec, *, + manifest: Manifest, slug: str, resolved_env: ResolvedEnv, agent_provision_plan: AgentProvisionPlan, @@ -65,6 +67,7 @@ class SmolmachinesBottleBackend( ) -> SmolmachinesBottlePlan: return _resolve_plan.resolve_plan( spec, + manifest=manifest, slug=slug, resolved_env=resolved_env, agent_provision_plan=agent_provision_plan, diff --git a/bot_bottle/backend/smolmachines/launch.py b/bot_bottle/backend/smolmachines/launch.py index 2a28339..366f797 100644 --- a/bot_bottle/backend/smolmachines/launch.py +++ b/bot_bottle/backend/smolmachines/launch.py @@ -130,7 +130,7 @@ def _teardown_smolmachines( except BaseException as exc: # noqa: W0718 — teardown must not fail teardown_exc = exc warn(f"smolmachines teardown failed: {exc!r}") - bottle = plan.spec.loaded_manifest.bottle + bottle = plan.manifest.bottle revoke_git_gate_provisioned_keys(bottle, git_gate_state_dir(plan.slug)) if teardown_exc is not None: raise teardown_exc diff --git a/bot_bottle/backend/smolmachines/resolve_plan.py b/bot_bottle/backend/smolmachines/resolve_plan.py index e49eece..8d8dfcb 100644 --- a/bot_bottle/backend/smolmachines/resolve_plan.py +++ b/bot_bottle/backend/smolmachines/resolve_plan.py @@ -13,6 +13,7 @@ from __future__ import annotations from pathlib import Path from .. import BottleSpec +from ...manifest import Manifest from ...env import ResolvedEnv from ...agent_provider import AgentProvisionPlan from ...egress import EgressPlan @@ -46,6 +47,7 @@ def build_guest_env(resolved_env: ResolvedEnv) -> dict[str, str]: def resolve_plan( spec: BottleSpec, + manifest: Manifest, slug: str, resolved_env: ResolvedEnv, agent_provision_plan: AgentProvisionPlan, @@ -67,6 +69,7 @@ def resolve_plan( return SmolmachinesBottlePlan( spec=spec, + manifest=manifest, stage_dir=stage_dir, slug=slug, bundle_subnet=subnet, diff --git a/bot_bottle/contrib/claude/agent_provider.py b/bot_bottle/contrib/claude/agent_provider.py index 91733cd..198d1dd 100644 --- a/bot_bottle/contrib/claude/agent_provider.py +++ b/bot_bottle/contrib/claude/agent_provider.py @@ -211,7 +211,7 @@ class ClaudeAgentProvider(AgentProvider): when the agent has no skills.""" from ...backend.util import host_skill_dir - agent = plan.spec.loaded_manifest.agent + agent = plan.manifest.agent if not agent.skills: return skills_dir = _skills_dir(plan.guest_home) @@ -240,7 +240,7 @@ class ClaudeAgentProvider(AgentProvider): f"chown node:node {prompt_path} && chmod 600 {prompt_path}", user="root", ) - agent = plan.spec.loaded_manifest.agent + agent = plan.manifest.agent return prompt_path if plan.agent_provision.has_prompt or agent.prompt else None def provision(self, plan: "BottlePlan", bottle: "Bottle") -> None: diff --git a/bot_bottle/contrib/codex/agent_provider.py b/bot_bottle/contrib/codex/agent_provider.py index 997a30f..386c838 100644 --- a/bot_bottle/contrib/codex/agent_provider.py +++ b/bot_bottle/contrib/codex/agent_provider.py @@ -177,7 +177,7 @@ class CodexAgentProvider(AgentProvider): skills.""" from ...backend.util import host_skill_dir - agent = plan.spec.loaded_manifest.agent + agent = plan.manifest.agent if not agent.skills: return skills_dir = _skills_dir(plan.guest_home) @@ -206,7 +206,7 @@ class CodexAgentProvider(AgentProvider): f"chown node:node {prompt_path} && chmod 600 {prompt_path}", user="root", ) - agent = plan.spec.loaded_manifest.agent + agent = plan.manifest.agent return prompt_path if plan.agent_provision.has_prompt or agent.prompt else None def provision(self, plan: "BottlePlan", bottle: "Bottle") -> None: diff --git a/bot_bottle/contrib/pi/agent_provider.py b/bot_bottle/contrib/pi/agent_provider.py index 73b12c3..dd88913 100644 --- a/bot_bottle/contrib/pi/agent_provider.py +++ b/bot_bottle/contrib/pi/agent_provider.py @@ -232,7 +232,7 @@ class PiAgentProvider(AgentProvider): def provision_skills(self, plan: "BottlePlan", bottle: "Bottle") -> None: from ...backend.util import host_skill_dir - agent = plan.spec.loaded_manifest.agent + agent = plan.manifest.agent if not agent.skills: return skills_dir = _skills_dir(plan.guest_home) diff --git a/tests/unit/test_compose.py b/tests/unit/test_compose.py index dafe3ef..cad8840 100644 --- a/tests/unit/test_compose.py +++ b/tests/unit/test_compose.py @@ -67,16 +67,6 @@ def _manifest(*, supervise: bool, with_git: bool, with_egress: bool) -> Manifest }) -def _spec(*, supervise: bool, with_git: bool, with_egress: bool) -> BottleSpec: - return BottleSpec( - manifest=_manifest( - supervise=supervise, with_git=with_git, with_egress=with_egress, - ), - agent_name="demo", - copy_cwd=False, - user_cwd="/tmp/x", - ) - def _git_gate_plan(upstreams: tuple[GitGateUpstream, ...] = ()) -> GitGatePlan: return GitGatePlan( @@ -146,9 +136,16 @@ def _plan( roles=(), ),) - spec = _spec(supervise=supervise, with_git=with_git, with_egress=with_egress) + index = _manifest(supervise=supervise, with_git=with_git, with_egress=with_egress) + spec = BottleSpec( + manifest=index, + agent_name="demo", + copy_cwd=False, + user_cwd="/tmp/x", + ) return DockerBottlePlan( spec=spec, + manifest=index.load_for_agent("demo"), stage_dir=STAGE, slug=SLUG, forwarded_env={"CLAUDE_CODE_OAUTH_TOKEN": "x"}, diff --git a/tests/unit/test_contrib_claude_provider.py b/tests/unit/test_contrib_claude_provider.py index cb806e8..1f6bdbc 100644 --- a/tests/unit/test_contrib_claude_provider.py +++ b/tests/unit/test_contrib_claude_provider.py @@ -55,7 +55,7 @@ def _plan( bottle_json: dict = {"agent_provider": {"template": "claude"}} # type: ignore if supervise: bottle_json["supervise"] = True - manifest = ManifestIndex.from_json_obj({ + index = ManifestIndex.from_json_obj({ "bottles": {"dev": bottle_json}, "agents": { "demo": { @@ -64,9 +64,10 @@ def _plan( "bottle": "dev", }, }, - }).load_for_agent("demo") + }) + manifest = index.load_for_agent("demo") spec = BottleSpec( - manifest=manifest, agent_name="demo", + manifest=index, agent_name="demo", copy_cwd=False, user_cwd="/tmp/x", ) supervise_plan = None @@ -78,6 +79,7 @@ def _plan( ) return DockerBottlePlan( spec=spec, + manifest=manifest, stage_dir=Path("/tmp/stage"), slug="demo-abc12", forwarded_env={}, diff --git a/tests/unit/test_contrib_codex_provider.py b/tests/unit/test_contrib_codex_provider.py index 8ba5b97..0d678c2 100644 --- a/tests/unit/test_contrib_codex_provider.py +++ b/tests/unit/test_contrib_codex_provider.py @@ -55,7 +55,7 @@ def _plan( bottle_json: dict = {"agent_provider": {"template": "codex"}} # type: ignore if supervise: bottle_json["supervise"] = True - manifest = ManifestIndex.from_json_obj({ + index = ManifestIndex.from_json_obj({ "bottles": {"dev": bottle_json}, "agents": { "demo": { @@ -64,9 +64,10 @@ def _plan( "bottle": "dev", }, }, - }).load_for_agent("demo") + }) + manifest = index.load_for_agent("demo") spec = BottleSpec( - manifest=manifest, agent_name="demo", + manifest=index, agent_name="demo", copy_cwd=False, user_cwd="/tmp/x", ) supervise_plan = None @@ -78,6 +79,7 @@ def _plan( ) return DockerBottlePlan( spec=spec, + manifest=manifest, stage_dir=Path("/tmp/stage"), slug="demo-abc12", forwarded_env={}, diff --git a/tests/unit/test_contrib_pi_provider.py b/tests/unit/test_contrib_pi_provider.py index f9d83c2..0abe959 100644 --- a/tests/unit/test_contrib_pi_provider.py +++ b/tests/unit/test_contrib_pi_provider.py @@ -43,7 +43,7 @@ def _plan( skills: list[str] | None = None, agent_provision: AgentProvisionPlan | None = None, ) -> DockerBottlePlan: - manifest = ManifestIndex.from_json_obj({ + index = ManifestIndex.from_json_obj({ "bottles": {"dev": {"agent_provider": {"template": "pi"}}}, "agents": { "demo": { @@ -52,13 +52,15 @@ def _plan( "bottle": "dev", }, }, - }).load_for_agent("demo") + }) + manifest = index.load_for_agent("demo") spec = BottleSpec( - manifest=manifest, agent_name="demo", + manifest=index, agent_name="demo", copy_cwd=False, user_cwd="/tmp/x", ) return DockerBottlePlan( spec=spec, + manifest=manifest, stage_dir=Path("/tmp/stage"), slug="demo-abc12", forwarded_env={}, diff --git a/tests/unit/test_docker_launch_teardown.py b/tests/unit/test_docker_launch_teardown.py index 2a765c9..983bbc6 100644 --- a/tests/unit/test_docker_launch_teardown.py +++ b/tests/unit/test_docker_launch_teardown.py @@ -23,22 +23,17 @@ from bot_bottle.egress import EgressPlan from bot_bottle.git_gate import GitGatePlan from bot_bottle.manifest import ManifestIndex - -from bot_bottle.manifest import Manifest, ManifestIndex - - -def _manifest() -> Manifest: - return ManifestIndex.from_json_obj({ - "bottles": {"dev": {}}, - "agents": {"demo": {"skills": [], "prompt": "", "bottle": "dev"}}, - }).load_for_agent("demo") +_INDEX = ManifestIndex.from_json_obj({ + "bottles": {"dev": {}}, + "agents": {"demo": {"skills": [], "prompt": "", "bottle": "dev"}}, +}) def _plan(tmp: str) -> DockerBottlePlan: stage = Path(tmp) - manifest = _manifest() + manifest = _INDEX.load_for_agent("demo") spec = BottleSpec( - manifest=manifest, + manifest=_INDEX, agent_name="demo", copy_cwd=False, user_cwd=tmp, @@ -46,6 +41,7 @@ def _plan(tmp: str) -> DockerBottlePlan: ) return DockerBottlePlan( spec=spec, + manifest=manifest, stage_dir=stage, git_gate_plan=GitGatePlan( slug="test-teardown-00001", diff --git a/tests/unit/test_docker_provision_git_user.py b/tests/unit/test_docker_provision_git_user.py index 4c572df..2fb7466 100644 --- a/tests/unit/test_docker_provision_git_user.py +++ b/tests/unit/test_docker_provision_git_user.py @@ -51,16 +51,18 @@ def _plan(*, git_user: dict | None = None, # type: ignore bottle_json: dict = {} # type: ignore if git_user is not None: bottle_json["git-gate"] = {"user": git_user} - manifest = ManifestIndex.from_json_obj({ + index = ManifestIndex.from_json_obj({ "bottles": {"dev": bottle_json}, "agents": {"demo": {"skills": [], "prompt": "", "bottle": "dev"}}, - }).load_for_agent("demo") + }) + manifest = index.load_for_agent("demo") spec = BottleSpec( - manifest=manifest, agent_name="demo", + manifest=index, agent_name="demo", copy_cwd=copy_cwd, user_cwd=user_cwd, ) return DockerBottlePlan( spec=spec, + manifest=manifest, stage_dir=stage_dir or Path("/tmp/stage"), slug="demo-abc12", forwarded_env={}, diff --git a/tests/unit/test_macos_container_launch.py b/tests/unit/test_macos_container_launch.py index 948eadc..2fbb373 100644 --- a/tests/unit/test_macos_container_launch.py +++ b/tests/unit/test_macos_container_launch.py @@ -11,6 +11,12 @@ from unittest.mock import patch from bot_bottle.backend.macos_container import launch from bot_bottle.backend.macos_container.bottle_plan import MacosContainerBottlePlan +from bot_bottle.manifest import ManifestIndex + +_MANIFEST = ManifestIndex.from_json_obj({ + "bottles": {"dev": {}}, + "agents": {"demo": {"skills": [], "prompt": "", "bottle": "dev"}}, +}).load_for_agent("demo") def _plan( @@ -67,6 +73,7 @@ def _plan( ) return cast(MacosContainerBottlePlan, SimpleNamespace( spec=SimpleNamespace(), + manifest=_MANIFEST, stage_dir=stage_dir, slug="dev-abc", container_name="bot-bottle-dev-abc", @@ -193,6 +200,7 @@ class TestMacosContainerLaunchArgv(unittest.TestCase): ) plan = MacosContainerBottlePlan( spec=base.spec, + manifest=base.manifest, stage_dir=base.stage_dir, git_gate_plan=base.git_gate_plan, egress_plan=base.egress_plan, diff --git a/tests/unit/test_plan_print_parity.py b/tests/unit/test_plan_print_parity.py index f80b079..f8b5648 100644 --- a/tests/unit/test_plan_print_parity.py +++ b/tests/unit/test_plan_print_parity.py @@ -22,16 +22,15 @@ from bot_bottle.git_gate import GitGatePlan, GitGateUpstream from bot_bottle.manifest import Manifest, ManifestIndex -def _manifest() -> Manifest: - return ManifestIndex.from_json_obj({ - "bottles": {"dev": {}}, - "agents": {"demo": {"skills": [], "prompt": "", "bottle": "dev"}}, - }).load_for_agent("demo") +_INDEX = ManifestIndex.from_json_obj({ + "bottles": {"dev": {}}, + "agents": {"demo": {"skills": [], "prompt": "", "bottle": "dev"}}, +}) -def _spec(manifest: Manifest, tmp: str) -> BottleSpec: +def _spec(index: ManifestIndex, tmp: str) -> BottleSpec: return BottleSpec( - manifest=manifest, + manifest=index, agent_name="demo", copy_cwd=False, user_cwd=tmp, @@ -92,10 +91,11 @@ def _agent_provision(tmp: str) -> AgentProvisionPlan: ) -def _docker_plan(spec: BottleSpec, tmp: str) -> DockerBottlePlan: +def _docker_plan(spec: BottleSpec, manifest: Manifest, tmp: str) -> DockerBottlePlan: stage = Path(tmp) return DockerBottlePlan( spec=spec, + manifest=manifest, stage_dir=stage, git_gate_plan=_git_gate_plan(tmp), egress_plan=_egress_plan(tmp), @@ -107,10 +107,11 @@ def _docker_plan(spec: BottleSpec, tmp: str) -> DockerBottlePlan: ) -def _smolmachines_plan(spec: BottleSpec, tmp: str) -> SmolmachinesBottlePlan: +def _smolmachines_plan(spec: BottleSpec, manifest: Manifest, tmp: str) -> SmolmachinesBottlePlan: stage = Path(tmp) return SmolmachinesBottlePlan( spec=spec, + manifest=manifest, stage_dir=stage, git_gate_plan=_git_gate_plan(tmp), egress_plan=_egress_plan(tmp), @@ -140,10 +141,10 @@ class TestGitGatePrintParity(unittest.TestCase): def setUp(self) -> None: self._tmp = tempfile.mkdtemp(prefix="plan-print-parity-") - manifest = _manifest() - spec = _spec(manifest, self._tmp) - self._docker_lines = _capture_print(_docker_plan(spec, self._tmp)) - self._smol_lines = _capture_print(_smolmachines_plan(spec, self._tmp)) + manifest = _INDEX.load_for_agent("demo") + spec = _spec(_INDEX, self._tmp) + self._docker_lines = _capture_print(_docker_plan(spec, manifest, self._tmp)) + self._smol_lines = _capture_print(_smolmachines_plan(spec, manifest, self._tmp)) def _git_gate_lines(self, lines: list[str]) -> list[str]: return [ln for ln in lines if "git gate" in ln] @@ -170,10 +171,10 @@ class TestEgressPrintParity(unittest.TestCase): def setUp(self) -> None: self._tmp = tempfile.mkdtemp(prefix="plan-print-parity-") - manifest = _manifest() - spec = _spec(manifest, self._tmp) - self._docker_lines = _capture_print(_docker_plan(spec, self._tmp)) - self._smol_lines = _capture_print(_smolmachines_plan(spec, self._tmp)) + manifest = _INDEX.load_for_agent("demo") + spec = _spec(_INDEX, self._tmp) + self._docker_lines = _capture_print(_docker_plan(spec, manifest, self._tmp)) + self._smol_lines = _capture_print(_smolmachines_plan(spec, manifest, self._tmp)) def _egress_section(self, lines: list[str]) -> list[str]: """Return lines from the egress label through the last route entry. diff --git a/tests/unit/test_smolmachines_provision.py b/tests/unit/test_smolmachines_provision.py index 5864916..e3fbbfc 100644 --- a/tests/unit/test_smolmachines_provision.py +++ b/tests/unit/test_smolmachines_provision.py @@ -110,7 +110,7 @@ def _plan( bottle_json["git-gate"] = git_gate_json if supervise: bottle_json["supervise"] = True - manifest = ManifestIndex.from_json_obj({ + index = ManifestIndex.from_json_obj({ "bottles": {"dev": bottle_json}, "agents": { "demo": { @@ -119,9 +119,10 @@ def _plan( "bottle": "dev", }, }, - }).load_for_agent("demo") + }) + manifest = index.load_for_agent("demo") spec = BottleSpec( - manifest=manifest, + manifest=index, agent_name="demo", copy_cwd=copy_cwd, user_cwd=user_cwd, @@ -135,6 +136,7 @@ def _plan( ) return SmolmachinesBottlePlan( spec=spec, + manifest=manifest, stage_dir=stage_dir or Path("/tmp/stage"), slug="demo-abc12", bundle_subnet="192.168.50.0/24",