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/cli/commands/run.py
    CHANGED
    
    | 
         @@ -5,20 +5,91 @@ WHY: This module handles the main 'run' command which starts Claude sessions. 
     | 
|
| 
       5 
5 
     | 
    
         
             
            It's the most commonly used command and handles both interactive and non-interactive modes.
         
     | 
| 
       6 
6 
     | 
    
         
             
            """
         
     | 
| 
       7 
7 
     | 
    
         | 
| 
      
 8 
     | 
    
         
            +
            import os
         
     | 
| 
       8 
9 
     | 
    
         
             
            import subprocess
         
     | 
| 
       9 
10 
     | 
    
         
             
            import sys
         
     | 
| 
      
 11 
     | 
    
         
            +
            import time
         
     | 
| 
      
 12 
     | 
    
         
            +
            import webbrowser
         
     | 
| 
       10 
13 
     | 
    
         
             
            from pathlib import Path
         
     | 
| 
       11 
14 
     | 
    
         | 
| 
       12 
     | 
    
         
            -
            from  
     | 
| 
      
 15 
     | 
    
         
            +
            from ...core.logger import get_logger
         
     | 
| 
      
 16 
     | 
    
         
            +
            from ...constants import LogLevel
         
     | 
| 
      
 17 
     | 
    
         
            +
            from ..utils import get_user_input, list_agent_versions_at_startup
         
     | 
| 
      
 18 
     | 
    
         
            +
            from ...utils.dependency_manager import ensure_socketio_dependencies
         
     | 
| 
      
 19 
     | 
    
         
            +
            from ...deployment_paths import get_monitor_html_path, get_scripts_dir, get_package_root
         
     | 
| 
       13 
20 
     | 
    
         | 
| 
       14 
     | 
    
         
            -
             
     | 
| 
       15 
     | 
    
         
            -
             
     | 
| 
       16 
     | 
    
         
            -
             
     | 
| 
       17 
     | 
    
         
            -
             
     | 
| 
       18 
     | 
    
         
            -
                 
     | 
| 
       19 
     | 
    
         
            -
                 
     | 
| 
       20 
     | 
    
         
            -
                 
     | 
| 
       21 
     | 
    
         
            -
             
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
            def filter_claude_mpm_args(claude_args):
         
     | 
| 
      
 23 
     | 
    
         
            +
                """
         
     | 
| 
      
 24 
     | 
    
         
            +
                Filter out claude-mpm specific arguments from claude_args before passing to Claude CLI.
         
     | 
| 
      
 25 
     | 
    
         
            +
                
         
     | 
| 
      
 26 
     | 
    
         
            +
                WHY: The argparse.REMAINDER captures ALL remaining arguments, including claude-mpm
         
     | 
| 
      
 27 
     | 
    
         
            +
                specific flags like --monitor, etc. Claude CLI doesn't understand these
         
     | 
| 
      
 28 
     | 
    
         
            +
                flags and will error if they're passed through.
         
     | 
| 
      
 29 
     | 
    
         
            +
                
         
     | 
| 
      
 30 
     | 
    
         
            +
                DESIGN DECISION: We maintain a list of known claude-mpm flags to filter out,
         
     | 
| 
      
 31 
     | 
    
         
            +
                ensuring only genuine Claude CLI arguments are passed through.
         
     | 
| 
      
 32 
     | 
    
         
            +
                
         
     | 
| 
      
 33 
     | 
    
         
            +
                Args:
         
     | 
| 
      
 34 
     | 
    
         
            +
                    claude_args: List of arguments captured by argparse.REMAINDER
         
     | 
| 
      
 35 
     | 
    
         
            +
                    
         
     | 
| 
      
 36 
     | 
    
         
            +
                Returns:
         
     | 
| 
      
 37 
     | 
    
         
            +
                    Filtered list of arguments safe to pass to Claude CLI
         
     | 
| 
      
 38 
     | 
    
         
            +
                """
         
     | 
| 
      
 39 
     | 
    
         
            +
                if not claude_args:
         
     | 
| 
      
 40 
     | 
    
         
            +
                    return []
         
     | 
| 
      
 41 
     | 
    
         
            +
                
         
     | 
| 
      
 42 
     | 
    
         
            +
                # Known claude-mpm specific flags that should NOT be passed to Claude CLI
         
     | 
| 
      
 43 
     | 
    
         
            +
                # This includes all MPM-specific arguments from the parser
         
     | 
| 
      
 44 
     | 
    
         
            +
                mpm_flags = {
         
     | 
| 
      
 45 
     | 
    
         
            +
                    # Run-specific flags
         
     | 
| 
      
 46 
     | 
    
         
            +
                    '--monitor',
         
     | 
| 
      
 47 
     | 
    
         
            +
                    '--websocket-port',
         
     | 
| 
      
 48 
     | 
    
         
            +
                    '--no-hooks',
         
     | 
| 
      
 49 
     | 
    
         
            +
                    '--no-tickets',
         
     | 
| 
      
 50 
     | 
    
         
            +
                    '--intercept-commands',
         
     | 
| 
      
 51 
     | 
    
         
            +
                    '--no-native-agents',
         
     | 
| 
      
 52 
     | 
    
         
            +
                    '--launch-method',
         
     | 
| 
      
 53 
     | 
    
         
            +
                    '--resume',
         
     | 
| 
      
 54 
     | 
    
         
            +
                    # Input/output flags (these are MPM-specific, not Claude CLI flags)
         
     | 
| 
      
 55 
     | 
    
         
            +
                    '--input',
         
     | 
| 
      
 56 
     | 
    
         
            +
                    '--non-interactive',
         
     | 
| 
      
 57 
     | 
    
         
            +
                    # Common logging flags (these are MPM-specific, not Claude CLI flags)
         
     | 
| 
      
 58 
     | 
    
         
            +
                    '--debug',
         
     | 
| 
      
 59 
     | 
    
         
            +
                    '--logging',
         
     | 
| 
      
 60 
     | 
    
         
            +
                    '--log-dir',
         
     | 
| 
      
 61 
     | 
    
         
            +
                    # Framework flags (these are MPM-specific)
         
     | 
| 
      
 62 
     | 
    
         
            +
                    '--framework-path',
         
     | 
| 
      
 63 
     | 
    
         
            +
                    '--agents-dir',
         
     | 
| 
      
 64 
     | 
    
         
            +
                    # Version flag (handled by MPM)
         
     | 
| 
      
 65 
     | 
    
         
            +
                    '--version',
         
     | 
| 
      
 66 
     | 
    
         
            +
                    # Short flags (MPM-specific equivalents)
         
     | 
| 
      
 67 
     | 
    
         
            +
                    '-i',  # --input (MPM-specific, not Claude CLI)
         
     | 
| 
      
 68 
     | 
    
         
            +
                    '-d'   # --debug (MPM-specific, not Claude CLI)
         
     | 
| 
      
 69 
     | 
    
         
            +
                }
         
     | 
| 
      
 70 
     | 
    
         
            +
                
         
     | 
| 
      
 71 
     | 
    
         
            +
                filtered_args = []
         
     | 
| 
      
 72 
     | 
    
         
            +
                i = 0
         
     | 
| 
      
 73 
     | 
    
         
            +
                while i < len(claude_args):
         
     | 
| 
      
 74 
     | 
    
         
            +
                    arg = claude_args[i]
         
     | 
| 
      
 75 
     | 
    
         
            +
                    
         
     | 
| 
      
 76 
     | 
    
         
            +
                    # Check if this is a claude-mpm flag
         
     | 
| 
      
 77 
     | 
    
         
            +
                    if arg in mpm_flags:
         
     | 
| 
      
 78 
     | 
    
         
            +
                        # Skip this flag
         
     | 
| 
      
 79 
     | 
    
         
            +
                        i += 1
         
     | 
| 
      
 80 
     | 
    
         
            +
                        # Also skip the next argument if this flag expects a value
         
     | 
| 
      
 81 
     | 
    
         
            +
                        value_expecting_flags = {
         
     | 
| 
      
 82 
     | 
    
         
            +
                            '--websocket-port', '--launch-method', '--logging', '--log-dir', 
         
     | 
| 
      
 83 
     | 
    
         
            +
                            '--framework-path', '--agents-dir', '-i', '--input', '--resume'
         
     | 
| 
      
 84 
     | 
    
         
            +
                        }
         
     | 
| 
      
 85 
     | 
    
         
            +
                        if arg in value_expecting_flags and i < len(claude_args):
         
     | 
| 
      
 86 
     | 
    
         
            +
                            i += 1  # Skip the value too
         
     | 
| 
      
 87 
     | 
    
         
            +
                    else:
         
     | 
| 
      
 88 
     | 
    
         
            +
                        # This is not a claude-mpm flag, keep it
         
     | 
| 
      
 89 
     | 
    
         
            +
                        filtered_args.append(arg)
         
     | 
| 
      
 90 
     | 
    
         
            +
                        i += 1
         
     | 
| 
      
 91 
     | 
    
         
            +
                
         
     | 
| 
      
 92 
     | 
    
         
            +
                return filtered_args
         
     | 
| 
       22 
93 
     | 
    
         | 
| 
       23 
94 
     | 
    
         | 
| 
       24 
95 
     | 
    
         
             
            def run_session(args):
         
     | 
| 
         @@ -28,29 +99,21 @@ def run_session(args): 
     | 
|
| 
       28 
99 
     | 
    
         
             
                WHY: This is the primary command that users interact with. It sets up the
         
     | 
| 
       29 
100 
     | 
    
         
             
                environment, optionally deploys agents, and launches Claude with the MPM framework.
         
     | 
| 
       30 
101 
     | 
    
         | 
| 
       31 
     | 
    
         
            -
                DESIGN DECISION: We use  
     | 
| 
      
 102 
     | 
    
         
            +
                DESIGN DECISION: We use ClaudeRunner to handle the complexity of
         
     | 
| 
       32 
103 
     | 
    
         
             
                subprocess management and hook integration, keeping this function focused
         
     | 
| 
       33 
104 
     | 
    
         
             
                on high-level orchestration.
         
     | 
| 
       34 
105 
     | 
    
         | 
| 
       35 
106 
     | 
    
         
             
                Args:
         
     | 
| 
       36 
107 
     | 
    
         
             
                    args: Parsed command line arguments
         
     | 
| 
       37 
108 
     | 
    
         
             
                """
         
     | 
