fix(pi): prepare runtime state and agent workdir
This commit is contained in:
@@ -289,7 +289,16 @@ class TestAgentProviderRuntime(unittest.TestCase):
|
||||
self.assertEqual("http://ollama:11434/v1", provider["baseUrl"])
|
||||
self.assertEqual("openai-completions", provider["api"])
|
||||
self.assertEqual("ollama", provider["apiKey"])
|
||||
self.assertEqual([{"id": "qwen2.5-coder:7b"}], provider["models"])
|
||||
self.assertEqual("max_tokens", provider["compat"]["maxTokensField"])
|
||||
self.assertEqual(
|
||||
[{
|
||||
"id": "qwen2.5-coder:7b",
|
||||
"name": "qwen2.5-coder:7b",
|
||||
"contextWindow": 3072,
|
||||
"maxTokens": 1024,
|
||||
}],
|
||||
provider["models"],
|
||||
)
|
||||
self.assertEqual("ollama", plan.egress_routes[0].host)
|
||||
self.assertEqual("", plan.egress_routes[0].auth_scheme)
|
||||
self.assertEqual("", plan.egress_routes[0].token_ref)
|
||||
@@ -307,6 +316,9 @@ class TestAgentProviderRuntime(unittest.TestCase):
|
||||
"api": "openai-responses",
|
||||
"api_key": "local",
|
||||
"models": ["gpt-oss:20b", "qwen3:14b"],
|
||||
"context_window": 65536,
|
||||
"max_tokens_field": "max_completion_tokens",
|
||||
"max_tokens": 12000,
|
||||
"supports_developer_role": True,
|
||||
"supports_reasoning_effort": True,
|
||||
},
|
||||
@@ -317,11 +329,28 @@ class TestAgentProviderRuntime(unittest.TestCase):
|
||||
self.assertEqual("openai-responses", provider["api"])
|
||||
self.assertEqual("local", provider["apiKey"])
|
||||
self.assertEqual(
|
||||
[{"id": "gpt-oss:20b"}, {"id": "qwen3:14b"}],
|
||||
[
|
||||
{
|
||||
"id": "gpt-oss:20b",
|
||||
"name": "gpt-oss:20b",
|
||||
"contextWindow": 53536,
|
||||
"maxTokens": 12000,
|
||||
},
|
||||
{
|
||||
"id": "qwen3:14b",
|
||||
"name": "qwen3:14b",
|
||||
"contextWindow": 53536,
|
||||
"maxTokens": 12000,
|
||||
},
|
||||
],
|
||||
provider["models"],
|
||||
)
|
||||
self.assertTrue(provider["compat"]["supportsDeveloperRole"])
|
||||
self.assertTrue(provider["compat"]["supportsReasoningEffort"])
|
||||
self.assertEqual(
|
||||
"max_completion_tokens",
|
||||
provider["compat"]["maxTokensField"],
|
||||
)
|
||||
|
||||
def test_pi_plan_can_target_openrouter_with_egress_injected_api_key(self):
|
||||
with tempfile.TemporaryDirectory(prefix="bb-provider.") as tmp:
|
||||
@@ -345,8 +374,14 @@ class TestAgentProviderRuntime(unittest.TestCase):
|
||||
self.assertEqual("https://openrouter.ai/api/v1", provider["baseUrl"])
|
||||
self.assertEqual("openai-completions", provider["api"])
|
||||
self.assertEqual("egress-placeholder", provider["apiKey"])
|
||||
self.assertEqual("max_tokens", provider["compat"]["maxTokensField"])
|
||||
self.assertEqual(
|
||||
[{"id": "google/gemma-4-26b-a4b-it:free"}],
|
||||
[{
|
||||
"id": "google/gemma-4-26b-a4b-it:free",
|
||||
"name": "google/gemma-4-26b-a4b-it:free",
|
||||
"contextWindow": 3072,
|
||||
"maxTokens": 1024,
|
||||
}],
|
||||
provider["models"],
|
||||
)
|
||||
self.assertEqual(
|
||||
|
||||
@@ -20,6 +20,7 @@ from bot_bottle.manifest import Manifest
|
||||
|
||||
|
||||
_URL = "http://supervise:9100/"
|
||||
_PI_DOCKERFILE = Path(__file__).resolve().parents[2] / "bot_bottle/contrib/pi/Dockerfile"
|
||||
|
||||
|
||||
def _make_bottle(exec_result: ExecResult | None = None) -> MagicMock:
|
||||
@@ -93,7 +94,7 @@ class TestPiProvisionPrompt(unittest.TestCase):
|
||||
result = PiAgentProvider().provision_prompt(
|
||||
_plan(agent_prompt="hello"), bottle,
|
||||
)
|
||||
self.assertEqual("/home/node/.bot-bottle-prompt.txt", result)
|
||||
self.assertIsNone(result)
|
||||
bottle.cp_in.assert_called_once_with(
|
||||
"/tmp/state/demo-abc12/agent/prompt.txt",
|
||||
"/home/node/.bot-bottle-prompt.txt",
|
||||
@@ -102,6 +103,12 @@ class TestPiProvisionPrompt(unittest.TestCase):
|
||||
self.assertTrue(
|
||||
any("chown node:node" in s
|
||||
and "/home/node/.bot-bottle-prompt.txt" in s
|
||||
and "/home/node/.pi/agent/APPEND_SYSTEM.md" in s
|
||||
for s in scripts)
|
||||
)
|
||||
self.assertTrue(
|
||||
any("cp /home/node/.bot-bottle-prompt.txt" in s
|
||||
and "/home/node/.pi/agent/APPEND_SYSTEM.md" in s
|
||||
for s in scripts)
|
||||
)
|
||||
|
||||
@@ -165,6 +172,14 @@ class TestPiProvision(unittest.TestCase):
|
||||
self.assertTrue(
|
||||
any("mkdir -p" in s and "/home/node/.pi/agent" in s for s in scripts)
|
||||
)
|
||||
self.assertTrue(
|
||||
any("/home/node/.pi/context-mode/sessions" in s
|
||||
and "/tmp/pi-subagents-uid-1000" in s
|
||||
and "chown node:node /home/node" in s
|
||||
and "chown -R node:node /home/node/.pi /tmp" in s
|
||||
and "chmod 755 /home/node" in s
|
||||
for s in scripts)
|
||||
)
|
||||
self.assertTrue(
|
||||
any("chown" in s and "/home/node/.pi/agent/models.json" in s
|
||||
for s in scripts)
|
||||
@@ -191,5 +206,20 @@ class TestPiSuperviseMcp(unittest.TestCase):
|
||||
bottle.exec.assert_not_called()
|
||||
|
||||
|
||||
class TestPiDockerfile(unittest.TestCase):
|
||||
def test_installs_pi_cwd_at_build_time(self):
|
||||
dockerfile = _PI_DOCKERFILE.read_text()
|
||||
self.assertIn("pi install npm:@harms-haus/pi-cwd", dockerfile)
|
||||
|
||||
def test_prepares_pi_extension_state_dirs_and_tmp_for_node(self):
|
||||
dockerfile = _PI_DOCKERFILE.read_text()
|
||||
self.assertIn("/home/node/.pi/context-mode/sessions", dockerfile)
|
||||
self.assertIn("/tmp/pi-subagents-uid-1000", dockerfile)
|
||||
self.assertIn("chown -R node:node /home/node/.pi /tmp", dockerfile)
|
||||
self.assertIn("chmod -R u+rwX /tmp", dockerfile)
|
||||
self.assertIn("chown root:root /tmp /var/tmp", dockerfile)
|
||||
self.assertIn("chmod 1777 /tmp /var/tmp", dockerfile)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -41,6 +41,15 @@ def _pi_bottle(prompt_path: str | None = None) -> DockerBottle:
|
||||
)
|
||||
|
||||
|
||||
def _workspace_bottle() -> DockerBottle:
|
||||
return DockerBottle(
|
||||
container="bot-bottle-dev-abc",
|
||||
teardown=lambda: None,
|
||||
prompt_path_in_container=None,
|
||||
agent_workdir="/home/node/workspace",
|
||||
)
|
||||
|
||||
|
||||
class TestClaudeArgv(unittest.TestCase):
|
||||
def test_minimal_argv_no_prompt(self):
|
||||
argv = _bottle().agent_argv([])
|
||||
@@ -89,6 +98,16 @@ class TestClaudeArgv(unittest.TestCase):
|
||||
argv,
|
||||
)
|
||||
|
||||
def test_workspace_workdir_is_used_when_set(self):
|
||||
argv = _workspace_bottle().agent_argv([])
|
||||
self.assertEqual(
|
||||
[
|
||||
"docker", "exec", "-it", "-w", "/home/node/workspace",
|
||||
"bot-bottle-dev-abc", "claude",
|
||||
],
|
||||
argv,
|
||||
)
|
||||
|
||||
def test_caller_argv_not_mutated(self):
|
||||
# `agent_argv` builds `full_argv` from a copy, so a
|
||||
# caller passing a long-lived list (e.g., the dashboard's
|
||||
|
||||
@@ -120,6 +120,9 @@ class TestAgentProviderHostCredentials(unittest.TestCase):
|
||||
"api": "openai-completions",
|
||||
"api_key": "ollama",
|
||||
"models": ["qwen2.5-coder:7b"],
|
||||
"context_window": 65536,
|
||||
"max_tokens_field": "max_tokens",
|
||||
"max_tokens": 12000,
|
||||
"supports_developer_role": False,
|
||||
"supports_reasoning_effort": False,
|
||||
},
|
||||
@@ -131,6 +134,9 @@ class TestAgentProviderHostCredentials(unittest.TestCase):
|
||||
"api": "openai-completions",
|
||||
"api_key": "ollama",
|
||||
"models": ["qwen2.5-coder:7b"],
|
||||
"context_window": 65536,
|
||||
"max_tokens_field": "max_tokens",
|
||||
"max_tokens": 12000,
|
||||
"supports_developer_role": False,
|
||||
"supports_reasoning_effort": False,
|
||||
},
|
||||
|
||||
@@ -37,6 +37,14 @@ def _pi_bottle(prompt_path: str | None = None) -> SmolmachinesBottle:
|
||||
)
|
||||
|
||||
|
||||
def _workspace_bottle() -> SmolmachinesBottle:
|
||||
return SmolmachinesBottle(
|
||||
"bot-bottle-dev-abc",
|
||||
prompt_path=None,
|
||||
agent_workdir="/home/node/workspace",
|
||||
)
|
||||
|
||||
|
||||
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
|
||||
@@ -141,6 +149,19 @@ class TestClaudeArgvWrapped(unittest.TestCase):
|
||||
)
|
||||
self.assertNotIn("-p", argv)
|
||||
|
||||
def test_workspace_workdir_wraps_agent_command(self):
|
||||
argv = _unwrap(_workspace_bottle().agent_argv([]))
|
||||
agent_idx = argv.index("claude")
|
||||
self.assertEqual(
|
||||
[
|
||||
"sh", "-lc",
|
||||
"cd /home/node/workspace && exec \"$@\"",
|
||||
"bot-bottle-agent",
|
||||
"claude",
|
||||
],
|
||||
argv[agent_idx - 4:agent_idx + 1],
|
||||
)
|
||||
|
||||
|
||||
class TestClaudeArgvNoTTY(unittest.TestCase):
|
||||
"""`tty=False` paths skip the pty_resize wrapper — there's no
|
||||
|
||||
Reference in New Issue
Block a user