claude-mpm 3.1.3__py3-none-any.whl → 3.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- claude_mpm/__init__.py +3 -3
 - claude_mpm/__main__.py +0 -17
 - claude_mpm/agents/INSTRUCTIONS.md +81 -18
 - claude_mpm/agents/backups/INSTRUCTIONS.md +238 -0
 - claude_mpm/agents/base_agent.json +1 -1
 - claude_mpm/agents/templates/pm.json +25 -0
 - claude_mpm/agents/templates/research.json +2 -1
 - claude_mpm/cli/__init__.py +19 -23
 - claude_mpm/cli/commands/__init__.py +3 -1
 - claude_mpm/cli/commands/agents.py +7 -18
 - claude_mpm/cli/commands/info.py +5 -10
 - claude_mpm/cli/commands/memory.py +232 -0
 - claude_mpm/cli/commands/run.py +501 -28
 - claude_mpm/cli/commands/tickets.py +10 -17
 - claude_mpm/cli/commands/ui.py +15 -37
 - claude_mpm/cli/parser.py +91 -1
 - claude_mpm/cli/utils.py +9 -28
 - claude_mpm/config/socketio_config.py +256 -0
 - claude_mpm/constants.py +9 -0
 - claude_mpm/core/__init__.py +2 -2
 - claude_mpm/core/agent_registry.py +4 -4
 - claude_mpm/core/claude_runner.py +919 -0
 - claude_mpm/core/config.py +21 -1
 - claude_mpm/core/factories.py +1 -1
 - claude_mpm/core/hook_manager.py +196 -0
 - claude_mpm/core/pm_hook_interceptor.py +205 -0
 - claude_mpm/core/service_registry.py +1 -1
 - claude_mpm/core/simple_runner.py +323 -33
 - claude_mpm/core/socketio_pool.py +582 -0
 - claude_mpm/core/websocket_handler.py +233 -0
 - claude_mpm/deployment_paths.py +261 -0
 - claude_mpm/hooks/builtin/memory_hooks_example.py +67 -0
 - claude_mpm/hooks/claude_hooks/hook_handler.py +667 -679
 - claude_mpm/hooks/claude_hooks/hook_wrapper.sh +9 -4
 - claude_mpm/hooks/memory_integration_hook.py +312 -0
 - claude_mpm/models/__init__.py +9 -91
 - claude_mpm/orchestration/__init__.py +1 -1
 - claude_mpm/scripts/claude-mpm-socketio +32 -0
 - claude_mpm/scripts/claude_mpm_monitor.html +567 -0
 - claude_mpm/scripts/install_socketio_server.py +407 -0
 - claude_mpm/scripts/launch_monitor.py +132 -0
 - claude_mpm/scripts/manage_version.py +479 -0
 - claude_mpm/scripts/socketio_daemon.py +181 -0
 - claude_mpm/scripts/socketio_server_manager.py +428 -0
 - claude_mpm/services/__init__.py +5 -0
 - claude_mpm/services/agent_lifecycle_manager.py +76 -25
 - claude_mpm/services/agent_memory_manager.py +684 -0
 - claude_mpm/services/agent_modification_tracker.py +98 -17
 - claude_mpm/services/agent_persistence_service.py +33 -13
 - claude_mpm/services/agent_registry.py +82 -43
 - claude_mpm/services/hook_service.py +362 -0
 - claude_mpm/services/socketio_client_manager.py +474 -0
 - claude_mpm/services/socketio_server.py +698 -0
 - claude_mpm/services/standalone_socketio_server.py +631 -0
 - claude_mpm/services/ticket_manager.py +4 -5
 - claude_mpm/services/{ticket_manager_dependency_injection.py → ticket_manager_di.py} +12 -39
 - claude_mpm/services/{legacy_ticketing_service.py → ticketing_service_original.py} +9 -16
 - claude_mpm/services/version_control/semantic_versioning.py +9 -10
 - claude_mpm/services/websocket_server.py +376 -0
 - claude_mpm/utils/dependency_manager.py +211 -0
 - claude_mpm/utils/import_migration_example.py +80 -0
 - claude_mpm/utils/path_operations.py +0 -20
 - claude_mpm/web/open_dashboard.py +34 -0
 - {claude_mpm-3.1.3.dist-info → claude_mpm-3.2.1.dist-info}/METADATA +20 -9
 - {claude_mpm-3.1.3.dist-info → claude_mpm-3.2.1.dist-info}/RECORD +70 -50
 - claude_mpm-3.2.1.dist-info/entry_points.txt +7 -0
 - claude_mpm/cli_old.py +0 -728
 - claude_mpm/models/common.py +0 -41
 - claude_mpm/models/lifecycle.py +0 -97
 - claude_mpm/models/modification.py +0 -126
 - claude_mpm/models/persistence.py +0 -57
 - claude_mpm/models/registry.py +0 -91
 - claude_mpm/security/__init__.py +0 -8
 - claude_mpm/security/bash_validator.py +0 -393
 - claude_mpm-3.1.3.dist-info/entry_points.txt +0 -4
 - /claude_mpm/{cli_enhancements.py → experimental/cli_enhancements.py} +0 -0
 - {claude_mpm-3.1.3.dist-info → claude_mpm-3.2.1.dist-info}/WHEEL +0 -0
 - {claude_mpm-3.1.3.dist-info → claude_mpm-3.2.1.dist-info}/licenses/LICENSE +0 -0
 - {claude_mpm-3.1.3.dist-info → claude_mpm-3.2.1.dist-info}/top_level.txt +0 -0
 
    
        claude_mpm/core/simple_runner.py
    CHANGED
    
    | 
         @@ -1,4 +1,4 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            """ 
     | 
| 
      
 1 
     | 
    
         
            +
            """Claude runner with both exec and subprocess launch methods."""
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
       3 
3 
     | 
    
         
             
            import json
         
     | 
| 
       4 
4 
     | 
    
         
             
            import os
         
     | 
| 
         @@ -8,6 +8,7 @@ import time 
     | 
|
| 
       8 
8 
     | 
    
         
             
            from datetime import datetime
         
     | 
| 
       9 
9 
     | 
    
         
             
            from pathlib import Path
         
     | 
| 
       10 
10 
     | 
    
         
             
            from typing import Optional
         
     | 
| 
      
 11 
     | 
    
         
            +
            import uuid
         
     | 
| 
       11 
12 
     | 
    
         | 
| 
       12 
13 
     | 
    
         
             
            try:
         
     | 
| 
       13 
14 
     | 
    
         
             
                from claude_mpm.services.agent_deployment import AgentDeploymentService
         
     | 
| 
         @@ -19,28 +20,38 @@ except ImportError: 
     | 
|
| 
       19 
20 
     | 
    
         
             
                from claude_mpm.core.logger import get_logger, get_project_logger, ProjectLogger
         
     | 
| 
       20 
21 
     | 
    
         | 
| 
       21 
22 
     | 
    
         | 
| 
       22 
     | 
    
         
            -
            class  
     | 
| 
      
 23 
     | 
    
         
            +
            class ClaudeRunner:
         
     | 
| 
       23 
24 
     | 
    
         
             
                """
         
     | 
