diff --git a/bot_bottle/agent_provider.py b/bot_bottle/agent_provider.py index f455d68..9d65154 100644 --- a/bot_bottle/agent_provider.py +++ b/bot_bottle/agent_provider.py @@ -45,7 +45,12 @@ PROVIDER_TEMPLATES = frozenset({PROVIDER_CLAUDE, PROVIDER_CODEX, PROVIDER_PI}) # forward_host_credentials is enabled. Pipelock must pass these through # (no TLS MITM) or its header DLP blocks the injected JWT. CODEX_HOST_CREDENTIAL_HOSTS = ("api.openai.com", "chatgpt.com") -PromptMode = Literal["append_file", "read_prompt_file", "print_read_prompt_file"] +PromptMode = Literal[ + "append_file", + "read_prompt_file", + "print_read_prompt_file", + "append_system_prompt", +] @dataclass(frozen=True) @@ -381,4 +386,6 @@ def prompt_args( return [f"Read and follow the instructions in {prompt_path}."] if prompt_mode == "print_read_prompt_file": return ["-p", f"Read and follow the instructions in {prompt_path}."] + if prompt_mode == "append_system_prompt": + return ["--append-system-prompt", prompt_path] raise ValueError(f"unknown provider prompt mode: {prompt_mode}") diff --git a/bot_bottle/contrib/pi/agent_provider.py b/bot_bottle/contrib/pi/agent_provider.py index c823a78..b2a1453 100644 --- a/bot_bottle/contrib/pi/agent_provider.py +++ b/bot_bottle/contrib/pi/agent_provider.py @@ -111,7 +111,7 @@ _RUNTIME = AgentProviderRuntime( template="pi", command="pi", image="bot-bottle-pi:latest", - prompt_mode="print_read_prompt_file", + prompt_mode="append_system_prompt", bypass_args=(), resume_args=(), remote_control_args=(), diff --git a/docs/prds/0058-pi-agent-provider.md b/docs/prds/0058-pi-agent-provider.md index 83a00fb..7191a77 100644 --- a/docs/prds/0058-pi-agent-provider.md +++ b/docs/prds/0058-pi-agent-provider.md @@ -93,7 +93,7 @@ endpoint. The Pi runtime uses: - `command="pi"` -- `prompt_mode="read_prompt_file"` +- `prompt_mode="append_system_prompt"` - `image="bot-bottle-pi:latest"` - `bypass_args=()` - `resume_args=()` diff --git a/tests/unit/test_agent_provider.py b/tests/unit/test_agent_provider.py index 3a3993e..57bfc55 100644 --- a/tests/unit/test_agent_provider.py +++ b/tests/unit/test_agent_provider.py @@ -273,7 +273,7 @@ class TestAgentProviderRuntime(unittest.TestCase): models = json.loads(Path(tmp, "pi-models.json").read_text()) self.assertEqual("pi", plan.template) self.assertEqual("pi", plan.command) - self.assertEqual("print_read_prompt_file", plan.prompt_mode) + self.assertEqual("append_system_prompt", plan.prompt_mode) self.assertEqual("/tmp/Dockerfile.pi", plan.dockerfile) self.assertEqual("bot-bottle-pi:latest", plan.image) self.assertEqual( @@ -354,10 +354,10 @@ class TestAgentProviderRuntime(unittest.TestCase): self.assertNotIn("OPENROUTER_API_KEY", plan.guest_env) self.assertTrue(provider["compat"]["supportsReasoningEffort"]) - def test_pi_prompt_mode_uses_print_flag(self): + def test_pi_prompt_mode_appends_system_prompt_interactively(self): self.assertEqual( - ["-p", "Read and follow the instructions in /home/node/.bot-bottle-prompt.txt."], - prompt_args("print_read_prompt_file", "/home/node/.bot-bottle-prompt.txt"), + ["--append-system-prompt", "/home/node/.bot-bottle-prompt.txt"], + prompt_args("append_system_prompt", "/home/node/.bot-bottle-prompt.txt"), ) diff --git a/tests/unit/test_contrib_pi_provider.py b/tests/unit/test_contrib_pi_provider.py index 143f216..17d3a18 100644 --- a/tests/unit/test_contrib_pi_provider.py +++ b/tests/unit/test_contrib_pi_provider.py @@ -77,7 +77,7 @@ def _plan( supervise_plan=None, use_runsc=False, agent_provision=agent_provision or AgentProvisionPlan( - template="pi", command="pi", prompt_mode="print_read_prompt_file", + template="pi", command="pi", prompt_mode="append_system_prompt", image="bot-bottle-pi:latest", dockerfile="", guest_home="/home/node", instance_name="bot-bottle-demo-abc12", @@ -144,7 +144,7 @@ class TestPiProvisionSkills(unittest.TestCase): class TestPiProvision(unittest.TestCase): def test_creates_dir_and_copies_models_config(self): provision = AgentProvisionPlan( - template="pi", command="pi", prompt_mode="print_read_prompt_file", + template="pi", command="pi", prompt_mode="append_system_prompt", image="", dockerfile="", guest_home="/home/node", instance_name="bot-bottle-demo-abc12", prompt_file=Path("/tmp/prompt.txt"), @@ -172,7 +172,7 @@ class TestPiProvision(unittest.TestCase): def test_dies_when_dir_creation_fails(self): provision = AgentProvisionPlan( - template="pi", command="pi", prompt_mode="print_read_prompt_file", + template="pi", command="pi", prompt_mode="append_system_prompt", image="", dockerfile="", guest_home="/home/node", instance_name="bot-bottle-demo-abc12", prompt_file=Path("/tmp/prompt.txt"), diff --git a/tests/unit/test_docker_bottle.py b/tests/unit/test_docker_bottle.py index 72a3644..dfbc3da 100644 --- a/tests/unit/test_docker_bottle.py +++ b/tests/unit/test_docker_bottle.py @@ -31,6 +31,16 @@ def _codex_bottle(prompt_path: str | None = None) -> DockerBottle: ) +def _pi_bottle(prompt_path: str | None = None) -> DockerBottle: + return DockerBottle( + container="bot-bottle-dev-abc", + teardown=lambda: None, + prompt_path_in_container=prompt_path, + agent_command="pi", + agent_prompt_mode="append_system_prompt", + ) + + class TestClaudeArgv(unittest.TestCase): def test_minimal_argv_no_prompt(self): argv = _bottle().agent_argv([]) @@ -117,6 +127,15 @@ class TestClaudeArgv(unittest.TestCase): argv, ) + def test_pi_provider_appends_system_prompt_without_print_mode(self): + argv = _pi_bottle("/home/node/.bot-bottle-prompt.txt").agent_argv([]) + self.assertEqual( + ["docker", "exec", "-it", "bot-bottle-dev-abc", "pi", + "--append-system-prompt", "/home/node/.bot-bottle-prompt.txt"], + argv, + ) + self.assertNotIn("-p", argv) + if __name__ == "__main__": unittest.main() diff --git a/tests/unit/test_smolmachines_bottle.py b/tests/unit/test_smolmachines_bottle.py index 0ad99bc..9446348 100644 --- a/tests/unit/test_smolmachines_bottle.py +++ b/tests/unit/test_smolmachines_bottle.py @@ -28,6 +28,15 @@ def _bottle(prompt_path: str | None = None, **env: str) -> SmolmachinesBottle: ) +def _pi_bottle(prompt_path: str | None = None) -> SmolmachinesBottle: + return SmolmachinesBottle( + "bot-bottle-dev-abc", + prompt_path=prompt_path, + agent_command="pi", + agent_prompt_mode="append_system_prompt", + ) + + def _unwrap(argv: list[str]) -> list[str]: """Strip the pty_resize wrapper from the front of a TTY-mode argv, return the inner smolvm argv. Mirrors what the kernel @@ -122,6 +131,16 @@ class TestClaudeArgvWrapped(unittest.TestCase): argv[agent_idx - 7:agent_idx - 2], ) + def test_pi_provider_appends_system_prompt_without_print_mode(self): + argv = _unwrap( + _pi_bottle("/home/node/.bot-bottle-prompt.txt").agent_argv([]) + ) + self.assertEqual( + ["pi", "--append-system-prompt", "/home/node/.bot-bottle-prompt.txt"], + argv[argv.index("pi"):], + ) + self.assertNotIn("-p", argv) + class TestClaudeArgvNoTTY(unittest.TestCase): """`tty=False` paths skip the pty_resize wrapper — there's no