refactor(types): move loaded manifest from BottleSpec to BottlePlan
test / integration (pull_request) Successful in 21s
test / unit (pull_request) Successful in 49s
lint / lint (push) Successful in 2m15s
test / unit (push) Successful in 56s
test / integration (push) Successful in 27s
Update Quality Badges / update-badges (push) Successful in 2m37s
test / integration (pull_request) Successful in 21s
test / unit (pull_request) Successful in 49s
lint / lint (push) Successful in 2m15s
test / unit (push) Successful in 56s
test / integration (push) Successful in 27s
Update Quality Badges / update-badges (push) Successful in 2m37s
BottleSpec.manifest was ManifestIndex | Manifest — a union encoding two lifecycle stages in one field. The union was unjustifiable: it forced a type-narrowing workaround (loaded_manifest property) on every consumer. Clean split: - BottleSpec.manifest: ManifestIndex (always; CLI-supplied intent) - BottlePlan.manifest: Manifest (always; loaded by _validate()) _validate() returns the loaded Manifest directly. prepare() passes it to _resolve_plan(), which stores it on the plan. All provisioner code now reads plan.manifest.agent / plan.manifest.bottle — no union, no asserts, no type: ignore.
This commit was merged in pull request #239.
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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 <identity>`) 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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"},
|
||||
|
||||
@@ -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={},
|
||||
|
||||
@@ -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={},
|
||||
|
||||
@@ -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={},
|
||||
|
||||
@@ -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({
|
||||
_INDEX = ManifestIndex.from_json_obj({
|
||||
"bottles": {"dev": {}},
|
||||
"agents": {"demo": {"skills": [], "prompt": "", "bottle": "dev"}},
|
||||
}).load_for_agent("demo")
|
||||
})
|
||||
|
||||
|
||||
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",
|
||||
|
||||
@@ -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={},
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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({
|
||||
_INDEX = ManifestIndex.from_json_obj({
|
||||
"bottles": {"dev": {}},
|
||||
"agents": {"demo": {"skills": [], "prompt": "", "bottle": "dev"}},
|
||||
}).load_for_agent("demo")
|
||||
})
|
||||
|
||||
|
||||
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.
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user