| 
       24 
     | 
    
         
            -
                 
     | 
| 
      
 25 
     | 
    
         
            +
                Claude runner that replaces the entire orchestrator system.
         
     | 
| 
       25 
26 
     | 
    
         | 
| 
       26 
27 
     | 
    
         
             
                This does exactly what we need:
         
     | 
| 
       27 
28 
     | 
    
         
             
                1. Deploy native agents to .claude/agents/
         
     | 
| 
       28 
     | 
    
         
            -
                2. Run Claude CLI with  
     | 
| 
      
 29 
     | 
    
         
            +
                2. Run Claude CLI with either exec or subprocess
         
     | 
| 
       29 
30 
     | 
    
         
             
                3. Extract tickets if needed
         
     | 
| 
       30 
31 
     | 
    
         
             
                4. Handle both interactive and non-interactive modes
         
     | 
| 
      
 32 
     | 
    
         
            +
                
         
     | 
| 
      
 33 
     | 
    
         
            +
                Supports two launch methods:
         
     | 
| 
      
 34 
     | 
    
         
            +
                - exec: Replace current process (default for backward compatibility)
         
     | 
| 
      
 35 
     | 
    
         
            +
                - subprocess: Launch as child process for more control
         
     | 
| 
       31 
36 
     | 
    
         
             
                """
         
     | 
| 
       32 
37 
     | 
    
         | 
| 
       33 
38 
     | 
    
         
             
                def __init__(
         
     | 
| 
       34 
39 
     | 
    
         
             
                    self,
         
     | 
| 
       35 
40 
     | 
    
         
             
                    enable_tickets: bool = True,
         
     | 
| 
       36 
41 
     | 
    
         
             
                    log_level: str = "OFF",
         
     | 
| 
       37 
     | 
    
         
            -
                    claude_args: Optional[list] = None
         
     | 
| 
      
 42 
     | 
    
         
            +
                    claude_args: Optional[list] = None,
         
     | 
| 
      
 43 
     | 
    
         
            +
                    launch_method: str = "exec",  # "exec" or "subprocess"
         
     | 
| 
      
 44 
     | 
    
         
            +
                    enable_websocket: bool = False,
         
     | 
| 
      
 45 
     | 
    
         
            +
                    websocket_port: int = 8765
         
     | 
| 
       38 
46 
     | 
    
         
             
                ):
         
     | 
| 
       39 
     | 
    
         
            -
                    """Initialize the  
     | 
| 
      
 47 
     | 
    
         
            +
                    """Initialize the Claude runner."""
         
     | 
| 
       40 
48 
     | 
    
         
             
                    self.enable_tickets = enable_tickets
         
     | 
| 
       41 
49 
     | 
    
         
             
                    self.log_level = log_level
         
     | 
