claude-mpm 3.7.4__py3-none-any.whl → 3.8.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/VERSION +1 -1
- claude_mpm/agents/BASE_PM.md +0 -106
- claude_mpm/agents/INSTRUCTIONS.md +0 -78
- claude_mpm/agents/MEMORY.md +88 -0
- claude_mpm/agents/WORKFLOW.md +86 -0
- claude_mpm/agents/schema/agent_schema.json +1 -1
- claude_mpm/agents/templates/code_analyzer.json +26 -11
- claude_mpm/agents/templates/data_engineer.json +4 -7
- claude_mpm/agents/templates/documentation.json +2 -2
- claude_mpm/agents/templates/engineer.json +2 -2
- claude_mpm/agents/templates/ops.json +3 -8
- claude_mpm/agents/templates/qa.json +2 -3
- claude_mpm/agents/templates/research.json +2 -3
- claude_mpm/agents/templates/security.json +3 -6
- claude_mpm/agents/templates/ticketing.json +4 -9
- claude_mpm/agents/templates/version_control.json +3 -3
- claude_mpm/agents/templates/web_qa.json +4 -4
- claude_mpm/agents/templates/web_ui.json +4 -4
- claude_mpm/cli/__init__.py +2 -2
- claude_mpm/cli/commands/__init__.py +2 -1
- claude_mpm/cli/commands/agents.py +118 -1
- claude_mpm/cli/commands/tickets.py +596 -19
- claude_mpm/cli/parser.py +228 -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 +5 -1
- claude_mpm/core/constants.py +339 -0
- claude_mpm/core/container.py +461 -22
- claude_mpm/core/exceptions.py +392 -0
- claude_mpm/core/framework_loader.py +208 -93
- claude_mpm/core/interactive_session.py +432 -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/dashboard/static/js/components/file-tool-tracker.js +46 -2
- claude_mpm/dashboard/templates/index.html +5 -5
- 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 +592 -269
- claude_mpm/services/agents/deployment/async_agent_deployment.py +5 -1
- claude_mpm/services/agents/management/agent_capabilities_generator.py +21 -11
- claude_mpm/services/agents/memory/agent_memory_manager.py +156 -1
- 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/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/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 +377 -51
- claude_mpm/utils/agent_dependency_loader.py +66 -15
- claude_mpm/utils/error_handler.py +1 -1
- claude_mpm/utils/robust_installer.py +587 -0
- claude_mpm/validation/agent_validator.py +27 -14
- claude_mpm/validation/frontmatter_validator.py +231 -0
- {claude_mpm-3.7.4.dist-info → claude_mpm-3.8.1.dist-info}/METADATA +74 -41
- {claude_mpm-3.7.4.dist-info → claude_mpm-3.8.1.dist-info}/RECORD +101 -76
- claude_mpm/.claude-mpm/logs/hooks_20250728.log +0 -10
- claude_mpm/agents/agent-template.yaml +0 -83
- claude_mpm/cli/README.md +0 -108
- claude_mpm/cli_module/refactoring_guide.md +0 -253
- claude_mpm/config/async_logging_config.yaml +0 -145
- claude_mpm/core/.claude-mpm/logs/hooks_20250730.log +0 -34
- claude_mpm/dashboard/.claude-mpm/memories/README.md +0 -36
- claude_mpm/dashboard/README.md +0 -121
- claude_mpm/dashboard/static/js/dashboard.js.backup +0 -1973
- claude_mpm/dashboard/templates/.claude-mpm/memories/README.md +0 -36
- claude_mpm/dashboard/templates/.claude-mpm/memories/engineer_agent.md +0 -39
- claude_mpm/dashboard/templates/.claude-mpm/memories/version_control_agent.md +0 -38
- claude_mpm/hooks/README.md +0 -96
- claude_mpm/schemas/agent_schema.json +0 -435
- claude_mpm/services/framework_claude_md_generator/README.md +0 -92
- claude_mpm/services/version_control/VERSION +0 -1
- {claude_mpm-3.7.4.dist-info → claude_mpm-3.8.1.dist-info}/WHEEL +0 -0
- {claude_mpm-3.7.4.dist-info → claude_mpm-3.8.1.dist-info}/entry_points.txt +0 -0
- {claude_mpm-3.7.4.dist-info → claude_mpm-3.8.1.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-3.7.4.dist-info → claude_mpm-3.8.1.dist-info}/top_level.txt +0 -0
| @@ -32,6 +32,7 @@ from collections import defaultdict, Counter | |
| 32 32 |  | 
| 33 33 | 
             
            from claude_mpm.core.config import Config
         | 
| 34 34 | 
             
            from claude_mpm.utils.paths import PathResolver
         | 
| 35 | 
            +
            from claude_mpm.core.interfaces import ProjectAnalyzerInterface
         | 
| 35 36 |  | 
| 36 37 |  | 
| 37 38 | 
             
            @dataclass
         | 
| @@ -76,7 +77,7 @@ class ProjectCharacteristics: | |
| 76 77 | 
             
                    return asdict(self)
         | 
| 77 78 |  | 
| 78 79 |  | 
| 79 | 
            -
            class ProjectAnalyzer:
         | 
