diff --git a/api/app.py b/api/app.py index 460c06ce..3ee8e02b 100644 --- a/api/app.py +++ b/api/app.py @@ -79,11 +79,16 @@ async def lifespan(app: FastAPI): api_url = f"http://{settings.host}:{settings.port}/v1" allowed_dirs = [workspace] if settings.allowed_dir else [] + plans_dir_abs = os.path.abspath( + os.path.join(settings.claude_workspace, "plans") + ) + plans_directory = os.path.relpath(plans_dir_abs, workspace) cli_manager = CLISessionManager( workspace_path=workspace, api_url=api_url, allowed_dirs=allowed_dirs, max_sessions=settings.max_cli_sessions, + plans_directory=plans_directory, ) # Initialize session store diff --git a/cli/manager.py b/cli/manager.py index 2d24a8c5..92b88c86 100644 --- a/cli/manager.py +++ b/cli/manager.py @@ -28,6 +28,7 @@ class CLISessionManager: api_url: str, allowed_dirs: Optional[List[str]] = None, max_sessions: int = 10, + plans_directory: Optional[str] = None, ): """ Initialize the session manager. @@ -37,10 +38,12 @@ class CLISessionManager: api_url: API URL for the proxy allowed_dirs: Directories the CLI is allowed to access max_sessions: Maximum concurrent sessions + plans_directory: Directory for Claude Code CLI plan files (passed via --settings) """ self.workspace = workspace_path self.api_url = api_url self.allowed_dirs = allowed_dirs or [] + self.plans_directory = plans_directory self.max_sessions = max_sessions self._sessions: Dict[str, CLISession] = {} @@ -83,6 +86,7 @@ class CLISessionManager: workspace_path=self.workspace, api_url=self.api_url, allowed_dirs=self.allowed_dirs, + plans_directory=self.plans_directory, ) self._pending_sessions[temp_id] = new_session logger.info(f"Created new session: {temp_id}") diff --git a/cli/session.py b/cli/session.py index 334f2c7b..d8cc32e8 100644 --- a/cli/session.py +++ b/cli/session.py @@ -17,10 +17,12 @@ class CLISession: workspace_path: str, api_url: str, allowed_dirs: Optional[List[str]] = None, + plans_directory: Optional[str] = None, ): self.workspace = os.path.normpath(os.path.abspath(workspace_path)) self.api_url = api_url self.allowed_dirs = [os.path.normpath(d) for d in (allowed_dirs or [])] + self.plans_directory = plans_directory self.process: Optional[asyncio.subprocess.Process] = None self.current_session_id: Optional[str] = None self._is_busy = False @@ -94,6 +96,10 @@ class CLISession: for d in self.allowed_dirs: cmd.extend(["--add-dir", d]) + if self.plans_directory is not None: + settings_json = json.dumps({"plansDirectory": self.plans_directory}) + cmd.extend(["--settings", settings_json]) + try: self.process = await asyncio.create_subprocess_exec( *cmd, diff --git a/tests/test_cli.py b/tests/test_cli.py index 7456ef5d..151a3267 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,8 +1,11 @@ """Tests for cli/ module.""" -import pytest import asyncio +import json import os + +import pytest + from unittest.mock import AsyncMock, MagicMock, patch from messaging.event_parser import parse_cli_event @@ -476,6 +479,36 @@ class TestCLISession: assert os.path.normpath("/dir1") in cmd assert os.path.normpath("/dir2") in cmd + @pytest.mark.asyncio + async def test_start_task_plans_directory(self): + """Test start_task includes --settings plansDirectory when plans_directory set.""" + from cli.session import CLISession + + session = CLISession( + "/tmp", + "http://localhost:8082/v1", + plans_directory="./agent_workspace/plans", + ) + + mock_process = AsyncMock() + mock_process.stdout.read.side_effect = [b""] + mock_process.stderr.read.return_value = b"" + mock_process.wait.return_value = 0 + + with patch( + "asyncio.create_subprocess_exec", new_callable=AsyncMock + ) as mock_exec: + mock_exec.return_value = mock_process + async for _ in session.start_task("test"): + pass + + cmd = mock_exec.call_args[0] + assert "--settings" in cmd + settings_idx = cmd.index("--settings") + assert settings_idx + 1 < len(cmd) + settings = json.loads(cmd[settings_idx + 1]) + assert settings["plansDirectory"] == "./agent_workspace/plans" + @pytest.mark.asyncio async def test_start_task_json_error(self): """Test handling of non-JSON output from CLI."""