claude-mpm 3.7.8__py3-none-any.whl → 3.9.0__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/VERSION +1 -1
- claude_mpm/agents/BASE_PM.md +0 -106
- claude_mpm/agents/INSTRUCTIONS.md +0 -96
- claude_mpm/agents/MEMORY.md +94 -0
- claude_mpm/agents/WORKFLOW.md +86 -0
- claude_mpm/agents/templates/code_analyzer.json +2 -2
- claude_mpm/agents/templates/data_engineer.json +1 -1
- claude_mpm/agents/templates/documentation.json +1 -1
- claude_mpm/agents/templates/engineer.json +1 -1
- claude_mpm/agents/templates/ops.json +1 -1
- claude_mpm/agents/templates/qa.json +1 -1
- claude_mpm/agents/templates/research.json +1 -1
- claude_mpm/agents/templates/security.json +1 -1
- claude_mpm/agents/templates/ticketing.json +3 -8
- claude_mpm/agents/templates/version_control.json +1 -1
- claude_mpm/agents/templates/web_qa.json +2 -2
- claude_mpm/agents/templates/web_ui.json +2 -2
- claude_mpm/cli/__init__.py +2 -2
- claude_mpm/cli/commands/__init__.py +2 -1
- claude_mpm/cli/commands/agents.py +8 -3
- claude_mpm/cli/commands/tickets.py +596 -19
- claude_mpm/cli/parser.py +217 -5
- claude_mpm/config/__init__.py +30 -39
- claude_mpm/config/socketio_config.py +8 -5
- claude_mpm/constants.py +13 -0
- claude_mpm/core/__init__.py +8 -18
- claude_mpm/core/cache.py +596 -0
- claude_mpm/core/claude_runner.py +166 -622
- claude_mpm/core/config.py +7 -3
- claude_mpm/core/constants.py +339 -0
- claude_mpm/core/container.py +548 -38
- claude_mpm/core/exceptions.py +392 -0
- claude_mpm/core/framework_loader.py +249 -93
- claude_mpm/core/interactive_session.py +479 -0
- claude_mpm/core/interfaces.py +424 -0
- claude_mpm/core/lazy.py +467 -0
- claude_mpm/core/logging_config.py +444 -0
- claude_mpm/core/oneshot_session.py +465 -0
- claude_mpm/core/optimized_agent_loader.py +485 -0
- claude_mpm/core/optimized_startup.py +490 -0
- claude_mpm/core/service_registry.py +52 -26
- claude_mpm/core/socketio_pool.py +162 -5
- claude_mpm/core/types.py +292 -0
- claude_mpm/core/typing_utils.py +477 -0
- claude_mpm/hooks/claude_hooks/hook_handler.py +213 -99
- claude_mpm/init.py +2 -1
- claude_mpm/services/__init__.py +78 -14
- claude_mpm/services/agent/__init__.py +24 -0
- claude_mpm/services/agent/deployment.py +2548 -0
- claude_mpm/services/agent/management.py +598 -0
- claude_mpm/services/agent/registry.py +813 -0
- claude_mpm/services/agents/deployment/agent_deployment.py +728 -308
- claude_mpm/services/agents/memory/agent_memory_manager.py +160 -4
- claude_mpm/services/async_session_logger.py +8 -3
- claude_mpm/services/communication/__init__.py +21 -0
- claude_mpm/services/communication/socketio.py +1933 -0
- claude_mpm/services/communication/websocket.py +479 -0
- claude_mpm/services/core/__init__.py +123 -0
- claude_mpm/services/core/base.py +247 -0
- claude_mpm/services/core/interfaces.py +951 -0
- claude_mpm/services/framework_claude_md_generator/__init__.py +10 -3
- claude_mpm/services/framework_claude_md_generator/deployment_manager.py +14 -11
- claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +23 -23
- claude_mpm/services/framework_claude_md_generator.py +3 -2
- claude_mpm/services/health_monitor.py +4 -3
- claude_mpm/services/hook_service.py +64 -4
- claude_mpm/services/infrastructure/__init__.py +21 -0
- claude_mpm/services/infrastructure/logging.py +202 -0
- claude_mpm/services/infrastructure/monitoring.py +893 -0
- claude_mpm/services/memory/indexed_memory.py +648 -0
- claude_mpm/services/project/__init__.py +21 -0
- claude_mpm/services/project/analyzer.py +864 -0
- claude_mpm/services/project/registry.py +608 -0
- claude_mpm/services/project_analyzer.py +95 -2
- claude_mpm/services/recovery_manager.py +15 -9
- claude_mpm/services/response_tracker.py +3 -5
- claude_mpm/services/socketio/__init__.py +25 -0
- claude_mpm/services/socketio/handlers/__init__.py +25 -0
- claude_mpm/services/socketio/handlers/base.py +121 -0
- claude_mpm/services/socketio/handlers/connection.py +198 -0
- claude_mpm/services/socketio/handlers/file.py +213 -0
- claude_mpm/services/socketio/handlers/git.py +723 -0
- claude_mpm/services/socketio/handlers/memory.py +27 -0
- claude_mpm/services/socketio/handlers/project.py +25 -0
- claude_mpm/services/socketio/handlers/registry.py +145 -0
- claude_mpm/services/socketio_client_manager.py +12 -7
- claude_mpm/services/socketio_server.py +156 -30
- claude_mpm/services/ticket_manager.py +172 -9
- claude_mpm/services/ticket_manager_di.py +1 -1
- claude_mpm/services/version_control/semantic_versioning.py +80 -7
- claude_mpm/services/version_control/version_parser.py +528 -0
- claude_mpm/utils/error_handler.py +1 -1
- claude_mpm/validation/agent_validator.py +27 -14
- claude_mpm/validation/frontmatter_validator.py +231 -0
- {claude_mpm-3.7.8.dist-info → claude_mpm-3.9.0.dist-info}/METADATA +38 -128
- {claude_mpm-3.7.8.dist-info → claude_mpm-3.9.0.dist-info}/RECORD +100 -59
- {claude_mpm-3.7.8.dist-info → claude_mpm-3.9.0.dist-info}/WHEEL +0 -0
- {claude_mpm-3.7.8.dist-info → claude_mpm-3.9.0.dist-info}/entry_points.txt +0 -0
- {claude_mpm-3.7.8.dist-info → claude_mpm-3.9.0.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-3.7.8.dist-info → claude_mpm-3.9.0.dist-info}/top_level.txt +0 -0
    
        claude_mpm/core/claude_runner.py
    CHANGED
    
    | @@ -7,22 +7,26 @@ import sys | |
| 7 7 | 
             
            import time
         | 
| 8 8 | 
             
            from datetime import datetime
         | 
| 9 9 | 
             
            from pathlib import Path
         | 
| 10 | 
            -
            from typing import Optional
         | 
| 10 | 
            +
            from typing import Optional, TYPE_CHECKING
         | 
| 11 11 | 
             
            import uuid
         | 
| 12 12 | 
             
            from claude_mpm.config.paths import paths
         | 
| 13 13 |  | 
| 14 | 
            -
             | 
| 15 | 
            -
             | 
| 16 | 
            -
             | 
| 17 | 
            -
             | 
| 18 | 
            -
             | 
| 19 | 
            -
             | 
| 20 | 
            -
             | 
| 14 | 
            +
            # Core imports that don't cause circular dependencies
         | 
| 15 | 
            +
            from claude_mpm.core.config import Config
         | 
| 16 | 
            +
            from claude_mpm.core.logging_config import get_logger, log_operation, log_performance_context
         | 
| 17 | 
            +
            from claude_mpm.core.logger import get_project_logger, ProjectLogger
         | 
| 18 | 
            +
            from claude_mpm.core.container import get_container, ServiceLifetime
         | 
| 19 | 
            +
            from claude_mpm.core.interfaces import (
         | 
| 20 | 
            +
                AgentDeploymentInterface,
         | 
| 21 | 
            +
                TicketManagerInterface, 
         | 
| 22 | 
            +
                HookServiceInterface
         | 
| 23 | 
            +
            )
         | 
| 24 | 
            +
             | 
| 25 | 
            +
            # Type checking imports to avoid circular dependencies
         | 
| 26 | 
            +
            if TYPE_CHECKING:
         | 
| 21 27 | 
             
                from claude_mpm.services.agents.deployment import AgentDeploymentService
         | 
| 22 28 | 
             
                from claude_mpm.services.ticket_manager import TicketManager
         | 