| 
       42 
     | 
    
         
            -
                    self.logger = get_logger(" 
     | 
| 
      
 50 
     | 
    
         
            +
                    self.logger = get_logger("claude_runner")
         
     | 
| 
       43 
51 
     | 
    
         
             
                    self.claude_args = claude_args or []
         
     | 
| 
      
 52 
     | 
    
         
            +
                    self.launch_method = launch_method
         
     | 
| 
      
 53 
     | 
    
         
            +
                    self.enable_websocket = enable_websocket
         
     | 
| 
      
 54 
     | 
    
         
            +
                    self.websocket_port = websocket_port
         
     | 
| 
       44 
55 
     | 
    
         | 
| 
       45 
56 
     | 
    
         
             
                    # Initialize project logger for session logging
         
     | 
| 
       46 
57 
     | 
    
         
             
                    self.project_logger = None
         
     | 
| 
         @@ -48,7 +59,7 @@ class SimpleClaudeRunner: 
     | 
|
| 
       48 
59 
     | 
    
         
             
                        try:
         
     | 
| 
       49 
60 
     | 
    
         
             
                            self.project_logger = get_project_logger(log_level)
         
     | 
| 
       50 
61 
     | 
    
         
             
                            self.project_logger.log_system(
         
     | 
| 
       51 
     | 
    
         
            -
                                "Initializing  
     | 
| 
      
 62 
     | 
    
         
            +
                                f"Initializing ClaudeRunner with {launch_method} launcher",
         
     | 
| 
       52 
63 
     | 
    
         
             
                                level="INFO",
         
     | 
| 
       53 
64 
     | 
    
         
             
                                component="runner"
         
     | 
| 
       54 
65 
     | 
    
         
             
                            )
         
     | 
| 
         @@ -76,12 +87,16 @@ class SimpleClaudeRunner: 
     | 
|
| 
       76 
87 
     | 
    
         
             
                            self.session_log_file = self.project_logger.session_dir / "system.jsonl"
         
     | 
| 
       77 
88 
     | 
    
         
             
                            self._log_session_event({
         
     | 
| 
       78 
89 
     | 
    
         
             
                                "event": "session_start",
         
     | 
| 
       79 
     | 
    
         
            -
                                "runner": " 
     | 
| 
      
 90 
     | 
    
         
            +
                                "runner": "ClaudeRunner",
         
     | 
| 
       80 
91 
     | 
    
         
             
                                "enable_tickets": enable_tickets,
         
     | 
| 
       81 
     | 
    
         
            -
                                "log_level": log_level
         
     | 
| 
      
 92 
     | 
    
         
            +
                                "log_level": log_level,
         
     | 
| 
      
 93 
     | 
    
         
            +
                                "launch_method": launch_method
         
     | 
| 
       82 
94 
     | 
    
         
             
                            })
         
     | 
| 
       83 
95 
     | 
    
         
             
                        except Exception as e:
         
     | 
| 
       84 
96 
     | 
    
         
             
                            self.logger.debug(f"Failed to create session log file: {e}")
         
     | 
| 
      
 97 
     | 
    
         
            +
                    
         
     | 
| 
      
 98 
     | 
    
         
            +
                    # Initialize WebSocket server reference
         
     | 
| 
      
 99 
     | 
    
         
            +
                    self.websocket_server = None
         
     | 
| 
       85 
100 
     | 
    
         | 
| 
       86 
101 
     | 
    
         
             
                def setup_agents(self) -> bool:
         
     | 
| 
       87 
102 
     | 
    
         
             
                    """Deploy native agents to .claude/agents/."""
         
     | 
| 
         @@ -137,6 +152,28 @@ class SimpleClaudeRunner: 
     | 
|
| 
       137 
152 
     | 
    
         | 
| 
       138 
153 
     | 
    
         
             
                def run_interactive(self, initial_context: Optional[str] = None):
         
     | 
| 
       139 
154 
     | 
    
         
             
                    """Run Claude in interactive mode."""
         
     | 
| 
      
 155 
     | 
    
         
            +
                    # Start WebSocket server if enabled
         
     | 
| 
      
 156 
     | 
    
         
            +
                    if self.enable_websocket:
         
     | 
| 
      
 157 
     | 
    
         
            +
                        try:
         
     | 
| 
      
 158 
     | 
    
         
            +
                            # Lazy import to avoid circular dependencies
         
     | 
| 
      
 159 
     | 
    
         
            +
                            from claude_mpm.services.websocket_server import WebSocketServer
         
     | 
| 
      
 160 
     | 
    
         
            +
                            self.websocket_server = WebSocketServer(port=self.websocket_port)
         
     | 
| 
      
 161 
     | 
    
         
            +
                            self.websocket_server.start()
         
     | 
| 
      
 162 
     | 
    
         
            +
                            
         
     | 
| 
      
 163 
     | 
    
         
            +
                            # Generate session ID
         
     | 
| 
      
 164 
     | 
    
         
            +
                            session_id = str(uuid.uuid4())
         
     | 
| 
      
 165 
     | 
    
         
            +
                            working_dir = os.getcwd()
         
     | 
| 
      
 166 
     | 
    
         
            +
                            
         
     | 
| 
      
 167 
     | 
    
         
            +
                            # Notify session start
         
     | 
| 
      
 168 
     | 
    
         
            +
                            self.websocket_server.session_started(
         
     | 
| 
      
 169 
     | 
    
         
            +
                                session_id=session_id,
         
     | 
| 
      
 170 
     | 
    
         
            +
                                launch_method=self.launch_method,
         
     | 
| 
      
 171 
     | 
    
         
            +
                                working_dir=working_dir
         
     | 
| 
      
 172 
     | 
    
         
            +
                            )
         
     | 
| 
      
 173 
     | 
    
         
            +
                        except Exception as e:
         
     | 
| 
      
 174 
     | 
    
         
            +
                            self.logger.warning(f"Failed to start WebSocket server: {e}")
         
     | 
| 
      
 175 
     | 
    
         
            +
                            self.websocket_server = None
         
     | 
| 
      
 176 
     | 
    
         
            +
                    
         
     | 
| 
       140 
177 
     | 
    
         
             
                    # Get version
         
     | 
| 
       141 
178 
     | 
    
         
             
                    try:
         
     | 
| 
       142 
179 
     | 
    
         
             
                        from claude_mpm import __version__
         
     | 
| 
         @@ -195,31 +232,50 @@ class SimpleClaudeRunner: 
     | 
|
| 
       195 
232 
     | 
    
         
             
                            clean_env.pop(var, None)
         
     | 
| 
       196 
233 
     | 
    
         | 
| 
       197 
234 
     | 
    
         
             
                        # Set the correct working directory for Claude Code
         
     | 
| 
       198 
     | 
    
         
            -
                        #  
     | 
| 
       199 
     | 
    
         
            -
                        # We just need to ensure CLAUDE_WORKSPACE is set correctly
         
     | 
| 
       200 
     | 
    
         
            -
                        current_dir = os.getcwd()
         
     | 
| 
       201 
     | 
    
         
            -
                        clean_env['CLAUDE_WORKSPACE'] = current_dir
         
     | 
| 
       202 
     | 
    
         
            -
                        self.logger.info(f"Working directory: {current_dir}")
         
     | 
| 
       203 
     | 
    
         
            -
                        
         
     | 
| 
       204 
     | 
    
         
            -
                        # Log directory context for debugging
         
     | 
| 
      
 235 
     | 
    
         
            +
                        # If CLAUDE_MPM_USER_PWD is set, use that as the working directory
         
     | 
| 
       205 
236 
     | 
    
         
             
                        if 'CLAUDE_MPM_USER_PWD' in clean_env:
         
     | 
| 
       206 
     | 
    
         
            -
                             
     | 
| 
      
 237 
     | 
    
         
            +
                            user_pwd = clean_env['CLAUDE_MPM_USER_PWD']
         
     | 
| 
      
 238 
     | 
    
         
            +
                            clean_env['CLAUDE_WORKSPACE'] = user_pwd
         
     | 
| 
      
 239 
     | 
    
         
            +
                            # Also change to that directory before launching Claude
         
     | 
| 
      
 240 
     | 
    
         
            +
                            try:
         
     | 
| 
      
 241 
     | 
    
         
            +
                                os.chdir(user_pwd)
         
     | 
| 
      
 242 
     | 
    
         
            +
                                self.logger.info(f"Changed working directory to: {user_pwd}")
         
     | 
| 
      
 243 
     | 
    
         
            +
                            except Exception as e:
         
     | 
| 
      
 244 
     | 
    
         
            +
                                self.logger.warning(f"Could not change to user directory {user_pwd}: {e}")
         
     | 
| 
       207 
245 
     | 
    
         | 
| 
       208 
246 
     | 
    
         
             
                        print("Launching Claude...")
         
     | 
| 
       209 
247 
     | 
    
         | 
| 
       210 
248 
     | 
    
         
             
                        if self.project_logger:
         
     | 
| 
       211 
249 
     | 
    
         
             
                            self.project_logger.log_system(
         
     | 
| 
       212 
     | 
    
         
            -
                                "Launching Claude interactive mode",
         
     | 
| 
      
 250 
     | 
    
         
            +
                                f"Launching Claude interactive mode with {self.launch_method}",
         
     | 
| 
       213 
251 
     | 
    
         
             
                                level="INFO",
         
     | 
| 
       214 
252 
     | 
    
         
             
                                component="session"
         
     | 
| 
       215 
253 
     | 
    
         
             
                            )
         
     | 
| 
       216 
254 
     | 
    
         
             
                            self._log_session_event({
         
     | 
| 
       217 
255 
     | 
    
         
             
                                "event": "launching_claude_interactive",
         
     | 
| 
       218 
     | 
    
         
            -
                                "command": " ".join(cmd)
         
     | 
| 
      
 256 
     | 
    
         
            +
                                "command": " ".join(cmd),
         
     | 
| 
      
 257 
     | 
    
         
            +
                                "method": self.launch_method
         
     | 
| 
       219 
258 
     | 
    
         
             
                            })
         
     | 
| 
       220 
259 
     | 
    
         | 
| 
       221 
     | 
    
         
            -
                        #  
     | 
| 
       222 
     | 
    
         
            -
                         
     | 
| 
      
 260 
     | 
    
         
            +
                        # Notify WebSocket clients
         
     | 
| 
      
 261 
     | 
    
         
            +
                        if self.websocket_server:
         
     | 
| 
      
 262 
     | 
    
         
            +
                            self.websocket_server.claude_status_changed(
         
     | 
| 
      
 263 
     | 
    
         
            +
                                status="starting",
         
     | 
| 
      
 264 
     | 
    
         
            +
                                message="Launching Claude interactive session"
         
     | 
| 
      
 265 
     | 
    
         
            +
                            )
         
     | 
| 
      
 266 
     | 
    
         
            +
                        
         
     | 
| 
      
 267 
     | 
    
         
            +
                        # Launch using selected method
         
     | 
| 
      
 268 
     | 
    
         
            +
                        if self.launch_method == "subprocess":
         
     | 
| 
      
 269 
     | 
    
         
            +
                            self._launch_subprocess_interactive(cmd, clean_env)
         
     | 
| 
      
 270 
     | 
    
         
            +
                        else:
         
     | 
| 
      
 271 
     | 
    
         
            +
                            # Default to exec for backward compatibility
         
     | 
| 
      
 272 
     | 
    
         
            +
                            if self.websocket_server:
         
     | 
| 
      
 273 
     | 
    
         
            +
                                # Notify before exec (we won't be able to after)
         
     | 
| 
      
 274 
     | 
    
         
            +
                                self.websocket_server.claude_status_changed(
         
     | 
| 
      
 275 
     | 
    
         
            +
                                    status="running",
         
     | 
| 
      
 276 
     | 
    
         
            +
                                    message="Claude process started (exec mode)"
         
     | 
| 
      
 277 
     | 
    
         
            +
                                )
         
     | 
| 
      
 278 
     | 
    
         
            +
                            os.execvpe(cmd[0], cmd, clean_env)
         
     | 
| 
       223 
279 
     | 
    
         | 
| 
       224 
280 
     | 
    
         
             
                    except Exception as e:
         
     | 
| 
       225 
281 
     | 
    
         
             
                        print(f"Failed to launch Claude: {e}")
         
     | 
| 
         @@ -234,6 +290,13 @@ class SimpleClaudeRunner: 
     | 
|
| 
       234 
290 
     | 
    
         
             
                                "error": str(e),
         
     | 
| 
       235 
291 
     | 
    
         
             
                                "exception_type": type(e).__name__
         
     | 
| 
       236 
292 
     | 
    
         
             
                            })
         
     | 
