claude-mpm 4.2.9__py3-none-any.whl → 4.2.11__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 (50) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/cli/commands/dashboard.py +59 -126
  3. claude_mpm/cli/commands/monitor.py +71 -212
  4. claude_mpm/cli/commands/run.py +33 -33
  5. claude_mpm/dashboard/static/css/code-tree.css +8 -16
  6. claude_mpm/dashboard/static/dist/components/code-tree.js +1 -1
  7. claude_mpm/dashboard/static/dist/components/file-viewer.js +2 -0
  8. claude_mpm/dashboard/static/dist/components/module-viewer.js +1 -1
  9. claude_mpm/dashboard/static/dist/components/unified-data-viewer.js +1 -1
  10. claude_mpm/dashboard/static/dist/dashboard.js +1 -1
  11. claude_mpm/dashboard/static/dist/socket-client.js +1 -1
  12. claude_mpm/dashboard/static/js/components/code-tree.js +692 -114
  13. claude_mpm/dashboard/static/js/components/file-viewer.js +538 -0
  14. claude_mpm/dashboard/static/js/components/module-viewer.js +26 -0
  15. claude_mpm/dashboard/static/js/components/unified-data-viewer.js +166 -14
  16. claude_mpm/dashboard/static/js/dashboard.js +108 -91
  17. claude_mpm/dashboard/static/js/socket-client.js +9 -7
  18. claude_mpm/dashboard/templates/index.html +2 -7
  19. claude_mpm/hooks/claude_hooks/hook_handler.py +1 -11
  20. claude_mpm/hooks/claude_hooks/services/connection_manager.py +54 -59
  21. claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +112 -72
  22. claude_mpm/services/agents/deployment/agent_template_builder.py +0 -1
  23. claude_mpm/services/cli/unified_dashboard_manager.py +354 -0
  24. claude_mpm/services/monitor/__init__.py +20 -0
  25. claude_mpm/services/monitor/daemon.py +256 -0
  26. claude_mpm/services/monitor/event_emitter.py +279 -0
  27. claude_mpm/services/monitor/handlers/__init__.py +20 -0
  28. claude_mpm/services/monitor/handlers/code_analysis.py +334 -0
  29. claude_mpm/services/monitor/handlers/dashboard.py +298 -0
  30. claude_mpm/services/monitor/handlers/hooks.py +491 -0
  31. claude_mpm/services/monitor/management/__init__.py +18 -0
  32. claude_mpm/services/monitor/management/health.py +124 -0
  33. claude_mpm/services/monitor/management/lifecycle.py +298 -0
  34. claude_mpm/services/monitor/server.py +442 -0
  35. claude_mpm/tools/code_tree_analyzer.py +33 -17
  36. {claude_mpm-4.2.9.dist-info → claude_mpm-4.2.11.dist-info}/METADATA +1 -1
  37. {claude_mpm-4.2.9.dist-info → claude_mpm-4.2.11.dist-info}/RECORD +41 -36
  38. claude_mpm/cli/commands/socketio_monitor.py +0 -233
  39. claude_mpm/scripts/socketio_daemon.py +0 -571
  40. claude_mpm/scripts/socketio_daemon_hardened.py +0 -937
  41. claude_mpm/scripts/socketio_daemon_wrapper.py +0 -78
  42. claude_mpm/scripts/socketio_server_manager.py +0 -349
  43. claude_mpm/services/cli/dashboard_launcher.py +0 -423
  44. claude_mpm/services/cli/socketio_manager.py +0 -595
  45. claude_mpm/services/dashboard/stable_server.py +0 -1020
  46. claude_mpm/services/socketio/monitor_server.py +0 -505
  47. {claude_mpm-4.2.9.dist-info → claude_mpm-4.2.11.dist-info}/WHEEL +0 -0
  48. {claude_mpm-4.2.9.dist-info → claude_mpm-4.2.11.dist-info}/entry_points.txt +0 -0
  49. {claude_mpm-4.2.9.dist-info → claude_mpm-4.2.11.dist-info}/licenses/LICENSE +0 -0
  50. {claude_mpm-4.2.9.dist-info → claude_mpm-4.2.11.dist-info}/top_level.txt +0 -0
