diff --git a/gsd-openclaw/.planning/ROADMAP.md b/gsd-openclaw/.planning/ROADMAP.md index c9227ab..1f6f199 100644 --- a/gsd-openclaw/.planning/ROADMAP.md +++ b/gsd-openclaw/.planning/ROADMAP.md @@ -14,7 +14,7 @@ Decimal phases appear between their surrounding integers in numeric order. - [x] **Phase 1: Core Infrastructure** - State persistence and tmux session lifecycle management - [x] **Phase 2: Integration Layer** - SKILL.md definition, response formatting, and prompt templates -- [ ] **Phase 3: Orchestration** - Command routing, user authentication, and bidirectional bridging +- [x] **Phase 3: Orchestration** - Command routing, user authentication, and bidirectional bridging - [ ] **Phase 4: GSD Integration** - Full slash command bridging, approval flows, and progress reporting ## Phase Details @@ -65,9 +65,9 @@ Plans: **Plans**: 3 plans Plans: -- [ ] 03-01-PLAN.md — Command executor with output capture and error detection (BRDG-02, BRDG-03) -- [ ] 03-02-PLAN.md — Approval handler and progress monitor (BRDG-04, BRDG-05) -- [ ] 03-03-PLAN.md — Orchestrator integration with command routing (BRDG-01, OCLW-03, OCLW-05, OCLW-06) +- [x] 03-01-PLAN.md — Command executor with output capture and error detection (BRDG-02, BRDG-03) +- [x] 03-02-PLAN.md — Approval handler and progress monitor (BRDG-04, BRDG-05) +- [x] 03-03-PLAN.md — Orchestrator integration with command routing (BRDG-01, OCLW-03, OCLW-05, OCLW-06) ### Phase 4: GSD Integration **Goal**: All GSD slash commands are fully bridged with complete workflow support @@ -76,7 +76,12 @@ Plans: **Success Criteria** (what must be TRUE): 1. All GSD slash commands work through the bridge (no command left behind) 2. Complete GSD workflow is accessible from any supported chat channel -**Plans**: TBD +**Plans**: 3 plans + +Plans: +- [ ] 04-01-PLAN.md — UserContextManager for per-user project context tracking (BRDG-06) +- [ ] 04-02-PLAN.md — Orchestrator special command handling for /gsd:new-project and /gsd:use (BRDG-06) +- [ ] 04-03-PLAN.md — Long command support and button callback for Create New Project (BRDG-06) ## Progress @@ -87,9 +92,9 @@ Phases execute in numeric order: 1 -> 2 -> 3 -> 4 |-------|----------------|--------|-----------| | 1. Core Infrastructure | 4/4 | Complete | 2026-03-11 | | 2. Integration Layer | 3/3 | Complete | 2026-03-11 | -| 3. Orchestration | 0/3 | Not started | - | -| 4. GSD Integration | 0/TBD | Not started | - | +| 3. Orchestration | 3/3 | Complete | 2026-03-11 | +| 4. GSD Integration | 0/3 | Not started | - | --- *Roadmap created: 2026-03-10* -*Last updated: 2026-03-11 - Planned Phase 3 orchestration* +*Last updated: 2026-03-11 - Planned Phase 4 GSD Integration* diff --git a/gsd-openclaw/.planning/phases/04-gsd-integration/04-01-PLAN.md b/gsd-openclaw/.planning/phases/04-gsd-integration/04-01-PLAN.md new file mode 100644 index 0000000..c5c6cbc --- /dev/null +++ b/gsd-openclaw/.planning/phases/04-gsd-integration/04-01-PLAN.md @@ -0,0 +1,235 @@ +--- +phase: 04-gsd-integration +plan: 01 +type: execute +wave: 1 +depends_on: [] +files_modified: + - src/gsd_openclaw/user_context.py + - src/gsd_openclaw/__init__.py + - tests/test_user_context.py + - tests/conftest.py +autonomous: true +requirements: + - BRDG-06 +user_setup: [] +must_haves: + truths: + - "User's project context persists across chat sessions" + - "User context is stored in isolated per-user files" + - "User context writes are atomic and crash-safe" + - "Each user has independent project tracking" + artifacts: + - path: "src/gsd_openclaw/user_context.py" + provides: "UserContextManager class for per-user project tracking" + min_lines: 100 + - path: "tests/test_user_context.py" + provides: "Tests for UserContextManager" + exports: ["TestUserContextManager", "TestAtomicWrites", "TestMultiUser"] + key_links: + - from: "src/gsd_openclaw/user_context.py" + to: "~/gsd-workspaces/.users/{user_id}/context.json" + via: "atomic write pattern (tempfile + os.replace)" + pattern: "tempfile\\.mkstemp|os\\.replace" +--- + + +Create UserContextManager class for per-user project context tracking with atomic persistence. + +Purpose: Enable users to have persistent project context across chat sessions, supporting the `/gsd:use` command requirement. + +Output: UserContextManager class with load/save operations, atomic writes, and multi-user isolation. + + + +@/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/state_manager.py (atomic write pattern to follow): +```python +def save_state(state: SessionState, state_path: Path) -> None: + """Save session state to file atomically with backup rotation.""" + # 1. Create parent directories if missing + state_path.parent.mkdir(parents=True, exist_ok=True) + # 2. Rotate backup + # 3. Write to temp file in same directory + fd, temp_path = tempfile.mkstemp(dir=state_path.parent, suffix='.tmp') + # 4. Atomic replace + os.replace(temp_path, state_path) +``` + +From src/gsd_openclaw/response_formatter.py: +```python +@dataclass +class CardButton: + """Interactive button for Feishu Card actions.""" + text: str + value: Dict[str, Any] + button_type: str = "primary" # primary, default, danger +``` + +User context storage location (from CONTEXT.md): +- Location: `~/gsd-workspaces/.users/{user_id}/context.json` +- Format: JSON, atomic writes (same as Phase 1 state.json) + +User context data structure (from CONTEXT.md): +- current_project: str - User's current project +- recent_projects: List[str] - Recent 5 projects +- last_active_at: str - ISO timestamp +- preferences: dict - Optional settings + + + + + + + Task 1: Write UserContext dataclass and UserContextManager interface + src/gsd_openclaw/user_context.py, tests/test_user_context.py + + - Test 1: UserContext can be created with all fields (user_id, current_project, recent_projects, last_active_at, preferences) + - Test 2: UserContext.to_dict() returns complete dict with all fields + - Test 3: UserContext.from_dict() reconstructs from dict, ignoring unknown fields + - Test 4: UserContextManager.__init__ sets users_root and creates context_path correctly + - Test 5: UserContextManager.load() returns None for new users (no existing context) + - Test 6: UserContextManager.load() returns UserContext for existing context file + + +Create UserContext dataclass and UserContextManager class skeleton in src/gsd_openclaw/user_context.py: + +1. Create UserContext dataclass with fields: + - schema_version: str = "1.0" + - user_id: str = "" + - current_project: str = "" + - recent_projects: List[str] = field(default_factory=list) + - last_active_at: str = "" + - preferences: dict = field(default_factory=dict) + +2. Add to_dict() and from_dict() methods (follow SessionState pattern exactly) + +3. Create UserContextManager class: + - USERS_ROOT = Path.home() / "gsd-workspaces" / ".users" + - __init__(self, user_id: str, users_root: Optional[Path] = None) + - context_path property: users_root / user_id / "context.json" + - load() -> Optional[UserContext] method + - save(context: UserContext) -> None method (stub for next task) + +4. Create tests/test_user_context.py with TestUserContextManager class + +5. Follow state_manager.py patterns exactly for consistency + + + pytest tests/test_user_context.py -x -v + + UserContext dataclass and UserContextManager skeleton exist with passing tests + + + + Task 2: Implement atomic save with backup rotation + src/gsd_openclaw/user_context.py, tests/test_user_context.py + + - Test 1: save() creates file atomically (no temp files remain after save) + - Test 2: save() creates parent directories if missing + - Test 3: save() rotates backup (context.json -> context.json.bak) + - Test 4: save() overwrites old backup on subsequent saves + - Test 5: save/load roundtrip preserves all data + - Test 6: load() falls back to backup when main file is corrupted + + +Implement UserContextManager.save() with atomic write pattern: + +1. Follow state_manager.py save_state() pattern exactly: + - Create parent directories: context_path.parent.mkdir(parents=True, exist_ok=True) + - Rotate backup: if context_path exists, rename to context.json.bak + - Write to temp file: tempfile.mkstemp(dir=context_path.parent, suffix='.tmp') + - Atomic replace: os.replace(temp_path, context_path) + - Exception handling: clean up temp file on failure + +2. Add TestAtomicWrites class to tests/test_user_context.py with tests for: + - Atomic file creation + - Parent directory creation + - Backup rotation + - Cleanup on failure + +3. Ensure load() has backup fallback (same as load_state in state_manager.py) + +4. Add imports: tempfile, os, json + + + pytest tests/test_user_context.py::TestAtomicWrites -x -v + + UserContextManager.save() implements atomic writes with backup rotation, matching state_manager.py pattern + + + + Task 3: Implement multi-user isolation and convenience methods + src/gsd_openclaw/user_context.py, tests/test_user_context.py, tests/conftest.py, src/gsd_openclaw/__init__.py + + - Test 1: Two users have completely isolated context files + - Test 2: get_current_project() returns current_project or None + - Test 3: set_current_project() updates current_project and adds to recent_projects + - Test 4: set_current_project() maintains only last 5 recent projects + - Test 5: get_recent_projects() returns list of recent projects + - Test 6: New user gets default context created on first access + + +Add convenience methods and multi-user support: + +1. Add to UserContextManager: + - get_current_project() -> Optional[str]: Returns current_project or None + - set_current_project(project: str) -> None: + * Creates default context if none exists + * Updates current_project + * Adds to recent_projects (max 5, remove oldest if needed) + * Updates last_active_at to now + * Calls save() + - get_recent_projects() -> List[str]: Returns recent_projects list + - create_default_context() -> UserContext: Creates new context with defaults + +2. Add TestMultiUser class to tests/test_user_context.py: + - Test isolated context paths for different users + - Test changes to one user don't affect another + +3. Add fixtures to tests/conftest.py: + - tmp_users_root(tmp_path): Temp directory for user contexts + - user_context_manager(tmp_users_root): UserContextManager with temp root + +4. Update src/gsd_openclaw/__init__.py: + - Add: from gsd_openclaw.user_context import UserContext, UserContextManager + - Add to __all__: "UserContext", "UserContextManager" + + + pytest tests/test_user_context.py::TestMultiUser tests/test_user_context.py::TestUserContextManager -x -v + + UserContextManager has convenience methods, multi-user isolation works, exports added to __init__.py + + + + + +- All tests in tests/test_user_context.py pass +- UserContext follows SessionState pattern for consistency +- Atomic write pattern matches state_manager.py exactly +- Multi-user isolation verified through tests + + + +- UserContextManager class exists with load/save/get_current_project/set_current_project methods +- Atomic writes with backup rotation work correctly +- Multi-user isolation verified by tests +- Exports added to __init__.py + + + +After completion, create `.planning/phases/04-gsd-integration/04-01-SUMMARY.md` + diff --git a/gsd-openclaw/.planning/phases/04-gsd-integration/04-02-PLAN.md b/gsd-openclaw/.planning/phases/04-gsd-integration/04-02-PLAN.md new file mode 100644 index 0000000..3c8ecdd --- /dev/null +++ b/gsd-openclaw/.planning/phases/04-gsd-integration/04-02-PLAN.md @@ -0,0 +1,250 @@ +--- +phase: 04-gsd-integration +plan: 02 +type: execute +wave: 1 +depends_on: [] +files_modified: + - src/gsd_openclaw/orchestrator.py + - tests/test_orchestrator.py +autonomous: true +requirements: + - BRDG-06 +user_setup: [] +must_haves: + truths: + - "User can send /gsd:new-project through chat and get a pre-created session" + - "User can send /gsd:use to switch their current project context" + - "User receives clear error when /gsd:use references non-existent project" + - "Generic GSD commands pass through correctly" + artifacts: + - path: "src/gsd_openclaw/orchestrator.py" + provides: "Extended Orchestrator with special command handling" + exports: ["Orchestrator._handle_new_project", "Orchestrator._handle_use_project"] + - path: "tests/test_orchestrator.py" + provides: "Tests for special command handling" + exports: ["TestSpecialCommands"] + key_links: + - from: "src/gsd_openclaw/orchestrator.py" + to: "src/gsd_openclaw/user_context.py" + via: "UserContextManager for context tracking" + pattern: "UserContextManager" + - from: "src/gsd_openclaw/orchestrator.py" + to: "src/gsd_openclaw/tmux_session.py" + via: "TmuxSessionManager.create_session for new-project" + pattern: "create_session" +--- + + +Extend Orchestrator with special command handling for /gsd:new-project and /gsd:use. + +Purpose: Enable key GSD commands that require orchestrator-level logic: pre-creating sessions for new-project and tracking user context for use. + +Output: Orchestrator with _handle_new_project and _handle_use_project methods integrated into handle_message routing. + + + +@/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/orchestrator.py (current implementation): +```python +class Orchestrator: + COMMAND_PATTERN = re.compile(r'^/gsd:(\S+)\s*(.*)$') + MAX_OUTPUT_LENGTH = 2000 + + def parse_command(self, message: str) -> Optional[Tuple[str, str]]: + """Parse a message for /gsd:* command pattern.""" + # Returns (command, args) or None + + def handle_message(self, user_id: str, message: str, channel: str, context: Dict) -> Dict: + """Main entry point - currently auto-creates session for all commands.""" +``` + +From src/gsd_openclaw/response_formatter.py: +```python +@dataclass +class CardButton: + """Interactive button for Feishu Card actions.""" + text: str + value: Dict[str, Any] + button_type: str = "primary" # primary, default, danger +``` + +From src/gsd_openclaw/tmux_session.py: +```python +class TmuxSessionManager: + def create_session(self, project_name: str, ...) -> TmuxSession: + """Create a new tmux session for a project.""" + + def get_session(self, project_name: str) -> Optional[TmuxSession]: + """Get existing session without creating new one.""" +``` + +Special command handling requirements (from CONTEXT.md): +- /gsd:new-project: Pre-create session, then pass command to tmux +- /gsd:use: Track user context, show error + "Create New Project" button if not exists + + + + + + + Task 1: Add UserContextManager to Orchestrator and implement /gsd:new-project handling + src/gsd_openclaw/orchestrator.py, tests/test_orchestrator.py + + - Test 1: Orchestrator initializes with UserContextManager instance + - Test 2: _handle_new_project creates tmux session before executing command + - Test 3: _handle_new_project updates user context with new project + - Test 4: _handle_new_project executes the command in the new session + - Test 5: _handle_new_project returns success card with output + - Test 6: handle_message routes 'new-project' command to _handle_new_project + + +Extend Orchestrator with UserContextManager and new-project handling: + +1. Add import: from gsd_openclaw.user_context import UserContextManager + +2. Update Orchestrator.__init__: + - Add: self.user_context_managers: Dict[str, UserContextManager] = {} + - Add helper: _get_user_context_manager(user_id) -> UserContextManager + +3. Add LONG_COMMANDS class attribute: + ```python + LONG_COMMANDS = {'execute-phase', 'plan-phase', 'discuss-phase', 'new-project'} + ``` + +4. Implement _handle_new_project(user_id: str, project_name: str) -> Dict: + - If project_name is empty, return format_error with "Project name required" + - Create session via tmux_manager.create_session(project_name) + - Execute "/gsd:new-project {project_name}" in the session with timeout=None + - Update user context: user_context.set_current_project(project_name) + - Return formatted result (success or error based on command result) + +5. Update handle_message to route 'new-project' to _handle_new_project + +6. Add TestSpecialCommands class to tests/test_orchestrator.py with tests for new-project + + + pytest tests/test_orchestrator.py::TestSpecialCommands::test_new_project -x -v + + Orchestrator has _handle_new_project method that pre-creates session and updates user context + + + + Task 2: Implement /gsd:use command handling with context tracking + src/gsd_openclaw/orchestrator.py, tests/test_orchestrator.py + + - Test 1: _handle_use_project updates user context to new project + - Test 2: _handle_use_project returns success card when project exists + - Test 3: _handle_use_project returns error card with "Create New Project" button when project doesn't exist + - Test 4: handle_message routes 'use' command to _handle_use_project + - Test 5: _handle_use_project with no args returns error card + - Test 6: Subsequent commands use the project from user context + + +Implement _handle_use_project method: + +1. Implement _handle_use_project(user_id: str, project_name: str) -> Dict: + - If project_name is empty, return format_error with "Project name required" + - Check if project session exists: tmux_manager.get_session(project_name) + - If session exists: + * Update user context: user_context.set_current_project(project_name) + * Return format_success with "Switched to project '{project_name}'" + - If session doesn't exist: + * Return format_error with: + - title: "Project not found" + - content: "Project '{project_name}' does not exist" + - buttons: [CardButton(text="Create New Project", value={"action": "new-project", "name": project_name})] + +2. Update handle_message to route 'use' command to _handle_use_project + +3. Add tests for _handle_use_project in TestSpecialCommands class + +4. Ensure CardButton import is available from response_formatter + + + pytest tests/test_orchestrator.py::TestSpecialCommands::test_use_project tests/test_orchestrator.py::TestSpecialCommands::test_use_nonexistent -x -v + + Orchestrator has _handle_use_project method with context tracking and error handling + + + + Task 3: Update handle_message routing to use user context for project selection + src/gsd_openclaw/orchestrator.py, tests/test_orchestrator.py + + - Test 1: Generic commands use project from user context when not in context param + - Test 2: Generic commands fall back to "default" project if no context + - Test 3: Special commands (new-project, use) are routed correctly + - Test 4: Generic pass-through works for all other commands + + +Update handle_message routing logic: + +1. Modify handle_message to get project from user context: + ```python + def handle_message(self, user_id, message, channel, context): + # Parse command + parsed = self.parse_command(message) + if not parsed: + return format_error(...) + + command, args = parsed + + # Special commands + if command == 'new-project': + return self._handle_new_project(user_id, args) + if command == 'use': + return self._handle_use_project(user_id, args) + + # Generic commands - get project from user context + user_ctx = self._get_user_context_manager(user_id) + project_name = user_ctx.get_current_project() + if not project_name: + project_name = context.get("project", "default") + + # Rest of existing logic... + ``` + +2. Ensure existing tests still pass (backward compatibility) + +3. Add integration tests in TestIntegrationFlows for: + - Command after /gsd:use goes to correct project + - Context persists across multiple commands + + + pytest tests/test_orchestrator.py -x -v + + handle_message routes special commands and uses user context for project selection + + + + + +- All tests in tests/test_orchestrator.py pass +- /gsd:new-project pre-creates session and updates context +- /gsd:use tracks context and shows error for non-existent projects +- Generic commands use user context for project selection + + + +- Orchestrator has _handle_new_project and _handle_use_project methods +- Special commands are routed correctly in handle_message +- User context is used for project selection in generic commands +- Error cards include "Create New Project" button for missing projects + + + +After completion, create `.planning/phases/04-gsd-integration/04-02-SUMMARY.md` + diff --git a/gsd-openclaw/.planning/phases/04-gsd-integration/04-03-PLAN.md b/gsd-openclaw/.planning/phases/04-gsd-integration/04-03-PLAN.md new file mode 100644 index 0000000..5d2eabf --- /dev/null +++ b/gsd-openclaw/.planning/phases/04-gsd-integration/04-03-PLAN.md @@ -0,0 +1,267 @@ +--- +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` +