| 
      
 293 
     | 
    
         
            +
                        
         
     | 
| 
      
 294 
     | 
    
         
            +
                        # Notify WebSocket clients of error
         
     | 
| 
      
 295 
     | 
    
         
            +
                        if self.websocket_server:
         
     | 
| 
      
 296 
     | 
    
         
            +
                            self.websocket_server.claude_status_changed(
         
     | 
| 
      
 297 
     | 
    
         
            +
                                status="error",
         
     | 
| 
      
 298 
     | 
    
         
            +
                                message=f"Failed to launch Claude: {e}"
         
     | 
| 
      
 299 
     | 
    
         
            +
                            )
         
     | 
| 
       237 
300 
     | 
    
         
             
                        # Fallback to subprocess
         
     | 
| 
       238 
301 
     | 
    
         
             
                        try:
         
     | 
| 
       239 
302 
     | 
    
         
             
                            # Use the same clean_env we prepared earlier
         
     | 
| 
         @@ -266,6 +329,28 @@ class SimpleClaudeRunner: 
     | 
|
| 
       266 
329 
     | 
    
         
             
                    """Run Claude with a single prompt and return success status."""
         
     | 
| 
       267 
330 
     | 
    
         
             
                    start_time = time.time()
         
     | 
| 
       268 
331 
     | 
    
         | 
| 
      
 332 
     | 
    
         
            +
                    # Start WebSocket server if enabled
         
     | 
| 
      
 333 
     | 
    
         
            +
                    if self.enable_websocket:
         
     | 
| 
      
 334 
     | 
    
         
            +
                        try:
         
     | 
| 
      
 335 
     | 
    
         
            +
                            # Lazy import to avoid circular dependencies
         
     | 
| 
      
 336 
     | 
    
         
            +
                            from claude_mpm.services.websocket_server import WebSocketServer
         
     | 
| 
      
 337 
     | 
    
         
            +
                            self.websocket_server = WebSocketServer(port=self.websocket_port)
         
     | 
| 
      
 338 
     | 
    
         
            +
                            self.websocket_server.start()
         
     | 
| 
      
 339 
     | 
    
         
            +
                            
         
     | 
| 
      
 340 
     | 
    
         
            +
                            # Generate session ID
         
     | 
| 
      
 341 
     | 
    
         
            +
                            session_id = str(uuid.uuid4())
         
     | 
| 
      
 342 
     | 
    
         
            +
                            working_dir = os.getcwd()
         
     | 
| 
      
 343 
     | 
    
         
            +
                            
         
     | 
| 
      
 344 
     | 
    
         
            +
                            # Notify session start
         
     | 
| 
      
 345 
     | 
    
         
            +
                            self.websocket_server.session_started(
         
     | 
| 
      
 346 
     | 
    
         
            +
                                session_id=session_id,
         
     | 
| 
      
 347 
     | 
    
         
            +
                                launch_method="oneshot",
         
     | 
| 
      
 348 
     | 
    
         
            +
                                working_dir=working_dir
         
     | 
| 
      
 349 
     | 
    
         
            +
                            )
         
     | 
| 
      
 350 
     | 
    
         
            +
                        except Exception as e:
         
     | 
| 
      
 351 
     | 
    
         
            +
                            self.logger.warning(f"Failed to start WebSocket server: {e}")
         
     | 
| 
      
 352 
     | 
    
         
            +
                            self.websocket_server = None
         
     | 
| 
      
 353 
     | 
    
         
            +
                    
         
     | 
| 
       269 
354 
     | 
    
         
             
                    # Check for /mpm: commands
         
     | 
| 
       270 
355 
     | 
    
         
             
                    if prompt.strip().startswith("/mpm:"):
         
     | 
| 
       271 
356 
     | 
    
         
             
                        return self._handle_mpm_command(prompt.strip())
         
     | 
| 
         @@ -312,15 +397,19 @@ class SimpleClaudeRunner: 
     | 
|
| 
       312 
397 
     | 
    
         
             
                        env = os.environ.copy()
         
     | 
| 
       313 
398 
     | 
    
         | 
| 
       314 
399 
     | 
    
         
             
                        # Set the correct working directory for Claude Code
         
     | 
| 
       315 
     | 
    
         
            -
                        # WHY: We're already in the user's directory thanks to __main__.py restoration
         
     | 
| 
       316 
     | 
    
         
            -
                        # We just need to ensure CLAUDE_WORKSPACE is set correctly
         
     | 
| 
       317 
     | 
    
         
            -
                        current_dir = os.getcwd()
         
     | 
| 
       318 
     | 
    
         
            -
                        env['CLAUDE_WORKSPACE'] = current_dir
         
     | 
| 
       319 
     | 
    
         
            -
                        self.logger.info(f"Working directory for subprocess: {current_dir}")
         
     | 
| 
       320 
     | 
    
         
            -
                        
         
     | 
