--- phase: 04-gsd-integration plan: 03 type: execute wave: 2 depends_on: - 04-01 - 04-02 files_modified: - src/gsd_openclaw/command_executor.py - src/gsd_openclaw/orchestrator.py - tests/test_command_executor.py - tests/test_orchestrator.py autonomous: true requirements: - BRDG-06 user_setup: [] must_haves: truths: - "Long commands (execute-phase, plan-phase, discuss-phase, new-project) don't timeout" - "Regular commands still use 60 second default timeout" - "Long command detection is based on command name matching" - "Create New Project button callback works from error card" artifacts: - path: "src/gsd_openclaw/command_executor.py" provides: "Extended CommandExecutor with long command support" exports: ["CommandExecutor.LONG_COMMANDS", "CommandExecutor._is_long_command"] - path: "src/gsd_openclaw/orchestrator.py" provides: "Orchestrator with adaptive timeout and button callback" exports: ["Orchestrator._is_long_command"] - path: "tests/test_command_executor.py" provides: "Tests for long command detection" exports: ["TestLongCommands"] key_links: - from: "src/gsd_openclaw/orchestrator.py" to: "src/gsd_openclaw/command_executor.py" via: "execute() with timeout=None for long commands" pattern: "_is_long_command|LONG_COMMANDS" --- Extend CommandExecutor with long command support and update Orchestrator for adaptive timeout and button callback. Purpose: Ensure long-running GSD commands don't timeout prematurely, and the "Create New Project" button from error cards works correctly. Output: CommandExecutor with LONG_COMMANDS detection, Orchestrator with adaptive timeout handling, and button callback support. @/home/starmen1265/.claude/get-shit-done/workflows/execute-plan.md @/home/starmen1265/.claude/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/04-gsd-integration/04-CONTEXT.md @.planning/phases/04-gsd-integration/04-RESEARCH.md From src/gsd_openclaw/command_executor.py: ```python class CommandExecutor: ERROR_KEYWORDS: List[str] = [...] DEFAULT_TIMEOUT: float = 60.0 def execute(self, session, command: str, timeout: Optional[float] = None) -> CommandResult: """Execute command with optional timeout override.""" timeout = timeout if timeout is not None else self.DEFAULT_TIMEOUT # ... ``` From src/gsd_openclaw/orchestrator.py: ```python class Orchestrator: COMMAND_PATTERN = re.compile(r'^/gsd:(\S+)\s*(.*)$') MAX_OUTPUT_LENGTH = 2000 def handle_callback(self, callback_data: Dict[str, Any]) -> Dict[str, Any]: """Handle button callback - currently only supports approve/reject.""" ``` Long commands from CONTEXT.md: - execute-phase, plan-phase, discuss-phase, new-project have no timeout - Detection via command name matching Task 1: Add LONG_COMMANDS detection to CommandExecutor src/gsd_openclaw/command_executor.py, tests/test_command_executor.py - Test 1: LONG_COMMANDS contains 'execute-phase', 'plan-phase', 'discuss-phase', 'new-project' - Test 2: _is_long_command returns True for commands in LONG_COMMANDS - Test 3: _is_long_command returns False for commands not in LONG_COMMANDS - Test 4: _is_long_command handles commands with arguments correctly Add long command detection to CommandExecutor: 1. Add LONG_COMMANDS class attribute: ```python class CommandExecutor: ERROR_KEYWORDS: List[str] = [...] DEFAULT_TIMEOUT: float = 60.0 LONG_COMMANDS: Set[str] = {'execute-phase', 'plan-phase', 'discuss-phase', 'new-project'} ``` 2. Add _is_long_command method: ```python def _is_long_command(self, command: str) -> bool: """Check if command requires no timeout. Args: command: The full command string (e.g., '/gsd:execute-phase'). Returns: True if command is in LONG_COMMANDS, False otherwise. """ # Extract command name from /gsd:command format match = re.match(r'^/gsd:(\S+)', command) if match: command_name = match.group(1) return command_name in self.LONG_COMMANDS return False ``` 3. Add TestLongCommands class to tests/test_command_executor.py: - Test LONG_COMMANDS set contents - Test _is_long_command for each long command - Test _is_long_command returns False for regular commands - Test _is_long_command handles commands with args pytest tests/test_command_executor.py::TestLongCommands -x -v CommandExecutor has LONG_COMMANDS set and _is_long_command method with tests Task 2: Update Orchestrator to use adaptive timeout src/gsd_openclaw/orchestrator.py, tests/test_orchestrator.py - Test 1: _is_long_command in Orchestrator returns True for long commands - Test 2: execute() is called with timeout=None for long commands - Test 3: execute() is called with default timeout for regular commands - Test 4: handle_message passes correct timeout based on command type Add adaptive timeout to Orchestrator: 1. Add LONG_COMMANDS and _is_long_command to Orchestrator (mirror CommandExecutor): ```python class Orchestrator: LONG_COMMANDS = {'execute-phase', 'plan-phase', 'discuss-phase', 'new-project'} def _is_long_command(self, command: str) -> bool: """Check if command requires no timeout.""" # Extract command name from parsed command if command in self.LONG_COMMANDS: return True return False ``` 2. Update handle_message to use adaptive timeout: ```python # In handle_message, before execute call: timeout = None if self._is_long_command(command) else self.executor.DEFAULT_TIMEOUT result = self.executor.execute(session, full_command, timeout=timeout) ``` 3. Update _handle_new_project to use timeout=None (already a long command): ```python result = self.executor.execute(session, f"/gsd:new-project {project_name}", timeout=None) ``` 4. Add tests in TestSpecialCommands for adaptive timeout: - Test that long commands get timeout=None - Test that regular commands get DEFAULT_TIMEOUT pytest tests/test_orchestrator.py::TestSpecialCommands -x -v Orchestrator uses adaptive timeout based on command type Task 3: Update handle_callback to support Create New Project button src/gsd_openclaw/orchestrator.py, tests/test_orchestrator.py - Test 1: Callback with action="new-project" creates new session - Test 2: Callback with action="new-project" executes /gsd:new-project command - Test 3: Callback with action="new-project" returns success card - Test 4: Callback with action="new-project" and missing name returns error Update handle_callback to support new-project action: 1. Add new-project action handling to handle_callback: ```python def handle_callback(self, callback_data: Dict[str, Any]) -> Dict[str, Any]: action = callback_data.get("action", "") if action == "new-project": project_name = callback_data.get("name", "") user_id = callback_data.get("user_id", "") if not project_name: return self.formatter.format_error( title="Invalid Request", content="Project name is required" ) return self._handle_new_project(user_id, project_name) # Existing approval handling... if action == "approve": ... ``` 2. Update _handle_use_project to include user_id in button value: ```python CardButton( text="Create New Project", value={ "action": "new-project", "name": project_name, "user_id": user_id } ) ``` 3. Add tests in TestHandleCallback for new-project action 4. Ensure backward compatibility with existing approval callbacks pytest tests/test_orchestrator.py::TestHandleCallback -x -v handle_callback supports "new-project" action from error card button - All tests in tests/test_command_executor.py pass - All tests in tests/test_orchestrator.py pass - Long commands execute with no timeout - Regular commands still use 60 second timeout - "Create New Project" button callback works correctly - CommandExecutor has LONG_COMMANDS detection - Orchestrator uses adaptive timeout based on command type - handle_callback supports "new-project" action - All existing tests still pass After completion, create `.planning/phases/04-gsd-integration/04-03-SUMMARY.md`