diff --git a/bot_bottle/agent_provider.py b/bot_bottle/agent_provider.py index c610116..28f380d 100644 --- a/bot_bottle/agent_provider.py +++ b/bot_bottle/agent_provider.py @@ -7,6 +7,7 @@ command, default image, and prompt/auth behavior. from __future__ import annotations +import json import os from dataclasses import dataclass, field from pathlib import Path @@ -136,9 +137,11 @@ def agent_provision_plan( auth_token: str = "", forward_host_credentials: bool = False, host_env: dict[str, str] | None = None, + trusted_project_path: str = "", ) -> AgentProvisionPlan: runtime = runtime_for(template) resolved_guest_env = dict(guest_env or {}) + trusted_path = trusted_project_path or guest_home env_vars: dict[str, str] = {} provisioned_env: dict[str, str] = {} dirs: list[AgentProvisionDir] = [] @@ -156,8 +159,9 @@ def agent_provision_plan( dirs.append(AgentProvisionDir(auth_dir)) config_path = f"{auth_dir}/config.toml" config_file = state_dir / "codex-config.toml" + toml_path = trusted_path.replace("\\", "\\\\").replace('"', '\\"') config_file.write_text( - f'[projects."{guest_home}"]\n' + f'[projects."{toml_path}"]\n' 'trust_level = "trusted"\n' ) config_file.chmod(0o600) @@ -202,6 +206,19 @@ def agent_provision_plan( if template == PROVIDER_CLAUDE: env_vars["CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC"] = "1" env_vars["DISABLE_ERROR_REPORTING"] = "1" + claude_config = state_dir / "claude.json" + claude_projects = { + guest_home: {"hasTrustDialogAccepted": True}, + } + claude_projects[trusted_path] = {"hasTrustDialogAccepted": True} + claude_config.write_text(json.dumps({ + "hasCompletedOnboarding": True, + "theme": "dark", + "bypassPermissionsModeAccepted": True, + "projects": claude_projects, + }, indent=2) + "\n") + claude_config.chmod(0o600) + files.append(AgentProvisionFile(claude_config, f"{guest_home}/.claude.json")) egress_routes.append(EgressRoute( host="api.anthropic.com", auth_scheme="Bearer" if auth_token else "", diff --git a/bot_bottle/backend/docker/prepare.py b/bot_bottle/backend/docker/prepare.py index dca847e..da34d22 100644 --- a/bot_bottle/backend/docker/prepare.py +++ b/bot_bottle/backend/docker/prepare.py @@ -184,6 +184,7 @@ def resolve_plan( forward_host_credentials=provider.forward_host_credentials, auth_token=provider.auth_token, host_env=dict(os.environ), + trusted_project_path=workspace_plan.workdir, ) guest_env = dict(agent_provision.guest_env) for key, val in agent_provision.env_vars.items(): diff --git a/bot_bottle/backend/smolmachines/prepare.py b/bot_bottle/backend/smolmachines/prepare.py index 2c6393a..53a6c42 100644 --- a/bot_bottle/backend/smolmachines/prepare.py +++ b/bot_bottle/backend/smolmachines/prepare.py @@ -138,6 +138,7 @@ def resolve_plan( forward_host_credentials=provider.forward_host_credentials, auth_token=provider.auth_token, host_env=dict(os.environ), + trusted_project_path=workspace_plan.workdir, ) merged_guest_env = dict(agent_provision.guest_env) for key, val in agent_provision.env_vars.items(): diff --git a/tests/unit/test_agent_provider.py b/tests/unit/test_agent_provider.py index 3f90db5..216e3f2 100644 --- a/tests/unit/test_agent_provider.py +++ b/tests/unit/test_agent_provider.py @@ -31,6 +31,7 @@ class TestAgentProviderRuntime(unittest.TestCase): dockerfile="/tmp/Dockerfile.codex", state_dir=Path(tmp), ) + config = Path(tmp, "codex-config.toml").read_text() self.assertEqual("codex", plan.template) self.assertEqual("codex", plan.command) self.assertEqual("read_prompt_file", plan.prompt_mode) @@ -45,6 +46,18 @@ class TestAgentProviderRuntime(unittest.TestCase): ("/home/node/.codex/config.toml",), tuple(f.guest_path for f in plan.files), ) + self.assertIn('[projects."/home/node"]', config) + + def test_codex_trusts_requested_project_path(self): + with tempfile.TemporaryDirectory(prefix="bb-provider.") as tmp: + agent_provision_plan( + template="codex", + dockerfile="", + state_dir=Path(tmp), + trusted_project_path="/home/node/workspace", + ) + config = Path(tmp, "codex-config.toml").read_text() + self.assertIn('[projects."/home/node/workspace"]', config) def test_codex_forward_host_credentials_adds_auth_and_verify(self): with tempfile.TemporaryDirectory(prefix="bb-provider.") as tmp: @@ -79,6 +92,7 @@ class TestAgentProviderRuntime(unittest.TestCase): state_dir=Path(tmp), auth_token="BOT_BOTTLE_CLAUDE_OAUTH_TOKEN", ) + claude_config = json.loads(Path(tmp, "claude.json").read_text()) self.assertEqual(1, len(plan.egress_routes)) route = plan.egress_routes[0] self.assertEqual("api.anthropic.com", route.host) @@ -89,6 +103,20 @@ class TestAgentProviderRuntime(unittest.TestCase): self.assertEqual("1", plan.env_vars["CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC"]) self.assertEqual("1", plan.env_vars["DISABLE_ERROR_REPORTING"]) self.assertEqual(frozenset({"CLAUDE_CODE_OAUTH_TOKEN"}), plan.hidden_env_names) + self.assertIn("/home/node", claude_config["projects"]) + self.assertIn("/home/node/.claude.json", {f.guest_path for f in plan.files}) + + def test_claude_trusts_requested_project_path(self): + with tempfile.TemporaryDirectory(prefix="bb-provider.") as tmp: + agent_provision_plan( + template="claude", + dockerfile="", + state_dir=Path(tmp), + trusted_project_path="/home/node/workspace", + ) + config = json.loads(Path(tmp, "claude.json").read_text()) + self.assertIn("/home/node", config["projects"]) + self.assertIn("/home/node/workspace", config["projects"]) def test_codex_forward_host_credentials_populates_egress_routes(self): with tempfile.TemporaryDirectory(prefix="bb-provider.") as tmp: