claude-mpm 4.2.1__py3-none-any.whl → 4.2.3__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 (57) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/templates/agent-manager.json +1 -1
  3. claude_mpm/agents/templates/agentic_coder_optimizer.json +1 -1
  4. claude_mpm/agents/templates/api_qa.json +1 -1
  5. claude_mpm/agents/templates/code_analyzer.json +1 -1
  6. claude_mpm/agents/templates/data_engineer.json +1 -1
  7. claude_mpm/agents/templates/documentation.json +1 -1
  8. claude_mpm/agents/templates/engineer.json +2 -2
  9. claude_mpm/agents/templates/gcp_ops_agent.json +14 -9
  10. claude_mpm/agents/templates/imagemagick.json +1 -1
  11. claude_mpm/agents/templates/memory_manager.json +1 -1
  12. claude_mpm/agents/templates/ops.json +1 -1
  13. claude_mpm/agents/templates/project_organizer.json +1 -1
  14. claude_mpm/agents/templates/qa.json +2 -2
  15. claude_mpm/agents/templates/refactoring_engineer.json +1 -1
  16. claude_mpm/agents/templates/research.json +3 -3
  17. claude_mpm/agents/templates/security.json +1 -1
  18. claude_mpm/agents/templates/test-non-mpm.json +20 -0
  19. claude_mpm/agents/templates/ticketing.json +1 -1
  20. claude_mpm/agents/templates/vercel_ops_agent.json +2 -2
  21. claude_mpm/agents/templates/version_control.json +1 -1
  22. claude_mpm/agents/templates/web_qa.json +3 -8
  23. claude_mpm/agents/templates/web_ui.json +1 -1
  24. claude_mpm/cli/commands/agents.py +3 -0
  25. claude_mpm/cli/commands/dashboard.py +3 -3
  26. claude_mpm/cli/commands/monitor.py +227 -64
  27. claude_mpm/core/config.py +25 -0
  28. claude_mpm/core/unified_agent_registry.py +2 -2
  29. claude_mpm/dashboard/static/css/code-tree.css +220 -1
  30. claude_mpm/dashboard/static/css/dashboard.css +286 -0
  31. claude_mpm/dashboard/static/dist/components/code-tree.js +1 -1
  32. claude_mpm/dashboard/static/js/components/code-simple.js +507 -15
  33. claude_mpm/dashboard/static/js/components/code-tree.js +2044 -124
  34. claude_mpm/dashboard/static/js/socket-client.js +5 -2
  35. claude_mpm/dashboard/templates/code_simple.html +79 -0
  36. claude_mpm/dashboard/templates/index.html +42 -41
  37. claude_mpm/services/agents/deployment/agent_deployment.py +4 -1
  38. claude_mpm/services/agents/deployment/agent_discovery_service.py +101 -2
  39. claude_mpm/services/agents/deployment/agent_format_converter.py +53 -9
  40. claude_mpm/services/agents/deployment/agent_template_builder.py +355 -25
  41. claude_mpm/services/agents/deployment/agent_validator.py +11 -6
  42. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +83 -15
  43. claude_mpm/services/agents/deployment/validation/template_validator.py +51 -40
  44. claude_mpm/services/cli/agent_listing_service.py +2 -2
  45. claude_mpm/services/dashboard/stable_server.py +389 -0
  46. claude_mpm/services/socketio/client_proxy.py +16 -0
  47. claude_mpm/services/socketio/dashboard_server.py +360 -0
  48. claude_mpm/services/socketio/handlers/code_analysis.py +27 -5
  49. claude_mpm/services/socketio/monitor_client.py +366 -0
  50. claude_mpm/services/socketio/monitor_server.py +505 -0
  51. claude_mpm/tools/code_tree_analyzer.py +95 -17
  52. {claude_mpm-4.2.1.dist-info → claude_mpm-4.2.3.dist-info}/METADATA +1 -1
  53. {claude_mpm-4.2.1.dist-info → claude_mpm-4.2.3.dist-info}/RECORD +57 -52
  54. {claude_mpm-4.2.1.dist-info → claude_mpm-4.2.3.dist-info}/WHEEL +0 -0
  55. {claude_mpm-4.2.1.dist-info → claude_mpm-4.2.3.dist-info}/entry_points.txt +0 -0
  56. {claude_mpm-4.2.1.dist-info → claude_mpm-4.2.3.dist-info}/licenses/LICENSE +0 -0
  57. {claude_mpm-4.2.1.dist-info → claude_mpm-4.2.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,360 @@
1
+ """
2
+ Dashboard Server with Monitor Client Integration for claude-mpm.
3
+
4
+ WHY: This module provides a dashboard server that connects to the independent
5
+ monitor server as a client, enabling decoupled architecture where:
6
+ - Dashboard provides UI on port 8765
7
+ - Dashboard connects to monitor server on port 8766 for events
8
+ - Dashboard can restart without affecting event collection
9
+ - Multiple dashboards can connect to the same monitor
10
+
11
+ DESIGN DECISIONS:
12
+ - Extends existing SocketIOServer with monitor client integration
13
+ - Provides UI functionality (HTTP server, static files, dashboard endpoints)
14
+ - Graceful degradation when monitor server is unavailable
15
+ - Event relay from monitor client to dashboard clients
16
+ - Maintains backward compatibility with existing dashboard clients
17
+ """
18
+
19
+ from datetime import datetime
20
+ from typing import Any, Dict, List, Optional
21
+
22
+ try:
23
+ import aiohttp
24
+ import socketio
25
+ from aiohttp import web
26
+
27
+ SOCKETIO_AVAILABLE = True
28
+ except ImportError:
29
+ SOCKETIO_AVAILABLE = False
30
+ socketio = None
31
+ aiohttp = None
32
+ web = None
33
+
34
+ from ...core.config import Config
35
+ from ...core.logging_config import get_logger
36
+ from ..core.interfaces.communication import SocketIOServiceInterface
37
+ from .monitor_client import MonitorClient
38
+ from .server.main import SocketIOServer
39
+
40
+
41
+ class DashboardServer(SocketIOServiceInterface):
42
+ """Dashboard server that connects to monitor server and provides UI.
43
+
44
+ WHY: This server provides the dashboard UI while staying connected to
45
+ the monitor server for event data. This decoupling allows:
46
+ - Stable event collection in monitor server
47
+ - Dashboard can be restarted for updates without losing events
48
+ - Multiple dashboard instances can share the same monitor
49
+ - Dashboard focuses on UI, monitor focuses on event collection
50
+ """
51
+
52
+ def __init__(
53
+ self,
54
+ host: str = None,
55
+ port: int = None,
56
+ monitor_host: str = None,
57
+ monitor_port: int = None,
58
+ ):
59
+ # Load configuration
60
+ config = Config()
61
+ dashboard_config = config.get("dashboard_server", {})
62
+
63
+ self.host = host or dashboard_config.get("host", "localhost")
64
+ self.port = port or dashboard_config.get("port", 8765)
65
+ self.monitor_host = monitor_host or dashboard_config.get(
66
+ "monitor_host", "localhost"
67
+ )
68
+ self.monitor_port = monitor_port or dashboard_config.get("monitor_port", 8766)
69
+
70
+ # Configuration-based settings
71
+ self.auto_connect_monitor = dashboard_config.get("auto_connect_monitor", True)
72
+ self.monitor_reconnect = dashboard_config.get("monitor_reconnect", True)
73
+ self.fallback_standalone = dashboard_config.get("fallback_standalone", True)
74
+
75
+ self.logger = get_logger(__name__ + ".DashboardServer")
76
+
77
+ # Dashboard server (provides UI and serves dashboard clients)
78
+ self.dashboard_server = SocketIOServer(host=self.host, port=self.port)
79
+
80
+ # Monitor client (connects to monitor server for events)
81
+ self.monitor_client = MonitorClient(
82
+ monitor_host=self.monitor_host,
83
+ monitor_port=self.monitor_port,
84
+ auto_reconnect=self.monitor_reconnect,
85
+ )
86
+
87
+ # Server state
88
+ self.running = False
89
+ self.stats = {
90
+ "start_time": None,
91
+ "dashboard_clients": 0,
92
+ "monitor_connected": False,
93
+ "events_relayed": 0,
94
+ }
95
+
96
+ # Setup event relay from monitor client to dashboard clients
97
+ self._setup_event_relay()
98
+
99
+ def start_sync(self):
100
+ """Start the dashboard server and connect to monitor."""
101
+ if not SOCKETIO_AVAILABLE:
102
+ self.logger.error("SocketIO not available - dashboard server cannot start")
103
+ return False
104
+
105
+ self.logger.info(f"Starting dashboard server on {self.host}:{self.port}")
106
+ self.logger.info(
107
+ f"Connecting to monitor server at {self.monitor_host}:{self.monitor_port}"
108
+ )
109
+
110
+ # Start the dashboard server (UI server)
111
+ self.dashboard_server.start_sync()
112
+
113
+ # Connect to monitor server for events (if auto-connect enabled)
114
+ monitor_connected = False
115
+ if self.auto_connect_monitor:
116
+ monitor_connected = self.monitor_client.start()
117
+ else:
118
+ self.logger.info("Auto-connect to monitor disabled by configuration")
119
+
120
+ if not monitor_connected:
121
+ self.logger.warning(
122
+ "Could not connect to monitor server - dashboard will run in standalone mode"
123
+ )
124
+
125
+ # Update state
126
+ self.running = self.dashboard_server.is_running()
127
+ if self.running:
128
+ self.stats["start_time"] = datetime.now().isoformat()
129
+ self.stats["monitor_connected"] = monitor_connected
130
+
131
+ self.logger.info(
132
+ f"Dashboard server started successfully on {self.host}:{self.port} "
133
+ f"(monitor connected: {monitor_connected})"
134
+ )
135
+ return self.running
136
+
137
+ def stop_sync(self):
138
+ """Stop the dashboard server and disconnect from monitor."""
139
+ self.logger.info("Stopping dashboard server")
140
+
141
+ # Stop monitor client first
142
+ self.monitor_client.stop()
143
+
144
+ # Stop dashboard server
145
+ self.dashboard_server.stop_sync()
146
+
147
+ self.running = False
148
+ self.logger.info("Dashboard server stopped")
149
+
150
+ def _setup_event_relay(self):
151
+ """Setup event relay from monitor client to dashboard clients."""
152
+
153
+ # Register handlers for all events we want to relay from monitor to dashboard
154
+ relay_events = [
155
+ "session_started",
156
+ "session_ended",
157
+ "claude_status",
158
+ "claude_output",
159
+ "agent_delegated",
160
+ "todos_updated",
161
+ "ticket_created",
162
+ "memory_loaded",
163
+ "memory_created",
164
+ "memory_updated",
165
+ "memory_injected",
166
+ "file_changed",
167
+ "git_status_changed",
168
+ "project_analyzed",
169
+ "connection_status",
170
+ "heartbeat",
171
+ ]
172
+
173
+ for event_name in relay_events:
174
+ self.monitor_client.add_event_handler(
175
+ event_name, self._create_relay_handler(event_name)
176
+ )
177
+
178
+ def _create_relay_handler(self, event_name: str):
179
+ """Create a relay handler for a specific event."""
180
+
181
+ def relay_handler(data):
182
+ """Relay event from monitor to dashboard clients."""
183
+ self.stats["events_relayed"] += 1
184
+ self.logger.debug(f"Relaying event from monitor to dashboard: {event_name}")
185
+
186
+ # Broadcast to all dashboard clients
187
+ self.dashboard_server.broadcast_event(event_name, data)
188
+
189
+ return relay_handler
190
+
191
+ # Delegate SocketIOServiceInterface methods to dashboard server
192
+ def broadcast_event(self, event_type: str, data: Dict[str, Any]):
193
+ """Broadcast an event to all connected dashboard clients."""
194
+ self.dashboard_server.broadcast_event(event_type, data)
195
+
196
+ def send_to_client(
197
+ self, client_id: str, event_type: str, data: Dict[str, Any]
198
+ ) -> bool:
199
+ """Send an event to a specific dashboard client."""
200
+ return self.dashboard_server.send_to_client(client_id, event_type, data)
201
+
202
+ def get_connection_count(self) -> int:
203
+ """Get number of connected dashboard clients."""
204
+ return self.dashboard_server.get_connection_count()
205
+
206
+ def is_running(self) -> bool:
207
+ """Check if dashboard server is running."""
208
+ return self.running
209
+
210
+ def get_stats(self) -> Dict[str, Any]:
211
+ """Get dashboard server statistics."""
212
+ dashboard_stats = (
213
+ self.dashboard_server.get_stats()
214
+ if hasattr(self.dashboard_server, "get_stats")
215
+ else {}
216
+ )
217
+ monitor_stats = self.monitor_client.get_stats()
218
+
219
+ return {
220
+ **self.stats,
221
+ "dashboard_clients": self.get_connection_count(),
222
+ "monitor_connected": self.monitor_client.is_connected(),
223
+ "dashboard_stats": dashboard_stats,
224
+ "monitor_stats": monitor_stats,
225
+ "uptime": (
226
+ (
227
+ datetime.now() - datetime.fromisoformat(self.stats["start_time"])
228
+ ).total_seconds()
229
+ if self.stats["start_time"]
230
+ else 0
231
+ ),
232
+ }
233
+
234
+ # Session tracking methods - these now send events to monitor server
235
+ def session_started(self, session_id: str, launch_method: str, working_dir: str):
236
+ """Track session start - send to monitor server."""
237
+ # Send to monitor server if connected
238
+ self.monitor_client.send_to_monitor(
239
+ "session_started",
240
+ {
241
+ "session_id": session_id,
242
+ "launch_method": launch_method,
243
+ "working_dir": working_dir,
244
+ },
245
+ )
246
+
247
+ # Also update dashboard server for local tracking
248
+ self.dashboard_server.session_started(session_id, launch_method, working_dir)
249
+
250
+ def session_ended(self):
251
+ """Track session end - send to monitor server."""
252
+ self.monitor_client.send_to_monitor("session_ended", {})
253
+ self.dashboard_server.session_ended()
254
+
255
+ def claude_status_changed(
256
+ self, status: str, pid: Optional[int] = None, message: str = ""
257
+ ):
258
+ """Track Claude status changes - send to monitor server."""
259
+ self.monitor_client.send_to_monitor(
260
+ "claude_status", {"status": status, "pid": pid, "message": message}
261
+ )
262
+ self.dashboard_server.claude_status_changed(status, pid, message)
263
+
264
+ def claude_output(self, content: str, stream: str = "stdout"):
265
+ """Relay Claude output - send to monitor server."""
266
+ self.monitor_client.send_to_monitor(
267
+ "claude_output", {"content": content, "stream": stream}
268
+ )
269
+ self.dashboard_server.claude_output(content, stream)
270
+
271
+ def agent_delegated(self, agent: str, task: str, status: str = "started"):
272
+ """Track agent delegation - send to monitor server."""
273
+ self.monitor_client.send_to_monitor(
274
+ "agent_delegated", {"agent": agent, "task": task, "status": status}
275
+ )
276
+ self.dashboard_server.agent_delegated(agent, task, status)
277
+
278
+ def todo_updated(self, todos: List[Dict[str, Any]]):
279
+ """Relay todo updates - send to monitor server."""
280
+ self.monitor_client.send_to_monitor("todos_updated", {"todos": todos})
281
+ self.dashboard_server.todo_updated(todos)
282
+
283
+ def ticket_created(self, ticket_id: str, title: str, priority: str = "medium"):
284
+ """Relay ticket creation - send to monitor server."""
285
+ self.monitor_client.send_to_monitor(
286
+ "ticket_created",
287
+ {"ticket_id": ticket_id, "title": title, "priority": priority},
288
+ )
289
+ self.dashboard_server.ticket_created(ticket_id, title, priority)
290
+
291
+ def memory_loaded(self, agent_id: str, memory_size: int, sections_count: int):
292
+ """Relay memory loaded event - send to monitor server."""
293
+ self.monitor_client.send_to_monitor(
294
+ "memory_loaded",
295
+ {
296
+ "agent_id": agent_id,
297
+ "memory_size": memory_size,
298
+ "sections_count": sections_count,
299
+ },
300
+ )
301
+ self.dashboard_server.memory_loaded(agent_id, memory_size, sections_count)
302
+
303
+ def memory_created(self, agent_id: str, template_type: str):
304
+ """Relay memory created event - send to monitor server."""
305
+ self.monitor_client.send_to_monitor(
306
+ "memory_created", {"agent_id": agent_id, "template_type": template_type}
307
+ )
308
+ self.dashboard_server.memory_created(agent_id, template_type)
309
+
310
+ def memory_updated(
311
+ self, agent_id: str, learning_type: str, content: str, section: str
312
+ ):
313
+ """Relay memory update event - send to monitor server."""
314
+ self.monitor_client.send_to_monitor(
315
+ "memory_updated",
316
+ {
317
+ "agent_id": agent_id,
318
+ "learning_type": learning_type,
319
+ "content": content,
320
+ "section": section,
321
+ },
322
+ )
323
+ self.dashboard_server.memory_updated(agent_id, learning_type, content, section)
324
+
325
+ def memory_injected(self, agent_id: str, context_size: int):
326
+ """Relay memory injection event - send to monitor server."""
327
+ self.monitor_client.send_to_monitor(
328
+ "memory_injected", {"agent_id": agent_id, "context_size": context_size}
329
+ )
330
+ self.dashboard_server.memory_injected(agent_id, context_size)
331
+
332
+ def get_active_sessions(self) -> List[Dict[str, Any]]:
333
+ """Get list of active sessions from dashboard server."""
334
+ return self.dashboard_server.get_active_sessions()
335
+
336
+ # Provide access to underlying dashboard server properties
337
+ @property
338
+ def sio(self):
339
+ """Access to the dashboard Socket.IO server instance."""
340
+ return self.dashboard_server.sio
341
+
342
+ @property
343
+ def clients(self):
344
+ """Access to connected dashboard clients."""
345
+ return getattr(self.dashboard_server, "connected_clients", set())
346
+
347
+ @property
348
+ def connected_clients(self):
349
+ """Access to connected dashboard clients set."""
350
+ return getattr(self.dashboard_server, "connected_clients", set())
351
+
352
+ @property
353
+ def file_handler(self):
354
+ """Access to file handler for HTTP endpoints."""
355
+ return getattr(self.dashboard_server, "file_handler", None)
356
+
357
+ @property
358
+ def git_handler(self):
359
+ """Access to git handler for HTTP endpoints."""
360
+ return getattr(self.dashboard_server, "git_handler", None)
@@ -591,8 +591,11 @@ class CodeAnalysisEventHandler(BaseEventHandler):
591
591
  return
592
592
 
593
593
  try:
594
+ self.logger.info(f"Starting file analysis for: {path}")
595
+
594
596
  # Ensure analyzer exists
595
597
  if not self.code_analyzer:
598
+ self.logger.info("Creating new CodeTreeAnalyzer instance")
596
599
  emitter = CodeTreeEventEmitter(use_stdout=False)
597
600
  # Override emit method to send to Socket.IO
598
601
  original_emit = emitter.emit
@@ -630,23 +633,42 @@ class CodeAnalysisEventHandler(BaseEventHandler):
630
633
  self.code_analyzer = CodeTreeAnalyzer(
631
634
  emit_events=False, emitter=emitter
632
635
  )
636
+ self.logger.info("CodeTreeAnalyzer created successfully")
633
637
 
634
638
  # Analyze file
639
+ self.logger.info(f"Calling analyze_file for: {path}")
635
640
  result = self.code_analyzer.analyze_file(path)
641
+ self.logger.info(
642
+ f"Analysis complete. Result keys: {list(result.keys()) if result else 'None'}"
643
+ )
644
+
645
+ if result:
646
+ self.logger.info(
647
+ f"Analysis result: elements={len(result.get('elements', []))}, nodes={len(result.get('nodes', []))}"
648
+ )
649
+ else:
650
+ self.logger.warning("Analysis returned None or empty result")
636
651
 
637
652
  # Send result with correct event name (using colons, not dots!)
653
+ response_data = {
654
+ "request_id": request_id,
655
+ "path": path,
656
+ **result,
657
+ }
658
+
659
+ self.logger.info(f"Emitting code:file:analyzed event to {sid}")
638
660
  await self.server.core.sio.emit(
639
661
  "code:file:analyzed",
640
- {
641
- "request_id": request_id,
642
- "path": path,
643
- **result,
644
- },
662
+ response_data,
645
663
  room=sid,
646
664
  )
665
+ self.logger.info("Event emitted successfully")
647
666
 
648
667
  except Exception as e:
649
668
  self.logger.error(f"Error analyzing file {path}: {e}")
669
+ import traceback
670
+
671
+ self.logger.error(f"Full traceback: {traceback.format_exc()}")
650
672
  await self.server.core.sio.emit(
651
673
  "code:analysis:error",
652
674
  {