| 80 | 
            +
            class ProjectAnalyzer(ProjectAnalyzerInterface):
         | 
| 80 81 | 
             
                """Analyzes project characteristics for context-aware memory creation.
         | 
| 81 82 |  | 
| 82 83 | 
             
                WHY: Generic agent memories aren't helpful for specific projects. This analyzer
         | 
| @@ -768,4 +769,96 @@ class ProjectAnalyzer: | |
| 768 769 | 
             
                            important_files.append(pattern)
         | 
| 769 770 |  | 
| 770 771 | 
             
                    # Remove duplicates and return
         | 
| 771 | 
            -
                    return list(set(important_files))
         | 
| 772 | 
            +
                    return list(set(important_files))
         | 
| 773 | 
            +
                
         | 
| 774 | 
            +
                # ================================================================================
         | 
| 775 | 
            +
                # Interface Adapter Methods
         | 
| 776 | 
            +
                # ================================================================================
         | 
| 777 | 
            +
                # These methods adapt the existing implementation to comply with ProjectAnalyzerInterface
         | 
| 778 | 
            +
                
         | 
| 779 | 
            +
                def detect_technology_stack(self) -> List[str]:
         | 
| 780 | 
            +
                    """Detect technologies used in the project.
         | 
| 781 | 
            +
                    
         | 
| 782 | 
            +
                    WHY: This adapter method provides interface compliance by extracting
         | 
| 783 | 
            +
                    technology information from the analyzed project characteristics.
         | 
| 784 | 
            +
                    
         | 
| 785 | 
            +
                    Returns:
         | 
| 786 | 
            +
                        List of detected technologies
         | 
| 787 | 
            +
                    """
         | 
| 788 | 
            +
                    characteristics = self.analyze_project()
         | 
| 789 | 
            +
                    
         | 
| 790 | 
            +
                    technologies = []
         | 
| 791 | 
            +
                    technologies.extend(characteristics.languages)
         | 
| 792 | 
            +
                    technologies.extend(characteristics.frameworks)
         | 
| 793 | 
            +
                    technologies.extend(characteristics.web_frameworks)
         | 
| 794 | 
            +
                    technologies.extend(characteristics.databases)
         | 
| 795 | 
            +
                    
         | 
| 796 | 
            +
                    # Add package manager as technology
         | 
| 797 | 
            +
                    if characteristics.package_manager:
         | 
| 798 | 
            +
                        technologies.append(characteristics.package_manager)
         | 
| 799 | 
            +
                    
         | 
| 800 | 
            +
                    # Add build tools
         | 
| 801 | 
            +
                    technologies.extend(characteristics.build_tools)
         | 
| 802 | 
            +
                    
         | 
| 803 | 
            +
                    # Remove duplicates
         | 
| 804 | 
            +
                    return list(set(technologies))
         | 
| 805 | 
            +
                
         | 
| 806 | 
            +
                def analyze_code_patterns(self) -> Dict[str, Any]:
         | 
| 807 | 
            +
                    """Analyze code patterns and conventions.
         | 
| 808 | 
            +
                    
         | 
| 809 | 
            +
                    WHY: This adapter method provides interface compliance by extracting
         | 
| 810 | 
            +
                    pattern information from the project characteristics.
         | 
| 811 | 
            +
                    
         | 
| 812 | 
            +
                    Returns:
         | 
| 813 | 
            +
                        Dictionary of pattern analysis results
         | 
| 814 | 
            +
                    """
         | 
| 815 | 
            +
                    characteristics = self.analyze_project()
         | 
| 816 | 
            +
                    
         | 
| 817 | 
            +
                    return {
         | 
| 818 | 
            +
                        "code_conventions": characteristics.code_conventions,
         | 
| 819 | 
            +
                        "test_patterns": characteristics.test_patterns,
         | 
| 820 | 
            +
                        "api_patterns": characteristics.api_patterns,
         | 
| 821 | 
            +
                        "configuration_patterns": characteristics.configuration_patterns,
         | 
| 822 | 
            +
                        "architecture_type": characteristics.architecture_type
         | 
| 823 | 
            +
                    }
         | 
| 824 | 
            +
                
         | 
| 825 | 
            +
                def get_project_structure(self) -> Dict[str, Any]:
         | 
| 826 | 
            +
                    """Get project directory structure analysis.
         | 
| 827 | 
            +
                    
         | 
| 828 | 
            +
                    WHY: This adapter method provides interface compliance by organizing
         | 
| 829 | 
            +
                    structural information from the project characteristics.
         | 
| 830 | 
            +
                    
         | 
| 831 | 
            +
                    Returns:
         | 
| 832 | 
            +
                        Dictionary representing project structure
         | 