| 
       321 
     | 
    
         
            -
                        # Log directory context for debugging
         
     | 
| 
       322 
400 
     | 
    
         
             
                        if 'CLAUDE_MPM_USER_PWD' in env:
         
     | 
| 
       323 
     | 
    
         
            -
                             
     | 
| 
      
 401 
     | 
    
         
            +
                            user_pwd = env['CLAUDE_MPM_USER_PWD']
         
     | 
| 
      
 402 
     | 
    
         
            +
                            env['CLAUDE_WORKSPACE'] = user_pwd
         
     | 
| 
      
 403 
     | 
    
         
            +
                            # Change to that directory before running Claude
         
     | 
| 
      
 404 
     | 
    
         
            +
                            try:
         
     | 
| 
      
 405 
     | 
    
         
            +
                                original_cwd = os.getcwd()
         
     | 
| 
      
 406 
     | 
    
         
            +
                                os.chdir(user_pwd)
         
     | 
| 
      
 407 
     | 
    
         
            +
                                self.logger.info(f"Changed working directory to: {user_pwd}")
         
     | 
| 
      
 408 
     | 
    
         
            +
                            except Exception as e:
         
     | 
| 
      
 409 
     | 
    
         
            +
                                self.logger.warning(f"Could not change to user directory {user_pwd}: {e}")
         
     | 
| 
      
 410 
     | 
    
         
            +
                                original_cwd = None
         
     | 
| 
      
 411 
     | 
    
         
            +
                        else:
         
     | 
| 
      
 412 
     | 
    
         
            +
                            original_cwd = None
         
     | 
| 
       324 
413 
     | 
    
         | 
| 
       325 
414 
     | 
    
         
             
                        # Run Claude
         
     | 
| 
       326 
415 
     | 
    
         
             
                        if self.project_logger:
         
     | 
| 
         @@ -330,15 +419,31 @@ class SimpleClaudeRunner: 
     | 
|
| 
       330 
419 
     | 
    
         
             
                                component="session"
         
     | 
| 
       331 
420 
     | 
    
         
             
                            )
         
     | 
| 
       332 
421 
     | 
    
         | 
| 
      
 422 
     | 
    
         
            +
                        # Notify WebSocket clients
         
     | 
| 
      
 423 
     | 
    
         
            +
                        if self.websocket_server:
         
     | 
| 
      
 424 
     | 
    
         
            +
                            self.websocket_server.claude_status_changed(
         
     | 
| 
      
 425 
     | 
    
         
            +
                                status="running",
         
     | 
| 
      
 426 
     | 
    
         
            +
                                message="Executing Claude oneshot command"
         
     | 
| 
      
 427 
     | 
    
         
            +
                            )
         
     | 
| 
      
 428 
     | 
    
         
            +
                        
         
     | 
| 
       333 
429 
     | 
    
         
             
                        result = subprocess.run(cmd, capture_output=True, text=True, env=env)
         
     | 
| 
       334 
430 
     | 
    
         | 
| 
       335 
     | 
    
         
            -
                        #  
     | 
| 
      
 431 
     | 
    
         
            +
                        # Restore original directory if we changed it
         
     | 
| 
      
 432 
     | 
    
         
            +
                        if original_cwd:
         
     | 
| 
      
 433 
     | 
    
         
            +
                            try:
         
     | 
| 
      
 434 
     | 
    
         
            +
                                os.chdir(original_cwd)
         
     | 
| 
      
 435 
     | 
    
         
            +
                            except Exception:
         
     | 
| 
      
 436 
     | 
    
         
            +
                                pass
         
     | 
| 
       336 
437 
     | 
    
         
             
                        execution_time = time.time() - start_time
         
     | 
| 
       337 
438 
     | 
    
         | 
| 
       338 
439 
     | 
    
         
             
                        if result.returncode == 0:
         
     | 
| 
       339 
440 
     | 
    
         
             
                            response = result.stdout.strip()
         
     | 
| 
       340 
441 
     | 
    
         
             
                            print(response)
         
     | 
| 
       341 
442 
     | 
    
         | 
| 
      
 443 
     | 
    
         
            +
                            # Broadcast output to WebSocket clients
         
     | 
| 
      
 444 
     | 
    
         
            +
                            if self.websocket_server and response:
         
     | 
| 
      
 445 
     | 
    
         
            +
                                self.websocket_server.claude_output(response, "stdout")
         
     | 
| 
      
 446 
     | 
    
         
            +
                            
         
     | 
| 
       342 
447 
     | 
    
         
             
                            if self.project_logger:
         
     | 
| 
       343 
448 
     | 
    
         
             
                                # Log successful completion
         
     | 
| 
       344 
449 
     | 
    
         
             
                                self.project_logger.log_system(
         
     | 
| 
         @@ -368,6 +473,17 @@ class SimpleClaudeRunner: 
     | 
|
| 
       368 
473 
     | 
    
         
             
                                        "indicators": [p for p in ["Task(", "subagent_type=", "engineer agent", "qa agent"] 
         
     | 
| 
       369 
474 
     | 
    
         
             
                                                      if p.lower() in response.lower()]
         
     | 
| 
       370 
475 
     | 
    
         
             
                                    })
         
     | 
| 
      
 476 
     | 
    
         
            +
                                    
         
     | 
| 
      
 477 
     | 
    
         
            +
                                    # Notify WebSocket clients about delegation
         
     | 
| 
      
 478 
     | 
    
         
            +
                                    if self.websocket_server:
         
     | 
| 
      
 479 
     | 
    
         
            +
                                        # Try to extract agent name
         
     | 
| 
      
 480 
     | 
    
         
            +
                                        agent_name = self._extract_agent_from_response(response)
         
     | 
| 
      
 481 
     | 
    
         
            +
                                        if agent_name:
         
     | 
| 
      
 482 
     | 
    
         
            +
                                            self.websocket_server.agent_delegated(
         
     | 
| 
      
 483 
     | 
    
         
            +
                                                agent=agent_name,
         
     | 
| 
      
 484 
     | 
    
         
            +
                                                task=prompt[:100],
         
     | 
| 
      
 485 
     | 
    
         
            +
                                                status="detected"
         
     | 
| 
      
 486 
     | 
    
         
            +
                                            )
         
     | 
| 
       371 
487 
     | 
    
         | 
| 
       372 
488 
     | 
    
         
             
                            # Extract tickets if enabled
         
     | 
| 
       373 
489 
     | 
    
         
             
                            if self.enable_tickets and self.ticket_manager and response:
         
     | 
| 
         @@ -378,6 +494,14 @@ class SimpleClaudeRunner: 
     | 
|
| 
       378 
494 
     | 
    
         
             
                            error_msg = result.stderr or "Unknown error"
         
     | 
| 
       379 
495 
     | 
    
         
             
                            print(f"Error: {error_msg}")
         
     | 
| 
       380 
