9.1 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, user_setup, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | user_setup | must_haves | |||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 04-gsd-integration | 03 | execute | 2 |
|
|
true |
|
|
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.
<execution_context> @/home/starmen1265/.claude/get-shit-done/workflows/execute-plan.md @/home/starmen1265/.claude/get-shit-done/templates/summary.md </execution_context>
@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/04-gsd-integration/04-CONTEXT.md @.planning/phases/04-gsd-integration/04-RESEARCH.mdFrom src/gsd_openclaw/command_executor.py:
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:
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
-
Add LONG_COMMANDS class attribute:
class CommandExecutor: ERROR_KEYWORDS: List[str] = [...] DEFAULT_TIMEOUT: float = 60.0 LONG_COMMANDS: Set[str] = {'execute-phase', 'plan-phase', 'discuss-phase', 'new-project'} -
Add _is_long_command method:
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 -
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
-
Add LONG_COMMANDS and _is_long_command to Orchestrator (mirror CommandExecutor):
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 -
Update handle_message to use adaptive timeout:
# 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) -
Update _handle_new_project to use timeout=None (already a long command):
result = self.executor.execute(session, f"/gsd:new-project {project_name}", timeout=None) -
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
-
Add new-project action handling to handle_callback:
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": ... -
Update _handle_use_project to include user_id in button value:
CardButton( text="Create New Project", value={ "action": "new-project", "name": project_name, "user_id": user_id } ) -
Add tests in TestHandleCallback for new-project action
-
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
<success_criteria>
- CommandExecutor has LONG_COMMANDS detection
- Orchestrator uses adaptive timeout based on command type
- handle_callback supports "new-project" action
- All existing tests still pass </success_criteria>