| 833 | 
            +
                    """
         | 
| 834 | 
            +
                    characteristics = self.analyze_project()
         | 
| 835 | 
            +
                    
         | 
| 836 | 
            +
                    return {
         | 
| 837 | 
            +
                        "project_name": characteristics.project_name,
         | 
| 838 | 
            +
                        "main_modules": characteristics.main_modules,
         | 
| 839 | 
            +
                        "key_directories": characteristics.key_directories,
         | 
| 840 | 
            +
                        "entry_points": characteristics.entry_points,
         | 
| 841 | 
            +
                        "documentation_files": characteristics.documentation_files,
         | 
| 842 | 
            +
                        "important_configs": characteristics.important_configs,
         | 
| 843 | 
            +
                        "architecture_type": characteristics.architecture_type
         | 
| 844 | 
            +
                    }
         | 
| 845 | 
            +
                
         | 
| 846 | 
            +
                def identify_entry_points(self) -> List[Path]:
         | 
| 847 | 
            +
                    """Identify project entry points.
         | 
| 848 | 
            +
                    
         | 
| 849 | 
            +
                    WHY: This adapter method provides interface compliance by converting
         | 
| 850 | 
            +
                    string entry points to Path objects as expected by the interface.
         | 
| 851 | 
            +
                    
         | 
| 852 | 
            +
                    Returns:
         | 
| 853 | 
            +
                        List of entry point paths
         | 
| 854 | 
            +
                    """
         | 
| 855 | 
            +
                    characteristics = self.analyze_project()
         | 
| 856 | 
            +
                    
         | 
| 857 | 
            +
                    # Convert string paths to Path objects
         | 
| 858 | 
            +
                    entry_paths = []
         | 
| 859 | 
            +
                    for entry_point in characteristics.entry_points:
         | 
| 860 | 
            +
                        entry_path = self.working_directory / entry_point
         | 
| 861 | 
            +
                        if entry_path.exists():
         | 
| 862 | 
            +
                            entry_paths.append(entry_path)
         | 
| 863 | 
            +
                    
         | 
| 864 | 
            +
                    return entry_paths
         | 
| @@ -28,6 +28,11 @@ from datetime import datetime, timezone | |
| 28 28 | 
             
            from enum import Enum
         | 
| 29 29 | 
             
            from typing import Any, Dict, List, Optional, Callable, Union
         | 
| 30 30 | 
             
            import json
         | 
| 31 | 
            +
            from claude_mpm.core.constants import (
         | 
| 32 | 
            +
                RetryConfig,
         | 
| 33 | 
            +
                TimeoutConfig,
         | 
| 34 | 
            +
                PerformanceConfig
         | 
| 35 | 
            +
            )
         | 
| 31 36 |  | 
| 32 37 | 
             
            from .health_monitor import HealthStatus, HealthCheckResult
         | 
| 33 38 |  | 
| @@ -112,9 +117,9 @@ class GradedRecoveryStrategy(RecoveryStrategy): | |
| 112 117 |  | 
| 113 118 | 
             
                    # Configuration with defaults
         | 
| 114 119 | 
             
                    self.warning_threshold = self.config.get('warning_threshold', 2)
         | 
| 115 | 
            -
                    self.critical_threshold = self.config.get('critical_threshold',  | 
| 116 | 
            -
                    self.failure_window_seconds = self.config.get('failure_window_seconds',  | 
| 117 | 
            -
                    self.min_recovery_interval = self.config.get('min_recovery_interval',  | 
| 120 | 
            +
                    self.critical_threshold = self.config.get('critical_threshold', RetryConfig.CRITICAL_THRESHOLD)
         | 
| 121 | 
            +
                    self.failure_window_seconds = self.config.get('failure_window_seconds', RetryConfig.FAILURE_WINDOW)
         | 
| 122 | 
            +
                    self.min_recovery_interval = self.config.get('min_recovery_interval', RetryConfig.MIN_RECOVERY_INTERVAL)
         | 
| 118 123 |  | 
| 119 124 | 
             
                    # Track recent failures
         | 
| 120 125 | 
             
                    self.recent_failures: deque = deque(maxlen=10)
         | 
| @@ -193,8 +198,9 @@ class CircuitBreaker: | |
| 193 198 | 
             
                - Gradually re-enable recovery after failures
         | 
| 194 199 | 
             
                """
         | 