| 
       38 
     | 
    
         
            -
                import os
         
     | 
| 
       39 
109 
     | 
    
         
             
                logger = get_logger("cli")
         
     | 
| 
       40 
110 
     | 
    
         
             
                if args.logging != LogLevel.OFF.value:
         
     | 
| 
       41 
111 
     | 
    
         
             
                    logger.info("Starting Claude MPM session")
         
     | 
| 
       42 
     | 
    
         
            -
             
     | 
| 
       43 
     | 
    
         
            -
             
     | 
| 
       44 
     | 
    
         
            -
                     
     | 
| 
       45 
     | 
    
         
            -
             
     | 
| 
       46 
     | 
    
         
            -
             
     | 
| 
       47 
     | 
    
         
            -
                
         
     | 
| 
       48 
     | 
    
         
            -
                # Import SimpleClaudeRunner using safe_import pattern
         
     | 
| 
       49 
     | 
    
         
            -
                SimpleClaudeRunner, create_simple_context = safe_import(
         
     | 
| 
       50 
     | 
    
         
            -
                    'claude_mpm.core.simple_runner',
         
     | 
| 
       51 
     | 
    
         
            -
                    None,
         
     | 
| 
       52 
     | 
    
         
            -
                    ['SimpleClaudeRunner', 'create_simple_context']
         
     | 
| 
       53 
     | 
    
         
            -
                )
         
     | 
| 
      
 112 
     | 
    
         
            +
                
         
     | 
| 
      
 113 
     | 
    
         
            +
                try:
         
     | 
| 
      
 114 
     | 
    
         
            +
                    from ...core.claude_runner import ClaudeRunner, create_simple_context
         
     | 
| 
      
 115 
     | 
    
         
            +
                except ImportError:
         
     | 
| 
      
 116 
     | 
    
         
            +
                    from claude_mpm.core.claude_runner import ClaudeRunner, create_simple_context
         
     | 
| 
       54 
117 
     | 
    
         | 
| 
       55 
118 
     | 
    
         
             
                # Skip native agents if disabled
         
     | 
| 
       56 
119 
     | 
    
         
             
                if getattr(args, 'no_native_agents', False):
         
     | 
| 
         @@ -61,16 +124,81 @@ def run_session(args): 
     | 
|
| 
       61 
124 
     | 
    
         | 
| 
       62 
125 
     | 
    
         
             
                # Create simple runner
         
     | 
| 
       63 
126 
     | 
    
         
             
                enable_tickets = not args.no_tickets
         
     | 
| 
       64 
     | 
    
         
            -
                 
     | 
| 
       65 
     | 
    
         
            -
                 
     | 
| 
      
 127 
     | 
    
         
            +
                raw_claude_args = getattr(args, 'claude_args', []) or []
         
     | 
| 
      
 128 
     | 
    
         
            +
                # Filter out claude-mpm specific flags before passing to Claude CLI
         
     | 
| 
      
 129 
     | 
    
         
            +
                claude_args = filter_claude_mpm_args(raw_claude_args)
         
     | 
| 
      
 130 
     | 
    
         
            +
                monitor_mode = getattr(args, 'monitor', False)
         
     | 
| 
      
 131 
     | 
    
         
            +
                
         
     | 
| 
      
 132 
     | 
    
         
            +
                # Debug logging for argument filtering
         
     | 
| 
      
 133 
     | 
    
         
            +
                if raw_claude_args != claude_args:
         
     | 
| 
      
 134 
     | 
    
         
            +
                    logger.debug(f"Filtered claude-mpm args: {set(raw_claude_args) - set(claude_args)}")
         
     | 
| 
      
 135 
     | 
    
         
            +
                    logger.debug(f"Passing to Claude CLI: {claude_args}")
         
     | 
| 
      
 136 
     | 
    
         
            +
                
         
     | 
| 
      
 137 
     | 
    
         
            +
                # Use the specified launch method (default: exec)
         
     | 
| 
      
 138 
     | 
    
         
            +
                launch_method = getattr(args, 'launch_method', 'exec')
         
     | 
| 
      
 139 
     | 
    
         
            +
                
         
     | 
| 
      
 140 
     | 
    
         
            +
                enable_websocket = getattr(args, 'monitor', False) or monitor_mode
         
     | 
| 
      
 141 
     | 
    
         
            +
                websocket_port = getattr(args, 'websocket_port', 8765)
         
     | 
| 
      
 142 
     | 
    
         
            +
                
         
     | 
| 
      
 143 
     | 
    
         
            +
                # Display Socket.IO server info if enabled
         
     | 
| 
      
 144 
     | 
    
         
            +
                if enable_websocket:
         
     | 
| 
      
 145 
     | 
    
         
            +
                    # Auto-install Socket.IO dependencies if needed
         
     | 
| 
      
 146 
     | 
    
         
            +
                    print("🔧 Checking Socket.IO dependencies...")
         
     | 
| 
      
 147 
     | 
    
         
            +
                    dependencies_ok, error_msg = ensure_socketio_dependencies(logger)
         
     | 
| 
      
 148 
     | 
    
         
            +
                    
         
     | 
| 
      
 149 
     | 
    
         
            +
                    if not dependencies_ok:
         
     | 
| 
      
 150 
     | 
    
         
            +
                        print(f"❌ Failed to install Socket.IO dependencies: {error_msg}")
         
     | 