496 
     | 
    
         | 
| 
      
 497 
     | 
    
         
            +
                            # Broadcast error to WebSocket clients
         
     | 
| 
      
 498 
     | 
    
         
            +
                            if self.websocket_server:
         
     | 
| 
      
 499 
     | 
    
         
            +
                                self.websocket_server.claude_output(error_msg, "stderr")
         
     | 
| 
      
 500 
     | 
    
         
            +
                                self.websocket_server.claude_status_changed(
         
     | 
| 
      
 501 
     | 
    
         
            +
                                    status="error",
         
     | 
| 
      
 502 
     | 
    
         
            +
                                    message=f"Command failed with code {result.returncode}"
         
     | 
| 
      
 503 
     | 
    
         
            +
                                )
         
     | 
| 
      
 504 
     | 
    
         
            +
                            
         
     | 
| 
       381 
505 
     | 
    
         
             
                            if self.project_logger:
         
     | 
| 
       382 
506 
     | 
    
         
             
                                self.project_logger.log_system(
         
     | 
| 
       383 
507 
     | 
    
         
             
                                    f"Non-interactive session failed: {error_msg}",
         
     | 
| 
         @@ -423,6 +547,14 @@ class SimpleClaudeRunner: 
     | 
|
| 
       423 
547 
     | 
    
         
             
                                )
         
     | 
| 
       424 
548 
     | 
    
         
             
                            except Exception as e:
         
     | 
| 
       425 
549 
     | 
    
         
             
                                self.logger.debug(f"Failed to log session summary: {e}")
         
     | 
| 
      
 550 
     | 
    
         
            +
                        
         
     | 
| 
      
 551 
     | 
    
         
            +
                        # End WebSocket session
         
     | 
| 
      
 552 
     | 
    
         
            +
                        if self.websocket_server:
         
     | 
| 
      
 553 
     | 
    
         
            +
                            self.websocket_server.claude_status_changed(
         
     | 
| 
      
 554 
     | 
    
         
            +
                                status="stopped",
         
     | 
| 
      
 555 
     | 
    
         
            +
                                message="Session completed"
         
     | 
| 
      
 556 
     | 
    
         
            +
                            )
         
     | 
| 
      
 557 
     | 
    
         
            +
                            self.websocket_server.session_ended()
         
     | 
| 
       426 
558 
     | 
    
         | 
| 
       427 
559 
     | 
    
         
             
                def _extract_tickets(self, text: str):
         
     | 
| 
       428 
560 
     | 
    
         
             
                    """Extract tickets from Claude's response."""
         
     | 
| 
         @@ -509,6 +641,28 @@ class SimpleClaudeRunner: 
     | 
|
| 
       509 
641 
     | 
    
         
             
                    text_lower = text.lower()
         
     | 
| 
       510 
642 
     | 
    
         
             
                    return any(pattern.lower() in text_lower for pattern in delegation_patterns)
         
     | 
| 
       511 
643 
     | 
    
         | 
| 
      
 644 
     | 
    
         
            +
                def _extract_agent_from_response(self, text: str) -> Optional[str]:
         
     | 
| 
      
 645 
     | 
    
         
            +
                    """Try to extract agent name from delegation response."""
         
     | 
| 
      
 646 
     | 
    
         
            +
                    # Look for common patterns
         
     | 
| 
      
 647 
     | 
    
         
            +
                    import re
         
     | 
| 
      
 648 
     | 
    
         
            +
                    
         
     | 
| 
      
 649 
     | 
    
         
            +
                    # Pattern 1: subagent_type="agent_name"
         
     | 
| 
      
 650 
     | 
    
         
            +
                    match = re.search(r'subagent_type=["\']([^"\']*)["\'\)]', text)
         
     | 
| 
      
 651 
     | 
    
         
            +
                    if match:
         
     | 
| 
      
 652 
     | 
    
         
            +
                        return match.group(1)
         
     | 
| 
      
 653 
     | 
    
         
            +
                    
         
     | 
| 
      
 654 
     | 
    
         
            +
                    # Pattern 2: "engineer agent" etc
         
     | 
| 
      
 655 
     | 
    
         
            +
                    agent_names = [
         
     | 
| 
      
 656 
     | 
    
         
            +
                        "engineer", "qa", "documentation", "research", 
         
     | 
| 
      
 657 
     | 
    
         
            +
                        "security", "ops", "version_control", "data_engineer"
         
     | 
| 
      
 658 
     | 
    
         
            +
                    ]
         
     | 
| 
      
 659 
     | 
    
         
            +
                    text_lower = text.lower()
         
     | 
| 
      
 660 
     | 
    
         
            +
                    for agent in agent_names:
         
     | 
| 
      
 661 
     | 
    
         
            +
                        if f"{agent} agent" in text_lower or f"agent: {agent}" in text_lower:
         
     | 
| 
      
 662 
     | 
    
         
            +
                            return agent
         
     | 
| 
      
 663 
     | 
    
         
            +
                    
         
     | 
| 
      
 664 
     | 
    
         
            +
                    return None
         
     | 
| 
      
 665 
     | 
    
         
            +
                
         
     | 
| 
       512 
666 
     | 
    
         
             
                def _handle_mpm_command(self, prompt: str) -> bool:
         
     | 
| 
       513 
667 
     | 
    
         
             
                    """Handle /mpm: commands directly without going to Claude."""
         
     | 
| 
       514 
668 
     | 
    
         
             
                    try:
         
     | 
| 
         @@ -584,6 +738,138 @@ class SimpleClaudeRunner: 
     | 
|
| 
       584 
738 
     | 
    
         
             
                                f.write(json.dumps(log_entry) + '\n')
         
     | 
| 
       585 
739 
     | 
    
         
             
                        except Exception as e:
         
     | 
| 
       586 
740 
     | 
    
         
             
                            self.logger.debug(f"Failed to log session event: {e}")
         
     | 
| 
      
 741 
     | 
    
         
            +
                
         
     | 
| 
      
 742 
     | 
    
         
            +
                def _launch_subprocess_interactive(self, cmd: list, env: dict):
         
     | 
| 
      
 743 
     | 
    
         
            +
                    """Launch Claude as a subprocess with PTY for interactive mode."""
         
     | 
| 
      
 744 
     | 
    
         
            +
                    import pty
         
     | 
| 
      
 745 
     | 
    
         
            +
                    import select
         
     | 
| 
      
 746 
     | 
    
         
            +
                    import termios
         
     | 
| 
      
 747 
     | 
    
         
            +
                    import tty
         
     | 
| 
      
 748 
     | 
    
         
            +
                    import signal
         
     | 
| 
      
 749 
     | 
    
         
            +
                    
         
     | 
| 
      
 750 
     | 
    
         
            +
                    # Save original terminal settings
         
     | 
| 
      
 751 
     | 
    
         
            +
                    original_tty = None
         
     | 
