claude-mpm 4.1.4__py3-none-any.whl → 4.1.6__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 (81) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/templates/research.json +39 -13
  3. claude_mpm/cli/__init__.py +2 -0
  4. claude_mpm/cli/commands/__init__.py +2 -0
  5. claude_mpm/cli/commands/configure.py +1221 -0
  6. claude_mpm/cli/commands/configure_tui.py +1921 -0
  7. claude_mpm/cli/commands/tickets.py +365 -784
  8. claude_mpm/cli/parsers/base_parser.py +7 -0
  9. claude_mpm/cli/parsers/configure_parser.py +119 -0
  10. claude_mpm/cli/startup_logging.py +39 -12
  11. claude_mpm/constants.py +1 -0
  12. claude_mpm/core/output_style_manager.py +24 -0
  13. claude_mpm/core/socketio_pool.py +35 -3
  14. claude_mpm/core/unified_agent_registry.py +46 -15
  15. claude_mpm/dashboard/static/css/connection-status.css +370 -0
  16. claude_mpm/dashboard/static/js/components/connection-debug.js +654 -0
  17. claude_mpm/dashboard/static/js/connection-manager.js +536 -0
  18. claude_mpm/dashboard/templates/index.html +11 -0
  19. claude_mpm/hooks/claude_hooks/services/__init__.py +3 -1
  20. claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +190 -0
  21. claude_mpm/services/agents/deployment/agent_discovery_service.py +12 -3
  22. claude_mpm/services/agents/deployment/agent_lifecycle_manager.py +172 -233
  23. claude_mpm/services/agents/deployment/agent_lifecycle_manager_refactored.py +575 -0
  24. claude_mpm/services/agents/deployment/agent_operation_service.py +573 -0
  25. claude_mpm/services/agents/deployment/agent_record_service.py +419 -0
  26. claude_mpm/services/agents/deployment/agent_state_service.py +381 -0
  27. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +4 -2
  28. claude_mpm/services/diagnostics/checks/__init__.py +2 -0
  29. claude_mpm/services/diagnostics/checks/instructions_check.py +418 -0
  30. claude_mpm/services/diagnostics/diagnostic_runner.py +15 -2
  31. claude_mpm/services/event_bus/direct_relay.py +173 -0
  32. claude_mpm/services/infrastructure/__init__.py +31 -5
  33. claude_mpm/services/infrastructure/monitoring/__init__.py +43 -0
  34. claude_mpm/services/infrastructure/monitoring/aggregator.py +437 -0
  35. claude_mpm/services/infrastructure/monitoring/base.py +130 -0
  36. claude_mpm/services/infrastructure/monitoring/legacy.py +203 -0
  37. claude_mpm/services/infrastructure/monitoring/network.py +218 -0
  38. claude_mpm/services/infrastructure/monitoring/process.py +342 -0
  39. claude_mpm/services/infrastructure/monitoring/resources.py +243 -0
  40. claude_mpm/services/infrastructure/monitoring/service.py +367 -0
  41. claude_mpm/services/infrastructure/monitoring.py +67 -1030
  42. claude_mpm/services/project/analyzer.py +13 -4
  43. claude_mpm/services/project/analyzer_refactored.py +450 -0
  44. claude_mpm/services/project/analyzer_v2.py +566 -0
  45. claude_mpm/services/project/architecture_analyzer.py +461 -0
  46. claude_mpm/services/project/dependency_analyzer.py +462 -0
  47. claude_mpm/services/project/language_analyzer.py +265 -0
  48. claude_mpm/services/project/metrics_collector.py +410 -0
  49. claude_mpm/services/socketio/handlers/connection_handler.py +345 -0
  50. claude_mpm/services/socketio/server/broadcaster.py +32 -1
  51. claude_mpm/services/socketio/server/connection_manager.py +516 -0
  52. claude_mpm/services/socketio/server/core.py +63 -0
  53. claude_mpm/services/socketio/server/eventbus_integration.py +20 -9
  54. claude_mpm/services/socketio/server/main.py +27 -1
  55. claude_mpm/services/ticket_manager.py +5 -1
  56. claude_mpm/services/ticket_services/__init__.py +26 -0
  57. claude_mpm/services/ticket_services/crud_service.py +328 -0
  58. claude_mpm/services/ticket_services/formatter_service.py +290 -0
  59. claude_mpm/services/ticket_services/search_service.py +324 -0
  60. claude_mpm/services/ticket_services/validation_service.py +303 -0
  61. claude_mpm/services/ticket_services/workflow_service.py +244 -0
  62. {claude_mpm-4.1.4.dist-info → claude_mpm-4.1.6.dist-info}/METADATA +3 -1
  63. {claude_mpm-4.1.4.dist-info → claude_mpm-4.1.6.dist-info}/RECORD +67 -46
  64. claude_mpm/agents/OUTPUT_STYLE.md +0 -73
  65. claude_mpm/agents/backups/INSTRUCTIONS.md +0 -352
  66. claude_mpm/agents/templates/OPTIMIZATION_REPORT.md +0 -156
  67. claude_mpm/agents/templates/backup/data_engineer_agent_20250726_234551.json +0 -79
  68. claude_mpm/agents/templates/backup/documentation_agent_20250726_234551.json +0 -68
  69. claude_mpm/agents/templates/backup/engineer_agent_20250726_234551.json +0 -77
  70. claude_mpm/agents/templates/backup/ops_agent_20250726_234551.json +0 -78
  71. claude_mpm/agents/templates/backup/qa_agent_20250726_234551.json +0 -67
  72. claude_mpm/agents/templates/backup/research_agent_2025011_234551.json +0 -88
  73. claude_mpm/agents/templates/backup/research_agent_20250726_234551.json +0 -72
  74. claude_mpm/agents/templates/backup/research_memory_efficient.json +0 -88
  75. claude_mpm/agents/templates/backup/security_agent_20250726_234551.json +0 -78
  76. claude_mpm/agents/templates/backup/version_control_agent_20250726_234551.json +0 -62
  77. claude_mpm/agents/templates/vercel_ops_instructions.md +0 -582
  78. {claude_mpm-4.1.4.dist-info → claude_mpm-4.1.6.dist-info}/WHEEL +0 -0
  79. {claude_mpm-4.1.4.dist-info → claude_mpm-4.1.6.dist-info}/entry_points.txt +0 -0
  80. {claude_mpm-4.1.4.dist-info → claude_mpm-4.1.6.dist-info}/licenses/LICENSE +0 -0
  81. {claude_mpm-4.1.4.dist-info → claude_mpm-4.1.6.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,345 @@
1
+ """
2
+ Enhanced Connection Event Handler for Socket.IO.
3
+
4
+ WHY: This module provides robust connection handling with state tracking,
5
+ event replay on reconnection, and health monitoring integration.
6
+
7
+ DESIGN DECISION: Centralized connection event handling ensures consistent
8
+ state management and provides resilient event delivery across reconnections.
9
+ """
10
+
11
+ from datetime import datetime
12
+
13
+ from .base import BaseEventHandler
14
+
15
+
16
+ class EnhancedConnectionEventHandler(BaseEventHandler):
17
+ """
18
+ Handles Socket.IO connection events with enhanced robustness.
19
+
20
+ Features:
21
+ - Persistent client tracking across reconnections
22
+ - Event replay on reconnection
23
+ - Connection health monitoring
24
+ - Acknowledgment system for guaranteed delivery
25
+ """
26
+
27
+ def register_events(self):
28
+ """Register enhanced connection event handlers."""
29
+ sio = self.server.core.sio
30
+
31
+ @sio.event
32
+ async def connect(sid, environ, auth):
33
+ """Handle client connection with enhanced tracking."""
34
+ try:
35
+ # Extract client ID from auth or create new
36
+ client_id = None
37
+ if auth and isinstance(auth, dict):
38
+ client_id = auth.get("client_id")
39
+
40
+ # Register connection with manager
41
+ if self.server.connection_manager:
42
+ conn = await self.server.connection_manager.register_connection(
43
+ sid, client_id
44
+ )
45
+
46
+ # Add to server's connected clients
47
+ self.server.connected_clients.add(sid)
48
+ self.server.stats["connections_total"] += 1
49
+
50
+ # Store client info
51
+ self.server.client_info[sid] = {
52
+ "client_id": conn.client_id,
53
+ "connected_at": datetime.now().isoformat(),
54
+ "user_agent": environ.get("HTTP_USER_AGENT", "unknown"),
55
+ "remote_addr": environ.get("REMOTE_ADDR", "unknown"),
56
+ }
57
+
58
+ # Send client ID back for future reconnections
59
+ await sio.emit(
60
+ "connection_established",
61
+ {
62
+ "client_id": conn.client_id,
63
+ "sid": sid,
64
+ "timestamp": datetime.now().isoformat(),
65
+ "server_version": self.get_server_version(),
66
+ },
67
+ room=sid,
68
+ )
69
+
70
+ # Send current server status
71
+ await self._send_server_status(sid)
72
+
73
+ # Check for events to replay
74
+ last_sequence = 0
75
+ if auth and isinstance(auth, dict):
76
+ last_sequence = auth.get("last_sequence", 0)
77
+
78
+ if last_sequence > 0:
79
+ replay_events = (
80
+ await self.server.connection_manager.get_replay_events(
81
+ sid, last_sequence
82
+ )
83
+ )
84
+
85
+ if replay_events:
86
+ self.logger.info(
87
+ f"Replaying {len(replay_events)} events for client {conn.client_id}"
88
+ )
89
+
90
+ # Send replay events in batch
91
+ await sio.emit(
92
+ "event_replay",
93
+ {
94
+ "events": replay_events,
95
+ "count": len(replay_events),
96
+ "from_sequence": last_sequence,
97
+ },
98
+ room=sid,
99
+ )
100
+
101
+ # Send event history for initial population
102
+ if (
103
+ hasattr(self.server, "event_history")
104
+ and self.server.event_history
105
+ ):
106
+ history_data = list(self.server.event_history)
107
+ await sio.emit(
108
+ "event_history",
109
+ {"events": history_data, "count": len(history_data)},
110
+ room=sid,
111
+ )
112
+
113
+ self.logger.info(
114
+ f"Client connected: {conn.client_id} (sid: {sid}, "
115
+ f"reconnect: {conn.metrics.reconnect_count > 0})"
116
+ )
117
+ else:
118
+ # Fallback to basic connection tracking
119
+ self.server.connected_clients.add(sid)
120
+ self.server.stats["connections_total"] += 1
121
+
122
+ self.server.client_info[sid] = {
123
+ "connected_at": datetime.now().isoformat(),
124
+ "user_agent": environ.get("HTTP_USER_AGENT", "unknown"),
125
+ "remote_addr": environ.get("REMOTE_ADDR", "unknown"),
126
+ }
127
+
128
+ await self._send_server_status(sid)
129
+
130
+ if (
131
+ hasattr(self.server, "event_history")
132
+ and self.server.event_history
133
+ ):
134
+ history_data = list(self.server.event_history)
135
+ await sio.emit(
136
+ "event_history",
137
+ {"events": history_data, "count": len(history_data)},
138
+ room=sid,
139
+ )
140
+
141
+ self.logger.info(f"Client connected: {sid}")
142
+
143
+ except Exception as e:
144
+ self.logger.error(f"Error handling connection for {sid}: {e}")
145
+
146
+ @sio.event
147
+ async def disconnect(sid):
148
+ """Handle client disconnection with state preservation."""
149
+ try:
150
+ # Get disconnection reason if available
151
+ reason = "client_disconnect"
152
+
153
+ # Unregister from connection manager but preserve state
154
+ if self.server.connection_manager:
155
+ await self.server.connection_manager.unregister_connection(
156
+ sid, reason
157
+ )
158
+
159
+ # Remove from connected clients
160
+ if sid in self.server.connected_clients:
161
+ self.server.connected_clients.remove(sid)
162
+
163
+ # Remove client info
164
+ if sid in self.server.client_info:
165
+ client_info = self.server.client_info[sid]
166
+ del self.server.client_info[sid]
167
+
168
+ client_id = client_info.get("client_id", sid)
169
+ self.logger.info(f"Client disconnected: {client_id} (sid: {sid})")
170
+ else:
171
+ self.logger.info(f"Client disconnected: {sid}")
172
+
173
+ except Exception as e:
174
+ self.logger.error(f"Error handling disconnection for {sid}: {e}")
175
+
176
+ @sio.event
177
+ async def ping(sid):
178
+ """Handle ping from client for health monitoring."""
179
+ try:
180
+ # Update activity in connection manager
181
+ if self.server.connection_manager:
182
+ await self.server.connection_manager.update_activity(sid, "ping")
183
+
184
+ # Send pong response with timestamp
185
+ await sio.emit(
186
+ "pong",
187
+ {
188
+ "timestamp": datetime.now().isoformat(),
189
+ "server_time": datetime.now().timestamp(),
190
+ },
191
+ room=sid,
192
+ )
193
+
194
+ except Exception as e:
195
+ self.logger.error(f"Error handling ping from {sid}: {e}")
196
+
197
+ @sio.event
198
+ async def acknowledge_event(sid, data):
199
+ """Handle event acknowledgment from client."""
200
+ try:
201
+ if not isinstance(data, dict):
202
+ return
203
+
204
+ sequence = data.get("sequence")
205
+ if sequence and self.server.connection_manager:
206
+ await self.server.connection_manager.acknowledge_event(
207
+ sid, sequence
208
+ )
209
+
210
+ # Optional: Send confirmation
211
+ await sio.emit(
212
+ "ack_confirmed",
213
+ {"sequence": sequence, "timestamp": datetime.now().isoformat()},
214
+ room=sid,
215
+ )
216
+
217
+ except Exception as e:
218
+ self.logger.error(f"Error handling acknowledgment from {sid}: {e}")
219
+
220
+ @sio.event
221
+ async def request_replay(sid, data):
222
+ """Handle replay request from client after reconnection."""
223
+ try:
224
+ if not isinstance(data, dict):
225
+ return
226
+
227
+ last_sequence = data.get("last_sequence", 0)
228
+
229
+ if self.server.connection_manager:
230
+ replay_events = (
231
+ await self.server.connection_manager.get_replay_events(
232
+ sid, last_sequence
233
+ )
234
+ )
235
+
236
+ if replay_events:
237
+ await sio.emit(
238
+ "event_replay",
239
+ {
240
+ "events": replay_events,
241
+ "count": len(replay_events),
242
+ "from_sequence": last_sequence,
243
+ },
244
+ room=sid,
245
+ )
246
+ else:
247
+ await sio.emit(
248
+ "event_replay",
249
+ {
250
+ "events": [],
251
+ "count": 0,
252
+ "from_sequence": last_sequence,
253
+ "message": "No events to replay",
254
+ },
255
+ room=sid,
256
+ )
257
+
258
+ except Exception as e:
259
+ self.logger.error(f"Error handling replay request from {sid}: {e}")
260
+
261
+ @sio.event
262
+ async def get_connection_stats(sid):
263
+ """Get connection statistics for debugging."""
264
+ try:
265
+ stats = {
266
+ "timestamp": datetime.now().isoformat(),
267
+ "total_connections": len(self.server.connected_clients),
268
+ "server_stats": self.server.stats,
269
+ }
270
+
271
+ if self.server.connection_manager:
272
+ conn = self.server.connection_manager.get_connection(sid)
273
+ if conn:
274
+ stats["connection"] = {
275
+ "client_id": conn.client_id,
276
+ "state": conn.state.value,
277
+ "connected_at": conn.connected_at,
278
+ "quality": conn.calculate_quality(),
279
+ "metrics": {
280
+ "events_sent": conn.metrics.events_sent,
281
+ "events_acked": conn.metrics.events_acked,
282
+ "events_buffered": conn.metrics.events_buffered,
283
+ "reconnect_count": conn.metrics.reconnect_count,
284
+ },
285
+ }
286
+
287
+ stats["manager_metrics"] = (
288
+ self.server.connection_manager.get_metrics()
289
+ )
290
+
291
+ await sio.emit("connection_stats", stats, room=sid)
292
+
293
+ except Exception as e:
294
+ self.logger.error(f"Error getting connection stats for {sid}: {e}")
295
+
296
+ @sio.event
297
+ async def heartbeat(sid):
298
+ """Handle client heartbeat for connection monitoring."""
299
+ try:
300
+ # Update activity
301
+ if self.server.connection_manager:
302
+ await self.server.connection_manager.update_activity(sid, "event")
303
+
304
+ # Send heartbeat response
305
+ await sio.emit(
306
+ "heartbeat_response",
307
+ {"timestamp": datetime.now().isoformat(), "status": "alive"},
308
+ room=sid,
309
+ )
310
+
311
+ except Exception as e:
312
+ self.logger.error(f"Error handling heartbeat from {sid}: {e}")
313
+
314
+ self.logger.info("Enhanced connection event handlers registered")
315
+
316
+ async def _send_server_status(self, sid: str):
317
+ """Send current server status to a client."""
318
+ try:
319
+ status_data = {
320
+ "server_running": self.server.running,
321
+ "claude_status": self.server.claude_status,
322
+ "claude_pid": self.server.claude_pid,
323
+ "session_id": self.server.session_id,
324
+ "connected_clients": len(self.server.connected_clients),
325
+ "server_start_time": (
326
+ self.server.stats.get("start_time").isoformat()
327
+ if self.server.stats.get("start_time")
328
+ else None
329
+ ),
330
+ "timestamp": datetime.now().isoformat(),
331
+ }
332
+
333
+ await self.server.core.sio.emit("server_status", status_data, room=sid)
334
+
335
+ except Exception as e:
336
+ self.logger.error(f"Error sending server status to {sid}: {e}")
337
+
338
+ def get_server_version(self) -> str:
339
+ """Get server version for client info."""
340
+ try:
341
+ from claude_mpm.services.version_service import VersionService
342
+
343
+ return VersionService().get_version()
344
+ except:
345
+ return "unknown"
@@ -153,6 +153,7 @@ class SocketIOEventBroadcaster:
153
153
  stats: Dict[str, Any],
154
154
  logger,
155
155
  server=None, # Add server reference for event history access
156
+ connection_manager=None, # Add connection manager for robust delivery
156
157
  ):