| 
      
 151 
     | 
    
         
            +
                        print("  Please install manually: pip install python-socketio aiohttp python-engineio")
         
     | 
| 
      
 152 
     | 
    
         
            +
                        print("  Or install with extras: pip install claude-mpm[monitor]")
         
     | 
| 
      
 153 
     | 
    
         
            +
                        # Continue anyway - some functionality might still work
         
     | 
| 
      
 154 
     | 
    
         
            +
                    else:
         
     | 
| 
      
 155 
     | 
    
         
            +
                        print("✓ Socket.IO dependencies ready")
         
     | 
| 
      
 156 
     | 
    
         
            +
                    
         
     | 
| 
      
 157 
     | 
    
         
            +
                    try:
         
     | 
| 
      
 158 
     | 
    
         
            +
                        import socketio
         
     | 
| 
      
 159 
     | 
    
         
            +
                        print(f"✓ Socket.IO server enabled at http://localhost:{websocket_port}")
         
     | 
| 
      
 160 
     | 
    
         
            +
                        if launch_method == "exec":
         
     | 
| 
      
 161 
     | 
    
         
            +
                            print("  Note: Socket.IO monitoring using exec mode with Claude Code hooks")
         
     | 
| 
      
 162 
     | 
    
         
            +
                        
         
     | 
| 
      
 163 
     | 
    
         
            +
                        # Launch Socket.IO dashboard if in monitor mode
         
     | 
| 
      
 164 
     | 
    
         
            +
                        if monitor_mode:
         
     | 
| 
      
 165 
     | 
    
         
            +
                            success, browser_opened = launch_socketio_monitor(websocket_port, logger)
         
     | 
| 
      
 166 
     | 
    
         
            +
                            if not success:
         
     | 
| 
      
 167 
     | 
    
         
            +
                                print(f"⚠️  Failed to launch Socket.IO monitor")
         
     | 
| 
      
 168 
     | 
    
         
            +
                                print(f"  You can manually run: python scripts/launch_socketio_dashboard.py --port {websocket_port}")
         
     | 
| 
      
 169 
     | 
    
         
            +
                            # Store whether browser was opened by CLI for coordination with ClaudeRunner
         
     | 
| 
      
 170 
     | 
    
         
            +
                            args._browser_opened_by_cli = browser_opened
         
     | 
| 
      
 171 
     | 
    
         
            +
                    except ImportError as e:
         
     | 
| 
      
 172 
     | 
    
         
            +
                        print(f"⚠️  Socket.IO still not available after installation attempt: {e}")
         
     | 
| 
      
 173 
     | 
    
         
            +
                        print("  This might be a virtual environment issue.")
         
     | 
| 
      
 174 
     | 
    
         
            +
                        print("  Try: pip install python-socketio aiohttp python-engineio")
         
     | 
| 
      
 175 
     | 
    
         
            +
                        print("  Or: pip install claude-mpm[monitor]")
         
     | 
| 
      
 176 
     | 
    
         
            +
                
         
     | 
| 
      
 177 
     | 
    
         
            +
                runner = ClaudeRunner(
         
     | 
| 
       66 
178 
     | 
    
         
             
                    enable_tickets=enable_tickets,
         
     | 
| 
       67 
179 
     | 
    
         
             
                    log_level=args.logging,
         
     | 
| 
       68 
     | 
    
         
            -
                    claude_args=claude_args
         
     | 
| 
      
 180 
     | 
    
         
            +
                    claude_args=claude_args,
         
     | 
| 
      
 181 
     | 
    
         
            +
                    launch_method=launch_method,
         
     | 
| 
      
 182 
     | 
    
         
            +
                    enable_websocket=enable_websocket,
         
     | 
| 
      
 183 
     | 
    
         
            +
                    websocket_port=websocket_port
         
     | 
| 
       69 
184 
     | 
    
         
             
                )
         
     | 
| 
       70 
185 
     | 
    
         | 
| 
      
 186 
     | 
    
         
            +
                # Set browser opening flag for monitor mode
         
     | 
| 
      
 187 
     | 
    
         
            +
                if monitor_mode:
         
     | 
| 
      
 188 
     | 
    
         
            +
                    runner._should_open_monitor_browser = True
         
     | 
| 
      
 189 
     | 
    
         
            +
                    # Pass information about whether we already opened the browser in run.py
         
     | 
| 
      
 190 
     | 
    
         
            +
                    runner._browser_opened_by_cli = getattr(args, '_browser_opened_by_cli', False)
         
     | 
| 
      
 191 
     | 
    
         
            +
                
         
     | 
| 
       71 
192 
     | 
    
         
             
                # Create basic context
         
     | 
| 
       72 
193 
     | 
    
         
             
                context = create_simple_context()
         
     | 
| 
       73 
194 
     | 
    
         | 
| 
      
 195 
     | 
    
         
            +
                # For monitor mode, we handled everything in launch_socketio_monitor
         
     | 
| 
      
 196 
     | 
    
         
            +
                # No need for ClaudeRunner browser delegation
         
     | 
| 
      
 197 
     | 
    
         
            +
                if monitor_mode:
         
     | 
| 
      
 198 
     | 
    
         
            +
                    # Clear any browser opening flags since we handled it completely
         
     | 
| 
      
 199 
     | 
    
         
            +
                    runner._should_open_monitor_browser = False
         
     | 
| 
      
 200 
     | 
    
         
            +
                    runner._browser_opened_by_cli = True  # Prevent duplicate opening
         
     | 
| 
      
 201 
     | 
    
         
            +
                
         
     | 
| 
       74 
202 
     | 
    
         
             
                # Run session based on mode
         
     | 
| 
       75 
203 
     | 
    
         
             
                if args.non_interactive or args.input:
         
     | 
| 
       76 
204 
     | 
    
         
             
                    # Non-interactive mode
         
     | 
| 
         @@ -84,7 +212,7 @@ def run_session(args): 
     | 
|
| 
       84 
212 
     | 
    
         
             
                        # Use the interactive wrapper for command interception
         
     | 
| 
       85 
213 
     | 
    
         
             
                        # WHY: Command interception requires special handling of stdin/stdout
         
     | 
| 
       86 
214 
     | 
    
         
             
                        # which is better done in a separate Python script
         
     | 
| 
       87 
     | 
    
         
            -
                        wrapper_path =  
     | 
| 
      
 215 
     | 
    
         
            +
                        wrapper_path = get_scripts_dir() / "interactive_wrapper.py"
         
     | 
| 
       88 
216 
     | 
    
         
             
                        if wrapper_path.exists():
         
     | 
| 
       89 
217 
     | 
    
         
             
                            print("Starting interactive session with command interception...")
         
     | 
| 
       90 
218 
     | 
    
         
             
                            subprocess.run([sys.executable, str(wrapper_path)])
         
     | 
| 
         @@ -92,4 +220,349 @@ def run_session(args): 
     | 
|
| 
       92 
220 
     | 
    
         
             
                            logger.warning("Interactive wrapper not found, falling back to normal mode")
         
     | 
| 
       93 
221 
     | 
    
         
             
                            runner.run_interactive(context)
         
     | 
| 
       94 
222 
     | 
    
         
             
                    else:
         
     | 
| 
       95 
     | 
    
         
            -
                        runner.run_interactive(context)
         
     | 
| 
      
 223 
     | 
    
         
            +
                        runner.run_interactive(context)
         
     | 
| 
      
 224 
     | 
    
         
            +
             
     | 
| 
      
 225 
     | 
    
         
            +
             
     | 
| 
      
 226 
     | 
    
         
            +
            def launch_socketio_monitor(port, logger):
         
     | 
