refactor(docker): move provision_prompt into provision/prompt.py
This commit is contained in:
@@ -35,6 +35,7 @@ from .pipelock import (
|
|||||||
pipelock_proxy_host_port,
|
pipelock_proxy_host_port,
|
||||||
pipelock_proxy_url,
|
pipelock_proxy_url,
|
||||||
)
|
)
|
||||||
|
from .provision import prompt as _prompt
|
||||||
|
|
||||||
|
|
||||||
# Where the repo root lives, for `docker build` context. Computed once.
|
# Where the repo root lives, for `docker build` context. Computed once.
|
||||||
@@ -282,35 +283,8 @@ class DockerBottleBackend(BottleBackend):
|
|||||||
info(f"name conflict; retrying as {container}")
|
info(f"name conflict; retrying as {container}")
|
||||||
|
|
||||||
def provision_prompt(self, plan: BottlePlan, target: str) -> str | None:
|
def provision_prompt(self, plan: BottlePlan, target: str) -> str | None:
|
||||||
"""Copy the prompt file into the container, fix ownership/mode.
|
|
||||||
Returns the in-container path if the agent has a non-empty
|
|
||||||
prompt (drives --append-system-prompt-file), else None. The
|
|
||||||
file is copied either way so the path always exists."""
|
|
||||||
assert isinstance(plan, DockerBottlePlan)
|
assert isinstance(plan, DockerBottlePlan)
|
||||||
container = target
|
return _prompt.provision_prompt(plan, target)
|
||||||
container_home = os.environ.get("CLAUDE_BOTTLE_CONTAINER_HOME", "/home/node")
|
|
||||||
in_container_prompt_path = f"{container_home}/.claude-bottle-prompt.txt"
|
|
||||||
|
|
||||||
subprocess.run(
|
|
||||||
["docker", "cp", str(plan.prompt_file), f"{container}:{in_container_prompt_path}"],
|
|
||||||
stdout=subprocess.DEVNULL,
|
|
||||||
check=True,
|
|
||||||
)
|
|
||||||
# `docker cp` preserves host UID; re-own/mode as root so node
|
|
||||||
# can read its own mode-600 prompt regardless of host UID.
|
|
||||||
subprocess.run(
|
|
||||||
["docker", "exec", "-u", "0", container, "chown", "node:node", in_container_prompt_path],
|
|
||||||
stdout=subprocess.DEVNULL,
|
|
||||||
check=True,
|
|
||||||
)
|
|
||||||
subprocess.run(
|
|
||||||
["docker", "exec", "-u", "0", container, "chmod", "600", in_container_prompt_path],
|
|
||||||
stdout=subprocess.DEVNULL,
|
|
||||||
check=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
agent = plan.spec.manifest.agents[plan.spec.agent_name]
|
|
||||||
return in_container_prompt_path if agent.prompt else None
|
|
||||||
|
|
||||||
def validate_skills(self, skills: list[str]) -> None:
|
def validate_skills(self, skills: list[str]) -> None:
|
||||||
"""Fail loudly if any named skill is missing from the host's
|
"""Fail loudly if any named skill is missing from the host's
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
"""Per-provisioner modules for the Docker backend.
|
||||||
|
|
||||||
|
Each module exports one top-level function:
|
||||||
|
provision_<thing>(plan: DockerBottlePlan, target: str) -> ...
|
||||||
|
|
||||||
|
`DockerBottleBackend.provision_*` methods delegate to these. The
|
||||||
|
abstract `BottleBackend.provision_*` surface is unchanged; this
|
||||||
|
subpackage exists only to keep `backend.py` from being a god-file."""
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
"""Copy the agent prompt into a running Docker bottle.
|
||||||
|
|
||||||
|
The prompt file is always copied (so the in-container path always
|
||||||
|
exists) but `--append-system-prompt-file` only fires when the agent
|
||||||
|
actually has a prompt — the return value signals which case."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
from ..bottle_plan import DockerBottlePlan
|
||||||
|
|
||||||
|
|
||||||
|
def provision_prompt(plan: DockerBottlePlan, target: str) -> str | None:
|
||||||
|
"""Copy the prompt file into the container, fix ownership/mode.
|
||||||
|
Returns the in-container path if the agent has a non-empty
|
||||||
|
prompt (drives --append-system-prompt-file), else None. The
|
||||||
|
file is copied either way so the path always exists."""
|
||||||
|
container = target
|
||||||
|
container_home = os.environ.get("CLAUDE_BOTTLE_CONTAINER_HOME", "/home/node")
|
||||||
|
in_container_prompt_path = f"{container_home}/.claude-bottle-prompt.txt"
|
||||||
|
|
||||||
|
subprocess.run(
|
||||||
|
["docker", "cp", str(plan.prompt_file), f"{container}:{in_container_prompt_path}"],
|
||||||
|
stdout=subprocess.DEVNULL,
|
||||||
|
check=True,
|
||||||
|
)
|
||||||
|
# `docker cp` preserves host UID; re-own/mode as root so node
|
||||||
|
# can read its own mode-600 prompt regardless of host UID.
|
||||||
|
subprocess.run(
|
||||||
|
["docker", "exec", "-u", "0", container, "chown", "node:node", in_container_prompt_path],
|
||||||
|
stdout=subprocess.DEVNULL,
|
||||||
|
check=True,
|
||||||
|
)
|
||||||
|
subprocess.run(
|
||||||
|
["docker", "exec", "-u", "0", container, "chmod", "600", in_container_prompt_path],
|
||||||
|
stdout=subprocess.DEVNULL,
|
||||||
|
check=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
agent = plan.spec.manifest.agents[plan.spec.agent_name]
|
||||||
|
return in_container_prompt_path if agent.prompt else None
|
||||||
Reference in New Issue
Block a user