@@ -1,9 +1,18 @@
1
1
  """Connection management service for Claude hook handler.
2
2
 
3
+ This service implements the SINGLE-PATH EVENT EMISSION ARCHITECTURE
4
+ to eliminate duplicate events and improve performance.
5
+
6
+ ARCHITECTURE: Hook → ConnectionManager → Direct Socket.IO → Dashboard
7
+ ↓ (fallback only)
8
+ HTTP POST → Monitor → Dashboard
9
+
10
+ CRITICAL: This service must maintain the single emission path principle.
11
+ See docs/developer/EVENT_EMISSION_ARCHITECTURE.md for full documentation.
12
+
3
13
  This service manages:
4
14
  - SocketIO connection pool initialization
5
- - EventBus initialization
6
- - Event emission through both channels
15
+ - Direct event emission with HTTP fallback
7
16
  - Connection cleanup
8
17
  """
9
18
 
@@ -61,14 +70,8 @@ except ImportError:
61
70
  )
62
71
 
63
72
 
64
- # Import EventBus for decoupled event distribution
65
- try:
66
- from claude_mpm.services.event_bus import EventBus
67
-
68
- EVENTBUS_AVAILABLE = True
69
- except ImportError:
70
- EVENTBUS_AVAILABLE = False
71
- EventBus = None
73
+ # EventBus removed - using direct Socket.IO calls with HTTP fallback
74
+ # This eliminates duplicate events and improves performance
72
75
 
73
76
 
74
77
  class ConnectionManagerService:
@@ -84,9 +87,7 @@ class ConnectionManagerService:
84
87
  self.connection_pool = None
85
88
  self._initialize_socketio_pool()
86
89
 
87
- # Initialize EventBus for in-process event distribution (optional)
88
- self.event_bus = None
89
- self._initialize_eventbus()
90
+ # EventBus removed - using direct Socket.IO with HTTP fallback only
90
91
 
91
92
  def _initialize_socketio_pool(self):
92
93
  """Initialize the SocketIO connection pool."""
@@ -102,25 +103,17 @@ class ConnectionManagerService:
102
103
  )
103
104
  self.connection_pool = None
104
105
 
105
- def _initialize_eventbus(self):
106
- """Initialize the EventBus for in-process distribution."""
107
- if EVENTBUS_AVAILABLE:
108
- try:
109
- self.event_bus = EventBus.get_instance()
110
- if DEBUG:
111
- print("✅ EventBus initialized for hook handler", file=sys.stderr)
112
- except Exception as e:
113
- if DEBUG:
114
- print(f"⚠️ Failed to initialize EventBus: {e}", file=sys.stderr)
115
- self.event_bus = None
116
-
117
106
  def emit_event(self, namespace: str, event: str, data: dict):
118
- """Emit event through both connection pool and EventBus.
107
+ """Emit event through direct Socket.IO connection with HTTP fallback.
108
+
109
+ 🚨 CRITICAL: This method implements the SINGLE-PATH EMISSION ARCHITECTURE.
110
+ DO NOT add additional emission paths (EventBus, etc.) as this creates duplicates.
111
+ See docs/developer/EVENT_EMISSION_ARCHITECTURE.md for details.
119
112
 