| 
      
 227 
     | 
    
         
            +
                """
         
     | 
| 
      
 228 
     | 
    
         
            +
                Launch the Socket.IO monitoring dashboard using static HTML file.
         
     | 
| 
      
 229 
     | 
    
         
            +
                
         
     | 
| 
      
 230 
     | 
    
         
            +
                WHY: This function opens a static HTML file that connects to the Socket.IO server.
         
     | 
| 
      
 231 
     | 
    
         
            +
                This approach is simpler and more reliable than serving the dashboard from the server.
         
     | 
| 
      
 232 
     | 
    
         
            +
                The HTML file connects to whatever Socket.IO server is running on the specified port.
         
     | 
| 
      
 233 
     | 
    
         
            +
                
         
     | 
| 
      
 234 
     | 
    
         
            +
                DESIGN DECISION: Use file:// protocol to open static HTML file directly from filesystem.
         
     | 
| 
      
 235 
     | 
    
         
            +
                Pass the server port as a URL parameter so the dashboard knows which port to connect to.
         
     | 
| 
      
 236 
     | 
    
         
            +
                This decouples the dashboard from the server serving and makes it more robust.
         
     | 
| 
      
 237 
     | 
    
         
            +
                
         
     | 
| 
      
 238 
     | 
    
         
            +
                Args:
         
     | 
| 
      
 239 
     | 
    
         
            +
                    port: Port number for the Socket.IO server
         
     | 
| 
      
 240 
     | 
    
         
            +
                    logger: Logger instance for output
         
     | 
| 
      
 241 
     | 
    
         
            +
                    
         
     | 
| 
      
 242 
     | 
    
         
            +
                Returns:
         
     | 
| 
      
 243 
     | 
    
         
            +
                    tuple: (success: bool, browser_opened: bool) - success status and whether browser was opened
         
     | 
| 
      
 244 
     | 
    
         
            +
                """
         
     | 
| 
      
 245 
     | 
    
         
            +
                try:
         
     | 
| 
      
 246 
     | 
    
         
            +
                    # Verify Socket.IO dependencies are available
         
     | 
| 
      
 247 
     | 
    
         
            +
                    try:
         
     | 
| 
      
 248 
     | 
    
         
            +
                        import socketio
         
     | 
| 
      
 249 
     | 
    
         
            +
                        import aiohttp
         
     | 
| 
      
 250 
     | 
    
         
            +
                        import engineio
         
     | 
| 
      
 251 
     | 
    
         
            +
                        logger.debug("Socket.IO dependencies verified")
         
     | 
| 
      
 252 
     | 
    
         
            +
                    except ImportError as e:
         
     | 
| 
      
 253 
     | 
    
         
            +
                        logger.error(f"Socket.IO dependencies not available: {e}")
         
     | 
| 
      
 254 
     | 
    
         
            +
                        print(f"❌ Socket.IO dependencies missing: {e}")
         
     | 
| 
      
 255 
     | 
    
         
            +
                        print("  This is unexpected - dependency installation may have failed.")
         
     | 
| 
      
 256 
     | 
    
         
            +
                        return False, False
         
     | 
| 
      
 257 
     | 
    
         
            +
                    
         
     | 
| 
      
 258 
     | 
    
         
            +
                    print(f"🚀 Setting up Socket.IO monitor on port {port}...")
         
     | 
| 
      
 259 
     | 
    
         
            +
                    logger.info(f"Launching Socket.IO monitor on port {port}")
         
     | 
| 
      
 260 
     | 
    
         
            +
                    
         
     | 
| 
      
 261 
     | 
    
         
            +
                    socketio_port = port
         
     | 
| 
      
 262 
     | 
    
         
            +
                    
         
     | 
| 
      
 263 
     | 
    
         
            +
                    # Get path to monitor HTML using deployment paths
         
     | 
| 
      
 264 
     | 
    
         
            +
                    html_file_path = get_monitor_html_path()
         
     | 
| 
      
 265 
     | 
    
         
            +
                    
         
     | 
| 
      
 266 
     | 
    
         
            +
                    if not html_file_path.exists():
         
     | 
| 
      
 267 
     | 
    
         
            +
                        logger.error(f"Monitor HTML file not found: {html_file_path}")
         
     | 
| 
      
 268 
     | 
    
         
            +
                        print(f"❌ Monitor HTML file not found: {html_file_path}")
         
     | 
| 
      
 269 
     | 
    
         
            +
                        return False, False
         
     | 
| 
      
 270 
     | 
    
         
            +
                    
         
     | 
| 
      
 271 
     | 
    
         
            +
                    # Create file:// URL with port parameter
         
     | 
| 
      
 272 
     | 
    
         
            +
                    dashboard_url = f'file://{html_file_path.absolute()}?port={socketio_port}'
         
     | 
| 
      
 273 
     | 
    
         
            +
                    
         
     | 
| 
      
 274 
     | 
    
         
            +
                    # Check if Socket.IO server is already running
         
     | 
| 
      
 275 
     | 
    
         
            +
                    server_running = _check_socketio_server_running(socketio_port, logger)
         
     | 
| 
      
 276 
     | 
    
         
            +
                    
         
     | 
| 
      
 277 
     | 
    
         
            +
                    if server_running:
         
     | 
| 
      
 278 
     | 
    
         
            +
                        print(f"✅ Socket.IO server already running on port {socketio_port}")
         
     | 
| 
      
 279 
     | 
    
         
            +
                        
         
     | 
| 
      
 280 
     | 
    
         
            +
                        # Check if it's managed by our daemon
         
     | 
| 
      
 281 
     | 
    
         
            +
                        daemon_script = get_package_root() / "scripts" / "socketio_daemon.py"
         
     | 
| 
      
 282 
     | 
    
         
            +
                        if daemon_script.exists():
         
     | 
| 
      
 283 
     | 
    
         
            +
                            status_result = subprocess.run(
         
     | 
| 
      
 284 
     | 
    
         
            +
                                [sys.executable, str(daemon_script), "status"],
         
     | 
| 
      
 285 
     | 
    
         
            +
                                capture_output=True,
         
     | 
| 
      
 286 
     | 
    
         
            +
                                text=True
         
     | 
| 
      
 287 
     | 
    
         
            +
                            )
         
     | 
| 
      
 288 
     | 
    
         
            +
                            if "is running" in status_result.stdout:
         
     | 
| 
      
 289 
     | 
    
         
            +
                                print(f"   (Managed by Python daemon)")
         
     | 
| 
      
 290 
     | 
    
         
            +
                        
         
     | 
| 
      
 291 
     | 
    
         
            +
                        print(f"📊 Dashboard: {dashboard_url}")
         
     | 
| 
      
 292 
     | 
    
         
            +
                        
         
     | 
| 
      
 293 
     | 
    
         
            +
                        # Open browser with static HTML file
         
     | 
| 
      
 294 
     | 
    
         
            +
                        try:
         
     | 
| 
      
 295 
     | 
    
         
            +
                            # Check if we should suppress browser opening (for tests)
         
     | 
| 
      
 296 
     | 
    
         
            +
                            if os.environ.get('CLAUDE_MPM_NO_BROWSER') != '1':
         
     | 
| 
      
 297 
     | 
    
         
            +
                                print(f"🌐 Opening dashboard in browser...")
         
     | 
| 
      
 298 
     | 
    
         
            +
                                open_in_browser_tab(dashboard_url, logger)
         
     | 
| 
      
 299 
     | 
    
         
            +
                                logger.info(f"Socket.IO dashboard opened: {dashboard_url}")
         
     | 
| 
      
 300 
     | 
    
         
            +
                            else:
         
     | 
| 
      
 301 
     | 
    
         
            +
                                print(f"🌐 Browser opening suppressed (CLAUDE_MPM_NO_BROWSER=1)")
         
     | 
| 
      
 302 
     | 
    
         
            +
                                logger.info(f"Browser opening suppressed by environment variable")
         
     | 
| 
      
 303 
     | 
    
         
            +
                            return True, True
         
     | 
