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,595 +0,0 @@
1
- """
2
- Socket.IO Server Manager Service
3
- =================================
4
-
5
- WHY: This service extracts Socket.IO server management from run.py to provide a
6
- centralized, reusable service for managing Socket.IO server lifecycle. This reduces
7
- run.py complexity by ~200-250 lines and provides a clean interface for server management.
8
-
9
- DESIGN DECISIONS:
10
- - Interface-based design (ISocketIOManager) for testability and flexibility
11
- - Integrates with existing PortManager for port allocation
12
- - Provides server process management with graceful shutdown
13
- - Handles dependency checking and installation prompts
14
- - Manages server lifecycle (start, stop, status, wait)
15
- - Thread-safe server state management
16
- """
17
-
18
- import os
19
- import signal
20
- import socket
21
- import subprocess
22
- import sys
23
- import threading
24
- import time
25
- from abc import ABC, abstractmethod
26
- from dataclasses import dataclass
27
- from typing import Optional, Tuple
28
-
29
- from ...core.logger import get_logger
30
- from ...core.unified_paths import get_package_root, get_scripts_dir
31
- from ...services.port_manager import PortManager
32
- from ...utils.dependency_manager import ensure_socketio_dependencies
33
-
34
-
35
- @dataclass
36
- class ServerInfo:
37
- """Information about a running Socket.IO server."""
38
-
39
- port: int
40
- pid: Optional[int]
41
- is_running: bool
42
- launch_time: Optional[float]
43
- url: str
44
-
45
-
46
- # Interface
47
- class ISocketIOManager(ABC):
48
- """Interface for Socket.IO server management."""
49
-
50
- @abstractmethod
51
- def start_server(
52
- self, port: Optional[int] = None, timeout: int = 30
53
- ) -> Tuple[bool, ServerInfo]:
54
- """
55
- Start the Socket.IO server.
56
-
57
- Args:
58
- port: Optional specific port to use. If None, finds available port.
59
- timeout: Maximum time to wait for server startup
60
-
61
- Returns:
62
- Tuple of (success, ServerInfo)
63
- """
64
-
65
- @abstractmethod
66
- def stop_server(self, port: Optional[int] = None, timeout: int = 10) -> bool:
67
- """
68
- Stop the Socket.IO server.
69
-
70
- Args:
71
- port: Optional specific port to stop. If None, stops all servers.
72
- timeout: Maximum time to wait for graceful shutdown
73
-
74
- Returns:
75
- True if server was stopped successfully
76
- """
77
-
78
- @abstractmethod
79
- def is_server_running(self, port: int) -> bool:
80
- """
81
- Check if a Socket.IO server is running on the specified port.
82
-
83
- Args:
84
- port: Port to check
85
-
86
- Returns:
87
- True if server is running on the port
88
- """
89
-
90
- @abstractmethod
91
- def get_server_info(self, port: int) -> ServerInfo:
92
- """
93
- Get information about a server on the specified port.
94
-
95
- Args:
96
- port: Port to check
97
-
98
- Returns:
99
- ServerInfo object with server details
100
- """
101
-
102
- @abstractmethod
103
- def wait_for_server(self, port: int, timeout: int = 30) -> bool:
104
- """
105
- Wait for a server to be ready on the specified port.
106
-
107
- Args:
108
- port: Port to wait for
109
- timeout: Maximum time to wait in seconds
110
-
111
- Returns:
112
- True if server became ready within timeout
113
- """
114
-
115
- @abstractmethod
116
- def find_available_port(self, preferred_port: int = 8765) -> int:
117
- """
118
- Find an available port for the Socket.IO server.
119
-
120
- Args:
121
- preferred_port: Preferred port to try first
122
-
123
- Returns:
124
- Available port number
125
- """
126
-
127
- @abstractmethod
128
- def check_dependencies(self) -> bool:
129
- """
130
- Check if monitoring dependencies are installed and print helpful messages.
131
-
132
- Returns:
133
- True if all dependencies are available
134
- """
135
-
136
- @abstractmethod
137
- def ensure_dependencies(self) -> Tuple[bool, Optional[str]]:
138
- """
139
- Ensure Socket.IO dependencies are installed.
140
-
141
- Returns:
142
- Tuple of (success, error_message)
143
- """
144
-
145
-
146
- # Implementation
147
- class SocketIOManager(ISocketIOManager):
148
- """Socket.IO server manager implementation."""
149
-
150
- def __init__(self, logger=None):
151
- """
152
- Initialize the Socket.IO manager.
153
-
154
- Args:
155
- logger: Optional logger instance
156
- """
157
- self.logger = logger or get_logger("SocketIOManager")
158
- self.port_manager = PortManager()
159
- self._server_processes = {} # port -> subprocess.Popen
160
- self._lock = threading.Lock()
161
-
162
- def start_server(
163
- self, port: Optional[int] = None, timeout: int = 30
164
- ) -> Tuple[bool, ServerInfo]:
165
- """
166
- Start the Socket.IO server.
167
-
168
- WHY: Provides a unified way to start Socket.IO servers with proper error handling,
169
- port management, and process lifecycle control.
170
-
171
- Args:
172
- port: Optional specific port to use. If None, finds available port.
173
- timeout: Maximum time to wait for server startup
174
-
175
- Returns:
176
- Tuple of (success, ServerInfo)
177
- """
178
- with self._lock:
179
- # Determine port to use
180
- target_port = port if port else self.find_available_port()
181
-
182
- # Check if server already running on this port
183
- if self.is_server_running(target_port):
184
- # Verify the server is healthy and responding
185
- if self.wait_for_server(target_port, timeout=2):
186
- self.logger.info(
187
- f"Healthy Socket.IO server already running on port {target_port}"
188
- )
189
- return True, self.get_server_info(target_port)
190
- # Server exists but not responding, try to clean it up
191
- self.logger.warning(
192
- f"Socket.IO server on port {target_port} not responding, attempting cleanup"
193
- )
194
- self.stop_server(port=target_port, timeout=5)
195
- # Continue with starting a new server
196
-
197
- # Ensure dependencies are available
198
- deps_ok, error_msg = self.ensure_dependencies()
199
- if not deps_ok:
200
- self.logger.error(f"Socket.IO dependencies not available: {error_msg}")
201
- return False, ServerInfo(
202
- port=target_port,
203
- pid=None,
204
- is_running=False,
205
- launch_time=None,
206
- url=f"http://localhost:{target_port}",
207
- )
208
-
209
- # Start the server
210
- try:
211
- self.logger.info(f"Starting Socket.IO server on port {target_port}")
212
-
213
- # Get the socketio daemon script path using proper resource resolution
214
- try:
215
- from ...core.unified_paths import get_package_resource_path
216
-
217
- daemon_script = get_package_resource_path(
218
- "scripts/socketio_daemon_wrapper.py"
219
- )
220
- except FileNotFoundError:
221
- # Fallback to old method for development environments
222
- scripts_dir = get_scripts_dir()
223
- daemon_script = scripts_dir / "socketio_daemon_wrapper.py"
224
-
225
- if not daemon_script.exists():
226
- self.logger.error(
227
- f"Socket.IO daemon script not found: {daemon_script}"
228
- )
229
- return False, ServerInfo(
230
- port=target_port,
231
- pid=None,
232
- is_running=False,
233
- launch_time=None,
234
- url=f"http://localhost:{target_port}",
235
- )
236
-
237
- # Start the server process
238
- env = os.environ.copy()
239
- env["PYTHONPATH"] = str(get_package_root())
240
-
241
- process = subprocess.Popen(
242
- [sys.executable, str(daemon_script), "--port", str(target_port)],
243
- env=env,
244
- stdout=subprocess.PIPE,
245
- stderr=subprocess.PIPE,
246
- start_new_session=True, # Detach from parent process group
247
- )
248
-
249
- # Store process reference
250
- self._server_processes[target_port] = process
251
-
252
- # Wait for server to be ready
253
- if self.wait_for_server(target_port, timeout):
254
- self.logger.info(
255
- f"Socket.IO server started successfully on port {target_port}"
256
- )
257
- return True, self.get_server_info(target_port)
258
- self.logger.error(
259
- f"Socket.IO server failed to start within {timeout} seconds"
260
- )
261
- # Clean up failed process
262
- self._cleanup_process(target_port)
263
- return False, ServerInfo(
264
- port=target_port,
265
- pid=None,
266
- is_running=False,
267
- launch_time=None,
268
- url=f"http://localhost:{target_port}",
269
- )
270
-
271
- except Exception as e:
272
- self.logger.error(f"Failed to start Socket.IO server: {e}")
273
- return False, ServerInfo(
274
- port=target_port,
275
- pid=None,
276
- is_running=False,
277
- launch_time=None,
278
- url=f"http://localhost:{target_port}",
279
- )
280
-
281
- def stop_server(self, port: Optional[int] = None, timeout: int = 10) -> bool:
282
- """
283
- Stop the Socket.IO server.
284
-
285
- WHY: Provides graceful shutdown of Socket.IO servers with proper cleanup
286
- and process termination.
287
-
288
- Args:
289
- port: Optional specific port to stop. If None, stops all servers.
290
- timeout: Maximum time to wait for graceful shutdown
291
-
292
- Returns:
293
- True if server was stopped successfully
294
- """
295
- with self._lock:
296
- if port:
297
- return self._stop_server_on_port(port, timeout)
298
- # Stop all managed servers
299
- success = True
300
- for server_port in list(self._server_processes.keys()):
301
- if not self._stop_server_on_port(server_port, timeout):
302
- success = False
303
- return success
304
-
305
- def _stop_server_on_port(self, port: int, timeout: int) -> bool:
306
- """Stop server on specific port."""
307
- try:
308
- # Check if we have a process reference
309
- if port in self._server_processes:
310
- process = self._server_processes[port]
311
-
312
- # Send graceful shutdown signal
313
- if process.poll() is None: # Process is still running
314
- self.logger.info(f"Stopping Socket.IO server on port {port}")
315
- process.terminate()
316
-
317
- # Wait for graceful shutdown
318
- try:
319
- process.wait(timeout=timeout)
320
- self.logger.info(
321
- f"Socket.IO server on port {port} stopped gracefully"
322
- )
323
- except subprocess.TimeoutExpired:
324
- # Force kill if graceful shutdown failed
325
- self.logger.warning(
326
- f"Force killing Socket.IO server on port {port}"
327
- )
328
- process.kill()
329
- process.wait()
330
-
331
- # Clean up process reference
332
- del self._server_processes[port]
333
- return True
334
-
335
- # Try to stop server by port (external process)
336
- process_info = self.port_manager.get_process_on_port(port)
337
- if process_info and process_info.is_ours:
338
- try:
339
- os.kill(process_info.pid, signal.SIGTERM)
340
- time.sleep(2) # Give it time to shut down
341
-
342
- # Check if still running
343
- try:
344
- os.kill(process_info.pid, 0) # Check if process exists
345
- # Still running, force kill
346
- os.kill(process_info.pid, signal.SIGKILL)
347
- except ProcessLookupError:
348
- pass # Process already terminated
349
-
350
- self.logger.info(
351
- f"Stopped Socket.IO server (PID: {process_info.pid}) on port {port}"
352
- )
353
- return True
354
- except Exception as e:
355
- self.logger.error(f"Failed to stop server on port {port}: {e}")
356
- return False
357
-
358
- return True # No server to stop
359
-
360
- except Exception as e:
361
- self.logger.error(f"Error stopping server on port {port}: {e}")
362
- return False
363
-
364
- def _cleanup_process(self, port: int):
365
- """Clean up failed or terminated process."""
366
- if port in self._server_processes:
367
- process = self._server_processes[port]
368
- try:
369
- if process.poll() is None:
370
- process.kill()
371
- process.wait()
372
- except:
373
- pass
374
- del self._server_processes[port]
375
-
376
- def is_server_running(self, port: int) -> bool:
377
- """
378
- Check if a Socket.IO server is running on the specified port.
379
-
380
- WHY: Quick check to determine if a server is already running to avoid
381
- conflicts and duplicates.
382
-
383
- Args:
384
- port: Port to check
385
-
386
- Returns:
387
- True if server is running on the port
388
- """
389
- # First check our managed processes
390
- if port in self._server_processes:
391
- process = self._server_processes[port]
392
- if process.poll() is None:
393
- return True
394
- # Process terminated, clean up
395
- self._cleanup_process(port)
396
-
397
- # Check if port is in use (by any process)
398
- process_info = self.port_manager.get_process_on_port(port)
399
- if process_info:
400
- # Check if it's a Socket.IO server
401
- return (
402
- "socketio" in process_info.cmdline.lower()
403
- or "socket-io" in process_info.cmdline.lower()
404
- or process_info.is_daemon
405
- )
406
-
407
- return False
408
-
409
- def get_server_info(self, port: int) -> ServerInfo:
410
- """
411
- Get information about a server on the specified port.
412
-
413
- Args:
414
- port: Port to check
415
-
416
- Returns:
417
- ServerInfo object with server details
418
- """
419
- is_running = self.is_server_running(port)
420
- pid = None
421
- launch_time = None
422
-
423
- if is_running:
424
- # Get process info
425
- if port in self._server_processes:
426
- process = self._server_processes[port]
427
- pid = process.pid
428
- launch_time = time.time() # Approximate
429
- else:
430
- process_info = self.port_manager.get_process_on_port(port)
431
- if process_info:
432
- pid = process_info.pid
433
-
434
- return ServerInfo(
435
- port=port,
436
- pid=pid,
437
- is_running=is_running,
438
- launch_time=launch_time,
439
- url=f"http://localhost:{port}",
440
- )
441
-
442
- def wait_for_server(self, port: int, timeout: int = 30) -> bool:
443
- """
444
- Wait for a server to be ready on the specified port.
445
-
446
- WHY: Ensures the server is fully started and ready to accept connections
447
- before proceeding with other operations.
448
-
449
- Args:
450
- port: Port to wait for
451
- timeout: Maximum time to wait in seconds
452
-
453
- Returns:
454
- True if server became ready within timeout
455
- """
456
- start_time = time.time()
457
-
458
- while time.time() - start_time < timeout:
459
- # Try to connect to the server
460
- try:
461
- with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
462
- sock.settimeout(1)
463
- result = sock.connect_ex(("localhost", port))
464
- if result == 0:
465
- self.logger.debug(f"Socket.IO server ready on port {port}")
466
- return True
467
- except Exception:
468
- pass
469
-
470
- time.sleep(0.5)
471
-
472
- return False
473
-
474
- def find_available_port(self, preferred_port: int = 8765) -> int:
475
- """
476
- Find an available port for the Socket.IO server.
477
-
478
- WHY: Automatically finds an available port to avoid conflicts when the
479
- preferred port is already in use.
480
-
481
- Args:
482
- preferred_port: Preferred port to try first
483
-
484
- Returns:
485
- Available port number
486
- """
487
- # First check if our Socket.IO server is already running on the preferred port
488
- if self.is_server_running(preferred_port):
489
- # Check if it's healthy
490
- if self.wait_for_server(preferred_port, timeout=2):
491
- self.logger.info(
492
- f"Healthy Socket.IO server already running on port {preferred_port}"
493
- )
494
- return preferred_port
495
- self.logger.warning(
496
- f"Socket.IO server on port {preferred_port} not responding, will try to restart"
497
- )
498
-
499
- # Try preferred port first if available
500
- if self.port_manager.is_port_available(preferred_port):
501
- return preferred_port
502
-
503
- # Find alternative port using the correct method name
504
- available_port = self.port_manager.find_available_port(
505
- preferred_port=preferred_port, reclaim=True
506
- )
507
-
508
- if available_port:
509
- self.logger.info(
510
- f"Port {preferred_port} unavailable, using {available_port}"
511
- )
512
- return available_port
513
- # If no port found, raise an error
514
- raise RuntimeError(
515
- f"No available ports in range {self.port_manager.PORT_RANGE.start}-"
516
- f"{self.port_manager.PORT_RANGE.stop-1}"
517
- )
518
-
519
- def check_dependencies(self) -> bool:
520
- """Check if monitoring dependencies are installed."""
521
- missing = []
522
- try:
523
- import socketio
524
- except ImportError:
525
- missing.append("python-socketio")
526
- try:
527
- import aiohttp
528
- except ImportError:
529
- missing.append("aiohttp")
530
- try:
531
- import engineio
532
- except ImportError:
533
- missing.append("python-engineio")
534
-
535
- if missing:
536
- print(f"Missing dependencies for monitoring: {', '.join(missing)}")
537
- print("\nTo install all monitoring dependencies:")
538
- print(" pip install claude-mpm[monitor]")
539
- print("\nOr install manually:")
540
- print(f" pip install {' '.join(missing)}")
541
- return False
542
- return True
543
-
544
- def ensure_dependencies(self) -> Tuple[bool, Optional[str]]:
545
- """
546
- Ensure Socket.IO dependencies are installed.
547
-
548
- WHY: Verifies that required Socket.IO packages are available before
549
- attempting to start the server.
550
-
551
- Returns:
552
- Tuple of (success, error_message)
553
- """
554
- try:
555
- # Use existing dependency manager
556
- success, error_msg = ensure_socketio_dependencies(self.logger)
557
-
558
- if not success:
559
- # Provide helpful error message with improved guidance
560
- missing = []
561
- try:
562
- import socketio
563
- except ImportError:
564
- missing.append("python-socketio")
565
- try:
566
- import aiohttp
567
- except ImportError:
568
- missing.append("aiohttp")
569
- try:
570
- import engineio
571
- except ImportError:
572
- missing.append("python-engineio")
573
-
574
- if missing:
575
- detailed_error = (
576
- f"Missing dependencies for monitoring: {', '.join(missing)}\n"
577
- "To install all monitoring dependencies:\n"
578
- " pip install claude-mpm[monitor]\n"
579
- "Or install manually:\n"
580
- f" pip install {' '.join(missing)}"
581
- )
582
- return False, detailed_error
583
-
584
- if error_msg:
585
- return False, error_msg
586
- return False, (
587
- "Socket.IO dependencies not installed. "
588
- "Install with: pip install python-socketio aiohttp python-engineio "
589
- "or pip install claude-mpm[monitor]"
590
- )
591
-
592
- return True, None
593
-
594
- except Exception as e:
595
- return False, str(e)