120
- WHY dual approach:
121
- - Connection pool: Direct Socket.IO connection for inter-process communication
122
- - EventBus: For in-process subscribers (if any)
123
- - Ensures events reach the dashboard regardless of process boundaries
113
+ High-performance single-path approach:
114
+ - Primary: Direct Socket.IO connection for ultra-low latency
115
+ - Fallback: HTTP POST for reliability when direct connection fails
116
+ - Eliminates duplicate events from multiple emission paths
124
117
  """
125
118
  # Create event data for normalization
126
119
  raw_event = {
@@ -152,49 +145,51 @@ class ConnectionManagerService:
152
145
  file=sys.stderr,
153
146
  )
154
147
 
155
- # First, try to emit through direct Socket.IO connection pool
156
- # This is the primary path for inter-process communication
148
+ # Emit through direct Socket.IO connection pool (primary path)
149
+ # This provides ultra-low latency direct async communication
157
150
  if self.connection_pool:
158
151
  try:
159
152
  # Emit to Socket.IO server directly
160
153
  self.connection_pool.emit("claude_event", claude_event_data)
161
154
  if DEBUG:
162
155
  print(f"✅ Emitted via connection pool: {event}", file=sys.stderr)
156
+ return # Success - no need for fallback
163
157
  except Exception as e:
164
158
  if DEBUG:
165
159
  print(f"⚠️ Failed to emit via connection pool: {e}", file=sys.stderr)
166
160
 
167
- # Also publish to EventBus for any in-process subscribers
168
- if self.event_bus and EVENTBUS_AVAILABLE:
169
- try:
170
- # Publish to EventBus with topic format: hook.{event}
171
- topic = f"hook.{event}"
172
- self.event_bus.publish(topic, claude_event_data)
161
+ # HTTP fallback for cross-process communication (when direct calls fail)
162
+ # This replaces EventBus for reliability without the complexity
163
+ self._try_http_fallback(claude_event_data)
173
164
 
174
- # Enhanced verification logging
175
- if DEBUG:
176
- print(f"✅ Published to EventBus: {topic}", file=sys.stderr)
177
- # Get EventBus stats to verify publication
178
- if hasattr(self.event_bus, "get_stats"):
179
- stats = self.event_bus.get_stats()
180
- print(
181
- f"📊 EventBus stats after publish: {stats}", file=sys.stderr
182
- )
183
- # Log the number of data keys being published
184
- if isinstance(claude_event_data, dict):
185
- print(
186
- f"📦 Published data keys: {list(claude_event_data.keys())}",
187
- file=sys.stderr,
188
- )
189
- except Exception as e:
165
+ def _try_http_fallback(self, claude_event_data: dict):
166
+ """HTTP fallback when direct Socket.IO connection fails."""
167
+ try:
168
+ import requests
169
+
170
+ # Send to monitor server HTTP API
171
+ response = requests.post(
172
+ "http://localhost:8765/api/events",
173
+ json=claude_event_data,
174
+ timeout=2.0,
175
+ headers={"Content-Type": "application/json"},
176
+ )
177
+
178
+ if response.status_code in [200, 204]:
190
179
  if DEBUG:
191
- print(f"⚠️ Failed to publish to EventBus: {e}", file=sys.stderr)
192
- import traceback
180
+ print(" HTTP fallback successful", file=sys.stderr)
181
+ elif DEBUG:
182
+ print(
183
+ f"⚠️ HTTP fallback failed: {response.status_code}",
184
+ file=sys.stderr,
185
+ )
193
186
 
194
- traceback.print_exc(file=sys.stderr)
187
+ except Exception as e:
188
+ if DEBUG:
189
+ print(f"⚠️ HTTP fallback error: {e}", file=sys.stderr)
195
190
 
196
- # Warn if neither method is available
197
- if not self.connection_pool and not self.event_bus and DEBUG:
191
+ # Warn if no emission method is available
192
+ if not self.connection_pool and DEBUG:
198
193
  print(f"⚠️ No event emission method available for: {event}", file=sys.stderr)
199
194
 
200
195
  def cleanup(self):
@@ -2,14 +2,14 @@
2
2
 
3
3
  This service manages:
4
4
  - HTTP POST event emission for ephemeral hook processes
5
- - EventBus initialization (optional)
6
- - Event emission through both channels
5
+ - Direct event emission without EventBus complexity
7
6
 
8
7
  DESIGN DECISION: Use stateless HTTP POST instead of persistent SocketIO
9
8
  connections because hook handlers are ephemeral processes (< 1 second lifetime).
10
9
  This eliminates disconnection issues and matches the process lifecycle.
11
10
  """
12
11
 
12
+ import asyncio
13
13
  import os
14
14
  import sys
15
15
  from datetime import datetime
@@ -26,6 +26,15 @@ except ImportError:
26
26
  REQUESTS_AVAILABLE = False
27
27
  requests = None
28
28
 
29
+ # Import high-performance event emitter
30
+ try:
31
+ from claude_mpm.services.monitor.event_emitter import get_event_emitter
32
+
33
+ EVENT_EMITTER_AVAILABLE = True
34
+ except ImportError:
35
+ EVENT_EMITTER_AVAILABLE = False
36
+ get_event_emitter = None
37
+
29
38
  # Import EventNormalizer for consistent event formatting
30
39
  try:
31
40
  from claude_mpm.services.socketio.event_normalizer import EventNormalizer
@@ -51,14 +60,8 @@ except ImportError:
51
60
  )
52
61
 
53
62
 
54
- # Import EventBus for decoupled event distribution
55
- try:
56
- from claude_mpm.services.event_bus import EventBus
57
-
58
- EVENTBUS_AVAILABLE = True
59
- except ImportError:
60
- EVENTBUS_AVAILABLE = False
61
- EventBus = None
63
+ # EventBus removed - using direct HTTP POST only
64
+ # This eliminates duplicate events and simplifies the architecture
62
65
 