| 
      
 304 
     | 
    
         
            +
                        except Exception as e:
         
     | 
| 
      
 305 
     | 
    
         
            +
                            logger.warning(f"Failed to open browser: {e}")
         
     | 
| 
      
 306 
     | 
    
         
            +
                            print(f"⚠️  Could not open browser automatically")
         
     | 
| 
      
 307 
     | 
    
         
            +
                            print(f"📊 Please open manually: {dashboard_url}")
         
     | 
| 
      
 308 
     | 
    
         
            +
                            return True, False
         
     | 
| 
      
 309 
     | 
    
         
            +
                    else:
         
     | 
| 
      
 310 
     | 
    
         
            +
                        # Start standalone Socket.IO server
         
     | 
| 
      
 311 
     | 
    
         
            +
                        print(f"🔧 Starting Socket.IO server on port {socketio_port}...")
         
     | 
| 
      
 312 
     | 
    
         
            +
                        server_started = _start_standalone_socketio_server(socketio_port, logger)
         
     | 
| 
      
 313 
     | 
    
         
            +
                        
         
     | 
| 
      
 314 
     | 
    
         
            +
                        if server_started:
         
     | 
| 
      
 315 
     | 
    
         
            +
                            print(f"✅ Socket.IO server started successfully")
         
     | 
| 
      
 316 
     | 
    
         
            +
                            print(f"📊 Dashboard: {dashboard_url}")
         
     | 
| 
      
 317 
     | 
    
         
            +
                            
         
     | 
| 
      
 318 
     | 
    
         
            +
                            # Final verification that server is responsive
         
     | 
| 
      
 319 
     | 
    
         
            +
                            final_check_passed = False
         
     | 
| 
      
 320 
     | 
    
         
            +
                            for i in range(3):
         
     | 
| 
      
 321 
     | 
    
         
            +
                                if _check_socketio_server_running(socketio_port, logger):
         
     | 
| 
      
 322 
     | 
    
         
            +
                                    final_check_passed = True
         
     | 
| 
      
 323 
     | 
    
         
            +
                                    break
         
     | 
| 
      
 324 
     | 
    
         
            +
                                time.sleep(1)
         
     | 
| 
      
 325 
     | 
    
         
            +
                            
         
     | 
| 
      
 326 
     | 
    
         
            +
                            if not final_check_passed:
         
     | 
| 
      
 327 
     | 
    
         
            +
                                logger.warning("Server started but final connectivity check failed")
         
     | 
| 
      
 328 
     | 
    
         
            +
                                print(f"⚠️  Server may still be initializing. Dashboard should work once fully ready.")
         
     | 
| 
      
 329 
     | 
    
         
            +
                            
         
     | 
| 
      
 330 
     | 
    
         
            +
                            # Open browser with static HTML file
         
     | 
| 
      
 331 
     | 
    
         
            +
                            try:
         
     | 
| 
      
 332 
     | 
    
         
            +
                                # Check if we should suppress browser opening (for tests)
         
     | 
| 
      
 333 
     | 
    
         
            +
                                if os.environ.get('CLAUDE_MPM_NO_BROWSER') != '1':
         
     | 
| 
      
 334 
     | 
    
         
            +
                                    print(f"🌐 Opening dashboard in browser...")
         
     | 
| 
      
 335 
     | 
    
         
            +
                                    open_in_browser_tab(dashboard_url, logger)
         
     | 
| 
      
 336 
     | 
    
         
            +
                                    logger.info(f"Socket.IO dashboard opened: {dashboard_url}")
         
     | 
| 
      
 337 
     | 
    
         
            +
                                else:
         
     | 
| 
      
 338 
     | 
    
         
            +
                                    print(f"🌐 Browser opening suppressed (CLAUDE_MPM_NO_BROWSER=1)")
         
     | 
| 
      
 339 
     | 
    
         
            +
                                    logger.info(f"Browser opening suppressed by environment variable")
         
     | 
| 
      
 340 
     | 
    
         
            +
                                return True, True
         
     | 
| 
      
 341 
     | 
    
         
            +
                            except Exception as e:
         
     | 
| 
      
 342 
     | 
    
         
            +
                                logger.warning(f"Failed to open browser: {e}")
         
     | 
| 
      
 343 
     | 
    
         
            +
                                print(f"⚠️  Could not open browser automatically")
         
     | 
| 
      
 344 
     | 
    
         
            +
                                print(f"📊 Please open manually: {dashboard_url}")
         
     | 
| 
      
 345 
     | 
    
         
            +
                                return True, False
         
     | 
| 
      
 346 
     | 
    
         
            +
                        else:
         
     | 
| 
      
 347 
     | 
    
         
            +
                            print(f"❌ Failed to start Socket.IO server")
         
     | 
| 
      
 348 
     | 
    
         
            +
                            print(f"💡 Troubleshooting tips:")
         
     | 
| 
      
 349 
     | 
    
         
            +
                            print(f"   - Check if port {socketio_port} is already in use")
         
     | 
| 
      
 350 
     | 
    
         
            +
                            print(f"   - Verify Socket.IO dependencies: pip install python-socketio aiohttp")
         
     | 
| 
      
 351 
     | 
    
         
            +
                            print(f"   - Try a different port with --websocket-port")
         
     | 
| 
      
 352 
     | 
    
         
            +
                            return False, False
         
     | 
| 
      
 353 
     | 
    
         
            +
                    
         
     | 
| 
      
 354 
     | 
    
         
            +
                except Exception as e:
         
     | 
| 
      
 355 
     | 
    
         
            +
                    logger.error(f"Failed to launch Socket.IO monitor: {e}")
         
     | 
| 
      
 356 
     | 
    
         
            +
                    print(f"❌ Failed to launch Socket.IO monitor: {e}")
         
     | 
| 
      
 357 
     | 
    
         
            +
                    return False, False
         
     | 
| 
      
 358 
     | 
    
         
            +
             
     | 
| 
      
 359 
     | 
    
         
            +
             
     | 
| 
      
 360 
     | 
    
         
            +
            def _check_socketio_server_running(port, logger):
         
     | 
| 
      
 361 
     | 
    
         
            +
                """
         
     | 
| 
      
 362 
     | 
    
         
            +
                Check if a Socket.IO server is running on the specified port.
         
     | 
| 
      
 363 
     | 
    
         
            +
                
         
     | 
| 
      
 364 
     | 
    
         
            +
                WHY: We need to detect existing servers to avoid conflicts and provide
         
     | 
| 
      
 365 
     | 
    
         
            +
                seamless experience regardless of whether server is already running.
         
     | 
| 
      
 366 
     | 
    
         
            +
                
         
     | 
| 
      
 367 
     | 
    
         
            +
                DESIGN DECISION: We try multiple endpoints and connection methods to ensure
         
     | 
| 
      
 368 
     | 
    
         
            +
                robust detection. Some servers may be starting up and only partially ready.
         
     | 
| 
      
 369 
     | 
    
         
            +
                
         
     | 
| 
      
 370 
     | 
    
         
            +
                Args:
         
     | 
| 
      
 371 
     | 
    
         
            +
                    port: Port number to check
         
     | 
| 
      
 372 
     | 
    
         
            +
                    logger: Logger instance for output
         
     | 
| 
      
 373 
     | 
    
         
            +
                    
         
     | 
| 
      
 374 
     | 
    
         
            +
                Returns:
         
     | 
| 
      
 375 
     | 
    
         
            +
                    bool: True if server is running and responding, False otherwise
         
     | 
| 
      
 376 
     | 
    
         
            +
                """
         
     | 
| 
      
 377 
     | 
    
         
            +
                try:
         
     | 
| 
      
 378 
     | 
    
         
            +
                    import urllib.request
         
     | 
| 
      
 379 
     | 
    
         
            +
                    import urllib.error
         
     | 
| 
      
 380 
     | 
    
         
            +
                    import socket
         
     | 