| 23 29 | 
             
                from claude_mpm.services.hook_service import HookService
         | 
| 24 | 
            -
                from claude_mpm.core.config import Config
         | 
| 25 | 
            -
                from claude_mpm.core.logger import get_logger, get_project_logger, ProjectLogger
         | 
| 26 30 |  | 
| 27 31 |  | 
| 28 32 | 
             
            class ClaudeRunner:
         | 
| @@ -52,7 +56,7 @@ class ClaudeRunner: | |
| 52 56 | 
             
                    """Initialize the Claude runner."""
         | 
| 53 57 | 
             
                    self.enable_tickets = enable_tickets
         | 
| 54 58 | 
             
                    self.log_level = log_level
         | 
| 55 | 
            -
                    self.logger = get_logger( | 
| 59 | 
            +
                    self.logger = get_logger(__name__)
         | 
| 56 60 | 
             
                    self.claude_args = claude_args or []
         | 
| 57 61 | 
             
                    self.launch_method = launch_method
         | 
| 58 62 | 
             
                    self.enable_websocket = enable_websocket
         | 
| @@ -73,49 +77,56 @@ class ClaudeRunner: | |
| 73 77 | 
             
                        except Exception as e:
         | 
| 74 78 | 
             
                            self.logger.warning(f"Failed to initialize project logger: {e}")
         | 
| 75 79 |  | 
| 76 | 
            -
                    # Initialize services  | 
| 80 | 
            +
                    # Initialize services using dependency injection
         | 
| 77 81 | 
             
                    # Determine the user's working directory from environment
         | 
| 78 82 | 
             
                    user_working_dir = None
         | 
| 79 83 | 
             
                    if 'CLAUDE_MPM_USER_PWD' in os.environ:
         | 
| 80 84 | 
             
                        user_working_dir = Path(os.environ['CLAUDE_MPM_USER_PWD'])
         | 
| 81 | 
            -
                        self.logger.info(f"Using user working directory from CLAUDE_MPM_USER_PWD:  | 
| 85 | 
            +
                        self.logger.info(f"Using user working directory from CLAUDE_MPM_USER_PWD", extra={"directory": str(user_working_dir)})
         | 
| 86 | 
            +
                    
         | 
| 87 | 
            +
                    # Get DI container and resolve services
         | 
| 88 | 
            +
                    container = get_container()
         | 
| 89 | 
            +
                    
         | 
| 90 | 
            +
                    # Register and resolve deployment service
         | 
| 91 | 
            +
                    if not container.is_registered(AgentDeploymentInterface):
         | 
| 92 | 
            +
                        # Lazy import to avoid circular dependencies
         | 
| 93 | 
            +
                        from claude_mpm.services.agents.deployment import AgentDeploymentService
         | 
| 94 | 
            +
                        container.register_factory(
         | 
| 95 | 
            +
                            AgentDeploymentInterface,
         | 
| 96 | 
            +
                            lambda c: AgentDeploymentService(working_directory=user_working_dir),
         | 
| 97 | 
            +
                            lifetime=ServiceLifetime.SINGLETON
         | 
| 98 | 
            +
                        )
         | 
| 82 99 |  | 
| 83 100 | 
             
                    try:
         | 
| 84 | 
            -
                         | 
| 85 | 
            -
                        # This ensures agents are deployed to the correct user directory, not the framework directory
         | 
| 86 | 
            -
                        self.deployment_service = AgentDeploymentService(working_directory=user_working_dir)
         | 
| 87 | 
            -
                    except ImportError as e:
         | 
| 88 | 
            -
                        self.logger.error(f"Failed to import AgentDeploymentService: {e}")
         | 
| 89 | 
            -
                        raise RuntimeError("Required module AgentDeploymentService not available. Please reinstall claude-mpm.") from e
         | 
| 101 | 
            +
                        self.deployment_service = container.get(AgentDeploymentInterface)
         | 
| 90 102 | 
             
                    except Exception as e:
         | 
| 91 | 
            -
                        self.logger.error(f"Failed to  | 
| 103 | 
            +
                        self.logger.error(f"Failed to resolve AgentDeploymentService", exc_info=True)
         | 
| 92 104 | 
             
                        raise RuntimeError(f"Agent deployment service initialization failed: {e}") from e
         | 
| 93 105 |  | 
| 94 | 
            -
                    # Initialize ticket manager if enabled
         | 
| 106 | 
            +
                    # Initialize ticket manager if enabled using DI
         | 
| 95 107 | 
             
                    if enable_tickets:
         | 
| 108 | 
            +
                        if not container.is_registered(TicketManagerInterface):
         | 
| 109 | 
            +
                            # Lazy import to avoid circular dependencies
         | 
| 110 | 
            +
                            from claude_mpm.services.ticket_manager import TicketManager
         | 
| 111 | 
            +
                            container.register_singleton(TicketManagerInterface, TicketManager)
         | 
| 112 | 
            +
                        
         | 
| 96 113 | 
             
                        try:
         | 
| 97 | 
            -
                            self.ticket_manager =  | 
| 98 | 
            -
                        except ImportError as e:
         | 
| 99 | 
            -
                            self.logger.warning(f"Ticket manager module not available: {e}")
         | 
| 100 | 
            -
                            self.ticket_manager = None
         | 
| 101 | 
            -
                            self.enable_tickets = False
         | 
| 102 | 
            -
                        except TypeError as e:
         | 
| 103 | 
            -
                            self.logger.warning(f"Ticket manager initialization error: {e}")
         | 
| 104 | 
            -
                            self.ticket_manager = None
         | 
| 105 | 
            -
                            self.enable_tickets = False
         | 
| 114 | 
            +
                            self.ticket_manager = container.get(TicketManagerInterface)
         | 
| 106 115 | 
             
                        except Exception as e:
         | 
| 107 | 
            -
                            self.logger.warning( | 
| 116 | 
            +
                            self.logger.warning("Failed to initialize TicketManager", exc_info=True)
         | 
| 108 117 | 
             
                            self.ticket_manager = None
         | 
| 109 118 | 
             
                            self.enable_tickets = False
         | 
| 119 | 
            +
                    else:
         | 
| 120 | 
            +
                        self.ticket_manager = None
         | 
| 110 121 |  | 
| 111 122 | 
             
                    # Initialize configuration
         | 
| 112 123 | 
             
                    try:
         | 
| 113 124 | 
             
                        self.config = Config()
         | 
| 114 125 | 
             
                    except FileNotFoundError as e:
         | 
| 115 | 
            -
                        self.logger.warning( | 
| 126 | 
            +
                        self.logger.warning("Configuration file not found, using defaults", extra={"error": str(e)})
         | 
| 116 127 | 
             
                        self.config = Config()  # Will use defaults
         | 
| 117 128 | 
             
                    except Exception as e:
         | 
| 118 | 
            -
                        self.logger.error( | 
| 129 | 
            +
                        self.logger.error("Failed to load configuration", exc_info=True)
         | 
| 119 130 | 
             
                        raise RuntimeError(f"Configuration initialization failed: {e}") from e
         | 
| 120 131 |  | 
| 121 132 | 
             
                    # Initialize response logging if enabled
         | 
| @@ -132,17 +143,23 @@ class ClaudeRunner: | |
| 132 143 | 
             
                                    component="logging"
         | 
| 133 144 | 
             
                                )
         | 
| 134 145 | 
             
                        except Exception as e:
         | 
| 135 | 
            -
                            self.logger.warning( | 
| 146 | 
            +
                            self.logger.warning("Failed to initialize response logger", exc_info=True)
         | 
| 147 | 
            +
                    
         | 
| 148 | 
            +
                    # Initialize hook service using DI
         | 
| 149 | 
            +
                    if not container.is_registered(HookServiceInterface):
         | 
| 150 | 
            +
                        # Lazy import to avoid circular dependencies
         | 
| 151 | 
            +
                        from claude_mpm.services.hook_service import HookService
         | 
| 152 | 
            +
                        container.register_factory(
         | 
| 153 | 
            +
                            HookServiceInterface,
         | 
| 154 | 
            +
                            lambda c: HookService(self.config),
         | 
| 155 | 
            +
                            lifetime=ServiceLifetime.SINGLETON
         | 
| 156 | 
            +
                        )
         | 
| 136 157 |  | 
| 137 | 
            -
                    # Initialize hook service
         | 
| 138 158 | 
             
                    try:
         | 
| 139 | 
            -
                        self.hook_service =  | 
| 159 | 
            +
                        self.hook_service = container.get(HookServiceInterface)
         | 
| 140 160 | 
             
                        self._register_memory_hooks()
         | 
| 141 | 
            -
                    except ImportError as e:
         | 
| 142 | 
            -
                        self.logger.warning(f"Hook service module not available: {e}")
         | 
| 143 | 
            -
                        self.hook_service = None
         | 
| 144 161 | 
             
                    except Exception as e:
         | 
| 145 | 
            -
                        self.logger.warning( | 
| 162 | 
            +
                        self.logger.warning("Failed to initialize hook service", exc_info=True)
         | 
| 146 163 | 
             
                        self.hook_service = None
         | 
| 147 164 |  | 
| 148 165 | 
             
                    # Load system instructions
         | 
| @@ -473,614 +490,96 @@ class ClaudeRunner: | |
| 473 490 | 
             
                def run_interactive(self, initial_context: Optional[str] = None):
         | 
| 474 491 | 
             
                    """Run Claude in interactive mode.
         | 
| 475 492 |  | 
| 476 | 
            -
                    WHY: This method  | 
| 477 | 
            -
                     | 
| 478 | 
            -
                     | 
| 493 | 
            +
                    WHY: This method now delegates to InteractiveSession class for better
         | 
| 494 | 
            +
                    maintainability and reduced complexity. The session class handles all
         | 
| 495 | 
            +
                    the details while this method provides the simple interface.
         | 
| 479 496 |  | 
| 480 | 
            -
                    DESIGN DECISION:  | 
| 481 | 
            -
                     | 
| 482 | 
            -
                     | 
| 483 | 
            -
                     | 
| 484 | 
            -
                     | 
| 485 | 
            -
                     | 
| 486 | 
            -
                     | 
| 497 | 
            +
                    DESIGN DECISION: Using delegation pattern to reduce complexity from
         | 
| 498 | 
            +
                    39 to <10 and lines from 262 to <80, while maintaining 100% backward
         | 
| 499 | 
            +
                    compatibility. All functionality including response logging through
         | 
| 500 | 
            +
                    the hook system is preserved.
         | 
| 501 | 
            +
                    
         | 
| 502 | 
            +
                    The hook system continues to capture Claude events (UserPromptSubmit,
         | 
| 503 | 
            +
                    PreToolUse, PostToolUse, Task delegations) directly from Claude Code,
         | 
| 504 | 
            +
                    providing comprehensive event capture without process control overhead.
         | 
| 487 505 |  | 
| 488 506 | 
             
                    Args:
         | 
| 489 507 | 
             
                        initial_context: Optional initial context to pass to Claude
         | 
| 490 508 | 
             
                    """
         | 
| 491 | 
            -
                     | 
| 492 | 
            -
                    effective_launch_method = self.launch_method
         | 
| 493 | 
            -
                    
         | 
| 494 | 
            -
                    # Connect to Socket.IO server if enabled
         | 
| 495 | 
            -
                    if self.enable_websocket:
         | 
| 496 | 
            -
                        try:
         | 
| 497 | 
            -
                            # Use Socket.IO client proxy to connect to monitoring server
         | 
| 498 | 
            -
                            from claude_mpm.services.socketio_server import SocketIOClientProxy
         | 
| 499 | 
            -
                            self.websocket_server = SocketIOClientProxy(port=self.websocket_port)
         | 
| 500 | 
            -
                            self.websocket_server.start()
         | 
| 501 | 
            -
                            self.logger.info("Connected to Socket.IO monitoring server")
         | 
| 502 | 
            -
                            
         | 
| 503 | 
            -
                            # Generate session ID
         | 
| 504 | 
            -
                            session_id = str(uuid.uuid4())
         | 
| 505 | 
            -
                            working_dir = os.getcwd()
         | 
| 506 | 
            -
                            
         | 
| 507 | 
            -
                            # Notify session start
         | 
| 508 | 
            -
                            self.websocket_server.session_started(
         | 
| 509 | 
            -
                                session_id=session_id,
         | 
| 510 | 
            -
                                launch_method=effective_launch_method,
         | 
| 511 | 
            -
                                working_dir=working_dir
         | 
| 512 | 
            -
                            )
         | 
| 513 | 
            -
                        except ImportError as e:
         | 
| 514 | 
            -
                            self.logger.warning(f"Socket.IO module not available: {e}")
         | 
| 515 | 
            -
                            self.websocket_server = None
         | 
| 516 | 
            -
                        except ConnectionError as e:
         | 
| 517 | 
            -
                            self.logger.warning(f"Cannot connect to Socket.IO server on port {self.websocket_port}: {e}")
         | 
| 518 | 
            -
                            self.websocket_server = None
         | 
| 519 | 
            -
                        except Exception as e:
         | 
| 520 | 
            -
                            self.logger.warning(f"Unexpected error with Socket.IO server: {e}")
         | 
| 521 | 
            -
                            self.websocket_server = None
         | 
| 522 | 
            -
                    
         | 
| 523 | 
            -
                    # Get version with robust fallback mechanisms
         | 
| 524 | 
            -
                    version_str = self._get_version()
         | 
| 525 | 
            -
                    
         | 
| 526 | 
            -
                    # Print styled welcome box
         | 
| 527 | 
            -
                    print("\033[32m╭───────────────────────────────────────────────────╮\033[0m")
         | 
| 528 | 
            -
                    print("\033[32m│\033[0m ✻ Claude MPM - Interactive Session                \033[32m│\033[0m")
         | 
| 529 | 
            -
                    print(f"\033[32m│\033[0m   Version {version_str:<40}\033[32m│\033[0m")
         | 
| 530 | 
            -
                    print("\033[32m│                                                   │\033[0m")
         | 
| 531 | 
            -
                    print("\033[32m│\033[0m   Type '/agents' to see available agents          \033[32m│\033[0m")
         | 
| 532 | 
            -
                    print("\033[32m╰───────────────────────────────────────────────────╯\033[0m")
         | 
| 533 | 
            -
                    print("")  # Add blank line after box
         | 
| 534 | 
            -
                    
         | 
| 535 | 
            -
                    if self.project_logger:
         | 
| 536 | 
            -
                        self.project_logger.log_system(
         | 
| 537 | 
            -
                            "Starting interactive session",
         | 
| 538 | 
            -
                            level="INFO",
         | 
| 539 | 
            -
                            component="session"
         | 
| 540 | 
            -
                        )
         | 
| 509 | 
            +
                    from claude_mpm.core.interactive_session import InteractiveSession
         | 
| 541 510 |  | 
| 542 | 
            -
                    #  | 
| 543 | 
            -
                     | 
| 544 | 
            -
                        print("Continuing without native agents...")
         | 
| 545 | 
            -
                    
         | 
| 546 | 
            -
                    # Deploy project-specific agents if they exist
         | 
| 547 | 
            -
                    self.deploy_project_agents_to_claude()
         | 
| 548 | 
            -
                    
         | 
| 549 | 
            -
                    # Build command with system instructions
         | 
| 550 | 
            -
                    cmd = [
         | 
| 551 | 
            -
                        "claude",
         | 
| 552 | 
            -
                        "--model", "opus", 
         | 
| 553 | 
            -
                        "--dangerously-skip-permissions"
         | 
| 554 | 
            -
                    ]
         | 
| 511 | 
            +
                    # Create session handler
         | 
| 512 | 
            +
                    session = InteractiveSession(self)
         | 
| 555 513 |  | 
| 556 | 
            -
                    # Add any custom Claude arguments
         | 
| 557 | 
            -
                    if self.claude_args:
         | 
| 558 | 
            -
                        cmd.extend(self.claude_args)
         | 
| 559 | 
            -
                    
         | 
| 560 | 
            -
                    # Add system instructions if available
         | 
| 561 | 
            -
                    system_prompt = self._create_system_prompt()
         | 
| 562 | 
            -
                    if system_prompt and system_prompt != create_simple_context():
         | 
| 563 | 
            -
                        cmd.extend(["--append-system-prompt", system_prompt])
         | 
| 564 | 
            -
                    
         | 
| 565 | 
            -
                    # Run interactive Claude directly
         | 
| 566 514 | 
             
                    try:
         | 
| 567 | 
            -
                        #  | 
| 568 | 
            -
                         | 
| 569 | 
            -
                        
         | 
| 570 | 
            -
             | 
| 571 | 
            -
             | 
| 572 | 
            -
                        claude_vars_to_remove = [
         | 
| 573 | 
            -
                            'CLAUDE_CODE_ENTRYPOINT', 'CLAUDECODE', 'CLAUDE_CONFIG_DIR',
         | 
| 574 | 
            -
                            'CLAUDE_MAX_PARALLEL_SUBAGENTS', 'CLAUDE_TIMEOUT'
         | 
| 575 | 
            -
                        ]
         | 
| 576 | 
            -
                        for var in claude_vars_to_remove:
         | 
| 577 | 
            -
                            clean_env.pop(var, None)
         | 
| 578 | 
            -
                        
         | 
| 579 | 
            -
                        # Set the correct working directory for Claude Code
         | 
| 580 | 
            -
                        # If CLAUDE_MPM_USER_PWD is set, use that as the working directory
         | 
| 581 | 
            -
                        if 'CLAUDE_MPM_USER_PWD' in clean_env:
         | 
| 582 | 
            -
                            user_pwd = clean_env['CLAUDE_MPM_USER_PWD']
         | 
| 583 | 
            -
                            clean_env['CLAUDE_WORKSPACE'] = user_pwd
         | 
| 584 | 
            -
                            # Also change to that directory before launching Claude
         | 
| 585 | 
            -
                            try:
         | 
| 586 | 
            -
                                os.chdir(user_pwd)
         | 
| 587 | 
            -
                                self.logger.info(f"Changed working directory to: {user_pwd}")
         | 
| 588 | 
            -
                            except PermissionError as e:
         | 
| 589 | 
            -
                                self.logger.warning(f"Permission denied accessing directory {user_pwd}: {e}")
         | 
| 590 | 
            -
                            except FileNotFoundError as e:
         | 
| 591 | 
            -
                                self.logger.warning(f"Directory not found {user_pwd}: {e}")
         | 
| 592 | 
            -
                            except OSError as e:
         | 
| 593 | 
            -
                                self.logger.warning(f"OS error changing to directory {user_pwd}: {e}")
         | 
| 594 | 
            -
                        
         | 
| 595 | 
            -
                        print("Launching Claude...")
         | 
| 596 | 
            -
                        
         | 
| 597 | 
            -
                        if self.project_logger:
         | 
| 598 | 
            -
                            self.project_logger.log_system(
         | 
| 599 | 
            -
                                f"Launching Claude interactive mode with {effective_launch_method}",
         | 
| 600 | 
            -
                                level="INFO",
         | 
| 601 | 
            -
                                component="session"
         | 
| 602 | 
            -
                            )
         | 
| 603 | 
            -
                            self._log_session_event({
         | 
| 604 | 
            -
                                "event": "launching_claude_interactive",
         | 
| 605 | 
            -
                                "command": " ".join(cmd),
         | 
| 606 | 
            -
                                "method": effective_launch_method
         | 
| 607 | 
            -
                            })
         | 
| 608 | 
            -
                        
         | 
| 609 | 
            -
                        # Notify WebSocket clients
         | 
| 610 | 
            -
                        if self.websocket_server:
         | 
| 611 | 
            -
                            self.websocket_server.claude_status_changed(
         | 
| 612 | 
            -
                                status="starting",
         | 
| 613 | 
            -
                                message="Launching Claude interactive session"
         | 
| 614 | 
            -
                            )
         | 
| 515 | 
            +
                        # Step 1: Initialize session
         | 
| 516 | 
            +
                        success, error = session.initialize_interactive_session()
         | 
| 517 | 
            +
                        if not success:
         | 
| 518 | 
            +
                            self.logger.error(f"Failed to initialize interactive session: {error}")
         | 
| 519 | 
            +
                            return
         | 
| 615 520 |  | 
| 616 | 
            -
                        #  | 
| 617 | 
            -
                         | 
| 618 | 
            -
             | 
| 619 | 
            -
             | 
| 620 | 
            -
                             | 
| 621 | 
            -
                            if self.websocket_server:
         | 
| 622 | 
            -
                                # Notify before exec (we won't be able to after)
         | 
| 623 | 
            -
                                self.websocket_server.claude_status_changed(
         | 
| 624 | 
            -
                                    status="running",
         | 
| 625 | 
            -
                                    message="Claude process started (exec mode)"
         | 
| 626 | 
            -
                                )
         | 
| 627 | 
            -
                            os.execvpe(cmd[0], cmd, clean_env)
         | 
| 521 | 
            +
                        # Step 2: Set up environment
         | 
| 522 | 
            +
                        success, environment = session.setup_interactive_environment()
         | 
| 523 | 
            +
                        if not success:
         | 
| 524 | 
            +
                            self.logger.error("Failed to setup interactive environment")
         | 
| 525 | 
            +
                            return
         | 
| 628 526 |  | 
| 629 | 
            -
             | 
| 630 | 
            -
                         | 
| 631 | 
            -
                         | 
| 632 | 
            -
                        if self.project_logger:
         | 
| 633 | 
            -
                            self.project_logger.log_system(error_msg, level="ERROR", component="session")
         | 
| 634 | 
            -
                            self._log_session_event({
         | 
| 635 | 
            -
                                "event": "interactive_launch_failed",
         | 
| 636 | 
            -
                                "error": str(e),
         | 
| 637 | 
            -
                                "exception_type": "FileNotFoundError",
         | 
| 638 | 
            -
                                "recovery_action": "fallback_to_subprocess"
         | 
| 639 | 
            -
                            })
         | 
| 640 | 
            -
                    except PermissionError as e:
         | 
| 641 | 
            -
                        error_msg = f"Permission denied executing Claude CLI: {e}"
         | 
| 642 | 
            -
                        print(f"❌ {error_msg}")
         | 
| 643 | 
            -
                        if self.project_logger:
         | 
| 644 | 
            -
                            self.project_logger.log_system(error_msg, level="ERROR", component="session")
         | 
| 645 | 
            -
                            self._log_session_event({
         | 
| 646 | 
            -
                                "event": "interactive_launch_failed",
         | 
| 647 | 
            -
                                "error": str(e),
         | 
| 648 | 
            -
                                "exception_type": "PermissionError",
         | 
| 649 | 
            -
                                "recovery_action": "check_file_permissions"
         | 
| 650 | 
            -
                            })
         | 
| 651 | 
            -
                    except OSError as e:
         | 
| 652 | 
            -
                        error_msg = f"OS error launching Claude: {e}"
         | 
| 653 | 
            -
                        print(f"❌ {error_msg}")
         | 
| 654 | 
            -
                        if self.project_logger:
         | 
| 655 | 
            -
                            self.project_logger.log_system(error_msg, level="ERROR", component="session")
         | 
| 656 | 
            -
                            self._log_session_event({
         | 
| 657 | 
            -
                                "event": "interactive_launch_failed",
         | 
| 658 | 
            -
                                "error": str(e),
         | 
| 659 | 
            -
                                "exception_type": "OSError",
         | 
| 660 | 
            -
                                "recovery_action": "fallback_to_subprocess"
         | 
| 661 | 
            -
                            })
         | 
| 662 | 
            -
                    except KeyboardInterrupt:
         | 
| 663 | 
            -
                        print("\n⚠️  Session interrupted by user")
         | 
| 664 | 
            -
                        if self.project_logger:
         | 
| 665 | 
            -
                            self.project_logger.log_system(
         | 
| 666 | 
            -
                                "Session interrupted by user",
         | 
| 667 | 
            -
                                level="INFO",
         | 
| 668 | 
            -
                                component="session"
         | 
| 669 | 
            -
                            )
         | 
| 670 | 
            -
                            self._log_session_event({
         | 
| 671 | 
            -
                                "event": "session_interrupted",
         | 
| 672 | 
            -
                                "reason": "user_interrupt"
         | 
| 673 | 
            -
                            })
         | 
| 674 | 
            -
                        return  # Clean exit on user interrupt
         | 
| 675 | 
            -
                    except Exception as e:
         | 
| 676 | 
            -
                        error_msg = f"Unexpected error launching Claude: {e}"
         | 
| 677 | 
            -
                        print(f"❌ {error_msg}")
         | 
| 678 | 
            -
                        if self.project_logger:
         | 
| 679 | 
            -
                            self.project_logger.log_system(error_msg, level="ERROR", component="session")
         | 
| 680 | 
            -
                            self._log_session_event({
         | 
| 681 | 
            -
                                "event": "interactive_launch_failed",
         | 
| 682 | 
            -
                                "error": str(e),
         | 
| 683 | 
            -
                                "exception_type": type(e).__name__,
         | 
| 684 | 
            -
                                "recovery_action": "fallback_to_subprocess"
         | 
| 685 | 
            -
                            })
         | 
| 527 | 
            +
                        # Step 3: Handle interactive input/output
         | 
| 528 | 
            +
                        # This is where the actual Claude process runs
         | 
| 529 | 
            +
                        session.handle_interactive_input(environment)
         | 
| 686 530 |  | 
| 687 | 
            -
             | 
| 688 | 
            -
                         | 
| 689 | 
            -
             | 
| 690 | 
            -
                                status="error",
         | 
| 691 | 
            -
                                message=f"Failed to launch Claude: {e}"
         | 
| 692 | 
            -
                            )
         | 
| 693 | 
            -
                        # Fallback to subprocess
         | 
| 694 | 
            -
                        print("\n🔄 Attempting fallback launch method...")
         | 
| 695 | 
            -
                        try:
         | 
| 696 | 
            -
                            # Use the same clean_env we prepared earlier
         | 
| 697 | 
            -
                            result = subprocess.run(cmd, stdin=None, stdout=None, stderr=None, env=clean_env)
         | 
| 698 | 
            -
                            if result.returncode == 0:
         | 
| 699 | 
            -
                                if self.project_logger:
         | 
| 700 | 
            -
                                    self.project_logger.log_system(
         | 
| 701 | 
            -
                                        "Interactive session completed (subprocess fallback)",
         | 
| 702 | 
            -
                                        level="INFO",
         | 
| 703 | 
            -
                                        component="session"
         | 
| 704 | 
            -
                                    )
         | 
| 705 | 
            -
                                    self._log_session_event({
         | 
| 706 | 
            -
                                        "event": "interactive_session_complete",
         | 
| 707 | 
            -
                                        "fallback": True,
         | 
| 708 | 
            -
                                        "return_code": result.returncode
         | 
| 709 | 
            -
                                    })
         | 
| 710 | 
            -
                            else:
         | 
| 711 | 
            -
                                print(f"⚠️  Claude exited with code {result.returncode}")
         | 
| 712 | 
            -
                                if self.project_logger:
         | 
| 713 | 
            -
                                    self.project_logger.log_system(
         | 
| 714 | 
            -
                                        f"Claude exited with non-zero code: {result.returncode}",
         | 
| 715 | 
            -
                                        level="WARNING",
         | 
| 716 | 
            -
                                        component="session"
         | 
| 717 | 
            -
                                    )
         | 
| 718 | 
            -
                        except FileNotFoundError as e:
         | 
| 719 | 
            -
                            print(f"❌ Fallback failed: Claude CLI not found in PATH")
         | 
| 720 | 
            -
                            print("\n💡 To fix this issue:")
         | 
| 721 | 
            -
                            print("   1. Install Claude CLI: npm install -g @anthropic-ai/claude-ai")
         | 
| 722 | 
            -
                            print("   2. Or specify the full path to the claude binary")
         | 
| 723 | 
            -
                            if self.project_logger:
         | 
| 724 | 
            -
                                self.project_logger.log_system(
         | 
| 725 | 
            -
                                    f"Fallback failed - Claude CLI not found: {e}",
         | 
| 726 | 
            -
                                    level="ERROR",
         | 
| 727 | 
            -
                                    component="session"
         | 
| 728 | 
            -
                                )
         | 
| 729 | 
            -
                        except KeyboardInterrupt:
         | 
| 730 | 
            -
                            print("\n⚠️  Fallback interrupted by user")
         | 
| 731 | 
            -
                            if self.project_logger:
         | 
| 732 | 
            -
                                self.project_logger.log_system(
         | 
| 733 | 
            -
                                    "Fallback interrupted by user",
         | 
| 734 | 
            -
                                    level="INFO",
         | 
| 735 | 
            -
                                    component="session"
         | 
| 736 | 
            -
                                )
         | 
| 737 | 
            -
                        except Exception as fallback_error:
         | 
| 738 | 
            -
                            print(f"❌ Fallback failed with unexpected error: {fallback_error}")
         | 
| 739 | 
            -
                            print(f"   Error type: {type(fallback_error).__name__}")
         | 
| 740 | 
            -
                            if self.project_logger:
         | 
| 741 | 
            -
                                self.project_logger.log_system(
         | 
| 742 | 
            -
                                    f"Fallback launch failed: {fallback_error}",
         | 
| 743 | 
            -
                                    level="ERROR",
         | 
| 744 | 
            -
                                    component="session"
         | 
| 745 | 
            -
                                )
         | 
| 746 | 
            -
                                self._log_session_event({
         | 
| 747 | 
            -
                                    "event": "interactive_fallback_failed",
         | 
| 748 | 
            -
                                    "error": str(fallback_error),
         | 
| 749 | 
            -
                                    "exception_type": type(fallback_error).__name__
         | 
| 750 | 
            -
                                })
         | 
| 531 | 
            +
                    finally:
         | 
| 532 | 
            +
                        # Step 4: Clean up session
         | 
| 533 | 
            +
                        session.cleanup_interactive_session()
         | 
| 751 534 |  | 
| 752 535 | 
             
                def run_oneshot(self, prompt: str, context: Optional[str] = None) -> bool:
         | 
| 753 | 
            -
                    """Run Claude with a single prompt and return success status. | 
| 754 | 
            -
                    start_time = time.time()
         | 
| 536 | 
            +
                    """Run Claude with a single prompt and return success status.
         | 
| 755 537 |  | 
| 756 | 
            -
                     | 
| 757 | 
            -
                     | 
| 758 | 
            -
             | 
| 759 | 
            -
                            # Use Socket.IO client proxy to connect to monitoring server
         | 
| 760 | 
            -
                            from claude_mpm.services.socketio_server import SocketIOClientProxy
         | 
| 761 | 
            -
                            self.websocket_server = SocketIOClientProxy(port=self.websocket_port)
         | 
| 762 | 
            -
                            self.websocket_server.start()
         | 
| 763 | 
            -
                            self.logger.info("Connected to Socket.IO monitoring server")
         | 
| 764 | 
            -
                            
         | 
| 765 | 
            -
                            # Generate session ID
         | 
| 766 | 
            -
                            session_id = str(uuid.uuid4())
         | 
| 767 | 
            -
                            working_dir = os.getcwd()
         | 
| 768 | 
            -
                            
         | 
| 769 | 
            -
                            # Notify session start
         | 
| 770 | 
            -
                            self.websocket_server.session_started(
         | 
| 771 | 
            -
                                session_id=session_id,
         | 
| 772 | 
            -
                                launch_method="oneshot",
         | 
| 773 | 
            -
                                working_dir=working_dir
         | 
| 774 | 
            -
                            )
         | 
| 775 | 
            -
                        except ImportError as e:
         | 
| 776 | 
            -
                            self.logger.warning(f"Socket.IO module not available: {e}")
         | 
| 777 | 
            -
                            self.websocket_server = None
         | 
| 778 | 
            -
                        except ConnectionError as e:
         | 
| 779 | 
            -
                            self.logger.warning(f"Cannot connect to Socket.IO server on port {self.websocket_port}: {e}")
         | 
| 780 | 
            -
                            self.websocket_server = None
         | 
| 781 | 
            -
                        except Exception as e:
         | 
| 782 | 
            -
                            self.logger.warning(f"Unexpected error with Socket.IO server: {e}")
         | 
| 783 | 
            -
                            self.websocket_server = None
         | 
| 784 | 
            -
                    
         | 
| 785 | 
            -
                    # Check for /mpm: commands
         | 
| 786 | 
            -
                    if prompt.strip().startswith("/mpm:"):
         | 
| 787 | 
            -
                        return self._handle_mpm_command(prompt.strip())
         | 
| 788 | 
            -
                    
         | 
| 789 | 
            -
                    if self.project_logger:
         | 
| 790 | 
            -
                        self.project_logger.log_system(
         | 
| 791 | 
            -
                            f"Starting non-interactive session with prompt: {prompt[:100]}",
         | 
| 792 | 
            -
                            level="INFO",
         | 
| 793 | 
            -
                            component="session"
         | 
| 794 | 
            -
                        )
         | 
| 795 | 
            -
                    
         | 
| 796 | 
            -
                    # Setup agents - first deploy system agents, then project agents
         | 
| 797 | 
            -
                    if not self.setup_agents():
         | 
| 798 | 
            -
                        print("Continuing without native agents...")
         | 
| 799 | 
            -
                    
         | 
| 800 | 
            -
                    # Deploy project-specific agents if they exist
         | 
| 801 | 
            -
                    self.deploy_project_agents_to_claude()
         | 
| 802 | 
            -
                    
         | 
| 803 | 
            -
                    # Combine context and prompt
         | 
| 804 | 
            -
                    full_prompt = prompt
         | 
| 805 | 
            -
                    if context:
         | 
| 806 | 
            -
                        full_prompt = f"{context}\n\n{prompt}"
         | 
| 807 | 
            -
                    
         | 
| 808 | 
            -
                    # Build command with system instructions
         | 
| 809 | 
            -
                    cmd = [
         | 
| 810 | 
            -
                        "claude",
         | 
| 811 | 
            -
                        "--model", "opus",
         | 
| 812 | 
            -
                        "--dangerously-skip-permissions"
         | 
| 813 | 
            -
                    ]
         | 
| 538 | 
            +
                    WHY: This method now delegates to OneshotSession class for better
         | 
| 539 | 
            +
                    maintainability and reduced complexity. The session class handles
         | 
| 540 | 
            +
                    all the details while this method provides the simple interface.
         | 
| 814 541 |  | 
| 815 | 
            -
                     | 
| 816 | 
            -
                     | 
| 817 | 
            -
             | 
| 542 | 
            +
                    DESIGN DECISION: Using delegation pattern to reduce complexity from
         | 
| 543 | 
            +
                    50 to <10 and lines from 332 to <80, while maintaining 100% backward
         | 
| 544 | 
            +
                    compatibility. All functionality is preserved through the session class.
         | 
| 818 545 |  | 
| 819 | 
            -
                     | 
| 820 | 
            -
             | 
| 546 | 
            +
                    Args:
         | 
| 547 | 
            +
                        prompt: The command or prompt to execute
         | 
| 548 | 
            +
                        context: Optional context to prepend to the prompt
         | 
| 549 | 
            +
                        
         | 
| 550 | 
            +
                    Returns:
         | 
| 551 | 
            +
                        bool: True if successful, False otherwise
         | 
| 552 | 
            +
                    """
         | 
| 553 | 
            +
                    from claude_mpm.core.oneshot_session import OneshotSession
         | 
| 821 554 |  | 
| 822 | 
            -
                    #  | 
| 823 | 
            -
                     | 
| 824 | 
            -
                    if system_prompt and system_prompt != create_simple_context():
         | 
| 825 | 
            -
                        # Insert system prompt before the user prompt
         | 
| 826 | 
            -
                        cmd.insert(-2, "--append-system-prompt")
         | 
| 827 | 
            -
                        cmd.insert(-2, system_prompt)
         | 
| 555 | 
            +
                    # Create session handler
         | 
| 556 | 
            +
                    session = OneshotSession(self)
         | 
| 828 557 |  | 
| 829 558 | 
             
                    try:
         | 
| 830 | 
            -
                        #  | 
| 831 | 
            -
                         | 
| 832 | 
            -
                        
         | 
| 833 | 
            -
             | 
| 834 | 
            -
                        if 'CLAUDE_MPM_USER_PWD' in env:
         | 
| 835 | 
            -
                            user_pwd = env['CLAUDE_MPM_USER_PWD']
         | 
| 836 | 
            -
                            env['CLAUDE_WORKSPACE'] = user_pwd
         | 
| 837 | 
            -
                            # Change to that directory before running Claude
         | 
| 838 | 
            -
                            try:
         | 
| 839 | 
            -
                                original_cwd = os.getcwd()
         | 
| 840 | 
            -
                                os.chdir(user_pwd)
         | 
| 841 | 
            -
                                self.logger.info(f"Changed working directory to: {user_pwd}")
         | 
| 842 | 
            -
                            except PermissionError as e:
         | 
| 843 | 
            -
                                self.logger.warning(f"Permission denied accessing directory {user_pwd}: {e}")
         | 
| 844 | 
            -
                                original_cwd = None
         | 
| 845 | 
            -
                            except FileNotFoundError as e:
         | 
| 846 | 
            -
                                self.logger.warning(f"Directory not found {user_pwd}: {e}")
         | 
| 847 | 
            -
                                original_cwd = None
         | 
| 848 | 
            -
                            except OSError as e:
         | 
| 849 | 
            -
                                self.logger.warning(f"OS error changing to directory {user_pwd}: {e}")
         | 
| 850 | 
            -
                                original_cwd = None
         | 
| 851 | 
            -
                        else:
         | 
| 852 | 
            -
                            original_cwd = None
         | 
| 559 | 
            +
                        # Step 1: Initialize session
         | 
| 560 | 
            +
                        success, error = session.initialize_session(prompt)
         | 
| 561 | 
            +
                        if not success:
         | 
| 562 | 
            +
                            return False
         | 
| 853 563 |  | 
| 854 | 
            -
                        #  | 
| 855 | 
            -
                        if  | 
| 856 | 
            -
                             | 
| 857 | 
            -
                                "Executing Claude subprocess",
         | 
| 858 | 
            -
                                level="INFO",
         | 
| 859 | 
            -
                                component="session"
         | 
| 860 | 
            -
                            )
         | 
| 564 | 
            +
                        # Special case: MPM commands return early
         | 
| 565 | 
            +
                        if error is None and prompt.strip().startswith("/mpm:"):
         | 
| 566 | 
            +
                            return success
         | 
| 861 567 |  | 
| 862 | 
            -
                        #  | 
| 863 | 
            -
                        if  | 
| 864 | 
            -
                            self. | 
| 865 | 
            -
                                status="running",
         | 
| 866 | 
            -
                                message="Executing Claude oneshot command"
         | 
| 867 | 
            -
                            )
         | 
| 568 | 
            +
                        # Step 2: Deploy agents
         | 
| 569 | 
            +
                        if not session.deploy_agents():
         | 
| 570 | 
            +
                            self.logger.warning("Agent deployment had issues, continuing...")
         | 
| 868 571 |  | 
| 869 | 
            -
                         | 
| 572 | 
            +
                        # Step 3: Set up infrastructure
         | 
| 573 | 
            +
                        infrastructure = session.setup_infrastructure()
         | 
| 870 574 |  | 
| 871 | 
            -
                        #  | 
| 872 | 
            -
                         | 
| 873 | 
            -
                            try:
         | 
| 874 | 
            -
                                os.chdir(original_cwd)
         | 
| 875 | 
            -
                            except Exception:
         | 
| 876 | 
            -
                                pass
         | 
| 877 | 
            -
                        execution_time = time.time() - start_time
         | 
| 878 | 
            -
                        
         | 
| 879 | 
            -
                        if result.returncode == 0:
         | 
| 880 | 
            -
                            response = result.stdout.strip()
         | 
| 881 | 
            -
                            print(response)
         | 
| 882 | 
            -
                            
         | 
| 883 | 
            -
                            # Log response if logging enabled
         | 
| 884 | 
            -
                            if self.response_logger and response:
         | 
| 885 | 
            -
                                execution_time = time.time() - start_time
         | 
| 886 | 
            -
                                response_summary = prompt[:200] + "..." if len(prompt) > 200 else prompt
         | 
| 887 | 
            -
                                self.response_logger.log_response(
         | 
| 888 | 
            -
                                    request_summary=response_summary,
         | 
| 889 | 
            -
                                    response_content=response,
         | 
| 890 | 
            -
                                    metadata={
         | 
| 891 | 
            -
                                        "mode": "oneshot",
         | 
| 892 | 
            -
                                        "model": "opus",
         | 
| 893 | 
            -
                                        "exit_code": result.returncode,
         | 
| 894 | 
            -
                                        "execution_time": execution_time
         | 
| 895 | 
            -
                                    },
         | 
| 896 | 
            -
                                    agent="claude-direct"
         | 
| 897 | 
            -
                                )
         | 
| 898 | 
            -
                            
         | 
| 899 | 
            -
                            # Broadcast output to WebSocket clients
         | 
| 900 | 
            -
                            if self.websocket_server and response:
         | 
| 901 | 
            -
                                self.websocket_server.claude_output(response, "stdout")
         | 
| 902 | 
            -
                            
         | 
| 903 | 
            -
                            if self.project_logger:
         | 
| 904 | 
            -
                                # Log successful completion
         | 
| 905 | 
            -
                                self.project_logger.log_system(
         | 
| 906 | 
            -
                                    f"Non-interactive session completed successfully in {execution_time:.2f}s",
         | 
| 907 | 
            -
                                    level="INFO",
         | 
| 908 | 
            -
                                    component="session"
         | 
| 909 | 
            -
                                )
         | 
| 910 | 
            -
                                
         | 
| 911 | 
            -
                                # Log session event
         | 
| 912 | 
            -
                                self._log_session_event({
         | 
| 913 | 
            -
                                    "event": "session_complete",
         | 
| 914 | 
            -
                                    "success": True,
         | 
| 915 | 
            -
                                    "execution_time": execution_time,
         | 
| 916 | 
            -
                                    "response_length": len(response)
         | 
| 917 | 
            -
                                })
         | 
| 918 | 
            -
                                
         | 
| 919 | 
            -
                                # Log agent invocation if we detect delegation patterns
         | 
| 920 | 
            -
                                if self._contains_delegation(response):
         | 
| 921 | 
            -
                                    self.project_logger.log_system(
         | 
| 922 | 
            -
                                        "Detected potential agent delegation in response",
         | 
| 923 | 
            -
                                        level="INFO",
         | 
| 924 | 
            -
                                        component="delegation"
         | 
| 925 | 
            -
                                    )
         | 
| 926 | 
            -
                                    self._log_session_event({
         | 
| 927 | 
            -
                                        "event": "delegation_detected",
         | 
| 928 | 
            -
                                        "prompt": prompt[:200],
         | 
| 929 | 
            -
                                        "indicators": [p for p in ["Task(", "subagent_type=", "engineer agent", "qa agent"] 
         | 
| 930 | 
            -
                                                      if p.lower() in response.lower()]
         | 
| 931 | 
            -
                                    })
         | 
| 932 | 
            -
                                    
         | 
| 933 | 
            -
                                    # Notify WebSocket clients about delegation
         | 
| 934 | 
            -
                                    if self.websocket_server:
         | 
| 935 | 
            -
                                        # Try to extract agent name
         | 
| 936 | 
            -
                                        agent_name = self._extract_agent_from_response(response)
         | 
| 937 | 
            -
                                        if agent_name:
         | 
| 938 | 
            -
                                            self.websocket_server.agent_delegated(
         | 
| 939 | 
            -
                                                agent=agent_name,
         | 
| 940 | 
            -
                                                task=prompt[:100],
         | 
| 941 | 
            -
                                                status="detected"
         | 
| 942 | 
            -
                                            )
         | 
| 943 | 
            -
                            
         | 
| 944 | 
            -
                            # Extract tickets if enabled
         | 
| 945 | 
            -
                            if self.enable_tickets and self.ticket_manager and response:
         | 
| 946 | 
            -
                                self._extract_tickets(response)
         | 
| 947 | 
            -
                            
         | 
| 948 | 
            -
                            return True
         | 
| 949 | 
            -
                        else:
         | 
| 950 | 
            -
                            error_msg = result.stderr or "Unknown error"
         | 
| 951 | 
            -
                            print(f"Error: {error_msg}")
         | 
| 952 | 
            -
                            
         | 
| 953 | 
            -
                            # Broadcast error to WebSocket clients
         | 
| 954 | 
            -
                            if self.websocket_server:
         | 
| 955 | 
            -
                                self.websocket_server.claude_output(error_msg, "stderr")
         | 
| 956 | 
            -
                                self.websocket_server.claude_status_changed(
         | 
| 957 | 
            -
                                    status="error",
         | 
| 958 | 
            -
                                    message=f"Command failed with code {result.returncode}"
         | 
| 959 | 
            -
                                )
         | 
| 960 | 
            -
                            
         | 
| 961 | 
            -
                            if self.project_logger:
         | 
| 962 | 
            -
                                self.project_logger.log_system(
         | 
| 963 | 
            -
                                    f"Non-interactive session failed: {error_msg}",
         | 
| 964 | 
            -
                                    level="ERROR",
         | 
| 965 | 
            -
                                    component="session"
         | 
| 966 | 
            -
                                )
         | 
| 967 | 
            -
                                self._log_session_event({
         | 
| 968 | 
            -
                                    "event": "session_failed",
         | 
| 969 | 
            -
                                    "success": False,
         | 
| 970 | 
            -
                                    "error": error_msg,
         | 
| 971 | 
            -
                                    "return_code": result.returncode
         | 
| 972 | 
            -
                                })
         | 
| 973 | 
            -
                            
         | 
| 974 | 
            -
                            return False
         | 
| 975 | 
            -
                    
         | 
| 976 | 
            -
                    except subprocess.TimeoutExpired as e:
         | 
| 977 | 
            -
                        error_msg = f"Command timed out after {e.timeout} seconds"
         | 
| 978 | 
            -
                        print(f"⏱️  {error_msg}")
         | 
| 979 | 
            -
                        if self.project_logger:
         | 
| 980 | 
            -
                            self.project_logger.log_system(error_msg, level="ERROR", component="session")
         | 
| 981 | 
            -
                            self._log_session_event({
         | 
| 982 | 
            -
                                "event": "session_timeout",
         | 
| 983 | 
            -
                                "success": False,
         | 
| 984 | 
            -
                                "timeout": e.timeout,
         | 
| 985 | 
            -
                                "exception_type": "TimeoutExpired"
         | 
| 986 | 
            -
                            })
         | 
| 987 | 
            -
                        return False
         | 
| 988 | 
            -
                    
         | 
| 989 | 
            -
                    except FileNotFoundError as e:
         | 
| 990 | 
            -
                        error_msg = "Claude CLI not found. Please ensure 'claude' is installed and in your PATH"
         | 
| 991 | 
            -
                        print(f"❌ {error_msg}")
         | 
| 992 | 
            -
                        print("\n💡 To fix: Install Claude CLI with 'npm install -g @anthropic-ai/claude-ai'")
         | 
| 993 | 
            -
                        if self.project_logger:
         | 
| 994 | 
            -
                            self.project_logger.log_system(f"{error_msg}: {e}", level="ERROR", component="session")
         | 
| 995 | 
            -
                            self._log_session_event({
         | 
| 996 | 
            -
                                "event": "session_exception",
         | 
| 997 | 
            -
                                "success": False,
         | 
| 998 | 
            -
                                "exception": str(e),
         | 
| 999 | 
            -
                                "exception_type": "FileNotFoundError"
         | 
| 1000 | 
            -
                            })
         | 
| 1001 | 
            -
                        return False
         | 
| 1002 | 
            -
                    
         | 
| 1003 | 
            -
                    except PermissionError as e:
         | 
| 1004 | 
            -
                        error_msg = f"Permission denied executing Claude CLI: {e}"
         | 
| 1005 | 
            -
                        print(f"❌ {error_msg}")
         | 
| 1006 | 
            -
                        if self.project_logger:
         | 
| 1007 | 
            -
                            self.project_logger.log_system(error_msg, level="ERROR", component="session")
         | 
| 1008 | 
            -
                            self._log_session_event({
         | 
| 1009 | 
            -
                                "event": "session_exception",
         | 
| 1010 | 
            -
                                "success": False,
         | 
| 1011 | 
            -
                                "exception": str(e),
         | 
| 1012 | 
            -
                                "exception_type": "PermissionError"
         | 
| 1013 | 
            -
                            })
         | 
| 1014 | 
            -
                        return False
         | 
| 1015 | 
            -
                    
         | 
| 1016 | 
            -
                    except KeyboardInterrupt:
         | 
| 1017 | 
            -
                        print("\n⚠️  Command interrupted by user")
         | 
| 1018 | 
            -
                        if self.project_logger:
         | 
| 1019 | 
            -
                            self.project_logger.log_system(
         | 
| 1020 | 
            -
                                "Session interrupted by user",
         | 
| 1021 | 
            -
                                level="INFO",
         | 
| 1022 | 
            -
                                component="session"
         | 
| 1023 | 
            -
                            )
         | 
| 1024 | 
            -
                            self._log_session_event({
         | 
| 1025 | 
            -
                                "event": "session_interrupted",
         | 
| 1026 | 
            -
                                "success": False,
         | 
| 1027 | 
            -
                                "reason": "user_interrupt"
         | 
| 1028 | 
            -
                            })
         | 
| 1029 | 
            -
                        return False
         | 
| 1030 | 
            -
                    
         | 
| 1031 | 
            -
                    except MemoryError as e:
         | 
| 1032 | 
            -
                        error_msg = "Out of memory while processing command"
         | 
| 1033 | 
            -
                        print(f"❌ {error_msg}")
         | 
| 1034 | 
            -
                        if self.project_logger:
         | 
| 1035 | 
            -
                            self.project_logger.log_system(f"{error_msg}: {e}", level="ERROR", component="session")
         | 
| 1036 | 
            -
                            self._log_session_event({
         | 
| 1037 | 
            -
                                "event": "session_exception",
         | 
| 1038 | 
            -
                                "success": False,
         | 
| 1039 | 
            -
                                "exception": str(e),
         | 
| 1040 | 
            -
                                "exception_type": "MemoryError"
         | 
| 1041 | 
            -
                            })
         | 
| 1042 | 
            -
                        return False
         | 
| 1043 | 
            -
                    
         | 
| 1044 | 
            -
                    except Exception as e:
         | 
| 1045 | 
            -
                        error_msg = f"Unexpected error: {e}"
         | 
| 1046 | 
            -
                        print(f"❌ {error_msg}")
         | 
| 1047 | 
            -
                        print(f"   Error type: {type(e).__name__}")
         | 
| 575 | 
            +
                        # Step 4: Execute command
         | 
| 576 | 
            +
                        success, response = session.execute_command(prompt, context, infrastructure)
         | 
| 1048 577 |  | 
| 1049 | 
            -
                         | 
| 1050 | 
            -
                            self.project_logger.log_system(
         | 
| 1051 | 
            -
                                f"Exception during non-interactive session: {e}",
         | 
| 1052 | 
            -
                                level="ERROR",
         | 
| 1053 | 
            -
                                component="session"
         | 
| 1054 | 
            -
                            )
         | 
| 1055 | 
            -
                            self._log_session_event({
         | 
| 1056 | 
            -
                                "event": "session_exception",
         | 
| 1057 | 
            -
                                "success": False,
         | 
| 1058 | 
            -
                                "exception": str(e),
         | 
| 1059 | 
            -
                                "exception_type": type(e).__name__
         | 
| 1060 | 
            -
                            })
         | 
| 578 | 
            +
                        return success
         | 
| 1061 579 |  | 
| 1062 | 
            -
                        return False
         | 
| 1063 580 | 
             
                    finally:
         | 
| 1064 | 
            -
                        #  | 
| 1065 | 
            -
                         | 
| 1066 | 
            -
                            try:
         | 
| 1067 | 
            -
                                # Log session summary
         | 
| 1068 | 
            -
                                summary = self.project_logger.get_session_summary()
         | 
| 1069 | 
            -
                                self.project_logger.log_system(
         | 
| 1070 | 
            -
                                    f"Session {summary['session_id']} completed",
         | 
| 1071 | 
            -
                                    level="INFO",
         | 
| 1072 | 
            -
                                    component="session"
         | 
| 1073 | 
            -
                                )
         | 
| 1074 | 
            -
                            except Exception as e:
         | 
| 1075 | 
            -
                                self.logger.debug(f"Failed to log session summary: {e}")
         | 
| 1076 | 
            -
                        
         | 
| 1077 | 
            -
                        # End WebSocket session
         | 
| 1078 | 
            -
                        if self.websocket_server:
         | 
| 1079 | 
            -
                            self.websocket_server.claude_status_changed(
         | 
| 1080 | 
            -
                                status="stopped",
         | 
| 1081 | 
            -
                                message="Session completed"
         | 
| 1082 | 
            -
                            )
         | 
| 1083 | 
            -
                            self.websocket_server.session_ended()
         | 
| 581 | 
            +
                        # Step 5: Clean up session
         | 
| 582 | 
            +
                        session.cleanup_session()
         | 
| 1084 583 |  | 
| 1085 584 | 
             
                def _extract_tickets(self, text: str):
         | 
| 1086 585 | 
             
                    """Extract tickets from Claude's response."""
         | 
| @@ -1153,6 +652,9 @@ class ClaudeRunner: | |
| 1153 652 | 
             
                        # Read raw instructions
         | 
| 1154 653 | 
             
                        raw_instructions = instructions_path.read_text()
         | 
| 1155 654 |  | 
| 655 | 
            +
                        # Strip HTML metadata comments before processing
         | 
| 656 | 
            +
                        raw_instructions = self._strip_metadata_comments(raw_instructions)
         | 
| 657 | 
            +
                        
         | 
| 1156 658 | 
             
                        # Process template variables if ContentAssembler is available
         | 
| 1157 659 | 
             
                        try:
         | 
| 1158 660 | 
             
                            from claude_mpm.services.framework_claude_md_generator.content_assembler import ContentAssembler
         | 
| @@ -1164,6 +666,9 @@ class ClaudeRunner: | |
| 1164 666 | 
             
                            if base_pm_path.exists():
         | 
| 1165 667 | 
             
                                base_pm_content = base_pm_path.read_text()
         | 
| 1166 668 |  | 
| 669 | 
            +
                                # Strip metadata comments from BASE_PM.md as well
         | 
| 670 | 
            +
                                base_pm_content = self._strip_metadata_comments(base_pm_content)
         | 
| 671 | 
            +
                                
         | 
| 1167 672 | 
             
                                # Process BASE_PM.md with dynamic content injection
         | 
| 1168 673 | 
             
                                base_pm_content = self._process_base_pm_content(base_pm_content)
         | 
| 1169 674 |  | 
| @@ -1205,6 +710,45 @@ class ClaudeRunner: | |
| 1205 710 |  | 
| 1206 711 | 
             
                    return base_pm_content
         | 
| 1207 712 |  | 
| 713 | 
            +
                def _strip_metadata_comments(self, content: str) -> str:
         | 
| 714 | 
            +
                    """Strip HTML metadata comments from content.
         | 
| 715 | 
            +
                    
         | 
| 716 | 
            +
                    Removes comments like:
         | 
| 717 | 
            +
                    <!-- FRAMEWORK_VERSION: 0010 -->
         | 
| 718 | 
            +
                    <!-- LAST_MODIFIED: 2025-08-10T00:00:00Z -->
         | 
| 719 | 
            +
                    <!-- WORKFLOW_VERSION: ... -->
         | 
| 720 | 
            +
                    <!-- PROJECT_WORKFLOW_VERSION: ... -->
         | 
| 721 | 
            +
                    <!-- CUSTOM_PROJECT_WORKFLOW -->
         | 
| 722 | 
            +
                    
         | 
| 723 | 
            +
                    WHY: These metadata comments are useful for internal tracking but should not
         | 
| 724 | 
            +
                    appear in the final instructions passed to Claude via --append-system-prompt.
         | 
| 725 | 
            +
                    They clutter the instructions and provide no value to the Claude agent.
         | 
| 726 | 
            +
                    
         | 
| 727 | 
            +
                    DESIGN DECISION: Using regex to remove all HTML comments that contain known
         | 
| 728 | 
            +
                    metadata patterns. Also removes any resulting leading blank lines.
         | 
| 729 | 
            +
                    """
         | 
| 730 | 
            +
                    import re
         | 
| 731 | 
            +
                    
         | 
| 732 | 
            +
                    # Remove HTML comments that contain metadata
         | 
| 733 | 
            +
                    patterns_to_strip = [
         | 
| 734 | 
            +
                        'FRAMEWORK_VERSION',
         | 
| 735 | 
            +
                        'LAST_MODIFIED', 
         | 
| 736 | 
            +
                        'WORKFLOW_VERSION',
         | 
| 737 | 
            +
                        'PROJECT_WORKFLOW_VERSION',
         | 
| 738 | 
            +
                        'CUSTOM_PROJECT_WORKFLOW',
         | 
| 739 | 
            +
                        'AGENT_VERSION',
         | 
| 740 | 
            +
                        'METADATA_VERSION'
         | 
| 741 | 
            +
                    ]
         | 
| 742 | 
            +
                    
         | 
| 743 | 
            +
                    # Build regex pattern to match any of these metadata comments
         | 
| 744 | 
            +
                    pattern = r'<!--\s*(' + '|'.join(patterns_to_strip) + r')[^>]*-->\n?'
         | 
| 745 | 
            +
                    cleaned = re.sub(pattern, '', content)
         | 
| 746 | 
            +
                    
         | 
| 747 | 
            +
                    # Also remove any leading blank lines that might result
         | 
| 748 | 
            +
                    cleaned = cleaned.lstrip('\n')
         | 
| 749 | 
            +
                    
         | 
| 750 | 
            +
                    return cleaned
         | 
| 751 | 
            +
                
         | 
| 1208 752 | 
             
                def _generate_deployed_agent_capabilities(self) -> str:
         | 
| 1209 753 | 
             
                    """Generate agent capabilities from deployed agents following Claude Code's hierarchy.
         | 
| 1210 754 |  |