63
66
 
64
67
  class ConnectionManagerService:
@@ -74,9 +77,7 @@ class ConnectionManagerService:
74
77
  self.server_port = int(os.environ.get("CLAUDE_MPM_SERVER_PORT", "8765"))
75
78
  self.http_endpoint = f"http://{self.server_host}:{self.server_port}/api/events"
76
79
 
77
- # Initialize EventBus for in-process event distribution (optional)
78
- self.event_bus = None
79
- self._initialize_eventbus()
80
+ # EventBus removed - using direct HTTP POST only
80
81
 
81
82
  # For backward compatibility with tests
82
83
  self.connection_pool = None # No longer used
@@ -87,26 +88,14 @@ class ConnectionManagerService:
87
88
  file=sys.stderr,
88
89
  )
89
90
 
90
- def _initialize_eventbus(self):
91
- """Initialize the EventBus for in-process distribution."""
92
- if EVENTBUS_AVAILABLE:
93
- try:
94
- self.event_bus = EventBus.get_instance()
95
- if DEBUG:
96
- print("✅ EventBus initialized for hook handler", file=sys.stderr)
97
- except Exception as e:
98
- if DEBUG:
99
- print(f"⚠️ Failed to initialize EventBus: {e}", file=sys.stderr)
100
- self.event_bus = None
101
-
102
91
  def emit_event(self, namespace: str, event: str, data: dict):
103
- """Emit event using HTTP POST and optionally EventBus.
92
+ """Emit event using high-performance async emitter with HTTP fallback.
104
93
 