157
158
  self.sio = sio
158
159
  self.connected_clients = connected_clients
@@ -162,6 +163,7 @@ class SocketIOEventBroadcaster:
162
163
  self.logger = logger
163
164
  self.loop = None # Will be set by main server
164
165
  self.server = server # Reference to main server for event history
166
+ self.connection_manager = connection_manager # For connection tracking
165
167
 
166
168
  # Initialize retry queue for resilient delivery
167
169
  self.retry_queue = RetryQueue(max_size=1000)
@@ -313,7 +315,7 @@ class SocketIOEventBroadcaster:
313
315
 
314
316
  WHY: Enhanced with retry queue to ensure reliable delivery
315
317
  even during transient network issues. Now uses EventNormalizer
316
- to ensure consistent event schema.
318
+ to ensure consistent event schema and ConnectionManager for tracking.
317
319
  """
318
320
  if not self.sio:
319
321
  return
@@ -342,6 +344,18 @@ class SocketIOEventBroadcaster:
342
344
  f"Added {event['type']}/{event['subtype']} to history (total: {len(self.server.event_history)})"
343
345
  )
344
346
 
347
+ # If we have a connection manager, buffer event for all connected clients
348
+ if self.connection_manager and self.loop:
349
+ # Buffer for each connected client asynchronously
350
+ async def buffer_for_clients():
351
+ for sid in list(self.connected_clients):
352
+ await self.connection_manager.buffer_event(sid, event)
353
+
354
+ try:
355
+ asyncio.run_coroutine_threadsafe(buffer_for_clients(), self.loop)
356
+ except Exception as e:
357
+ self.logger.warning(f"Failed to buffer event for clients: {e}")
358
+
345
359
  # Broadcast to all connected clients
346
360
  broadcast_success = False
347
361
  try:
@@ -360,6 +374,23 @@ class SocketIOEventBroadcaster:
360
374
  future.result(timeout=0.5) # 500ms timeout
361
375
  broadcast_success = True
362
376
  self.stats["events_sent"] += 1
377
+
378
+ # Update activity for all connected clients
379
+ if self.connection_manager:
380
+
381
+ async def update_activities():
382
+ for sid in list(self.connected_clients):
383
+ await self.connection_manager.update_activity(
384
+ sid, "event"
385
+ )
386
+
387
+ try:
388
+ asyncio.run_coroutine_threadsafe(
389
+ update_activities(), self.loop
390
+ )
391
+ except:
392
+ pass # Non-critical
393
+
363
394
  self.logger.debug(f"Broadcasted event: {event_type}")
364
395
  except:
365
396
  # Will be added to retry queue below