| 
      
 752 
     | 
    
         
            +
                    if sys.stdin.isatty():
         
     | 
| 
      
 753 
     | 
    
         
            +
                        original_tty = termios.tcgetattr(sys.stdin)
         
     | 
| 
      
 754 
     | 
    
         
            +
                    
         
     | 
| 
      
 755 
     | 
    
         
            +
                    # Create PTY
         
     | 
| 
      
 756 
     | 
    
         
            +
                    master_fd, slave_fd = pty.openpty()
         
     | 
| 
      
 757 
     | 
    
         
            +
                    
         
     | 
| 
      
 758 
     | 
    
         
            +
                    try:
         
     | 
| 
      
 759 
     | 
    
         
            +
                        # Start Claude process
         
     | 
| 
      
 760 
     | 
    
         
            +
                        process = subprocess.Popen(
         
     | 
| 
      
 761 
     | 
    
         
            +
                            cmd,
         
     | 
| 
      
 762 
     | 
    
         
            +
                            stdin=slave_fd,
         
     | 
| 
      
 763 
     | 
    
         
            +
                            stdout=slave_fd,
         
     | 
| 
      
 764 
     | 
    
         
            +
                            stderr=slave_fd,
         
     | 
| 
      
 765 
     | 
    
         
            +
                            env=env
         
     | 
| 
      
 766 
     | 
    
         
            +
                        )
         
     | 
| 
      
 767 
     | 
    
         
            +
                        
         
     | 
| 
      
 768 
     | 
    
         
            +
                        # Close slave in parent
         
     | 
| 
      
 769 
     | 
    
         
            +
                        os.close(slave_fd)
         
     | 
| 
      
 770 
     | 
    
         
            +
                        
         
     | 
| 
      
 771 
     | 
    
         
            +
                        if self.project_logger:
         
     | 
| 
      
 772 
     | 
    
         
            +
                            self.project_logger.log_system(
         
     | 
| 
      
 773 
     | 
    
         
            +
                                f"Claude subprocess started with PID {process.pid}",
         
     | 
| 
      
 774 
     | 
    
         
            +
                                level="INFO",
         
     | 
| 
      
 775 
     | 
    
         
            +
                                component="subprocess"
         
     | 
| 
      
 776 
     | 
    
         
            +
                            )
         
     | 
| 
      
 777 
     | 
    
         
            +
                        
         
     | 
| 
      
 778 
     | 
    
         
            +
                        # Notify WebSocket clients
         
     | 
| 
      
 779 
     | 
    
         
            +
                        if self.websocket_server:
         
     | 
| 
      
 780 
     | 
    
         
            +
                            self.websocket_server.claude_status_changed(
         
     | 
| 
      
 781 
     | 
    
         
            +
                                status="running",
         
     | 
| 
      
 782 
     | 
    
         
            +
                                pid=process.pid,
         
     | 
| 
      
 783 
     | 
    
         
            +
                                message="Claude subprocess started"
         
     | 
| 
      
 784 
     | 
    
         
            +
                            )
         
     | 
| 
      
 785 
     | 
    
         
            +
                        
         
     | 
| 
      
 786 
     | 
    
         
            +
                        # Set terminal to raw mode for proper interaction
         
     | 
| 
      
 787 
     | 
    
         
            +
                        if sys.stdin.isatty():
         
     | 
| 
      
 788 
     | 
    
         
            +
                            tty.setraw(sys.stdin)
         
     | 
| 
      
 789 
     | 
    
         
            +
                        
         
     | 
| 
      
 790 
     | 
    
         
            +
                        # Handle Ctrl+C gracefully
         
     | 
| 
      
 791 
     | 
    
         
            +
                        def signal_handler(signum, frame):
         
     | 
| 
      
 792 
     | 
    
         
            +
                            if process.poll() is None:
         
     | 
| 
      
 793 
     | 
    
         
            +
                                process.terminate()
         
     | 
| 
      
 794 
     | 
    
         
            +
                            raise KeyboardInterrupt()
         
     | 
| 
      
 795 
     | 
    
         
            +
                        
         
     | 
| 
      
 796 
     | 
    
         
            +
                        signal.signal(signal.SIGINT, signal_handler)
         
     | 
| 
      
 797 
     | 
    
         
            +
                        
         
     | 
| 
      
 798 
     | 
    
         
            +
                        # I/O loop
         
     | 
| 
      
 799 
     | 
    
         
            +
                        while True:
         
     | 
| 
      
 800 
     | 
    
         
            +
                            # Check if process is still running
         
     | 
| 
      
 801 
     | 
    
         
            +
                            if process.poll() is not None:
         
     | 
| 
      
 802 
     | 
    
         
            +
                                break
         
     | 
| 
      
 803 
     | 
    
         
            +
                            
         
     | 
| 
      
 804 
     | 
    
         
            +
                            # Check for data from Claude or stdin
         
     | 
| 
      
 805 
     | 
    
         
            +
                            r, _, _ = select.select([master_fd, sys.stdin], [], [], 0)
         
     | 
| 
      
 806 
     | 
    
         
            +
                            
         
     | 
| 
      
 807 
     | 
    
         
            +
                            if master_fd in r:
         
     | 
| 
      
 808 
     | 
    
         
            +
                                try:
         
     | 
| 
      
 809 
     | 
    
         
            +
                                    data = os.read(master_fd, 4096)
         
     | 
| 
      
 810 
     | 
    
         
            +
                                    if data:
         
     | 
| 
      
 811 
     | 
    
         
            +
                                        os.write(sys.stdout.fileno(), data)
         
     | 
| 
      
 812 
     | 
    
         
            +
                                        # Broadcast output to WebSocket clients
         
     | 
| 
      
 813 
     | 
    
         
            +
                                        if self.websocket_server:
         
     | 
| 
      
 814 
     | 
    
         
            +
                                            try:
         
     | 
| 
      
 815 
     | 
    
         
            +
                                                # Decode and send
         
     | 
| 
      
 816 
     | 
    
         
            +
                                                output = data.decode('utf-8', errors='replace')
         
     | 
| 
      
 817 
     | 
    
         
            +
                                                self.websocket_server.claude_output(output, "stdout")
         
     | 
| 
      
 818 
     | 
    
         
            +
                                            except Exception as e:
         
     | 
| 
      
 819 
     | 
    
         
            +
                                                self.logger.debug(f"Failed to broadcast output: {e}")
         
     | 
| 
      
 820 
     | 
    
         
            +
                                    else:
         
     | 
| 
      
 821 
     | 
    
         
            +
                                        break  # EOF
         
     | 
| 
      
 822 
     | 
    
         
            +
                                except OSError:
         
     | 
| 
      
 823 
     | 
    
         
            +
                                    break
         
     | 
| 
      
 824 
     | 
    
         
            +
                            
         
     | 
| 
      
 825 
     | 
    
         
            +
                            if sys.stdin in r:
         
     | 