| 
      
 381 
     | 
    
         
            +
                    
         
     | 
| 
      
 382 
     | 
    
         
            +
                    # First, do a basic TCP connection check
         
     | 
| 
      
 383 
     | 
    
         
            +
                    try:
         
     | 
| 
      
 384 
     | 
    
         
            +
                        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
         
     | 
| 
      
 385 
     | 
    
         
            +
                            s.settimeout(1.0)
         
     | 
| 
      
 386 
     | 
    
         
            +
                            result = s.connect_ex(('127.0.0.1', port))
         
     | 
| 
      
 387 
     | 
    
         
            +
                            if result != 0:
         
     | 
| 
      
 388 
     | 
    
         
            +
                                logger.debug(f"TCP connection to port {port} failed (not listening)")
         
     | 
| 
      
 389 
     | 
    
         
            +
                                return False
         
     | 
| 
      
 390 
     | 
    
         
            +
                    except Exception as e:
         
     | 
| 
      
 391 
     | 
    
         
            +
                        logger.debug(f"TCP socket check failed for port {port}: {e}")
         
     | 
| 
      
 392 
     | 
    
         
            +
                        return False
         
     | 
| 
      
 393 
     | 
    
         
            +
                    
         
     | 
| 
      
 394 
     | 
    
         
            +
                    # If TCP connection succeeds, try HTTP health check
         
     | 
| 
      
 395 
     | 
    
         
            +
                    try:
         
     | 
| 
      
 396 
     | 
    
         
            +
                        response = urllib.request.urlopen(f'http://localhost:{port}/status', timeout=5)
         
     | 
| 
      
 397 
     | 
    
         
            +
                        
         
     | 
| 
      
 398 
     | 
    
         
            +
                        if response.getcode() == 200:
         
     | 
| 
      
 399 
     | 
    
         
            +
                            content = response.read().decode()
         
     | 
| 
      
 400 
     | 
    
         
            +
                            logger.debug(f"✅ Socket.IO server health check passed on port {port}")
         
     | 
| 
      
 401 
     | 
    
         
            +
                            logger.debug(f"📄 Server response: {content[:100]}...")
         
     | 
| 
      
 402 
     | 
    
         
            +
                            return True
         
     | 
| 
      
 403 
     | 
    
         
            +
                        else:
         
     | 
| 
      
 404 
     | 
    
         
            +
                            logger.debug(f"⚠️ HTTP response code {response.getcode()} from port {port}")
         
     | 
| 
      
 405 
     | 
    
         
            +
                            return False
         
     | 
| 
      
 406 
     | 
    
         
            +
                            
         
     | 
| 
      
 407 
     | 
    
         
            +
                    except urllib.error.HTTPError as e:
         
     | 
| 
      
 408 
     | 
    
         
            +
                        logger.debug(f"⚠️ HTTP error {e.code} from server on port {port}")
         
     | 
| 
      
 409 
     | 
    
         
            +
                        return False
         
     | 
| 
      
 410 
     | 
    
         
            +
                    except urllib.error.URLError as e:
         
     | 
| 
      
 411 
     | 
    
         
            +
                        logger.debug(f"⚠️ URL error connecting to port {port}: {e.reason}")
         
     | 
| 
      
 412 
     | 
    
         
            +
                        return False
         
     | 
| 
      
 413 
     | 
    
         
            +
                        
         
     | 
| 
      
 414 
     | 
    
         
            +
                except (ConnectionError, OSError) as e:
         
     | 
| 
      
 415 
     | 
    
         
            +
                    logger.debug(f"🔌 Connection error checking port {port}: {e}")
         
     | 
| 
      
 416 
     | 
    
         
            +
                except Exception as e:
         
     | 
| 
      
 417 
     | 
    
         
            +
                    logger.debug(f"❌ Unexpected error checking Socket.IO server on port {port}: {e}")
         
     | 
| 
      
 418 
     | 
    
         
            +
                
         
     | 
| 
      
 419 
     | 
    
         
            +
                return False
         
     | 
| 
      
 420 
     | 
    
         
            +
             
     | 
| 
      
 421 
     | 
    
         
            +
             
     | 
| 
      
 422 
     | 
    
         
            +
            def _start_standalone_socketio_server(port, logger):
         
     | 
| 
      
 423 
     | 
    
         
            +
                """
         
     | 
| 
      
 424 
     | 
    
         
            +
                Start a standalone Socket.IO server using the Python daemon.
         
     | 
| 
      
 425 
     | 
    
         
            +
                
         
     | 
| 
      
 426 
     | 
    
         
            +
                WHY: For monitor mode, we want a persistent server that runs independently
         
     | 
| 
      
 427 
     | 
    
         
            +
                of the Claude session. This allows users to monitor multiple sessions and
         
     | 
| 
      
 428 
     | 
    
         
            +
                keeps the dashboard available even when Claude isn't running.
         
     | 
| 
      
 429 
     | 
    
         
            +
                
         
     | 
| 
      
 430 
     | 
    
         
            +
                DESIGN DECISION: We use a pure Python daemon script to manage the server
         
     | 
| 
      
 431 
     | 
    
         
            +
                process. This avoids Node.js dependencies (like PM2) and provides proper
         
     | 
| 
      
 432 
     | 
    
         
            +
                process management with PID tracking.
         
     | 
| 
      
 433 
     | 
    
         
            +
                
         
     | 
| 
      
 434 
     | 
    
         
            +
                Args:
         
     | 
| 
      
 435 
     | 
    
         
            +
                    port: Port number for the server
         
     | 
| 
      
 436 
     | 
    
         
            +
                    logger: Logger instance for output
         
     | 
| 
      
 437 
     | 
    
         
            +
                    
         
     | 
| 
      
 438 
     | 
    
         
            +
                Returns:
         
     | 
| 
      
 439 
     | 
    
         
            +
                    bool: True if server started successfully, False otherwise
         
     | 
| 
      
 440 
     | 
    
         
            +
                """
         
     | 
| 
      
 441 
     | 
    
         
            +
                try:
         
     | 
| 
      
 442 
     | 
    
         
            +
                    from ...deployment_paths import get_scripts_dir
         
     | 
| 
      
 443 
     | 
    
         
            +
                    import subprocess
         
     | 
| 
      
 444 
     | 
    
         
            +
                    
         
     | 
| 
      
 445 
     | 
    
         
            +
                    # Get path to daemon script in package
         
     | 
| 
      
 446 
     | 
    
         
            +
                    daemon_script = get_package_root() / "scripts" / "socketio_daemon.py"
         
     | 
| 
      
 447 
     | 
    
         
            +
                    
         
     | 
| 
      
 448 
     | 
    
         
            +
                    if not daemon_script.exists():
         
     | 
| 
      
 449 
     | 
    
         
            +
                        logger.error(f"Socket.IO daemon script not found: {daemon_script}")
         
     | 
| 
      
 450 
     | 
    
         
            +
                        return False
         
     | 
| 
      
 451 
     | 
    
         
            +
                    
         
     | 
| 
      
 452 
     | 
    
         
            +
                    logger.info(f"Starting Socket.IO server daemon on port {port}")
         
     | 
| 
      
 453 
     | 
    
         
            +
                    
         
     | 
| 
      
 454 
     | 
    
         
            +
                    # Start the daemon
         
     | 
| 
      
 455 
     | 
    
         
            +
                    result = subprocess.run(
         
     | 
| 
      
 456 
     | 
    
         
            +
                        [sys.executable, str(daemon_script), "start"],
         
     | 
| 
      
 457 
     | 
    
         
            +
                        capture_output=True,
         
     | 
| 
      
 458 
     | 
    
         
            +
                        text=True
         
     | 
| 
      
 459 
     | 
    
         
            +
                    )
         
     | 
| 
      
 460 
     | 
    
         
            +
                    
         
     | 
