claude-mpm 3.7.8__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.
Files changed (93) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/BASE_PM.md +0 -106
  3. claude_mpm/agents/INSTRUCTIONS.md +0 -96
  4. claude_mpm/agents/MEMORY.md +88 -0
  5. claude_mpm/agents/WORKFLOW.md +86 -0
  6. claude_mpm/agents/templates/code_analyzer.json +2 -2
  7. claude_mpm/agents/templates/data_engineer.json +1 -1
  8. claude_mpm/agents/templates/documentation.json +1 -1
  9. claude_mpm/agents/templates/engineer.json +1 -1
  10. claude_mpm/agents/templates/ops.json +1 -1
  11. claude_mpm/agents/templates/qa.json +1 -1
  12. claude_mpm/agents/templates/research.json +1 -1
  13. claude_mpm/agents/templates/security.json +1 -1
  14. claude_mpm/agents/templates/ticketing.json +2 -7
  15. claude_mpm/agents/templates/version_control.json +1 -1
  16. claude_mpm/agents/templates/web_qa.json +2 -2
  17. claude_mpm/agents/templates/web_ui.json +2 -2
  18. claude_mpm/cli/__init__.py +2 -2
  19. claude_mpm/cli/commands/__init__.py +2 -1
  20. claude_mpm/cli/commands/tickets.py +596 -19
  21. claude_mpm/cli/parser.py +217 -5
  22. claude_mpm/config/__init__.py +30 -39
  23. claude_mpm/config/socketio_config.py +8 -5
  24. claude_mpm/constants.py +13 -0
  25. claude_mpm/core/__init__.py +8 -18
  26. claude_mpm/core/cache.py +596 -0
  27. claude_mpm/core/claude_runner.py +166 -622
  28. claude_mpm/core/config.py +5 -1
  29. claude_mpm/core/constants.py +339 -0
  30. claude_mpm/core/container.py +461 -22
  31. claude_mpm/core/exceptions.py +392 -0
  32. claude_mpm/core/framework_loader.py +208 -94
  33. claude_mpm/core/interactive_session.py +432 -0
  34. claude_mpm/core/interfaces.py +424 -0
  35. claude_mpm/core/lazy.py +467 -0
  36. claude_mpm/core/logging_config.py +444 -0
  37. claude_mpm/core/oneshot_session.py +465 -0
  38. claude_mpm/core/optimized_agent_loader.py +485 -0
  39. claude_mpm/core/optimized_startup.py +490 -0
  40. claude_mpm/core/service_registry.py +52 -26
  41. claude_mpm/core/socketio_pool.py +162 -5
  42. claude_mpm/core/types.py +292 -0
  43. claude_mpm/core/typing_utils.py +477 -0
  44. claude_mpm/hooks/claude_hooks/hook_handler.py +213 -99
  45. claude_mpm/init.py +2 -1
  46. claude_mpm/services/__init__.py +78 -14
  47. claude_mpm/services/agent/__init__.py +24 -0
  48. claude_mpm/services/agent/deployment.py +2548 -0
  49. claude_mpm/services/agent/management.py +598 -0
  50. claude_mpm/services/agent/registry.py +813 -0
  51. claude_mpm/services/agents/deployment/agent_deployment.py +587 -268
  52. claude_mpm/services/agents/memory/agent_memory_manager.py +156 -1
  53. claude_mpm/services/async_session_logger.py +8 -3
  54. claude_mpm/services/communication/__init__.py +21 -0
  55. claude_mpm/services/communication/socketio.py +1933 -0
  56. claude_mpm/services/communication/websocket.py +479 -0
  57. claude_mpm/services/core/__init__.py +123 -0
  58. claude_mpm/services/core/base.py +247 -0
  59. claude_mpm/services/core/interfaces.py +951 -0
  60. claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +23 -23
  61. claude_mpm/services/framework_claude_md_generator.py +3 -2
  62. claude_mpm/services/health_monitor.py +4 -3
  63. claude_mpm/services/hook_service.py +64 -4
  64. claude_mpm/services/infrastructure/__init__.py +21 -0
  65. claude_mpm/services/infrastructure/logging.py +202 -0
  66. claude_mpm/services/infrastructure/monitoring.py +893 -0
  67. claude_mpm/services/memory/indexed_memory.py +648 -0
  68. claude_mpm/services/project/__init__.py +21 -0
  69. claude_mpm/services/project/analyzer.py +864 -0
  70. claude_mpm/services/project/registry.py +608 -0
  71. claude_mpm/services/project_analyzer.py +95 -2
  72. claude_mpm/services/recovery_manager.py +15 -9
  73. claude_mpm/services/socketio/__init__.py +25 -0
  74. claude_mpm/services/socketio/handlers/__init__.py +25 -0
  75. claude_mpm/services/socketio/handlers/base.py +121 -0
  76. claude_mpm/services/socketio/handlers/connection.py +198 -0
  77. claude_mpm/services/socketio/handlers/file.py +213 -0
  78. claude_mpm/services/socketio/handlers/git.py +723 -0
  79. claude_mpm/services/socketio/handlers/memory.py +27 -0
  80. claude_mpm/services/socketio/handlers/project.py +25 -0
  81. claude_mpm/services/socketio/handlers/registry.py +145 -0
  82. claude_mpm/services/socketio_client_manager.py +12 -7
  83. claude_mpm/services/socketio_server.py +156 -30
  84. claude_mpm/services/ticket_manager.py +170 -7
  85. claude_mpm/utils/error_handler.py +1 -1
  86. claude_mpm/validation/agent_validator.py +27 -14
  87. claude_mpm/validation/frontmatter_validator.py +231 -0
  88. {claude_mpm-3.7.8.dist-info → claude_mpm-3.8.1.dist-info}/METADATA +58 -21
  89. {claude_mpm-3.7.8.dist-info → claude_mpm-3.8.1.dist-info}/RECORD +93 -53
  90. {claude_mpm-3.7.8.dist-info → claude_mpm-3.8.1.dist-info}/WHEEL +0 -0
  91. {claude_mpm-3.7.8.dist-info → claude_mpm-3.8.1.dist-info}/entry_points.txt +0 -0
  92. {claude_mpm-3.7.8.dist-info → claude_mpm-3.8.1.dist-info}/licenses/LICENSE +0 -0
  93. {claude_mpm-3.7.8.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', 1)
116
- self.failure_window_seconds = self.config.get('failure_window_seconds', 300)
117
- self.min_recovery_interval = self.config.get('min_recovery_interval', 60)
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 = 5, timeout_seconds: int = 300,
197
- success_threshold: int = 3):
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', 5),
342
- timeout_seconds=circuit_config.get('timeout_seconds', 300),
343
- success_threshold=circuit_config.get('success_threshold', 3)
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) * 1000
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
+ })