| 
      
 826 
     | 
    
         
            +
                                try:
         
     | 
| 
      
 827 
     | 
    
         
            +
                                    data = os.read(sys.stdin.fileno(), 4096)
         
     | 
| 
      
 828 
     | 
    
         
            +
                                    if data:
         
     | 
| 
      
 829 
     | 
    
         
            +
                                        os.write(master_fd, data)
         
     | 
| 
      
 830 
     | 
    
         
            +
                                except OSError:
         
     | 
| 
      
 831 
     | 
    
         
            +
                                    break
         
     | 
| 
      
 832 
     | 
    
         
            +
                        
         
     | 
| 
      
 833 
     | 
    
         
            +
                        # Wait for process to complete
         
     | 
| 
      
 834 
     | 
    
         
            +
                        process.wait()
         
     | 
| 
      
 835 
     | 
    
         
            +
                        
         
     | 
| 
      
 836 
     | 
    
         
            +
                        if self.project_logger:
         
     | 
| 
      
 837 
     | 
    
         
            +
                            self.project_logger.log_system(
         
     | 
| 
      
 838 
     | 
    
         
            +
                                f"Claude subprocess exited with code {process.returncode}",
         
     | 
| 
      
 839 
     | 
    
         
            +
                                level="INFO",
         
     | 
| 
      
 840 
     | 
    
         
            +
                                component="subprocess"
         
     | 
| 
      
 841 
     | 
    
         
            +
                            )
         
     | 
| 
      
 842 
     | 
    
         
            +
                        
         
     | 
| 
      
 843 
     | 
    
         
            +
                        # Notify WebSocket clients
         
     | 
| 
      
 844 
     | 
    
         
            +
                        if self.websocket_server:
         
     | 
| 
      
 845 
     | 
    
         
            +
                            self.websocket_server.claude_status_changed(
         
     | 
| 
      
 846 
     | 
    
         
            +
                                status="stopped",
         
     | 
| 
      
 847 
     | 
    
         
            +
                                message=f"Claude subprocess exited with code {process.returncode}"
         
     | 
| 
      
 848 
     | 
    
         
            +
                            )
         
     | 
| 
      
 849 
     | 
    
         
            +
                        
         
     | 
| 
      
 850 
     | 
    
         
            +
                    finally:
         
     | 
| 
      
 851 
     | 
    
         
            +
                        # Restore terminal
         
     | 
| 
      
 852 
     | 
    
         
            +
                        if original_tty and sys.stdin.isatty():
         
     | 
| 
      
 853 
     | 
    
         
            +
                            termios.tcsetattr(sys.stdin, termios.TCSADRAIN, original_tty)
         
     | 
| 
      
 854 
     | 
    
         
            +
                        
         
     | 
| 
      
 855 
     | 
    
         
            +
                        # Close PTY
         
     | 
| 
      
 856 
     | 
    
         
            +
                        try:
         
     | 
| 
      
 857 
     | 
    
         
            +
                            os.close(master_fd)
         
     | 
| 
      
 858 
     | 
    
         
            +
                        except:
         
     | 
| 
      
 859 
     | 
    
         
            +
                            pass
         
     | 
| 
      
 860 
     | 
    
         
            +
                        
         
     | 
| 
      
 861 
     | 
    
         
            +
                        # Ensure process is terminated
         
     | 
| 
      
 862 
     | 
    
         
            +
                        if 'process' in locals() and process.poll() is None:
         
     | 
| 
      
 863 
     | 
    
         
            +
                            process.terminate()
         
     | 
| 
      
 864 
     | 
    
         
            +
                            try:
         
     | 
| 
      
 865 
     | 
    
         
            +
                                process.wait(timeout=2)
         
     | 
| 
      
 866 
     | 
    
         
            +
                            except subprocess.TimeoutExpired:
         
     | 
| 
      
 867 
     | 
    
         
            +
                                process.kill()
         
     | 
| 
      
 868 
     | 
    
         
            +
                                process.wait()
         
     | 
| 
      
 869 
     | 
    
         
            +
                        
         
     | 
| 
      
 870 
     | 
    
         
            +
                        # End WebSocket session if in subprocess mode
         
     | 
| 
      
 871 
     | 
    
         
            +
                        if self.websocket_server:
         
     | 
| 
      
 872 
     | 
    
         
            +
                            self.websocket_server.session_ended()
         
     | 
| 
       587 
873 
     | 
    
         | 
| 
       588 
874 
     | 
    
         | 
| 
       589 
875 
     | 
    
         
             
            def create_simple_context() -> str:
         
     | 
| 
         @@ -612,10 +898,14 @@ automatically normalize them to lowercase-hyphenated format for the Task tool. 
     | 
|
| 
       612 
898 
     | 
    
         
             
            Work efficiently and delegate appropriately to subagents when needed."""
         
     | 
| 
       613 
899 
     | 
    
         | 
| 
       614 
900 
     | 
    
         | 
| 
      
 901 
     | 
    
         
            +
            # Backward compatibility alias
         
     | 
| 
      
 902 
     | 
    
         
            +
            SimpleClaudeRunner = ClaudeRunner
         
     | 
| 
      
 903 
     | 
    
         
            +
             
     | 
| 
      
 904 
     | 
    
         
            +
             
     | 
| 
       615 
905 
     | 
    
         
             
            # Convenience functions for backward compatibility
         
     | 
| 
       616 
906 
     | 
    
         
             
            def run_claude_interactive(context: Optional[str] = None):
         
     | 
| 
       617 
907 
     | 
    
         
             
                """Run Claude interactively with optional context."""
         
     | 
| 
       618 
     | 
    
         
            -
                runner =  
     | 
| 
      
 908 
     | 
    
         
            +
                runner = ClaudeRunner()
         
     | 
| 
       619 
909 
     | 
    
         
             
                if context is None:
         
     | 
| 
       620 
910 
     | 
    
         
             
                    context = create_simple_context()
         
     | 
| 
       621 
911 
     | 
    
         
             
                runner.run_interactive(context)
         
     | 
| 
         @@ -623,7 +913,7 @@ def run_claude_interactive(context: Optional[str] = None): 
     | 
|
| 
       623 
913 
     | 
    
         | 
| 
       624 
914 
     | 
    
         
             
            def run_claude_oneshot(prompt: str, context: Optional[str] = None) -> bool:
         
     | 
| 
       625 
915 
     | 
    
         
             
                """Run Claude with a single prompt."""
         
     | 
| 
       626 
     | 
    
         
            -
                runner =  
     | 
| 
      
 916 
     | 
    
         
            +
                runner = ClaudeRunner()
         
     | 
| 
       627 
917 
     | 
    
         
             
                if context is None:
         
     | 
| 
       628 
918 
     | 
    
         
             
                    context = create_simple_context()
         
     | 
| 
       629 
919 
     | 
    
         
             
                return runner.run_oneshot(prompt, context)
         
     |