| 
      
 461 
     | 
    
         
            +
                    if result.returncode != 0:
         
     | 
| 
      
 462 
     | 
    
         
            +
                        logger.error(f"Failed to start Socket.IO daemon: {result.stderr}")
         
     | 
| 
      
 463 
     | 
    
         
            +
                        return False
         
     | 
| 
      
 464 
     | 
    
         
            +
                    
         
     | 
| 
      
 465 
     | 
    
         
            +
                    # Wait for server to be ready with longer timeouts and progressive delays
         
     | 
| 
      
 466 
     | 
    
         
            +
                    # WHY: Socket.IO server startup involves complex async initialization:
         
     | 
| 
      
 467 
     | 
    
         
            +
                    # 1. Thread creation (~0.1s)
         
     | 
| 
      
 468 
     | 
    
         
            +
                    # 2. Event loop setup (~1s) 
         
     | 
| 
      
 469 
     | 
    
         
            +
                    # 3. aiohttp server binding (~2-5s)
         
     | 
| 
      
 470 
     | 
    
         
            +
                    # 4. Socket.IO service initialization (~1-3s)
         
     | 
| 
      
 471 
     | 
    
         
            +
                    # Total: up to 10 seconds for full readiness
         
     | 
| 
      
 472 
     | 
    
         
            +
                    max_attempts = 20  # Increased from 10
         
     | 
| 
      
 473 
     | 
    
         
            +
                    initial_delay = 0.5  # seconds
         
     | 
| 
      
 474 
     | 
    
         
            +
                    max_delay = 2.0  # seconds
         
     | 
| 
      
 475 
     | 
    
         
            +
                    
         
     | 
| 
      
 476 
     | 
    
         
            +
                    logger.info(f"Waiting up to {max_attempts * max_delay} seconds for server to be fully ready...")
         
     | 
| 
      
 477 
     | 
    
         
            +
                    
         
     | 
| 
      
 478 
     | 
    
         
            +
                    for attempt in range(max_attempts):
         
     | 
| 
      
 479 
     | 
    
         
            +
                        # Progressive delay - start fast, then slow down for socket binding
         
     | 
| 
      
 480 
     | 
    
         
            +
                        if attempt < 5:
         
     | 
| 
      
 481 
     | 
    
         
            +
                            delay = initial_delay
         
     | 
| 
      
 482 
     | 
    
         
            +
                        else:
         
     | 
| 
      
 483 
     | 
    
         
            +
                            delay = min(max_delay, initial_delay + (attempt - 5) * 0.2)
         
     | 
| 
      
 484 
     | 
    
         
            +
                        
         
     | 
| 
      
 485 
     | 
    
         
            +
                        logger.debug(f"Checking server readiness (attempt {attempt + 1}/{max_attempts}, waiting {delay}s)")
         
     | 
| 
      
 486 
     | 
    
         
            +
                        
         
     | 
| 
      
 487 
     | 
    
         
            +
                        # Check if thread is alive first
         
     | 
| 
      
 488 
     | 
    
         
            +
                        if hasattr(server, 'thread') and server.thread and server.thread.is_alive():
         
     | 
| 
      
 489 
     | 
    
         
            +
                            logger.debug("Server thread is alive, checking connectivity...")
         
     | 
| 
      
 490 
     | 
    
         
            +
                            
         
     | 
| 
      
 491 
     | 
    
         
            +
                            # Give it time for socket binding (progressive delay)
         
     | 
| 
      
 492 
     | 
    
         
            +
                            time.sleep(delay)
         
     | 
| 
      
 493 
     | 
    
         
            +
                            
         
     | 
| 
      
 494 
     | 
    
         
            +
                            # Verify it's actually accepting connections
         
     | 
| 
      
 495 
     | 
    
         
            +
                            if _check_socketio_server_running(port, logger):
         
     | 
| 
      
 496 
     | 
    
         
            +
                                logger.info(f"✅ Standalone Socket.IO server started successfully on port {port}")
         
     | 
| 
      
 497 
     | 
    
         
            +
                                logger.info(f"🕐 Server ready after {attempt + 1} attempts ({(attempt + 1) * delay:.1f}s)")
         
     | 
| 
      
 498 
     | 
    
         
            +
                                return True
         
     | 
| 
      
 499 
     | 
    
         
            +
                            else:
         
     | 
| 
      
 500 
     | 
    
         
            +
                                logger.debug(f"Server not yet accepting connections on attempt {attempt + 1}")
         
     | 
| 
      
 501 
     | 
    
         
            +
                        else:
         
     | 
| 
      
 502 
     | 
    
         
            +
                            logger.warning(f"Server thread not alive or not created on attempt {attempt + 1}")
         
     | 
| 
      
 503 
     | 
    
         
            +
                            # Give thread more time to start
         
     | 
| 
      
 504 
     | 
    
         
            +
                            time.sleep(delay)
         
     | 
| 
      
 505 
     | 
    
         
            +
                    
         
     | 
| 
      
 506 
     | 
    
         
            +
                    logger.error(f"❌ Socket.IO server failed to start properly on port {port} after {max_attempts} attempts")
         
     | 
| 
      
 507 
     | 
    
         
            +
                    logger.error(f"💡 This may indicate a port conflict or dependency issue")
         
     | 
| 
      
 508 
     | 
    
         
            +
                    logger.error(f"🔧 Try a different port with --websocket-port or check for conflicts")
         
     | 
| 
      
 509 
     | 
    
         
            +
                    return False
         
     | 
| 
      
 510 
     | 
    
         
            +
                        
         
     | 
| 
      
 511 
     | 
    
         
            +
                except Exception as e:
         
     | 
| 
      
 512 
     | 
    
         
            +
                    logger.error(f"❌ Failed to start standalone Socket.IO server: {e}")
         
     | 
| 
      
 513 
     | 
    
         
            +
                    import traceback
         
     | 
| 
      
 514 
     | 
    
         
            +
                    logger.error(f"📋 Stack trace: {traceback.format_exc()}")
         
     | 
| 
      
 515 
     | 
    
         
            +
                    logger.error(f"💡 This may be a dependency issue - try: pip install python-socketio aiohttp")
         
     | 
| 
      
 516 
     | 
    
         
            +
                    return False
         
     | 
| 
      
 517 
     | 
    
         
            +
             
     | 
| 
      
 518 
     | 
    
         
            +
             
     | 
| 
      
 519 
     | 
    
         
            +
             
     | 
| 
      
 520 
     | 
    
         
            +
            def open_in_browser_tab(url, logger):
         
     | 
| 
      
 521 
     | 
    
         
            +
                """
         
     | 
| 
      
 522 
     | 
    
         
            +
                Open URL in browser, attempting to reuse existing tabs when possible.
         
     | 
| 
      
 523 
     | 
    
         
            +
                
         
     | 
| 
      
 524 
     | 
    
         
            +
                WHY: Users prefer reusing browser tabs instead of opening new ones constantly.
         
     | 
| 
      
 525 
     | 
    
         
            +
                This function attempts platform-specific solutions for tab reuse.
         
     | 
| 
      
 526 
     | 
    
         
            +
                
         
     | 
| 
      
 527 
     | 
    
         
            +
                DESIGN DECISION: We try different methods based on platform capabilities,
         
     | 
| 
      
 528 
     | 
    
         
            +
                falling back to standard webbrowser.open() if needed.
         
     | 
| 
      
 529 
     | 
    
         
            +
                
         
     | 
| 
      
 530 
     | 
    
         
            +
                Args:
         
     | 
| 
      
 531 
     | 
    
         
            +
                    url: URL to open
         
     | 
| 
      
 532 
     | 
    
         
            +
                    logger: Logger instance for output
         
     | 
| 
      
 533 
     | 
    
         
            +
                """
         
     | 
| 
      
 534 
     | 
    
         
            +
                try:
         
     | 