105
- WHY HTTP POST approach:
106
- - Stateless: Perfect for ephemeral hook processes
107
- - Fire-and-forget: No connection management needed
108
- - Fast: Minimal overhead, no handshake
109
- - Reliable: Server handles buffering and retries
94
+ WHY Hybrid approach:
95
+ - Direct async calls for ultra-low latency in-process events
96
+ - HTTP POST fallback for cross-process communication
97
+ - Connection pooling for memory protection
98
+ - Automatic routing based on availability
110
99
  """
111
100
  # Create event data for normalization
112
101
  raw_event = {
@@ -138,52 +127,103 @@ class ConnectionManagerService:
138
127
  file=sys.stderr,
139
128
  )
140
129
 
141
- # Primary method: HTTP POST to server
142
- # This is fire-and-forget with a short timeout
143
- if REQUESTS_AVAILABLE:
130
+ # Try high-performance async emitter first (direct calls)
131
+ success = self._try_async_emit(namespace, event, claude_event_data)
132
+ if success:
133
+ return
134
+
135
+ # Fallback to HTTP POST for cross-process communication
136
+ self._try_http_emit(namespace, event, claude_event_data)
137
+
138
+ def _try_async_emit(self, namespace: str, event: str, data: dict) -> bool:
139
+ """Try to emit event using high-performance async emitter."""
140
+ if not EVENT_EMITTER_AVAILABLE:
141
+ return False
142
+
143
+ try:
144
+ # Run async emission in the current event loop or create one
145
+ loop = None
144
146
  try:
145
- # Send HTTP POST with short timeout (fire-and-forget pattern)
146
- response = requests.post(
147
- self.http_endpoint,
148
- json=claude_event_data,
149
- timeout=0.5, # 500ms timeout - don't wait long
150
- headers={"Content-Type": "application/json"},
151
- )
152
- if DEBUG and response.status_code == 204:
153
- print(f"✅ Emitted via HTTP POST: {event}", file=sys.stderr)
154
- elif DEBUG and response.status_code != 204:
155
- print(
156
- f"⚠️ HTTP POST returned status {response.status_code} for: {event}",
157
- file=sys.stderr,
158
- )
159
- except requests.exceptions.Timeout:
160
- # Timeout is expected for fire-and-forget pattern
161
- if DEBUG:
162
- print(f"✅ HTTP POST sent (timeout OK): {event}", file=sys.stderr)
163
- except requests.exceptions.ConnectionError:
164
- # Server might not be running - this is OK
147
+ loop = asyncio.get_running_loop()
148
+ except RuntimeError:
149
+ # No running loop, create a new one
150
+ pass
151
+
152
+ if loop:
153
+ # We're in an async context, create a task
154
+ task = loop.create_task(self._async_emit(namespace, event, data))
155
+ # Don't wait for completion to maintain low latency
165
156
  if DEBUG:
166
- print(f"⚠️ Server not available for: {event}", file=sys.stderr)
167
- except Exception as e:
168
- if DEBUG:
169
- print(f"⚠️ Failed to emit via HTTP POST: {e}", file=sys.stderr)
170
- elif DEBUG:
171
- print(
172
- "⚠️ requests module not available - cannot emit via HTTP",
173
- file=sys.stderr,
157
+ print(f" Async emit scheduled: {event}", file=sys.stderr)
158
+ return True
159
+ # No event loop, run synchronously
160
+ success = asyncio.run(self._async_emit(namespace, event, data))
161
+ if DEBUG and success:
162
+ print(f"✅ Async emit successful: {event}", file=sys.stderr)
163
+ return success
164
+
165
+ except Exception as e:
166
+ if DEBUG:
167
+ print(f"⚠️ Async emit failed: {e}", file=sys.stderr)
168
+ return False
169
+
170
+ async def _async_emit(self, namespace: str, event: str, data: dict) -> bool:
171
+ """Async helper for event emission."""
172
+ try:
173
+ emitter = await get_event_emitter()
174
+ return await emitter.emit_event(namespace, "claude_event", data)
175
+ except Exception as e:
176
+ if DEBUG:
177
+ print(f"⚠️ Async emitter error: {e}", file=sys.stderr)
178
+ return False
179
+
180
+ def _try_http_emit(self, namespace: str, event: str, data: dict):
181
+ """Try to emit event using HTTP POST fallback."""
182
+ if not REQUESTS_AVAILABLE:
183
+ if DEBUG:
184
+ print(
185
+ "⚠️ requests module not available - cannot emit via HTTP",
186
+ file=sys.stderr,
187
+ )
188
+ return
189
+
190
+ try:
191
+ # Create payload for HTTP API
192
+ payload = {
193
+ "namespace": namespace,
194
+ "event": "claude_event", # Standard event name for dashboard
195
+ "data": data,
196
+ }
197
+
198
+ # Send HTTP POST with reasonable timeout
199
+ response = requests.post(
200
+ self.http_endpoint,
201
+ json=payload,
202
+ timeout=2.0, # 2 second timeout
203
+ headers={"Content-Type": "application/json"},
174
204
  )
175
205
 
176
- # Also publish to EventBus for any in-process subscribers
177
- if self.event_bus and EVENTBUS_AVAILABLE:
178
- try:
179
- # Publish to EventBus with topic format: hook.{event}
180
- topic = f"hook.{event}"
181
- self.event_bus.publish(topic, claude_event_data)
182
- if DEBUG:
183
- print(f"✅ Published to EventBus: {topic}", file=sys.stderr)
184
- except Exception as e:
206
+ if response.status_code in [200, 204]:
185
207
  if DEBUG:
186
- print(f"⚠️ Failed to publish to EventBus: {e}", file=sys.stderr)
208
+ print(f" HTTP POST successful: {event}", file=sys.stderr)
209
+ elif DEBUG:
210
+ print(
211
+ f"⚠️ HTTP POST failed with status {response.status_code}: {event}",
212
+ file=sys.stderr,
213
+ )
214
+
215
+ except requests.exceptions.Timeout:
216
+ if DEBUG:
217
+ print(f"⚠️ HTTP POST timeout for: {event}", file=sys.stderr)
218
+ except requests.exceptions.ConnectionError:
219
+ if DEBUG:
220
+ print(
221
+ f"⚠️ HTTP POST connection failed for: {event} (server not running?)",
222
+ file=sys.stderr,
223
+ )
224
+ except Exception as e:
225
+ if DEBUG:
226
+ print(f"⚠️ HTTP POST error for {event}: {e}", file=sys.stderr)
187
227
 
188
228
  def cleanup(self):
189
229
  """Cleanup connections on service destruction."""
@@ -613,7 +613,6 @@ tools:
613
613
  single_line = re.sub(r"\s+([,.!?;:])", r"\1", single_line)
614
614
  return re.sub(r"([,.!?;:])\s+", r"\1 ", single_line)
615
615
 
616
-
617
616
  def _create_enhanced_description(
618
617
  self,
619
618
  raw_description: str,