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.
- claude_mpm/VERSION +1 -1
- claude_mpm/cli/commands/dashboard.py +59 -126
- claude_mpm/cli/commands/monitor.py +71 -212
- claude_mpm/cli/commands/run.py +33 -33
- claude_mpm/dashboard/static/css/code-tree.css +8 -16
- claude_mpm/dashboard/static/dist/components/code-tree.js +1 -1
- claude_mpm/dashboard/static/dist/components/file-viewer.js +2 -0
- claude_mpm/dashboard/static/dist/components/module-viewer.js +1 -1
- claude_mpm/dashboard/static/dist/components/unified-data-viewer.js +1 -1
- claude_mpm/dashboard/static/dist/dashboard.js +1 -1
- claude_mpm/dashboard/static/dist/socket-client.js +1 -1
- claude_mpm/dashboard/static/js/components/code-tree.js +692 -114
- claude_mpm/dashboard/static/js/components/file-viewer.js +538 -0
- claude_mpm/dashboard/static/js/components/module-viewer.js +26 -0
- claude_mpm/dashboard/static/js/components/unified-data-viewer.js +166 -14
- claude_mpm/dashboard/static/js/dashboard.js +108 -91
- claude_mpm/dashboard/static/js/socket-client.js +9 -7
- claude_mpm/dashboard/templates/index.html +2 -7
- claude_mpm/hooks/claude_hooks/hook_handler.py +1 -11
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +54 -59
- claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +112 -72
- claude_mpm/services/agents/deployment/agent_template_builder.py +0 -1
- claude_mpm/services/cli/unified_dashboard_manager.py +354 -0
- claude_mpm/services/monitor/__init__.py +20 -0
- claude_mpm/services/monitor/daemon.py +256 -0
- claude_mpm/services/monitor/event_emitter.py +279 -0
- claude_mpm/services/monitor/handlers/__init__.py +20 -0
- claude_mpm/services/monitor/handlers/code_analysis.py +334 -0
- claude_mpm/services/monitor/handlers/dashboard.py +298 -0
- claude_mpm/services/monitor/handlers/hooks.py +491 -0
- claude_mpm/services/monitor/management/__init__.py +18 -0
- claude_mpm/services/monitor/management/health.py +124 -0
- claude_mpm/services/monitor/management/lifecycle.py +298 -0
- claude_mpm/services/monitor/server.py +442 -0
- claude_mpm/tools/code_tree_analyzer.py +33 -17
- {claude_mpm-4.2.9.dist-info → claude_mpm-4.2.11.dist-info}/METADATA +1 -1
- {claude_mpm-4.2.9.dist-info → claude_mpm-4.2.11.dist-info}/RECORD +41 -36
- claude_mpm/cli/commands/socketio_monitor.py +0 -233
- claude_mpm/scripts/socketio_daemon.py +0 -571
- claude_mpm/scripts/socketio_daemon_hardened.py +0 -937
- claude_mpm/scripts/socketio_daemon_wrapper.py +0 -78
- claude_mpm/scripts/socketio_server_manager.py +0 -349
- claude_mpm/services/cli/dashboard_launcher.py +0 -423
- claude_mpm/services/cli/socketio_manager.py +0 -595
- claude_mpm/services/dashboard/stable_server.py +0 -1020
- claude_mpm/services/socketio/monitor_server.py +0 -505
- {claude_mpm-4.2.9.dist-info → claude_mpm-4.2.11.dist-info}/WHEEL +0 -0
- {claude_mpm-4.2.9.dist-info → claude_mpm-4.2.11.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.2.9.dist-info → claude_mpm-4.2.11.dist-info}/licenses/LICENSE +0 -0
- {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)
|