| 195 200 |  | 
| 196 | 
            -
                def __init__(self, failure_threshold: int =  | 
| 197 | 
            -
                              | 
| 201 | 
            +
                def __init__(self, failure_threshold: int = RetryConfig.FAILURE_THRESHOLD, 
         | 
| 202 | 
            +
                             timeout_seconds: int = RetryConfig.CIRCUIT_TIMEOUT, 
         | 
| 203 | 
            +
                             success_threshold: int = RetryConfig.SUCCESS_THRESHOLD):
         | 
| 198 204 | 
             
                    """Initialize circuit breaker.
         | 
| 199 205 |  | 
| 200 206 | 
             
                    Args:
         | 
| @@ -338,9 +344,9 @@ class RecoveryManager: | |
| 338 344 | 
             
                    # Initialize circuit breaker
         | 
| 339 345 | 
             
                    circuit_config = self.config.get('circuit_breaker', {})
         | 
| 340 346 | 
             
                    self.circuit_breaker = CircuitBreaker(
         | 
| 341 | 
            -
                        failure_threshold=circuit_config.get('failure_threshold',  | 
| 342 | 
            -
                        timeout_seconds=circuit_config.get('timeout_seconds',  | 
| 343 | 
            -
                        success_threshold=circuit_config.get('success_threshold',  | 
| 347 | 
            +
                        failure_threshold=circuit_config.get('failure_threshold', RetryConfig.FAILURE_THRESHOLD),
         | 
| 348 | 
            +
                        timeout_seconds=circuit_config.get('timeout_seconds', RetryConfig.CIRCUIT_TIMEOUT),
         | 
| 349 | 
            +
                        success_threshold=circuit_config.get('success_threshold', RetryConfig.SUCCESS_THRESHOLD)
         | 
| 344 350 | 
             
                    )
         | 
| 345 351 |  | 
| 346 352 | 
             
                    # Initialize recovery strategy
         | 
| @@ -451,7 +457,7 @@ class RecoveryManager: | |
| 451 457 |  | 
| 452 458 | 
             
                    finally:
         | 
| 453 459 | 
             
                        self.recovery_in_progress = False
         | 
| 454 | 
            -
                        duration_ms = (time.time() - start_time) *  | 
| 460 | 
            +
                        duration_ms = (time.time() - start_time) * PerformanceConfig.SECONDS_TO_MS
         | 
| 455 461 |  | 
| 456 462 | 
             
                        # Create recovery event
         | 
| 457 463 | 
             
                        event = RecoveryEvent(
         | 
| @@ -0,0 +1,25 @@ | |
| 1 | 
            +
            """Socket.IO service module.
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            WHY: This module provides the modular Socket.IO server implementation
         | 
| 4 | 
            +
            with separated event handlers for improved maintainability.
         | 
| 5 | 
            +
            """
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            from .handlers import (
         | 
| 8 | 
            +
                BaseEventHandler,
         | 
| 9 | 
            +
                ConnectionEventHandler,
         | 
| 10 | 
            +
                ProjectEventHandler,
         | 
| 11 | 
            +
                MemoryEventHandler,
         | 
| 12 | 
            +
                FileEventHandler,
         | 
| 13 | 
            +
                GitEventHandler,
         | 
| 14 | 
            +
                EventHandlerRegistry,
         | 
| 15 | 
            +
            )
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            __all__ = [
         | 
| 18 | 
            +
                "BaseEventHandler",
         | 
| 19 | 
            +
                "ConnectionEventHandler",
         | 
| 20 | 
            +
                "ProjectEventHandler",
         | 
| 21 | 
            +
                "MemoryEventHandler",
         | 
| 22 | 
            +
                "FileEventHandler",
         | 
| 23 | 
            +
                "GitEventHandler",
         | 
| 24 | 
            +
                "EventHandlerRegistry",
         | 
| 25 | 
            +
            ]
         | 
| @@ -0,0 +1,25 @@ | |
| 1 | 
            +
            """Socket.IO event handlers module.
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            WHY: This module provides a modular, maintainable structure for Socket.IO event handling,
         | 
| 4 | 
            +
            replacing the monolithic _register_events() method with focused handler classes.
         | 
| 5 | 
            +
            Each handler class manages a specific domain of functionality, improving testability
         | 
| 6 | 
            +
            and maintainability.
         | 
| 7 | 
            +
            """
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            from .base import BaseEventHandler
         | 
| 10 | 
            +
            from .connection import ConnectionEventHandler
         | 
| 11 | 
            +
            from .project import ProjectEventHandler
         | 
| 12 | 
            +
            from .memory import MemoryEventHandler
         | 
| 13 | 
            +
            from .file import FileEventHandler
         | 
| 14 | 
            +
            from .git import GitEventHandler
         | 
| 15 | 
            +
            from .registry import EventHandlerRegistry
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            __all__ = [
         | 
| 18 | 
            +
                "BaseEventHandler",
         | 
| 19 | 
            +
                "ConnectionEventHandler",
         | 
| 20 | 
            +
                "ProjectEventHandler",
         | 
| 21 | 
            +
                "MemoryEventHandler",
         | 
| 22 | 
            +
                "FileEventHandler",
         | 
| 23 | 
            +
                "GitEventHandler",
         | 
| 24 | 
            +
                "EventHandlerRegistry",
         | 
| 25 | 
            +
            ]
         | 
| @@ -0,0 +1,121 @@ | |
| 1 | 
            +
            """Base event handler class for Socket.IO events.
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            WHY: This provides common functionality for all event handlers, including
         | 
| 4 | 
            +
            logging, error handling, and access to the server instance. All handler
         | 
| 5 | 
            +
            classes inherit from this to ensure consistent behavior.
         | 
| 6 | 
            +
            """
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            import logging
         | 
| 9 | 
            +
            from typing import Any, Dict, Optional, List, TYPE_CHECKING
         | 
| 10 | 
            +
            from datetime import datetime
         | 
| 11 | 
            +
            from logging import Logger
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            from ....core.logger import get_logger
         | 
| 14 | 
            +
            from ....core.typing_utils import EventName, EventData, SocketId
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            if TYPE_CHECKING:
         | 
| 17 | 
            +
                from ..server import SocketIOServer
         | 
| 18 | 
            +
                import socketio
         | 
| 19 | 
            +
             | 
| 20 | 
            +
             | 
| 21 | 
            +
            class BaseEventHandler:
         | 
| 22 | 
            +
                """Base class for Socket.IO event handlers.
         | 
| 23 | 
            +
                
         | 
| 24 | 
            +
                WHY: Provides common functionality and structure for all event handlers,
         | 
| 25 | 
            +
                ensuring consistent error handling, logging, and server access patterns.
         | 
| 26 | 
            +
                Each handler focuses on a specific domain while sharing common infrastructure.
         | 
| 27 | 
            +
                """
         | 
| 28 | 
            +
                
         | 
| 29 | 
            +
                def __init__(self, server: 'SocketIOServer') -> None:
         | 
| 30 | 
            +
                    """Initialize the base handler.
         | 
| 31 | 
            +
                    
         | 
| 32 | 
            +
                    Args:
         | 
| 33 | 
            +
                        server: The SocketIOServer instance that owns this handler
         | 
| 34 | 
            +
                    """
         | 
| 35 | 
            +
                    self.server: 'SocketIOServer' = server
         | 
| 36 | 
            +
                    self.sio: 'socketio.AsyncServer' = server.sio
         | 
| 37 | 
            +
                    self.logger: Logger = get_logger(self.__class__.__name__)
         | 
| 38 | 
            +
                    self.clients: Dict[SocketId, Dict[str, Any]] = server.clients
         | 
| 39 | 
            +
                    self.event_history: List[Dict[str, Any]] = server.event_history
         | 
| 40 | 
            +
                
         | 
| 41 | 
            +
                def register_events(self) -> None:
         | 
| 42 | 
            +
                    """Register all events handled by this handler.
         | 
| 43 | 
            +
                    
         | 
| 44 | 
            +
                    WHY: This method must be implemented by each handler subclass
         | 
| 45 | 
            +
                    to register its specific events with the Socket.IO server.
         | 
| 46 | 
            +
                    """
         | 
| 47 | 
            +
                    raise NotImplementedError("Subclasses must implement register_events()")
         | 
| 48 | 
            +
                
         | 
| 49 | 
            +
                async def emit_to_client(self, sid: SocketId, event: EventName, data: EventData) -> None:
         | 
| 50 | 
            +
                    """Emit an event to a specific client.
         | 
| 51 | 
            +
                    
         | 
| 52 | 
            +
                    WHY: Centralizes client communication with consistent error handling
         | 
| 53 | 
            +
                    and logging for debugging connection issues.
         | 
| 54 | 
            +
                    
         | 
| 55 | 
            +
                    Args:
         | 
| 56 | 
            +
                        sid: Socket.IO session ID of the client
         | 
| 57 | 
            +
                        event: Event name to emit
         | 
| 58 | 
            +
                        data: Data to send with the event
         | 
| 59 | 
            +
                    """
         | 
| 60 | 
            +
                    try:
         | 
| 61 | 
            +
                        await self.sio.emit(event, data, room=sid)
         | 
| 62 | 
            +
                        self.logger.debug(f"Sent {event} to client {sid}")
         | 
| 63 | 
            +
                    except Exception as e:
         | 
| 64 | 
            +
                        self.logger.error(f"Failed to emit {event} to client {sid}: {e}")
         | 
| 65 | 
            +
                        import traceback
         | 
| 66 | 
            +
                        self.logger.error(f"Stack trace: {traceback.format_exc()}")
         | 
| 67 | 
            +
                
         | 
| 68 | 
            +
                async def broadcast_event(self, event: EventName, data: EventData, skip_sid: Optional[SocketId] = None) -> None:
         | 
| 69 | 
            +
                    """Broadcast an event to all connected clients.
         | 
| 70 | 
            +
                    
         | 
| 71 | 
            +
                    WHY: Provides consistent broadcasting with optional exclusion
         | 
| 72 | 
            +
                    of the originating client.
         | 
| 73 | 
            +
                    
         | 
| 74 | 
            +
                    Args:
         | 
| 75 | 
            +
                        event: Event name to broadcast
         | 
| 76 | 
            +
                        data: Data to send with the event
         | 
| 77 | 
            +
                        skip_sid: Optional session ID to skip
         | 
| 78 | 
            +
                    """
         | 
| 79 | 
            +
                    try:
         | 
| 80 | 
            +
                        if skip_sid:
         | 
| 81 | 
            +
                            await self.sio.emit(event, data, skip_sid=skip_sid)
         | 
| 82 | 
            +
                        else:
         | 
| 83 | 
            +
                            await self.sio.emit(event, data)
         | 
| 84 | 
            +
                        self.logger.debug(f"Broadcasted {event} to all clients")
         | 
| 85 | 
            +
                    except Exception as e:
         | 
| 86 | 
            +
                        self.logger.error(f"Failed to broadcast {event}: {e}")
         | 
| 87 | 
            +
                
         | 
| 88 | 
            +
                def add_to_history(self, event_type: str, data: EventData) -> None:
         | 
| 89 | 
            +
                    """Add an event to the server's event history.
         | 
| 90 | 
            +
                    
         | 
| 91 | 
            +
                    WHY: Maintains a history of events for new clients to receive
         | 
| 92 | 
            +
                    when they connect, ensuring they have context.
         | 
| 93 | 
            +
                    
         | 
| 94 | 
            +
                    Args:
         | 
| 95 | 
            +
                        event_type: Type of the event
         | 
| 96 | 
            +
                        data: Event data
         | 
| 97 | 
            +
                    """
         | 
| 98 | 
            +
                    event = {
         | 
| 99 | 
            +
                        "type": event_type,
         | 
| 100 | 
            +
                        "timestamp": datetime.utcnow().isoformat() + "Z",
         | 
| 101 | 
            +
                        "data": data
         | 
| 102 | 
            +
                    }
         | 
| 103 | 
            +
                    self.event_history.append(event)
         | 
| 104 | 
            +
                    self.logger.debug(f"Added {event_type} to history (total: {len(self.event_history)})")
         | 
| 105 | 
            +
                
         | 
| 106 | 
            +
                def log_error(self, operation: str, error: Exception, context: Optional[Dict[str, Any]] = None) -> None:
         | 
| 107 | 
            +
                    """Log an error with context.
         | 
| 108 | 
            +
                    
         | 
| 109 | 
            +
                    WHY: Provides consistent error logging with context information
         | 
| 110 | 
            +
                    for debugging issues in production.
         | 
| 111 | 
            +
                    
         | 
| 112 | 
            +
                    Args:
         | 
| 113 | 
            +
                        operation: Description of the operation that failed
         | 
| 114 | 
            +
                        error: The exception that occurred
         | 
| 115 | 
            +
                        context: Optional context information
         | 
| 116 | 
            +
                    """
         | 
| 117 | 
            +
                    self.logger.error(f"Error in {operation}: {error}")
         | 
| 118 | 
            +
                    if context:
         | 
| 119 | 
            +
                        self.logger.error(f"Context: {context}")
         | 
| 120 | 
            +
                    import traceback
         | 
| 121 | 
            +
                    self.logger.error(f"Stack trace: {traceback.format_exc()}")
         | 
| @@ -0,0 +1,198 @@ | |
| 1 | 
            +
            """Connection event handlers for Socket.IO.
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            WHY: This module handles all connection-related events including connect,
         | 
| 4 | 
            +
            disconnect, status requests, and history management. Separating these
         | 
| 5 | 
            +
            from other handlers makes connection management more maintainable.
         | 
| 6 | 
            +
            """
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            from datetime import datetime
         | 
| 9 | 
            +
            from typing import Optional, List, Dict, Any, Set
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            from .base import BaseEventHandler
         | 
| 12 | 
            +
            from ....core.typing_utils import SocketId, EventData, ClaudeStatus
         | 
| 13 | 
            +
             | 
| 14 | 
            +
             | 
| 15 | 
            +
            class ConnectionEventHandler(BaseEventHandler):
         | 
| 16 | 
            +
                """Handles Socket.IO connection lifecycle events.
         | 
| 17 | 
            +
                
         | 
| 18 | 
            +
                WHY: Connection management is a critical aspect of the Socket.IO server
         | 
| 19 | 
            +
                that deserves its own focused handler. This includes client connections,
         | 
| 20 | 
            +
                disconnections, status updates, and event history management.
         | 
| 21 | 
            +
                """
         | 
| 22 | 
            +
                
         | 
| 23 | 
            +
                def register_events(self) -> None:
         | 
| 24 | 
            +
                    """Register connection-related event handlers."""
         | 
| 25 | 
            +
                    
         | 
| 26 | 
            +
                    @self.sio.event
         | 
| 27 | 
            +
                    async def connect(sid, environ, *args):
         | 
| 28 | 
            +
                        """Handle client connection.
         | 
| 29 | 
            +
                        
         | 
| 30 | 
            +
                        WHY: When a client connects, we need to track them, send initial
         | 
| 31 | 
            +
                        status information, and provide recent event history so they have
         | 
| 32 | 
            +
                        context for what's happening in the session.
         | 
| 33 | 
            +
                        """
         | 
| 34 | 
            +
                        self.clients.add(sid)
         | 
| 35 | 
            +
                        client_addr = environ.get('REMOTE_ADDR', 'unknown') 
         | 
| 36 | 
            +
                        user_agent = environ.get('HTTP_USER_AGENT', 'unknown')
         | 
| 37 | 
            +
                        self.logger.info(f"🔗 NEW CLIENT CONNECTED: {sid} from {client_addr}")
         | 
| 38 | 
            +
                        self.logger.info(f"📱 User Agent: {user_agent[:100]}...")
         | 
| 39 | 
            +
                        self.logger.info(f"📈 Total clients now: {len(self.clients)}")
         | 
| 40 | 
            +
                        
         | 
| 41 | 
            +
                        # Send initial status immediately with enhanced data
         | 
| 42 | 
            +
                        status_data = {
         | 
| 43 | 
            +
                            "server": "claude-mpm-python-socketio",
         | 
| 44 | 
            +
                            "timestamp": datetime.utcnow().isoformat() + "Z",
         | 
| 45 | 
            +
                            "clients_connected": len(self.clients),
         | 
| 46 | 
            +
                            "session_id": self.server.session_id,
         | 
| 47 | 
            +
                            "claude_status": self.server.claude_status,
         | 
| 48 | 
            +
                            "claude_pid": self.server.claude_pid,
         | 
| 49 | 
            +
                            "server_version": "2.0.0",
         | 
| 50 | 
            +
                            "client_id": sid
         | 
| 51 | 
            +
                        }
         | 
| 52 | 
            +
                        
         | 
| 53 | 
            +
                        try:
         | 
| 54 | 
            +
                            await self.emit_to_client(sid, 'status', status_data)
         | 
| 55 | 
            +
                            await self.emit_to_client(sid, 'welcome', {
         | 
| 56 | 
            +
                                "message": "Connected to Claude MPM Socket.IO server",
         | 
| 57 | 
            +
                                "client_id": sid,
         | 
| 58 | 
            +
                                "server_time": datetime.utcnow().isoformat() + "Z"
         | 
| 59 | 
            +
                            })
         | 
| 60 | 
            +
                            
         | 
| 61 | 
            +
                            # Automatically send the last 50 events to new clients
         | 
| 62 | 
            +
                            await self._send_event_history(sid, limit=50)
         | 
| 63 | 
            +
                            
         | 
| 64 | 
            +
                            self.logger.debug(f"✅ Sent welcome messages and event history to client {sid}")
         | 
| 65 | 
            +
                        except Exception as e:
         | 
| 66 | 
            +
                            self.log_error(f"sending welcome to client {sid}", e)
         | 
| 67 | 
            +
                    
         | 
| 68 | 
            +
                    @self.sio.event
         | 
| 69 | 
            +
                    async def disconnect(sid):
         | 
| 70 | 
            +
                        """Handle client disconnection.
         | 
| 71 | 
            +
                        
         | 
| 72 | 
            +
                        WHY: We need to clean up client tracking when they disconnect
         | 
| 73 | 
            +
                        to maintain accurate connection counts and avoid memory leaks.
         | 
| 74 | 
            +
                        """
         | 
| 75 | 
            +
                        if sid in self.clients:
         | 
| 76 | 
            +
                            self.clients.remove(sid)
         | 
| 77 | 
            +
                            self.logger.info(f"🔌 CLIENT DISCONNECTED: {sid}")
         | 
| 78 | 
            +
                            self.logger.info(f"📉 Total clients now: {len(self.clients)}")
         | 
| 79 | 
            +
                        else:
         | 
| 80 | 
            +
                            self.logger.warning(f"⚠️  Attempted to disconnect unknown client: {sid}")
         | 
| 81 | 
            +
                    
         | 
| 82 | 
            +
                    @self.sio.event
         | 
| 83 | 
            +
                    async def get_status(sid):
         | 
| 84 | 
            +
                        """Handle status request.
         | 
| 85 | 
            +
                        
         | 
| 86 | 
            +
                        WHY: Clients need to query current server status on demand
         | 
| 87 | 
            +
                        to update their UI or verify connection health.
         | 
| 88 | 
            +
                        """
         | 
| 89 | 
            +
                        status_data = {
         | 
| 90 | 
            +
                            "server": "claude-mpm-python-socketio",
         | 
| 91 | 
            +
                            "timestamp": datetime.utcnow().isoformat() + "Z",
         | 
| 92 | 
            +
                            "clients_connected": len(self.clients),
         | 
| 93 | 
            +
                            "session_id": self.server.session_id,
         | 
| 94 | 
            +
                            "claude_status": self.server.claude_status,
         | 
| 95 | 
            +
                            "claude_pid": self.server.claude_pid
         | 
| 96 | 
            +
                        }
         | 
| 97 | 
            +
                        await self.emit_to_client(sid, 'status', status_data)
         | 
| 98 | 
            +
                    
         | 
| 99 | 
            +
                    @self.sio.event
         | 
| 100 | 
            +
                    async def get_history(sid, data=None):
         | 
| 101 | 
            +
                        """Handle history request.
         | 
| 102 | 
            +
                        
         | 
| 103 | 
            +
                        WHY: Clients may need to request specific event history
         | 
| 104 | 
            +
                        to reconstruct state or filter by event types.
         | 
| 105 | 
            +
                        """
         | 
| 106 | 
            +
                        params = data or {}
         | 
| 107 | 
            +
                        event_types = params.get("event_types", [])
         | 
| 108 | 
            +
                        limit = min(params.get("limit", 100), len(self.event_history))
         | 
| 109 | 
            +
                        
         | 
| 110 | 
            +
                        await self._send_event_history(sid, event_types=event_types, limit=limit)
         | 
| 111 | 
            +
                    
         | 
| 112 | 
            +
                    @self.sio.event
         | 
| 113 | 
            +
                    async def request_history(sid, data=None):
         | 
| 114 | 
            +
                        """Handle legacy history request (for client compatibility).
         | 
| 115 | 
            +
                        
         | 
| 116 | 
            +
                        WHY: Maintains backward compatibility with clients using
         | 
| 117 | 
            +
                        the older 'request.history' event name.
         | 
| 118 | 
            +
                        """
         | 
| 119 | 
            +
                        params = data or {}
         | 
| 120 | 
            +
                        event_types = params.get("event_types", [])
         | 
| 121 | 
            +
                        limit = min(params.get("limit", 50), len(self.event_history))
         | 
| 122 | 
            +
                        
         | 
| 123 | 
            +
                        await self._send_event_history(sid, event_types=event_types, limit=limit)
         | 
| 124 | 
            +
                    
         | 
| 125 | 
            +
                    @self.sio.event
         | 
| 126 | 
            +
                    async def subscribe(sid, data=None):
         | 
| 127 | 
            +
                        """Handle subscription request.
         | 
| 128 | 
            +
                        
         | 
| 129 | 
            +
                        WHY: Allows clients to subscribe to specific event channels
         | 
| 130 | 
            +
                        for filtered event streaming.
         | 
| 131 | 
            +
                        """
         | 
| 132 | 
            +
                        channels = data.get("channels", ["*"]) if data else ["*"]
         | 
| 133 | 
            +
                        await self.emit_to_client(sid, 'subscribed', {
         | 
| 134 | 
            +
                            "channels": channels
         | 
| 135 | 
            +
                        })
         | 
| 136 | 
            +
                    
         | 
| 137 | 
            +
                    @self.sio.event
         | 
| 138 | 
            +
                    async def claude_event(sid, data):
         | 
| 139 | 
            +
                        """Handle events from client proxies.
         | 
| 140 | 
            +
                        
         | 
| 141 | 
            +
                        WHY: Client proxies send events that need to be stored
         | 
| 142 | 
            +
                        in history and re-broadcast to other clients.
         | 
| 143 | 
            +
                        """
         | 
| 144 | 
            +
                        # Store in history
         | 
| 145 | 
            +
                        self.event_history.append(data)
         | 
| 146 | 
            +
                        self.logger.debug(f"📚 Event from client stored in history (total: {len(self.event_history)})")
         | 
| 147 | 
            +
                        
         | 
| 148 | 
            +
                        # Re-broadcast to all other clients
         | 
| 149 | 
            +
                        await self.broadcast_event('claude_event', data, skip_sid=sid)
         | 
| 150 | 
            +
                
         | 
| 151 | 
            +
                async def _send_event_history(self, sid: str, event_types: Optional[List[str]] = None, limit: int = 50):
         | 
| 152 | 
            +
                    """Send event history to a specific client.
         | 
| 153 | 
            +
                    
         | 
| 154 | 
            +
                    WHY: When clients connect to the dashboard, they need context from recent events
         | 
| 155 | 
            +
                    to understand what's been happening. This sends the most recent events in
         | 
| 156 | 
            +
                    chronological order (oldest first) so the dashboard displays them properly.
         | 
| 157 | 
            +
                    
         | 
| 158 | 
            +
                    Args:
         | 
| 159 | 
            +
                        sid: Socket.IO session ID of the client
         | 
| 160 | 
            +
                        event_types: Optional list of event types to filter by
         | 
| 161 | 
            +
                        limit: Maximum number of events to send (default: 50)
         | 
| 162 | 
            +
                    """
         | 
| 163 | 
            +
                    try:
         | 
| 164 | 
            +
                        if not self.event_history:
         | 
| 165 | 
            +
                            self.logger.debug(f"No event history to send to client {sid}")
         | 
| 166 | 
            +
                            return
         | 
| 167 | 
            +
                            
         | 
| 168 | 
            +
                        # Limit to reasonable number to avoid overwhelming client
         | 
| 169 | 
            +
                        limit = min(limit, 100)
         | 
| 170 | 
            +
                        
         | 
| 171 | 
            +
                        # Get the most recent events, filtered by type if specified
         | 
| 172 | 
            +
                        history = []
         | 
| 173 | 
            +
                        for event in reversed(self.event_history):
         | 
| 174 | 
            +
                            if not event_types or event.get("type") in event_types:
         | 
| 175 | 
            +
                                history.append(event)
         | 
| 176 | 
            +
                                if len(history) >= limit:
         | 
| 177 | 
            +
                                    break
         | 
| 178 | 
            +
                        
         | 
| 179 | 
            +
                        # Reverse to get chronological order (oldest first)
         | 
| 180 | 
            +
                        history = list(reversed(history))
         | 
| 181 | 
            +
                        
         | 
| 182 | 
            +
                        if history:
         | 
| 183 | 
            +
                            # Send as 'history' event that the client expects
         | 
| 184 | 
            +
                            await self.emit_to_client(sid, 'history', {
         | 
| 185 | 
            +
                                "events": history,
         | 
| 186 | 
            +
                                "count": len(history),
         | 
| 187 | 
            +
                                "total_available": len(self.event_history)
         | 
| 188 | 
            +
                            })
         | 
| 189 | 
            +
                            
         | 
| 190 | 
            +
                            self.logger.info(f"📚 Sent {len(history)} historical events to client {sid}")
         | 
| 191 | 
            +
                        else:
         | 
| 192 | 
            +
                            self.logger.debug(f"No matching events found for client {sid} with filters: {event_types}")
         | 
| 193 | 
            +
                            
         | 
| 194 | 
            +
                    except Exception as e:
         | 
| 195 | 
            +
                        self.log_error(f"sending event history to client {sid}", e, {
         | 
| 196 | 
            +
                            "event_types": event_types,
         | 
| 197 | 
            +
                            "limit": limit
         | 
| 198 | 
            +
                        })
         |