refactor: make AgentProvisionPlan the source of truth for instance_name, prompt_file, image, dockerfile, guest_home
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 <noreply@anthropic.com>
This commit is contained in:
@@ -104,6 +104,8 @@ class AgentProvisionPlan:
|
|||||||
image: str
|
image: str
|
||||||
dockerfile: str
|
dockerfile: str
|
||||||
guest_home: str
|
guest_home: str
|
||||||
|
instance_name: str
|
||||||
|
prompt_file: Path
|
||||||
guest_env: dict[str, str]
|
guest_env: dict[str, str]
|
||||||
env_vars: dict[str, str] = field(default_factory=dict)
|
env_vars: dict[str, str] = field(default_factory=dict)
|
||||||
dirs: tuple[AgentProvisionDir, ...] = ()
|
dirs: tuple[AgentProvisionDir, ...] = ()
|
||||||
@@ -128,6 +130,14 @@ class AgentProvider(ABC):
|
|||||||
"""The static command / image / prompt-mode table for this
|
"""The static command / image / prompt-mode table for this
|
||||||
template."""
|
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
|
@property
|
||||||
def dockerfile(self) -> Path:
|
def dockerfile(self) -> Path:
|
||||||
"""Path to the provider's Dockerfile.
|
"""Path to the provider's Dockerfile.
|
||||||
@@ -143,7 +153,8 @@ class AgentProvider(ABC):
|
|||||||
*,
|
*,
|
||||||
dockerfile: str,
|
dockerfile: str,
|
||||||
state_dir: Path,
|
state_dir: Path,
|
||||||
guest_home: str,
|
instance_name: str,
|
||||||
|
prompt_file: Path,
|
||||||
guest_env: dict[str, str] | None = None,
|
guest_env: dict[str, str] | None = None,
|
||||||
auth_token: str = "",
|
auth_token: str = "",
|
||||||
forward_host_credentials: bool = False,
|
forward_host_credentials: bool = False,
|
||||||
@@ -333,7 +344,8 @@ def build_agent_provision_plan(
|
|||||||
template: str,
|
template: str,
|
||||||
dockerfile: str,
|
dockerfile: str,
|
||||||
state_dir: Path,
|
state_dir: Path,
|
||||||
guest_home: str,
|
instance_name: str,
|
||||||
|
prompt_file: Path,
|
||||||
guest_env: dict[str, str] | None = None,
|
guest_env: dict[str, str] | None = None,
|
||||||
auth_token: str = "",
|
auth_token: str = "",
|
||||||
forward_host_credentials: bool = False,
|
forward_host_credentials: bool = False,
|
||||||
@@ -347,7 +359,8 @@ def build_agent_provision_plan(
|
|||||||
return get_provider(template).provision_plan(
|
return get_provider(template).provision_plan(
|
||||||
dockerfile=dockerfile,
|
dockerfile=dockerfile,
|
||||||
state_dir=state_dir,
|
state_dir=state_dir,
|
||||||
guest_home=guest_home,
|
instance_name=instance_name,
|
||||||
|
prompt_file=prompt_file,
|
||||||
guest_env=guest_env,
|
guest_env=guest_env,
|
||||||
auth_token=auth_token,
|
auth_token=auth_token,
|
||||||
forward_host_credentials=forward_host_credentials,
|
forward_host_credentials=forward_host_credentials,
|
||||||
|
|||||||
@@ -292,7 +292,6 @@ class BottleBackend(ABC, Generic[PlanT, CleanupT]):
|
|||||||
manifest_bottle = manifest.bottle_for(spec.agent_name)
|
manifest_bottle = manifest.bottle_for(spec.agent_name)
|
||||||
manfiest_agent_provider = manifest_bottle.agent_provider
|
manfiest_agent_provider = manifest_bottle.agent_provider
|
||||||
agent_provider = get_provider(manfiest_agent_provider.template)
|
agent_provider = get_provider(manfiest_agent_provider.template)
|
||||||
agent_image = agent_provider.runtime.image
|
|
||||||
resolved_env = resolve_env(manifest, spec.agent_name)
|
resolved_env = resolve_env(manifest, spec.agent_name)
|
||||||
|
|
||||||
slug = mint_slug(spec)
|
slug = mint_slug(spec)
|
||||||
@@ -307,7 +306,6 @@ class BottleBackend(ABC, Generic[PlanT, CleanupT]):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
agent_dockerfile_path = str(agent_provider.dockerfile)
|
agent_dockerfile_path = str(agent_provider.dockerfile)
|
||||||
instance_name = f"bot-bottle-{slug}"
|
|
||||||
|
|
||||||
agent_dir, prompt_file = prepare_agent_state_dir(slug, spec)
|
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,
|
template=manfiest_agent_provider.template,
|
||||||
dockerfile=agent_dockerfile_path,
|
dockerfile=agent_dockerfile_path,
|
||||||
state_dir=agent_dir,
|
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),
|
guest_env=self._build_guest_env(resolved_env),
|
||||||
forward_host_credentials=manfiest_agent_provider.forward_host_credentials,
|
forward_host_credentials=manfiest_agent_provider.forward_host_credentials,
|
||||||
auth_token=manfiest_agent_provider.auth_token,
|
auth_token=manfiest_agent_provider.auth_token,
|
||||||
@@ -333,10 +332,6 @@ class BottleBackend(ABC, Generic[PlanT, CleanupT]):
|
|||||||
spec,
|
spec,
|
||||||
slug=slug,
|
slug=slug,
|
||||||
resolved_env=resolved_env,
|
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,
|
agent_provision_plan=agent_provision_plan,
|
||||||
egress_plan=egress_plan,
|
egress_plan=egress_plan,
|
||||||
supervise_plan=supervise_plan,
|
supervise_plan=supervise_plan,
|
||||||
@@ -408,18 +403,16 @@ class BottleBackend(ABC, Generic[PlanT, CleanupT]):
|
|||||||
*,
|
*,
|
||||||
slug: str,
|
slug: str,
|
||||||
resolved_env: ResolvedEnv,
|
resolved_env: ResolvedEnv,
|
||||||
instance_name: str,
|
|
||||||
agent_image: str,
|
|
||||||
prompt_file: Path,
|
|
||||||
agent_provision_plan: AgentProvisionPlan,
|
agent_provision_plan: AgentProvisionPlan,
|
||||||
agent_dockerfile_path: str,
|
|
||||||
egress_plan: EgressPlan,
|
egress_plan: EgressPlan,
|
||||||
git_gate_plan: GitGatePlan,
|
git_gate_plan: GitGatePlan,
|
||||||
supervise_plan: SupervisePlan | None,
|
supervise_plan: SupervisePlan | None,
|
||||||
stage_dir: Path) -> PlanT:
|
stage_dir: Path) -> PlanT:
|
||||||
"""Backend-specific plan resolution: image/container names,
|
"""Backend-specific plan resolution: image/container names,
|
||||||
env-file, prompt-file, proxy plan, runtime detection. Called by
|
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
|
@abstractmethod
|
||||||
def launch(self, plan: PlanT) -> AbstractContextManager[Bottle]:
|
def launch(self, plan: PlanT) -> AbstractContextManager[Bottle]:
|
||||||
|
|||||||
@@ -54,11 +54,7 @@ class DockerBottleBackend(BottleBackend["DockerBottlePlan", "DockerBottleCleanup
|
|||||||
*,
|
*,
|
||||||
slug: str,
|
slug: str,
|
||||||
resolved_env,
|
resolved_env,
|
||||||
instance_name: str,
|
|
||||||
agent_image: str,
|
|
||||||
prompt_file: Path,
|
|
||||||
agent_provision_plan,
|
agent_provision_plan,
|
||||||
agent_dockerfile_path: str,
|
|
||||||
egress_plan,
|
egress_plan,
|
||||||
git_gate_plan,
|
git_gate_plan,
|
||||||
supervise_plan,
|
supervise_plan,
|
||||||
@@ -68,10 +64,6 @@ class DockerBottleBackend(BottleBackend["DockerBottlePlan", "DockerBottleCleanup
|
|||||||
spec,
|
spec,
|
||||||
slug=slug,
|
slug=slug,
|
||||||
resolved_env=resolved_env,
|
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,
|
agent_provision_plan=agent_provision_plan,
|
||||||
egress_plan=egress_plan,
|
egress_plan=egress_plan,
|
||||||
supervise_plan=supervise_plan,
|
supervise_plan=supervise_plan,
|
||||||
|
|||||||
@@ -22,21 +22,32 @@ class DockerBottlePlan(BottlePlan):
|
|||||||
`agent_provision` from BottlePlan."""
|
`agent_provision` from BottlePlan."""
|
||||||
|
|
||||||
slug: str
|
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/<slug>/Dockerfile) after a
|
|
||||||
# capability-block remediation (PRD 0016).
|
|
||||||
dockerfile_path: str
|
|
||||||
# name -> value for vars forwarded into the docker-run child process
|
# name -> value for vars forwarded into the docker-run child process
|
||||||
# via subprocess env (so values never land on argv or in a file).
|
# via subprocess env (so values never land on argv or in a file).
|
||||||
# repr=False keeps secret/interpolated/OAuth values out of any
|
# repr=False keeps secret/interpolated/OAuth values out of any
|
||||||
# accidental log of the plan dataclass.
|
# accidental log of the plan dataclass.
|
||||||
forwarded_env: dict[str, str] = field(repr=False)
|
forwarded_env: dict[str, str] = field(repr=False)
|
||||||
prompt_file: Path
|
|
||||||
use_runsc: bool
|
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
|
@property
|
||||||
def agent_command(self) -> str:
|
def agent_command(self) -> str:
|
||||||
return self.agent_provision.command
|
return self.agent_provision.command
|
||||||
|
|||||||
@@ -34,10 +34,6 @@ def resolve_plan(
|
|||||||
spec: BottleSpec,
|
spec: BottleSpec,
|
||||||
slug: str,
|
slug: str,
|
||||||
resolved_env: ResolvedEnv,
|
resolved_env: ResolvedEnv,
|
||||||
instance_name: str,
|
|
||||||
agent_image: str,
|
|
||||||
agent_dockerfile_path: str,
|
|
||||||
prompt_file: Path,
|
|
||||||
agent_provision_plan: AgentProvisionPlan,
|
agent_provision_plan: AgentProvisionPlan,
|
||||||
egress_plan: EgressPlan,
|
egress_plan: EgressPlan,
|
||||||
supervise_plan: SupervisePlan,
|
supervise_plan: SupervisePlan,
|
||||||
@@ -55,12 +51,7 @@ def resolve_plan(
|
|||||||
spec=spec,
|
spec=spec,
|
||||||
stage_dir=stage_dir,
|
stage_dir=stage_dir,
|
||||||
slug=slug,
|
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),
|
forwarded_env=dict(resolved_env.forwarded),
|
||||||
prompt_file=prompt_file,
|
|
||||||
git_gate_plan=git_gate_plan,
|
git_gate_plan=git_gate_plan,
|
||||||
egress_plan=egress_plan,
|
egress_plan=egress_plan,
|
||||||
supervise_plan=supervise_plan,
|
supervise_plan=supervise_plan,
|
||||||
|
|||||||
@@ -47,11 +47,7 @@ class SmolmachinesBottleBackend(
|
|||||||
*,
|
*,
|
||||||
slug: str,
|
slug: str,
|
||||||
resolved_env,
|
resolved_env,
|
||||||
instance_name: str,
|
|
||||||
agent_image: str,
|
|
||||||
prompt_file: Path,
|
|
||||||
agent_provision_plan,
|
agent_provision_plan,
|
||||||
agent_dockerfile_path: str,
|
|
||||||
egress_plan,
|
egress_plan,
|
||||||
git_gate_plan,
|
git_gate_plan,
|
||||||
supervise_plan,
|
supervise_plan,
|
||||||
@@ -61,10 +57,6 @@ class SmolmachinesBottleBackend(
|
|||||||
spec,
|
spec,
|
||||||
slug=slug,
|
slug=slug,
|
||||||
resolved_env=resolved_env,
|
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,
|
agent_provision_plan=agent_provision_plan,
|
||||||
egress_plan=egress_plan,
|
egress_plan=egress_plan,
|
||||||
supervise_plan=supervise_plan,
|
supervise_plan=supervise_plan,
|
||||||
|
|||||||
@@ -29,27 +29,6 @@ class SmolmachinesBottlePlan(BottlePlan):
|
|||||||
bundle_subnet: str
|
bundle_subnet: str
|
||||||
bundle_gateway: str
|
bundle_gateway: str
|
||||||
bundle_ip: 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
|
# In-guest env vars (HTTPS_PROXY etc) — IP-literal URLs since
|
||||||
# the guest has no DNS resolver inside the TSI allowlist.
|
# the guest has no DNS resolver inside the TSI allowlist.
|
||||||
# Passed to `smolvm machine create` as `-e K=V` flags.
|
# Passed to `smolvm machine create` as `-e K=V` flags.
|
||||||
@@ -57,11 +36,6 @@ class SmolmachinesBottlePlan(BottlePlan):
|
|||||||
# `--smolfile` is mutually exclusive with `--from`, and
|
# `--smolfile` is mutually exclusive with `--from`, and
|
||||||
# `--from` is the path that avoids the registry-pull race).
|
# `--from` is the path that avoids the registry-pull race).
|
||||||
guest_env: dict[str, str]
|
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
|
# Inner Plans for the sidecar bundle daemons. The same shape the
|
||||||
# docker backend uses — same `.prepare()` calls produced
|
# docker backend uses — same `.prepare()` calls produced
|
||||||
# them — but our launch step doesn't populate the
|
# them — but our launch step doesn't populate the
|
||||||
@@ -82,6 +56,34 @@ class SmolmachinesBottlePlan(BottlePlan):
|
|||||||
agent_git_gate_host: str = ""
|
agent_git_gate_host: str = ""
|
||||||
agent_supervise_url: 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
|
@property
|
||||||
def git_gate_insteadof_host(self) -> str:
|
def git_gate_insteadof_host(self) -> str:
|
||||||
return self.agent_git_gate_host
|
return self.agent_git_gate_host
|
||||||
|
|||||||
@@ -51,10 +51,6 @@ def resolve_plan(
|
|||||||
spec: BottleSpec,
|
spec: BottleSpec,
|
||||||
slug: str,
|
slug: str,
|
||||||
resolved_env: ResolvedEnv,
|
resolved_env: ResolvedEnv,
|
||||||
instance_name: str,
|
|
||||||
agent_image: str,
|
|
||||||
agent_dockerfile_path: str,
|
|
||||||
prompt_file: Path,
|
|
||||||
agent_provision_plan: AgentProvisionPlan,
|
agent_provision_plan: AgentProvisionPlan,
|
||||||
egress_plan: EgressPlan,
|
egress_plan: EgressPlan,
|
||||||
supervise_plan: SupervisePlan,
|
supervise_plan: SupervisePlan,
|
||||||
@@ -79,10 +75,7 @@ def resolve_plan(
|
|||||||
bundle_subnet=subnet,
|
bundle_subnet=subnet,
|
||||||
bundle_gateway=gateway,
|
bundle_gateway=gateway,
|
||||||
bundle_ip=bundle_ip,
|
bundle_ip=bundle_ip,
|
||||||
machine_name=instance_name,
|
|
||||||
agent_image=agent_image,
|
|
||||||
guest_env=agent_provision_plan.guest_env,
|
guest_env=agent_provision_plan.guest_env,
|
||||||
prompt_file=prompt_file,
|
|
||||||
git_gate_plan=git_gate_plan,
|
git_gate_plan=git_gate_plan,
|
||||||
egress_plan=egress_plan,
|
egress_plan=egress_plan,
|
||||||
supervise_plan=supervise_plan,
|
supervise_plan=supervise_plan,
|
||||||
|
|||||||
@@ -59,7 +59,8 @@ class ClaudeAgentProvider(AgentProvider):
|
|||||||
*,
|
*,
|
||||||
dockerfile: str,
|
dockerfile: str,
|
||||||
state_dir: Path,
|
state_dir: Path,
|
||||||
guest_home: str,
|
instance_name: str,
|
||||||
|
prompt_file: Path,
|
||||||
guest_env: dict[str, str] | None = None,
|
guest_env: dict[str, str] | None = None,
|
||||||
auth_token: str = "",
|
auth_token: str = "",
|
||||||
forward_host_credentials: bool = False,
|
forward_host_credentials: bool = False,
|
||||||
@@ -70,6 +71,7 @@ class ClaudeAgentProvider(AgentProvider):
|
|||||||
) -> AgentProvisionPlan:
|
) -> AgentProvisionPlan:
|
||||||
del forward_host_credentials, host_env # Codex-only knobs
|
del forward_host_credentials, host_env # Codex-only knobs
|
||||||
resolved_guest_env = dict(guest_env or {})
|
resolved_guest_env = dict(guest_env or {})
|
||||||
|
guest_home = self.guest_home
|
||||||
trusted_path = trusted_project_path or guest_home
|
trusted_path = trusted_project_path or guest_home
|
||||||
|
|
||||||
env_vars: dict[str, str] = {
|
env_vars: dict[str, str] = {
|
||||||
@@ -111,6 +113,8 @@ class ClaudeAgentProvider(AgentProvider):
|
|||||||
image=_RUNTIME.image,
|
image=_RUNTIME.image,
|
||||||
dockerfile=dockerfile,
|
dockerfile=dockerfile,
|
||||||
guest_home=guest_home,
|
guest_home=guest_home,
|
||||||
|
instance_name=instance_name,
|
||||||
|
prompt_file=prompt_file,
|
||||||
env_vars=env_vars,
|
env_vars=env_vars,
|
||||||
guest_env=resolved_guest_env,
|
guest_env=resolved_guest_env,
|
||||||
files=files,
|
files=files,
|
||||||
|
|||||||
@@ -67,7 +67,8 @@ class CodexAgentProvider(AgentProvider):
|
|||||||
*,
|
*,
|
||||||
dockerfile: str,
|
dockerfile: str,
|
||||||
state_dir: Path,
|
state_dir: Path,
|
||||||
guest_home: str,
|
instance_name: str,
|
||||||
|
prompt_file: Path,
|
||||||
guest_env: dict[str, str] | None = None,
|
guest_env: dict[str, str] | None = None,
|
||||||
auth_token: str = "",
|
auth_token: str = "",
|
||||||
forward_host_credentials: bool = False,
|
forward_host_credentials: bool = False,
|
||||||
@@ -78,6 +79,7 @@ class CodexAgentProvider(AgentProvider):
|
|||||||
) -> AgentProvisionPlan:
|
) -> AgentProvisionPlan:
|
||||||
del auth_token, label, color # Claude-only knobs
|
del auth_token, label, color # Claude-only knobs
|
||||||
resolved_guest_env = dict(guest_env or {})
|
resolved_guest_env = dict(guest_env or {})
|
||||||
|
guest_home = self.guest_home
|
||||||
trusted_path = trusted_project_path or guest_home
|
trusted_path = trusted_project_path or guest_home
|
||||||
|
|
||||||
env_vars: dict[str, str] = {
|
env_vars: dict[str, str] = {
|
||||||
@@ -148,6 +150,8 @@ class CodexAgentProvider(AgentProvider):
|
|||||||
image=_RUNTIME.image,
|
image=_RUNTIME.image,
|
||||||
dockerfile=dockerfile,
|
dockerfile=dockerfile,
|
||||||
guest_home=guest_home,
|
guest_home=guest_home,
|
||||||
|
instance_name=instance_name,
|
||||||
|
prompt_file=prompt_file,
|
||||||
env_vars=env_vars,
|
env_vars=env_vars,
|
||||||
guest_env=resolved_guest_env,
|
guest_env=resolved_guest_env,
|
||||||
dirs=tuple(dirs),
|
dirs=tuple(dirs),
|
||||||
|
|||||||
@@ -26,10 +26,11 @@ class TestAgentProviderRuntime(unittest.TestCase):
|
|||||||
def test_codex_plan_declares_home_state(self):
|
def test_codex_plan_declares_home_state(self):
|
||||||
with tempfile.TemporaryDirectory(prefix="bb-provider.") as tmp:
|
with tempfile.TemporaryDirectory(prefix="bb-provider.") as tmp:
|
||||||
plan = build_agent_provision_plan(
|
plan = build_agent_provision_plan(
|
||||||
guest_home="/home/node",
|
|
||||||
template="codex",
|
template="codex",
|
||||||
dockerfile="/tmp/Dockerfile.codex",
|
dockerfile="/tmp/Dockerfile.codex",
|
||||||
state_dir=Path(tmp),
|
state_dir=Path(tmp),
|
||||||
|
instance_name="bot-bottle-test",
|
||||||
|
prompt_file=Path(tmp) / "prompt.txt",
|
||||||
)
|
)
|
||||||
config = Path(tmp, "codex-config.toml").read_text()
|
config = Path(tmp, "codex-config.toml").read_text()
|
||||||
self.assertEqual("codex", plan.template)
|
self.assertEqual("codex", plan.template)
|
||||||
@@ -51,10 +52,11 @@ class TestAgentProviderRuntime(unittest.TestCase):
|
|||||||
def test_codex_trusts_requested_project_path(self):
|
def test_codex_trusts_requested_project_path(self):
|
||||||
with tempfile.TemporaryDirectory(prefix="bb-provider.") as tmp:
|
with tempfile.TemporaryDirectory(prefix="bb-provider.") as tmp:
|
||||||
build_agent_provision_plan(
|
build_agent_provision_plan(
|
||||||
guest_home="/home/node",
|
|
||||||
template="codex",
|
template="codex",
|
||||||
dockerfile="",
|
dockerfile="",
|
||||||
state_dir=Path(tmp),
|
state_dir=Path(tmp),
|
||||||
|
instance_name="bot-bottle-test",
|
||||||
|
prompt_file=Path(tmp) / "prompt.txt",
|
||||||
trusted_project_path="/home/node/workspace",
|
trusted_project_path="/home/node/workspace",
|
||||||
)
|
)
|
||||||
config = Path(tmp, "codex-config.toml").read_text()
|
config = Path(tmp, "codex-config.toml").read_text()
|
||||||
@@ -69,10 +71,11 @@ class TestAgentProviderRuntime(unittest.TestCase):
|
|||||||
"tokens": {"access_token": _jwt(2000000000)},
|
"tokens": {"access_token": _jwt(2000000000)},
|
||||||
}))
|
}))
|
||||||
plan = build_agent_provision_plan(
|
plan = build_agent_provision_plan(
|
||||||
guest_home="/home/node",
|
|
||||||
template="codex",
|
template="codex",
|
||||||
dockerfile="",
|
dockerfile="",
|
||||||
state_dir=Path(tmp),
|
state_dir=Path(tmp),
|
||||||
|
instance_name="bot-bottle-test",
|
||||||
|
prompt_file=Path(tmp) / "prompt.txt",
|
||||||
guest_env={"CODEX_HOME": "/run/codex-home"},
|
guest_env={"CODEX_HOME": "/run/codex-home"},
|
||||||
forward_host_credentials=True,
|
forward_host_credentials=True,
|
||||||
host_env={"CODEX_HOME": str(home)},
|
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):
|
def test_claude_with_auth_token_injects_provider_route_and_placeholder(self):
|
||||||
with tempfile.TemporaryDirectory(prefix="bb-provider.") as tmp:
|
with tempfile.TemporaryDirectory(prefix="bb-provider.") as tmp:
|
||||||
plan = build_agent_provision_plan(
|
plan = build_agent_provision_plan(
|
||||||
guest_home="/home/node",
|
|
||||||
template="claude",
|
template="claude",
|
||||||
dockerfile="/tmp/Dockerfile.claude",
|
dockerfile="/tmp/Dockerfile.claude",
|
||||||
state_dir=Path(tmp),
|
state_dir=Path(tmp),
|
||||||
|
instance_name="bot-bottle-test",
|
||||||
|
prompt_file=Path(tmp) / "prompt.txt",
|
||||||
auth_token="BOT_BOTTLE_CLAUDE_OAUTH_TOKEN",
|
auth_token="BOT_BOTTLE_CLAUDE_OAUTH_TOKEN",
|
||||||
)
|
)
|
||||||
claude_config = json.loads(Path(tmp, "claude.json").read_text())
|
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):
|
def test_claude_trusts_requested_project_path(self):
|
||||||
with tempfile.TemporaryDirectory(prefix="bb-provider.") as tmp:
|
with tempfile.TemporaryDirectory(prefix="bb-provider.") as tmp:
|
||||||
build_agent_provision_plan(
|
build_agent_provision_plan(
|
||||||
guest_home="/home/node",
|
|
||||||
template="claude",
|
template="claude",
|
||||||
dockerfile="",
|
dockerfile="",
|
||||||
state_dir=Path(tmp),
|
state_dir=Path(tmp),
|
||||||
|
instance_name="bot-bottle-test",
|
||||||
|
prompt_file=Path(tmp) / "prompt.txt",
|
||||||
trusted_project_path="/home/node/workspace",
|
trusted_project_path="/home/node/workspace",
|
||||||
)
|
)
|
||||||
config = json.loads(Path(tmp, "claude.json").read_text())
|
config = json.loads(Path(tmp, "claude.json").read_text())
|
||||||
@@ -130,10 +135,11 @@ class TestAgentProviderRuntime(unittest.TestCase):
|
|||||||
"tokens": {"access_token": _jwt(2000000000)},
|
"tokens": {"access_token": _jwt(2000000000)},
|
||||||
}))
|
}))
|
||||||
plan = build_agent_provision_plan(
|
plan = build_agent_provision_plan(
|
||||||
guest_home="/home/node",
|
|
||||||
template="codex",
|
template="codex",
|
||||||
dockerfile="",
|
dockerfile="",
|
||||||
state_dir=Path(tmp),
|
state_dir=Path(tmp),
|
||||||
|
instance_name="bot-bottle-test",
|
||||||
|
prompt_file=Path(tmp) / "prompt.txt",
|
||||||
forward_host_credentials=True,
|
forward_host_credentials=True,
|
||||||
host_env={"CODEX_HOME": str(home)},
|
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):
|
def test_codex_without_forward_host_credentials_has_passthrough_egress_routes(self):
|
||||||
with tempfile.TemporaryDirectory(prefix="bb-provider.") as tmp:
|
with tempfile.TemporaryDirectory(prefix="bb-provider.") as tmp:
|
||||||
plan = build_agent_provision_plan(
|
plan = build_agent_provision_plan(
|
||||||
guest_home="/home/node",
|
|
||||||
template="codex",
|
template="codex",
|
||||||
dockerfile="",
|
dockerfile="",
|
||||||
state_dir=Path(tmp),
|
state_dir=Path(tmp),
|
||||||
|
instance_name="bot-bottle-test",
|
||||||
|
prompt_file=Path(tmp) / "prompt.txt",
|
||||||
forward_host_credentials=False,
|
forward_host_credentials=False,
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
@@ -163,10 +170,11 @@ class TestAgentProviderRuntime(unittest.TestCase):
|
|||||||
def test_claude_without_auth_token_has_passthrough_egress_route(self):
|
def test_claude_without_auth_token_has_passthrough_egress_route(self):
|
||||||
with tempfile.TemporaryDirectory(prefix="bb-provider.") as tmp:
|
with tempfile.TemporaryDirectory(prefix="bb-provider.") as tmp:
|
||||||
plan = build_agent_provision_plan(
|
plan = build_agent_provision_plan(
|
||||||
guest_home="/home/node",
|
|
||||||
template="claude",
|
template="claude",
|
||||||
dockerfile="",
|
dockerfile="",
|
||||||
state_dir=Path(tmp),
|
state_dir=Path(tmp),
|
||||||
|
instance_name="bot-bottle-test",
|
||||||
|
prompt_file=Path(tmp) / "prompt.txt",
|
||||||
)
|
)
|
||||||
self.assertEqual(1, len(plan.egress_routes))
|
self.assertEqual(1, len(plan.egress_routes))
|
||||||
route = plan.egress_routes[0]
|
route = plan.egress_routes[0]
|
||||||
@@ -186,10 +194,11 @@ class TestAgentProviderRuntime(unittest.TestCase):
|
|||||||
"tokens": {"access_token": access},
|
"tokens": {"access_token": access},
|
||||||
}))
|
}))
|
||||||
plan = build_agent_provision_plan(
|
plan = build_agent_provision_plan(
|
||||||
guest_home="/home/node",
|
|
||||||
template="codex",
|
template="codex",
|
||||||
dockerfile="",
|
dockerfile="",
|
||||||
state_dir=Path(tmp),
|
state_dir=Path(tmp),
|
||||||
|
instance_name="bot-bottle-test",
|
||||||
|
prompt_file=Path(tmp) / "prompt.txt",
|
||||||
forward_host_credentials=True,
|
forward_host_credentials=True,
|
||||||
host_env={"CODEX_HOME": str(home)},
|
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):
|
def test_codex_without_forward_host_credentials_has_empty_provisioned_env(self):
|
||||||
with tempfile.TemporaryDirectory(prefix="bb-provider.") as tmp:
|
with tempfile.TemporaryDirectory(prefix="bb-provider.") as tmp:
|
||||||
plan = build_agent_provision_plan(
|
plan = build_agent_provision_plan(
|
||||||
guest_home="/home/node",
|
|
||||||
template="codex",
|
template="codex",
|
||||||
dockerfile="",
|
dockerfile="",
|
||||||
state_dir=Path(tmp),
|
state_dir=Path(tmp),
|
||||||
|
instance_name="bot-bottle-test",
|
||||||
|
prompt_file=Path(tmp) / "prompt.txt",
|
||||||
forward_host_credentials=False,
|
forward_host_credentials=False,
|
||||||
)
|
)
|
||||||
self.assertEqual({}, plan.provisioned_env)
|
self.assertEqual({}, plan.provisioned_env)
|
||||||
|
|||||||
@@ -152,11 +152,7 @@ def _plan(
|
|||||||
spec=spec,
|
spec=spec,
|
||||||
stage_dir=STAGE,
|
stage_dir=STAGE,
|
||||||
slug=SLUG,
|
slug=SLUG,
|
||||||
container_name=f"bot-bottle-{SLUG}",
|
|
||||||
image="bot-bottle-claude:latest",
|
|
||||||
dockerfile_path="",
|
|
||||||
forwarded_env={"CLAUDE_CODE_OAUTH_TOKEN": "x"},
|
forwarded_env={"CLAUDE_CODE_OAUTH_TOKEN": "x"},
|
||||||
prompt_file=STAGE / "prompt",
|
|
||||||
git_gate_plan=_git_gate_plan(upstreams),
|
git_gate_plan=_git_gate_plan(upstreams),
|
||||||
egress_plan=_egress_plan(routes),
|
egress_plan=_egress_plan(routes),
|
||||||
supervise_plan=_supervise_plan() if supervise else None,
|
supervise_plan=_supervise_plan() if supervise else None,
|
||||||
@@ -168,6 +164,8 @@ def _plan(
|
|||||||
image="bot-bottle-claude:latest",
|
image="bot-bottle-claude:latest",
|
||||||
dockerfile="",
|
dockerfile="",
|
||||||
guest_home="/home/node",
|
guest_home="/home/node",
|
||||||
|
instance_name=f"bot-bottle-{SLUG}",
|
||||||
|
prompt_file=STAGE / "prompt",
|
||||||
guest_env={},
|
guest_env={},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -248,6 +246,8 @@ class TestAgentAlwaysPresent(unittest.TestCase):
|
|||||||
image="bot-bottle-codex:latest",
|
image="bot-bottle-codex:latest",
|
||||||
dockerfile="",
|
dockerfile="",
|
||||||
guest_home="/home/node",
|
guest_home="/home/node",
|
||||||
|
instance_name=f"bot-bottle-{SLUG}",
|
||||||
|
prompt_file=STAGE / "prompt",
|
||||||
guest_env={"CODEX_HOME": "/home/node/.codex"},
|
guest_env={"CODEX_HOME": "/home/node/.codex"},
|
||||||
)
|
)
|
||||||
plan = type(plan)(**{**vars(plan), "agent_provision": provision}) # type: ignore
|
plan = type(plan)(**{**vars(plan), "agent_provision": provision}) # type: ignore
|
||||||
|
|||||||
@@ -79,11 +79,7 @@ def _plan(
|
|||||||
spec=spec,
|
spec=spec,
|
||||||
stage_dir=Path("/tmp/stage"),
|
stage_dir=Path("/tmp/stage"),
|
||||||
slug="demo-abc12",
|
slug="demo-abc12",
|
||||||
container_name="bot-bottle-demo-abc12",
|
|
||||||
image="bot-bottle-claude:latest",
|
|
||||||
dockerfile_path="",
|
|
||||||
forwarded_env={},
|
forwarded_env={},
|
||||||
prompt_file=Path("/tmp/state/demo-abc12/agent/prompt.txt"),
|
|
||||||
git_gate_plan=GitGatePlan(
|
git_gate_plan=GitGatePlan(
|
||||||
slug="demo-abc12",
|
slug="demo-abc12",
|
||||||
entrypoint_script=Path("/tmp/git-gate-entrypoint.sh"),
|
entrypoint_script=Path("/tmp/git-gate-entrypoint.sh"),
|
||||||
@@ -101,7 +97,11 @@ def _plan(
|
|||||||
use_runsc=False,
|
use_runsc=False,
|
||||||
agent_provision=agent_provision or AgentProvisionPlan(
|
agent_provision=agent_provision or AgentProvisionPlan(
|
||||||
template="claude", command="claude", prompt_mode="append_file",
|
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):
|
def test_copies_files_and_chowns(self):
|
||||||
provision = AgentProvisionPlan(
|
provision = AgentProvisionPlan(
|
||||||
template="claude", command="claude", prompt_mode="append_file",
|
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(
|
files=(AgentProvisionFile(
|
||||||
Path("/tmp/claude.json"), "/home/node/.claude.json",
|
Path("/tmp/claude.json"), "/home/node/.claude.json",
|
||||||
),),
|
),),
|
||||||
@@ -228,7 +231,10 @@ class TestClaudeProvision(unittest.TestCase):
|
|||||||
def test_dies_when_file_chown_fails(self):
|
def test_dies_when_file_chown_fails(self):
|
||||||
provision = AgentProvisionPlan(
|
provision = AgentProvisionPlan(
|
||||||
template="claude", command="claude", prompt_mode="append_file",
|
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(
|
files=(AgentProvisionFile(
|
||||||
Path("/tmp/claude.json"), "/home/node/.claude.json",
|
Path("/tmp/claude.json"), "/home/node/.claude.json",
|
||||||
),),
|
),),
|
||||||
@@ -244,7 +250,10 @@ class TestClaudeProvision(unittest.TestCase):
|
|||||||
def test_runs_verify_commands(self):
|
def test_runs_verify_commands(self):
|
||||||
provision = AgentProvisionPlan(
|
provision = AgentProvisionPlan(
|
||||||
template="claude", command="claude", prompt_mode="append_file",
|
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(
|
verify=(AgentProvisionCommand(
|
||||||
("/usr/bin/true",), "verify failed",
|
("/usr/bin/true",), "verify failed",
|
||||||
),),
|
),),
|
||||||
|
|||||||
@@ -80,11 +80,7 @@ def _plan(
|
|||||||
spec=spec,
|
spec=spec,
|
||||||
stage_dir=Path("/tmp/stage"),
|
stage_dir=Path("/tmp/stage"),
|
||||||
slug="demo-abc12",
|
slug="demo-abc12",
|
||||||
container_name="bot-bottle-demo-abc12",
|
|
||||||
image="bot-bottle-codex:latest",
|
|
||||||
dockerfile_path="",
|
|
||||||
forwarded_env={},
|
forwarded_env={},
|
||||||
prompt_file=Path("/tmp/state/demo-abc12/agent/prompt.txt"),
|
|
||||||
git_gate_plan=GitGatePlan(
|
git_gate_plan=GitGatePlan(
|
||||||
slug="demo-abc12",
|
slug="demo-abc12",
|
||||||
entrypoint_script=Path("/tmp/git-gate-entrypoint.sh"),
|
entrypoint_script=Path("/tmp/git-gate-entrypoint.sh"),
|
||||||
@@ -102,7 +98,11 @@ def _plan(
|
|||||||
use_runsc=False,
|
use_runsc=False,
|
||||||
agent_provision=agent_provision or AgentProvisionPlan(
|
agent_provision=agent_provision or AgentProvisionPlan(
|
||||||
template="codex", command="codex", prompt_mode="read_prompt_file",
|
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(
|
provision = AgentProvisionPlan(
|
||||||
template="codex", command="codex",
|
template="codex", command="codex",
|
||||||
prompt_mode="read_prompt_file",
|
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"),),
|
dirs=(AgentProvisionDir("/home/node/.codex"),),
|
||||||
files=(AgentProvisionFile(
|
files=(AgentProvisionFile(
|
||||||
Path("/tmp/codex-config.toml"),
|
Path("/tmp/codex-config.toml"),
|
||||||
@@ -195,7 +198,10 @@ class TestCodexProvision(unittest.TestCase):
|
|||||||
provision = AgentProvisionPlan(
|
provision = AgentProvisionPlan(
|
||||||
template="codex", command="codex",
|
template="codex", command="codex",
|
||||||
prompt_mode="read_prompt_file",
|
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(
|
pre_copy=(AgentProvisionCommand(
|
||||||
("find", "/home/node/.codex", "-name", "*.sqlite", "-delete"),
|
("find", "/home/node/.codex", "-name", "*.sqlite", "-delete"),
|
||||||
"could not reset runtime db files",
|
"could not reset runtime db files",
|
||||||
@@ -217,7 +223,10 @@ class TestCodexProvision(unittest.TestCase):
|
|||||||
provision = AgentProvisionPlan(
|
provision = AgentProvisionPlan(
|
||||||
template="codex", command="codex",
|
template="codex", command="codex",
|
||||||
prompt_mode="read_prompt_file",
|
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"),),
|
dirs=(AgentProvisionDir("/home/node/.codex"),),
|
||||||
)
|
)
|
||||||
bottle = _make_bottle(exec_result=ExecResult(1, "", "mkdir: nope\n"))
|
bottle = _make_bottle(exec_result=ExecResult(1, "", "mkdir: nope\n"))
|
||||||
|
|||||||
@@ -63,17 +63,15 @@ def _plan(tmp: str) -> DockerBottlePlan:
|
|||||||
template="claude",
|
template="claude",
|
||||||
command="claude",
|
command="claude",
|
||||||
prompt_mode="append_file",
|
prompt_mode="append_file",
|
||||||
image="",
|
image="bot-bottle-claude:latest",
|
||||||
dockerfile="",
|
dockerfile="",
|
||||||
guest_home="/home/node",
|
guest_home="/home/node",
|
||||||
|
instance_name="bot-bottle-test-teardown-abc",
|
||||||
|
prompt_file=stage / "prompt.txt",
|
||||||
guest_env={},
|
guest_env={},
|
||||||
),
|
),
|
||||||
slug="test-teardown-00001",
|
slug="test-teardown-00001",
|
||||||
container_name="bot-bottle-test-teardown-abc",
|
|
||||||
image="bot-bottle-claude:latest",
|
|
||||||
dockerfile_path="",
|
|
||||||
forwarded_env={},
|
forwarded_env={},
|
||||||
prompt_file=stage / "prompt.txt",
|
|
||||||
use_runsc=False,
|
use_runsc=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -64,11 +64,7 @@ def _plan(*, git_user: dict | None = None, # type: ignore
|
|||||||
spec=spec,
|
spec=spec,
|
||||||
stage_dir=stage_dir or Path("/tmp/stage"),
|
stage_dir=stage_dir or Path("/tmp/stage"),
|
||||||
slug="demo-abc12",
|
slug="demo-abc12",
|
||||||
container_name="bot-bottle-demo-abc12",
|
|
||||||
image="bot-bottle-claude:latest",
|
|
||||||
dockerfile_path="",
|
|
||||||
forwarded_env={},
|
forwarded_env={},
|
||||||
prompt_file=Path("/tmp/prompt.txt"),
|
|
||||||
git_gate_plan=GitGatePlan(
|
git_gate_plan=GitGatePlan(
|
||||||
slug="demo-abc12",
|
slug="demo-abc12",
|
||||||
entrypoint_script=Path("/tmp/git-gate-entrypoint.sh"),
|
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",
|
image="bot-bottle-claude:latest",
|
||||||
dockerfile="",
|
dockerfile="",
|
||||||
guest_home="/home/node",
|
guest_home="/home/node",
|
||||||
|
instance_name="bot-bottle-demo-abc12",
|
||||||
|
prompt_file=Path("/tmp/prompt.txt"),
|
||||||
guest_env={},
|
guest_env={},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -79,14 +79,16 @@ def _egress_plan(tmp: str) -> EgressPlan:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _agent_provision() -> AgentProvisionPlan:
|
def _agent_provision(tmp: str) -> AgentProvisionPlan:
|
||||||
return AgentProvisionPlan(
|
return AgentProvisionPlan(
|
||||||
template="claude",
|
template="claude",
|
||||||
command="claude",
|
command="claude",
|
||||||
prompt_mode="append_file",
|
prompt_mode="append_file",
|
||||||
image="",
|
image="bot-bottle-claude:latest",
|
||||||
dockerfile="",
|
dockerfile="",
|
||||||
guest_home="/home/node",
|
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"},
|
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),
|
git_gate_plan=_git_gate_plan(tmp),
|
||||||
egress_plan=_egress_plan(tmp),
|
egress_plan=_egress_plan(tmp),
|
||||||
supervise_plan=None,
|
supervise_plan=None,
|
||||||
agent_provision=_agent_provision(),
|
agent_provision=_agent_provision(tmp),
|
||||||
slug="test-00001",
|
slug="test-00001",
|
||||||
container_name="bot-bottle-test-00001",
|
|
||||||
image="bot-bottle-claude:latest",
|
|
||||||
dockerfile_path="",
|
|
||||||
forwarded_env={},
|
forwarded_env={},
|
||||||
prompt_file=stage / "prompt.txt",
|
|
||||||
use_runsc=False,
|
use_runsc=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -118,15 +116,12 @@ def _smolmachines_plan(spec: BottleSpec, tmp: str) -> SmolmachinesBottlePlan:
|
|||||||
git_gate_plan=_git_gate_plan(tmp),
|
git_gate_plan=_git_gate_plan(tmp),
|
||||||
egress_plan=_egress_plan(tmp),
|
egress_plan=_egress_plan(tmp),
|
||||||
supervise_plan=None,
|
supervise_plan=None,
|
||||||
agent_provision=_agent_provision(),
|
agent_provision=_agent_provision(tmp),
|
||||||
slug="test-00001",
|
slug="test-00001",
|
||||||
bundle_subnet="10.99.0.0/24",
|
bundle_subnet="10.99.0.0/24",
|
||||||
bundle_gateway="10.99.0.1",
|
bundle_gateway="10.99.0.1",
|
||||||
bundle_ip="10.99.0.2",
|
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"},
|
guest_env={"HTTPS_PROXY": "http://127.0.0.1:9999"},
|
||||||
prompt_file=stage / "prompt.txt",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -146,10 +146,7 @@ def _plan(
|
|||||||
bundle_subnet="192.168.50.0/24",
|
bundle_subnet="192.168.50.0/24",
|
||||||
bundle_gateway="192.168.50.1",
|
bundle_gateway="192.168.50.1",
|
||||||
bundle_ip=bundle_ip,
|
bundle_ip=bundle_ip,
|
||||||
machine_name="bot-bottle-demo-abc12",
|
|
||||||
agent_image="bot-bottle-claude:latest",
|
|
||||||
guest_env=dict(guest_env or {}),
|
guest_env=dict(guest_env or {}),
|
||||||
prompt_file=Path("/tmp/state/demo-abc12/agent/prompt.txt"),
|
|
||||||
git_gate_plan=GitGatePlan(
|
git_gate_plan=GitGatePlan(
|
||||||
slug="demo-abc12",
|
slug="demo-abc12",
|
||||||
entrypoint_script=Path("/tmp/git-gate-entrypoint.sh"),
|
entrypoint_script=Path("/tmp/git-gate-entrypoint.sh"),
|
||||||
@@ -186,9 +183,11 @@ def _agent_provision(
|
|||||||
template=template,
|
template=template,
|
||||||
command=template,
|
command=template,
|
||||||
prompt_mode="append_file",
|
prompt_mode="append_file",
|
||||||
image="",
|
image="bot-bottle-claude:latest",
|
||||||
dockerfile="",
|
dockerfile="",
|
||||||
guest_home="/home/node",
|
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 {}),
|
guest_env=dict(guest_env or {}),
|
||||||
)
|
)
|
||||||
auth_dir = (guest_env or {}).get("CODEX_HOME", "/home/node/.codex")
|
auth_dir = (guest_env or {}).get("CODEX_HOME", "/home/node/.codex")
|
||||||
@@ -227,6 +226,8 @@ def _agent_provision(
|
|||||||
image="bot-bottle-codex:latest",
|
image="bot-bottle-codex:latest",
|
||||||
dockerfile="",
|
dockerfile="",
|
||||||
guest_home="/home/node",
|
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 {}),
|
guest_env=dict(guest_env or {}),
|
||||||
dirs=(AgentProvisionDir(auth_dir),),
|
dirs=(AgentProvisionDir(auth_dir),),
|
||||||
files=tuple(files),
|
files=tuple(files),
|
||||||
|
|||||||
Reference in New Issue
Block a user