claude-mpm 4.2.9__py3-none-any.whl → 4.2.12__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 (51) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/cli/commands/dashboard.py +59 -126
  3. claude_mpm/cli/commands/monitor.py +82 -212
  4. claude_mpm/cli/commands/run.py +33 -33
  5. claude_mpm/cli/parsers/monitor_parser.py +12 -2
  6. claude_mpm/dashboard/static/css/code-tree.css +8 -16
  7. claude_mpm/dashboard/static/dist/components/code-tree.js +1 -1
  8. claude_mpm/dashboard/static/dist/components/file-viewer.js +2 -0
  9. claude_mpm/dashboard/static/dist/components/module-viewer.js +1 -1
  10. claude_mpm/dashboard/static/dist/components/unified-data-viewer.js +1 -1
  11. claude_mpm/dashboard/static/dist/dashboard.js +1 -1
  12. claude_mpm/dashboard/static/dist/socket-client.js +1 -1
  13. claude_mpm/dashboard/static/js/components/code-tree.js +692 -114
  14. claude_mpm/dashboard/static/js/components/file-viewer.js +538 -0
  15. claude_mpm/dashboard/static/js/components/module-viewer.js +26 -0
  16. claude_mpm/dashboard/static/js/components/unified-data-viewer.js +166 -14
  17. claude_mpm/dashboard/static/js/dashboard.js +108 -91
  18. claude_mpm/dashboard/static/js/socket-client.js +9 -7
  19. claude_mpm/dashboard/templates/index.html +2 -7
  20. claude_mpm/hooks/claude_hooks/hook_handler.py +1 -11
  21. claude_mpm/hooks/claude_hooks/services/connection_manager.py +54 -59
  22. claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +112 -72
  23. claude_mpm/services/agents/deployment/agent_template_builder.py +0 -1
  24. claude_mpm/services/cli/unified_dashboard_manager.py +354 -0
  25. claude_mpm/services/monitor/__init__.py +20 -0
  26. claude_mpm/services/monitor/daemon.py +378 -0
  27. claude_mpm/services/monitor/event_emitter.py +342 -0
  28. claude_mpm/services/monitor/handlers/__init__.py +20 -0
  29. claude_mpm/services/monitor/handlers/code_analysis.py +334 -0
  30. claude_mpm/services/monitor/handlers/dashboard.py +298 -0
  31. claude_mpm/services/monitor/handlers/hooks.py +491 -0
  32. claude_mpm/services/monitor/management/__init__.py +18 -0
  33. claude_mpm/services/monitor/management/health.py +124 -0
  34. claude_mpm/services/monitor/management/lifecycle.py +338 -0
  35. claude_mpm/services/monitor/server.py +596 -0
  36. claude_mpm/tools/code_tree_analyzer.py +33 -17
  37. {claude_mpm-4.2.9.dist-info → claude_mpm-4.2.12.dist-info}/METADATA +1 -1
  38. {claude_mpm-4.2.9.dist-info → claude_mpm-4.2.12.dist-info}/RECORD +42 -37
  39. claude_mpm/cli/commands/socketio_monitor.py +0 -233
  40. claude_mpm/scripts/socketio_daemon.py +0 -571
  41. claude_mpm/scripts/socketio_daemon_hardened.py +0 -937
  42. claude_mpm/scripts/socketio_daemon_wrapper.py +0 -78
  43. claude_mpm/scripts/socketio_server_manager.py +0 -349
  44. claude_mpm/services/cli/dashboard_launcher.py +0 -423
  45. claude_mpm/services/cli/socketio_manager.py +0 -595
  46. claude_mpm/services/dashboard/stable_server.py +0 -1020
  47. claude_mpm/services/socketio/monitor_server.py +0 -505
  48. {claude_mpm-4.2.9.dist-info → claude_mpm-4.2.12.dist-info}/WHEEL +0 -0
  49. {claude_mpm-4.2.9.dist-info → claude_mpm-4.2.12.dist-info}/entry_points.txt +0 -0
  50. {claude_mpm-4.2.9.dist-info → claude_mpm-4.2.12.dist-info}/licenses/LICENSE +0 -0
  51. {claude_mpm-4.2.9.dist-info → claude_mpm-4.2.12.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,491 @@
1
+ """
2
+ Claude Code Hooks Handler for Unified Monitor
3
+ =============================================
4
+
5
+ WHY: This handler ingests Claude Code hooks and events, providing integration
6
+ between Claude Code sessions and the unified monitor daemon. It processes
7
+ hook events and broadcasts them to dashboard clients.
8
+
9
+ DESIGN DECISIONS:
10
+ - Ingests Claude Code hooks via HTTP and WebSocket
11
+ - Processes and normalizes hook events
12
+ - Broadcasts events to connected dashboard clients
13
+ - Maintains event history and replay capability
14
+ """
15
+
16
+ import asyncio
17
+ from collections import deque
18
+ from typing import Dict, List
19
+
20
+ import socketio
21
+
22
+ from ....core.logging_config import get_logger
23
+
24
+
25
+ class HookHandler:
26
+ """Event handler for Claude Code hooks integration.
27
+
28
+ WHY: Provides integration between Claude Code sessions and the unified
29
+ monitor daemon, allowing real-time monitoring of Claude Code activities.
30
+ """
31
+
32
+ def __init__(self, sio: socketio.AsyncServer):
33
+ """Initialize the hooks handler.
34
+
35
+ Args:
36
+ sio: Socket.IO server instance
37
+ """
38
+ self.sio = sio
39
+ self.logger = get_logger(__name__)
40
+
41
+ # Event storage
42
+ self.event_history: deque = deque(maxlen=1000) # Keep last 1000 events
43
+ self.active_sessions: Dict[str, Dict] = {}
44
+
45
+ def register(self):
46
+ """Register Socket.IO event handlers."""
47
+ try:
48
+ # Claude Code hook events (HTTP POST pathway)
49
+ self.sio.on("claude_event", self.handle_claude_event)
50
+
51
+ # Hook ingestion events (alternative format)
52
+ self.sio.on("hook:ingest", self.handle_hook_ingest)
53
+ self.sio.on("hook:session:start", self.handle_session_start)
54
+ self.sio.on("hook:session:end", self.handle_session_end)
55
+
56
+ # Event replay and history
57
+ self.sio.on("hook:history:get", self.handle_get_history)
58
+ self.sio.on("hook:replay:start", self.handle_replay_start)
59
+
60
+ # Session management
61
+ self.sio.on("hook:sessions:list", self.handle_list_sessions)
62
+ self.sio.on("hook:session:info", self.handle_session_info)
63
+
64
+ self.logger.info("Hook event handlers registered")
65
+
66
+ except Exception as e:
67
+ self.logger.error(f"Error registering hook handlers: {e}")
68
+ raise
69
+
70
+ async def handle_claude_event(self, sid: str, data: Dict):
71
+ """Handle Claude Code hook events sent via 'claude_event'.
72
+
73
+ This is the primary integration point for Claude Code hooks.
74
+
75
+ Args:
76
+ sid: Socket.IO session ID
77
+ data: Claude event data
78
+ """
79
+ try:
80
+ self.logger.info(
81
+ f"Received Claude Code hook event: {data.get('type', 'unknown')}"
82
+ )
83
+
84
+ # Process the Claude event
85
+ processed_event = self._process_claude_event(data)
86
+
87
+ # Store in history
88
+ self.event_history.append(processed_event)
89
+
90
+ # Update session tracking
91
+ session_id = processed_event.get("session_id")
92
+ if session_id:
93
+ self._update_session_tracking(session_id, processed_event)
94
+
95
+ # Broadcast to all dashboard clients
96
+ # Use only one event type to avoid duplication
97
+ await self.sio.emit("hook:event", processed_event)
98
+
99
+ self.logger.debug(
100
+ f"Claude hook event processed and broadcasted: {processed_event.get('type', 'unknown')}"
101
+ )
102
+
103
+ except Exception as e:
104
+ self.logger.error(f"Error processing Claude hook event: {e}")
105
+ await self.sio.emit(
106
+ "hook:error",
107
+ {"error": f"Claude event processing error: {e!s}"},
108
+ room=sid,
109
+ )
110
+
111
+ async def handle_hook_ingest(self, sid: str, data: Dict):
112
+ """Handle incoming Claude Code hook event.
113
+
114
+ Args:
115
+ sid: Socket.IO session ID
116
+ data: Hook event data
117
+ """
118
+ try:
119
+ # Validate hook data
120
+ if not self._validate_hook_data(data):
121
+ await self.sio.emit(
122
+ "hook:error", {"error": "Invalid hook data format"}, room=sid
123
+ )
124
+ return
125
+
126
+ # Process and normalize the hook event
127
+ processed_event = self._process_hook_event(data)
128
+
129
+ # Store in history
130
+ self.event_history.append(processed_event)
131
+
132
+ # Update session tracking
133
+ session_id = processed_event.get("session_id")
134
+ if session_id:
135
+ self._update_session_tracking(session_id, processed_event)
136
+
137
+ # Broadcast to all dashboard clients
138
+ await self.sio.emit("hook:event", processed_event)
139
+
140
+ self.logger.debug(
141
+ f"Hook event processed: {processed_event.get('type', 'unknown')}"
142
+ )
143
+
144
+ except Exception as e:
145
+ self.logger.error(f"Error processing hook event: {e}")
146
+ await self.sio.emit(
147
+ "hook:error", {"error": f"Hook processing error: {e!s}"}, room=sid
148
+ )
149
+
150
+ async def handle_session_start(self, sid: str, data: Dict):
151
+ """Handle Claude Code session start.
152
+
153
+ Args:
154
+ sid: Socket.IO session ID
155
+ data: Session start data
156
+ """
157
+ try:
158
+ session_id = data.get("session_id")
159
+ if not session_id:
160
+ await self.sio.emit(
161
+ "hook:error", {"error": "No session ID provided"}, room=sid
162
+ )
163
+ return
164
+
165
+ # Create session tracking
166
+ session_info = {
167
+ "session_id": session_id,
168
+ "start_time": asyncio.get_event_loop().time(),
169
+ "status": "active",
170
+ "event_count": 0,
171
+ "last_activity": asyncio.get_event_loop().time(),
172
+ "metadata": data.get("metadata", {}),
173
+ }
174
+
175
+ self.active_sessions[session_id] = session_info
176
+
177
+ # Broadcast session start
178
+ await self.sio.emit("hook:session:started", session_info)
179
+
180
+ self.logger.info(f"Claude Code session started: {session_id}")
181
+
182
+ except Exception as e:
183
+ self.logger.error(f"Error handling session start: {e}")
184
+ await self.sio.emit(
185
+ "hook:error", {"error": f"Session start error: {e!s}"}, room=sid
186
+ )
187
+
188
+ async def handle_session_end(self, sid: str, data: Dict):
189
+ """Handle Claude Code session end.
190
+
191
+ Args:
192
+ sid: Socket.IO session ID
193
+ data: Session end data
194
+ """
195
+ try:
196
+ session_id = data.get("session_id")
197
+ if not session_id:
198
+ await self.sio.emit(
199
+ "hook:error", {"error": "No session ID provided"}, room=sid
200
+ )
201
+ return
202
+
203
+ # Update session status
204
+ if session_id in self.active_sessions:
205
+ session_info = self.active_sessions[session_id]
206
+ session_info["status"] = "ended"
207
+ session_info["end_time"] = asyncio.get_event_loop().time()
208
+ session_info["duration"] = (
209
+ session_info["end_time"] - session_info["start_time"]
210
+ )
211
+
212
+ # Broadcast session end
213
+ await self.sio.emit("hook:session:ended", session_info)
214
+
215
+ # Remove from active sessions after a delay
216
+ asyncio.create_task(
217
+ self._cleanup_session(session_id, delay=300)
218
+ ) # 5 minutes
219
+
220
+ self.logger.info(f"Claude Code session ended: {session_id}")
221
+ else:
222
+ self.logger.warning(f"Session end for unknown session: {session_id}")
223
+
224
+ except Exception as e:
225
+ self.logger.error(f"Error handling session end: {e}")
226
+ await self.sio.emit(
227
+ "hook:error", {"error": f"Session end error: {e!s}"}, room=sid
228
+ )
229
+
230
+ async def handle_get_history(self, sid: str, data: Dict):
231
+ """Handle request for event history.
232
+
233
+ Args:
234
+ sid: Socket.IO session ID
235
+ data: History request data
236
+ """
237
+ try:
238
+ limit = data.get("limit", 100)
239
+ event_type = data.get("type")
240
+ session_id = data.get("session_id")
241
+
242
+ # Filter events
243
+ filtered_events = list(self.event_history)
244
+
245
+ if event_type:
246
+ filtered_events = [
247
+ e for e in filtered_events if e.get("type") == event_type
248
+ ]
249
+
250
+ if session_id:
251
+ filtered_events = [
252
+ e for e in filtered_events if e.get("session_id") == session_id
253
+ ]
254
+
255
+ # Apply limit
256
+ if limit > 0:
257
+ filtered_events = filtered_events[-limit:]
258
+
259
+ await self.sio.emit(
260
+ "hook:history:response",
261
+ {
262
+ "events": filtered_events,
263
+ "total": len(filtered_events),
264
+ "filters": {
265
+ "type": event_type,
266
+ "session_id": session_id,
267
+ "limit": limit,
268
+ },
269
+ },
270
+ room=sid,
271
+ )
272
+
273
+ except Exception as e:
274
+ self.logger.error(f"Error getting event history: {e}")
275
+ await self.sio.emit(
276
+ "hook:error", {"error": f"History error: {e!s}"}, room=sid
277
+ )
278
+
279
+ async def handle_replay_start(self, sid: str, data: Dict):
280
+ """Handle event replay request.
281
+
282
+ Args:
283
+ sid: Socket.IO session ID
284
+ data: Replay request data
285
+ """
286
+ try:
287
+ session_id = data.get("session_id")
288
+ speed = data.get("speed", 1.0) # Replay speed multiplier
289
+
290
+ if not session_id:
291
+ await self.sio.emit(
292
+ "hook:error",
293
+ {"error": "No session ID provided for replay"},
294
+ room=sid,
295
+ )
296
+ return
297
+
298
+ # Get events for session
299
+ session_events = [
300
+ e for e in self.event_history if e.get("session_id") == session_id
301
+ ]
302
+
303
+ if not session_events:
304
+ await self.sio.emit(
305
+ "hook:error",
306
+ {"error": f"No events found for session: {session_id}"},
307
+ room=sid,
308
+ )
309
+ return
310
+
311
+ # Start replay
312
+ await self._replay_events(sid, session_events, speed)
313
+
314
+ except Exception as e:
315
+ self.logger.error(f"Error starting event replay: {e}")
316
+ await self.sio.emit(
317
+ "hook:error", {"error": f"Replay error: {e!s}"}, room=sid
318
+ )
319
+
320
+ async def handle_list_sessions(self, sid: str, data: Dict):
321
+ """Handle request for active sessions list.
322
+
323
+ Args:
324
+ sid: Socket.IO session ID
325
+ data: Request data
326
+ """
327
+ try:
328
+ sessions = list(self.active_sessions.values())
329
+
330
+ await self.sio.emit(
331
+ "hook:sessions:response",
332
+ {"sessions": sessions, "total": len(sessions)},
333
+ room=sid,
334
+ )
335
+
336
+ except Exception as e:
337
+ self.logger.error(f"Error listing sessions: {e}")
338
+ await self.sio.emit(
339
+ "hook:error", {"error": f"Sessions list error: {e!s}"}, room=sid
340
+ )
341
+
342
+ async def handle_session_info(self, sid: str, data: Dict):
343
+ """Handle request for specific session info.
344
+
345
+ Args:
346
+ sid: Socket.IO session ID
347
+ data: Request data
348
+ """
349
+ try:
350
+ session_id = data.get("session_id")
351
+ if not session_id:
352
+ await self.sio.emit(
353
+ "hook:error", {"error": "No session ID provided"}, room=sid
354
+ )
355
+ return
356
+
357
+ session_info = self.active_sessions.get(session_id)
358
+ if not session_info:
359
+ await self.sio.emit(
360
+ "hook:error",
361
+ {"error": f"Session not found: {session_id}"},
362
+ room=sid,
363
+ )
364
+ return
365
+
366
+ await self.sio.emit("hook:session:info:response", session_info, room=sid)
367
+
368
+ except Exception as e:
369
+ self.logger.error(f"Error getting session info: {e}")
370
+ await self.sio.emit(
371
+ "hook:error", {"error": f"Session info error: {e!s}"}, room=sid
372
+ )
373
+
374
+ def _validate_hook_data(self, data: Dict) -> bool:
375
+ """Validate hook event data format.
376
+
377
+ Args:
378
+ data: Hook event data
379
+
380
+ Returns:
381
+ True if valid, False otherwise
382
+ """
383
+ required_fields = ["type", "timestamp"]
384
+ return all(field in data for field in required_fields)
385
+
386
+ def _process_claude_event(self, data: Dict) -> Dict:
387
+ """Process and normalize Claude Code hook event data.
388
+
389
+ Args:
390
+ data: Raw Claude event data
391
+
392
+ Returns:
393
+ Processed event data
394
+ """
395
+ processed = {
396
+ "type": data.get("type", "hook"),
397
+ "subtype": data.get("subtype", "unknown"),
398
+ "timestamp": data.get("timestamp", asyncio.get_event_loop().time()),
399
+ "session_id": data.get("session_id"),
400
+ "source": data.get("source", "claude_hooks"),
401
+ "data": data.get("data", {}),
402
+ "metadata": data.get("metadata", {}),
403
+ "processed_at": asyncio.get_event_loop().time(),
404
+ "original_event": data, # Keep original for debugging
405
+ }
406
+
407
+ return processed
408
+
409
+ def _process_hook_event(self, data: Dict) -> Dict:
410
+ """Process and normalize hook event data.
411
+
412
+ Args:
413
+ data: Raw hook event data
414
+
415
+ Returns:
416
+ Processed event data
417
+ """
418
+ processed = {
419
+ "type": data.get("type"),
420
+ "timestamp": data.get("timestamp"),
421
+ "session_id": data.get("session_id"),
422
+ "data": data.get("data", {}),
423
+ "metadata": data.get("metadata", {}),
424
+ "processed_at": asyncio.get_event_loop().time(),
425
+ }
426
+
427
+ return processed
428
+
429
+ def _update_session_tracking(self, session_id: str, event: Dict):
430
+ """Update session tracking with new event.
431
+
432
+ Args:
433
+ session_id: Session ID
434
+ event: Event data
435
+ """
436
+ if session_id in self.active_sessions:
437
+ session = self.active_sessions[session_id]
438
+ session["event_count"] += 1
439
+ session["last_activity"] = event.get(
440
+ "timestamp", asyncio.get_event_loop().time()
441
+ )
442
+
443
+ async def _replay_events(self, sid: str, events: List[Dict], speed: float):
444
+ """Replay events to a specific client.
445
+
446
+ Args:
447
+ sid: Socket.IO session ID
448
+ events: Events to replay
449
+ speed: Replay speed multiplier
450
+ """
451
+ try:
452
+ await self.sio.emit(
453
+ "hook:replay:started",
454
+ {"event_count": len(events), "speed": speed},
455
+ room=sid,
456
+ )
457
+
458
+ for i, event in enumerate(events):
459
+ # Calculate delay based on speed
460
+ if i > 0:
461
+ time_diff = event["timestamp"] - events[i - 1]["timestamp"]
462
+ delay = max(0.1, time_diff / speed) # Minimum 0.1s delay
463
+ await asyncio.sleep(delay)
464
+
465
+ # Emit replay event
466
+ await self.sio.emit(
467
+ "hook:replay:event",
468
+ {"index": i, "total": len(events), "event": event},
469
+ room=sid,
470
+ )
471
+
472
+ await self.sio.emit(
473
+ "hook:replay:completed", {"event_count": len(events)}, room=sid
474
+ )
475
+
476
+ except Exception as e:
477
+ self.logger.error(f"Error during event replay: {e}")
478
+ await self.sio.emit(
479
+ "hook:error", {"error": f"Replay error: {e!s}"}, room=sid
480
+ )
481
+
482
+ async def _cleanup_session(self, session_id: str, delay: int = 300):
483
+ """Cleanup session after delay.
484
+
485
+ Args:
486
+ session_id: Session ID to cleanup
487
+ delay: Delay in seconds before cleanup
488
+ """
489
+ await asyncio.sleep(delay)
490
+ self.active_sessions.pop(session_id, None)
491
+ self.logger.debug(f"Cleaned up session: {session_id}")
@@ -0,0 +1,18 @@
1
+ """
2
+ Daemon Management for Unified Monitor
3
+ =====================================
4
+
5
+ WHY: These modules provide daemon lifecycle management, health monitoring,
6
+ and process supervision for the unified monitor daemon.
7
+
8
+ DESIGN DECISIONS:
9
+ - Proper daemon process management with PID files
10
+ - Health monitoring and auto-restart capabilities
11
+ - Graceful shutdown and cleanup
12
+ - Production-ready daemon operation
13
+ """
14
+
15
+ from .health import HealthMonitor
16
+ from .lifecycle import DaemonLifecycle
17
+
18
+ __all__ = ["DaemonLifecycle", "HealthMonitor"]
@@ -0,0 +1,124 @@
1
+ """
2
+ Health Monitoring for Unified Monitor
3
+ =====================================
4
+
5
+ WHY: This module provides health monitoring for the unified monitor daemon.
6
+ It tracks system resources and service health to ensure stability.
7
+
8
+ DESIGN DECISIONS:
9
+ - Simple health monitoring without external dependencies
10
+ - Basic resource tracking and service health checks
11
+ - Configurable thresholds for alerts
12
+ """
13
+
14
+ import threading
15
+ import time
16
+ from typing import Dict
17
+
18
+ from ....core.logging_config import get_logger
19
+
20
+
21
+ class HealthMonitor:
22
+ """Health monitoring system for the unified monitor daemon."""
23
+
24
+ def __init__(self, port: int = 8765):
25
+ """Initialize health monitor.
26
+
27
+ Args:
28
+ port: Port to monitor for service health
29
+ """
30
+ self.port = port
31
+ self.logger = get_logger(__name__)
32
+
33
+ # Monitoring state
34
+ self.running = False
35
+ self.monitor_thread = None
36
+
37
+ # Health metrics
38
+ self.metrics = {
39
+ "service_responsive": True,
40
+ "last_check": time.time(),
41
+ "uptime": 0.0,
42
+ "error_count": 0,
43
+ }
44
+
45
+ def start(self):
46
+ """Start health monitoring."""
47
+ try:
48
+ if self.running:
49
+ self.logger.warning("Health monitor already running")
50
+ return
51
+
52
+ self.running = True
53
+ self.monitor_thread = threading.Thread(
54
+ target=self._monitor_loop, daemon=True
55
+ )
56
+ self.monitor_thread.start()
57
+
58
+ self.logger.info("Health monitor started")
59
+
60
+ except Exception as e:
61
+ self.logger.error(f"Error starting health monitor: {e}")
62
+ self.running = False
63
+
64
+ def stop(self):
65
+ """Stop health monitoring."""
66
+ try:
67
+ self.running = False
68
+
69
+ if self.monitor_thread and self.monitor_thread.is_alive():
70
+ self.monitor_thread.join(timeout=5)
71
+
72
+ self.logger.info("Health monitor stopped")
73
+
74
+ except Exception as e:
75
+ self.logger.error(f"Error stopping health monitor: {e}")
76
+
77
+ def _monitor_loop(self):
78
+ """Main monitoring loop."""
79
+ start_time = time.time()
80
+
81
+ while self.running:
82
+ try:
83
+ # Update metrics
84
+ self.metrics["uptime"] = time.time() - start_time
85
+ self.metrics["last_check"] = time.time()
86
+ self.metrics["service_responsive"] = self._check_service_health()
87
+
88
+ # Sleep before next check
89
+ time.sleep(30) # Check every 30 seconds
90
+
91
+ except Exception as e:
92
+ self.logger.error(f"Error in health monitoring loop: {e}")
93
+ self.metrics["error_count"] += 1
94
+ time.sleep(10) # Shorter sleep on error
95
+
96
+ def _check_service_health(self) -> bool:
97
+ """Check if the service is responsive."""
98
+ try:
99
+ import socket
100
+
101
+ # Try to connect to the service port
102
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
103
+ sock.settimeout(5) # 5 second timeout
104
+ result = sock.connect_ex(("localhost", self.port))
105
+ sock.close()
106
+
107
+ return result == 0
108
+
109
+ except Exception as e:
110
+ self.logger.debug(f"Service health check failed: {e}")
111
+ return False
112
+
113
+ def get_status(self) -> Dict:
114
+ """Get current health status."""
115
+ try:
116
+ return {
117
+ "monitoring": self.running,
118
+ "healthy": self.metrics["service_responsive"],
119
+ "metrics": self.metrics.copy(),
120
+ }
121
+
122
+ except Exception as e:
123
+ self.logger.error(f"Error getting health status: {e}")
124
+ return {"monitoring": False, "healthy": False, "error": str(e)}