From b817b457e9ed19a55bb02dcddf25c69f5e97fbe2 Mon Sep 17 00:00:00 2001 From: starmen1265 Date: Wed, 11 Mar 2026 18:58:35 +0800 Subject: [PATCH] feat(04-03): add long command detection to CommandExecutor - Add LONG_COMMANDS set with execute-phase, plan-phase, discuss-phase, new-project - Add _is_long_command method to detect long-running commands - Add TestLongCommands test class with 5 test cases - Long commands will use no timeout in execute() Co-Authored-By: Claude Opus 4.6 --- .../src/gsd_openclaw/command_executor.py | 22 ++++++++ gsd-openclaw/tests/test_command_executor.py | 53 +++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/gsd-openclaw/src/gsd_openclaw/command_executor.py b/gsd-openclaw/src/gsd_openclaw/command_executor.py index 5d0b6e9..77f1258 100644 --- a/gsd-openclaw/src/gsd_openclaw/command_executor.py +++ b/gsd-openclaw/src/gsd_openclaw/command_executor.py @@ -152,6 +152,9 @@ class CommandExecutor: DEFAULT_TIMEOUT: float = 60.0 + # Commands that require no timeout (long-running operations) + LONG_COMMANDS: set = {'execute-phase', 'plan-phase', 'discuss-phase', 'new-project'} + def __init__(self, output_capture: Optional[OutputCapture] = None): """Initialize CommandExecutor. @@ -196,6 +199,25 @@ class CommandExecutor: return False, None + def _is_long_command(self, command: str) -> bool: + """Check if command requires no timeout. + + Long commands like execute-phase, plan-phase, discuss-phase, and new-project + can take an extended time to complete and should not have a timeout limit. + + 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 + def execute( self, session, # TmuxSession type - avoiding circular import diff --git a/gsd-openclaw/tests/test_command_executor.py b/gsd-openclaw/tests/test_command_executor.py index fe14613..1187d4d 100644 --- a/gsd-openclaw/tests/test_command_executor.py +++ b/gsd-openclaw/tests/test_command_executor.py @@ -339,3 +339,56 @@ class TestCommandExecutorSendCommand: # Verify it was called - the exact implementation should include enter call_args = mock_pane.send_keys.call_args assert call_args is not None + + +class TestLongCommands: + """Tests for long command detection in CommandExecutor.""" + + def test_long_commands_set_contains_expected_commands(self): + """LONG_COMMANDS should contain execute-phase, plan-phase, discuss-phase, new-project.""" + executor = CommandExecutor() + + assert 'execute-phase' in executor.LONG_COMMANDS + assert 'plan-phase' in executor.LONG_COMMANDS + assert 'discuss-phase' in executor.LONG_COMMANDS + assert 'new-project' in executor.LONG_COMMANDS + + def test_is_long_command_returns_true_for_long_commands(self): + """_is_long_command should return True for commands in LONG_COMMANDS.""" + executor = CommandExecutor() + + assert executor._is_long_command('/gsd:execute-phase') is True + assert executor._is_long_command('/gsd:plan-phase') is True + assert executor._is_long_command('/gsd:discuss-phase') is True + assert executor._is_long_command('/gsd:new-project') is True + + def test_is_long_command_returns_false_for_regular_commands(self): + """_is_long_command should return False for commands not in LONG_COMMANDS.""" + executor = CommandExecutor() + + assert executor._is_long_command('/gsd:status') is False + assert executor._is_long_command('/gsd:list') is False + assert executor._is_long_command('/gsd:help') is False + assert executor._is_long_command('/gsd:use') is False + + def test_is_long_command_handles_commands_with_arguments(self): + """_is_long_command should handle commands with arguments correctly.""" + executor = CommandExecutor() + + # Long commands with args should still be detected + assert executor._is_long_command('/gsd:execute-phase 01-core') is True + assert executor._is_long_command('/gsd:plan-phase feature-x') is True + assert executor._is_long_command('/gsd:discuss-phase about architecture') is True + assert executor._is_long_command('/gsd:new-project my-project') is True + + # Regular commands with args should not be detected as long + assert executor._is_long_command('/gsd:status --verbose') is False + assert executor._is_long_command('/gsd:use my-project') is False + + def test_is_long_command_returns_false_for_non_gsd_commands(self): + """_is_long_command should return False for non-/gsd: commands.""" + executor = CommandExecutor() + + assert executor._is_long_command('status') is False + assert executor._is_long_command('/help') is False + assert executor._is_long_command('regular message') is False