| 
      
 535 
     | 
    
         
            +
                    # Platform-specific optimizations for tab reuse
         
     | 
| 
      
 536 
     | 
    
         
            +
                    import platform
         
     | 
| 
      
 537 
     | 
    
         
            +
                    system = platform.system().lower()
         
     | 
| 
      
 538 
     | 
    
         
            +
                    
         
     | 
| 
      
 539 
     | 
    
         
            +
                    if system == "darwin":  # macOS
         
     | 
| 
      
 540 
     | 
    
         
            +
                        # Just use the standard webbrowser module on macOS
         
     | 
| 
      
 541 
     | 
    
         
            +
                        # The AppleScript approach is too unreliable
         
     | 
| 
      
 542 
     | 
    
         
            +
                        webbrowser.open(url, new=0, autoraise=True)  # new=0 tries to reuse window
         
     | 
| 
      
 543 
     | 
    
         
            +
                        logger.info("Opened browser on macOS")
         
     | 
| 
      
 544 
     | 
    
         
            +
                            
         
     | 
| 
      
 545 
     | 
    
         
            +
                    elif system == "linux":
         
     | 
| 
      
 546 
     | 
    
         
            +
                        # On Linux, try to use existing browser session
         
     | 
| 
      
 547 
     | 
    
         
            +
                        try:
         
     | 
| 
      
 548 
     | 
    
         
            +
                            # This is a best-effort approach for common browsers
         
     | 
| 
      
 549 
     | 
    
         
            +
                            webbrowser.get().open(url, new=0)  # new=0 tries to reuse existing window
         
     | 
| 
      
 550 
     | 
    
         
            +
                            logger.info("Attempted Linux browser tab reuse")
         
     | 
| 
      
 551 
     | 
    
         
            +
                        except Exception:
         
     | 
| 
      
 552 
     | 
    
         
            +
                            webbrowser.open(url, autoraise=True)
         
     | 
| 
      
 553 
     | 
    
         
            +
                            
         
     | 
| 
      
 554 
     | 
    
         
            +
                    elif system == "windows":
         
     | 
| 
      
 555 
     | 
    
         
            +
                        # On Windows, try to use existing browser
         
     | 
| 
      
 556 
     | 
    
         
            +
                        try:
         
     | 
| 
      
 557 
     | 
    
         
            +
                            webbrowser.get().open(url, new=0)  # new=0 tries to reuse existing window
         
     | 
| 
      
 558 
     | 
    
         
            +
                            logger.info("Attempted Windows browser tab reuse")
         
     | 
| 
      
 559 
     | 
    
         
            +
                        except Exception:
         
     | 
| 
      
 560 
     | 
    
         
            +
                            webbrowser.open(url, autoraise=True)
         
     | 
| 
      
 561 
     | 
    
         
            +
                    else:
         
     | 
| 
      
 562 
     | 
    
         
            +
                        # Unknown platform, use standard opening
         
     | 
| 
      
 563 
     | 
    
         
            +
                        webbrowser.open(url, autoraise=True)
         
     | 
| 
      
 564 
     | 
    
         
            +
                        
         
     | 
| 
      
 565 
     | 
    
         
            +
                except Exception as e:
         
     | 
| 
      
 566 
     | 
    
         
            +
                    logger.warning(f"Browser opening failed: {e}")
         
     | 
| 
      
 567 
     | 
    
         
            +
                    # Final fallback
         
     | 
| 
      
 568 
     | 
    
         
            +
                    webbrowser.open(url)
         
     | 
| 
         @@ -5,10 +5,7 @@ WHY: This module handles ticket listing functionality, allowing users to view 
     | 
|
| 
       5 
5 
     | 
    
         
             
            recent tickets created during Claude sessions.
         
     | 
| 
       6 
6 
     | 
    
         
             
            """
         
     | 
| 
       7 
7 
     | 
    
         | 
| 
       8 
     | 
    
         
            -
            from  
     | 
| 
       9 
     | 
    
         
            -
             
     | 
| 
       10 
     | 
    
         
            -
            # Import logger using safe_import pattern
         
     | 
| 
       11 
     | 
    
         
            -
            get_logger = safe_import('claude_mpm.core.logger', None, ['get_logger'])
         
     | 
| 
      
 8 
     | 
    
         
            +
            from ...core.logger import get_logger
         
     | 
| 
       12 
9 
     | 
    
         | 
| 
       13 
10 
     | 
    
         | 
| 
       14 
11 
     | 
    
         
             
            def list_tickets(args):
         
     | 
| 
         @@ -27,20 +24,12 @@ def list_tickets(args): 
     | 
|
| 
       27 
24 
     | 
    
         
             
                """
         
     | 
| 
       28 
25 
     | 
    
         
             
                logger = get_logger("cli")
         
     | 
| 
       29 
26 
     | 
    
         | 
| 
       30 
     | 
    
         
            -
                # Import TicketManager using safe_import pattern
         
     | 
| 
       31 
     | 
    
         
            -
                TicketManager = safe_import(
         
     | 
| 
       32 
     | 
    
         
            -
                    '...services.ticket_manager',
         
     | 
| 
       33 
     | 
    
         
            -
                    'claude_mpm.services.ticket_manager',
         
     | 
| 
       34 
     | 
    
         
            -
                    ['TicketManager']
         
     | 
| 
       35 
     | 
    
         
            -
                )
         
     | 
| 
       36 
     | 
    
         
            -
                
         
     | 
| 
       37 
     | 
    
         
            -
                if not TicketManager:
         
     | 
| 
       38 
     | 
    
         
            -
                    logger.error("ai-trackdown-pytools not installed")
         
     | 
| 
       39 
     | 
    
         
            -
                    print("Error: ai-trackdown-pytools not installed")
         
     | 
| 
       40 
     | 
    
         
            -
                    print("Install with: pip install ai-trackdown-pytools")
         
     | 
| 
       41 
     | 
    
         
            -
                    return
         
     | 
| 
       42 
     | 
    
         
            -
                
         
     | 
| 
       43 
27 
     | 
    
         
             
                try:
         
     | 
| 
      
 28 
     | 
    
         
            +
                    try:
         
     | 
| 
      
 29 
     | 
    
         
            +
                        from ...services.ticket_manager import TicketManager
         
     | 
| 
      
 30 
     | 
    
         
            +
                    except ImportError:
         
     | 
| 
      
 31 
     | 
    
         
            +
                        from claude_mpm.services.ticket_manager import TicketManager
         
     | 
| 
      
 32 
     | 
    
         
            +
                    
         
     | 
| 
       44 
33 
     | 
    
         
             
                    ticket_manager = TicketManager()
         
     | 
| 
       45 
34 
     | 
    
         
             
                    tickets = ticket_manager.list_recent_tickets(limit=args.limit)
         
     | 
| 
       46 
35 
     | 
    
         | 
| 
         @@ -65,6 +54,10 @@ def list_tickets(args): 
     | 
|
| 
       65 
54 
     | 
    
         
             
                        print(f"   Created: {ticket['created_at']}")
         
     | 
| 
       66 
55 
     | 
    
         
             
                        print()
         
     | 
| 
       67 
56 
     | 
    
         | 
| 
      
 57 
     | 
    
         
            +
                except ImportError:
         
     | 
| 
      
 58 
     | 
    
         
            +
                    logger.error("ai-trackdown-pytools not installed")
         
     | 
| 
      
 59 
     | 
    
         
            +
                    print("Error: ai-trackdown-pytools not installed")
         
     | 
| 
      
 60 
     | 
    
         
            +
                    print("Install with: pip install ai-trackdown-pytools")
         
     | 
| 
       68 
61 
     | 
    
         
             
                except Exception as e:
         
     | 
| 
       69 
62 
     | 
    
         
             
                    logger.error(f"Error listing tickets: {e}")
         
     | 
| 
       70 
63 
     | 
    
         
             
                    print(f"Error: {e}")
         
     |