claude-mpm 4.1.2__py3-none-any.whl → 4.1.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.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/templates/engineer.json +33 -11
- claude_mpm/cli/commands/agents.py +556 -1009
- claude_mpm/cli/commands/memory.py +248 -927
- claude_mpm/cli/commands/run.py +139 -484
- claude_mpm/cli/startup_logging.py +76 -0
- claude_mpm/core/agent_registry.py +6 -10
- claude_mpm/core/framework_loader.py +114 -595
- claude_mpm/core/logging_config.py +2 -4
- claude_mpm/hooks/claude_hooks/event_handlers.py +7 -117
- claude_mpm/hooks/claude_hooks/hook_handler.py +91 -755
- claude_mpm/hooks/claude_hooks/hook_handler_original.py +1040 -0
- claude_mpm/hooks/claude_hooks/hook_handler_refactored.py +347 -0
- claude_mpm/hooks/claude_hooks/services/__init__.py +13 -0
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +190 -0
- claude_mpm/hooks/claude_hooks/services/duplicate_detector.py +106 -0
- claude_mpm/hooks/claude_hooks/services/state_manager.py +282 -0
- claude_mpm/hooks/claude_hooks/services/subagent_processor.py +374 -0
- claude_mpm/services/agents/deployment/agent_deployment.py +42 -454
- claude_mpm/services/agents/deployment/base_agent_locator.py +132 -0
- claude_mpm/services/agents/deployment/deployment_results_manager.py +185 -0
- claude_mpm/services/agents/deployment/single_agent_deployer.py +315 -0
- claude_mpm/services/agents/memory/agent_memory_manager.py +42 -508
- claude_mpm/services/agents/memory/memory_categorization_service.py +165 -0
- claude_mpm/services/agents/memory/memory_file_service.py +103 -0
- claude_mpm/services/agents/memory/memory_format_service.py +201 -0
- claude_mpm/services/agents/memory/memory_limits_service.py +99 -0
- claude_mpm/services/agents/registry/__init__.py +1 -1
- claude_mpm/services/cli/__init__.py +18 -0
- claude_mpm/services/cli/agent_cleanup_service.py +407 -0
- claude_mpm/services/cli/agent_dependency_service.py +395 -0
- claude_mpm/services/cli/agent_listing_service.py +463 -0
- claude_mpm/services/cli/agent_output_formatter.py +605 -0
- claude_mpm/services/cli/agent_validation_service.py +589 -0
- claude_mpm/services/cli/dashboard_launcher.py +424 -0
- claude_mpm/services/cli/memory_crud_service.py +617 -0
- claude_mpm/services/cli/memory_output_formatter.py +604 -0
- claude_mpm/services/cli/session_manager.py +513 -0
- claude_mpm/services/cli/socketio_manager.py +498 -0
- claude_mpm/services/cli/startup_checker.py +370 -0
- claude_mpm/services/core/cache_manager.py +311 -0
- claude_mpm/services/core/memory_manager.py +637 -0
- claude_mpm/services/core/path_resolver.py +498 -0
- claude_mpm/services/core/service_container.py +520 -0
- claude_mpm/services/core/service_interfaces.py +436 -0
- claude_mpm/services/diagnostics/checks/agent_check.py +65 -19
- {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.3.dist-info}/METADATA +1 -1
- {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.3.dist-info}/RECORD +52 -22
- claude_mpm/cli/commands/run_config_checker.py +0 -159
- {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.3.dist-info}/WHEEL +0 -0
- {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.3.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.3.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.3.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,498 @@
|
|
|
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 ensure_dependencies(self) -> Tuple[bool, Optional[str]]:
|
|
129
|
+
"""
|
|
130
|
+
Ensure Socket.IO dependencies are installed.
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
Tuple of (success, error_message)
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
# Implementation
|
|
138
|
+
class SocketIOManager(ISocketIOManager):
|
|
139
|
+
"""Socket.IO server manager implementation."""
|
|
140
|
+
|
|
141
|
+
def __init__(self, logger=None):
|
|
142
|
+
"""
|
|
143
|
+
Initialize the Socket.IO manager.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
logger: Optional logger instance
|
|
147
|
+
"""
|
|
148
|
+
self.logger = logger or get_logger("SocketIOManager")
|
|
149
|
+
self.port_manager = PortManager()
|
|
150
|
+
self._server_processes = {} # port -> subprocess.Popen
|
|
151
|
+
self._lock = threading.Lock()
|
|
152
|
+
|
|
153
|
+
def start_server(
|
|
154
|
+
self, port: Optional[int] = None, timeout: int = 30
|
|
155
|
+
) -> Tuple[bool, ServerInfo]:
|
|
156
|
+
"""
|
|
157
|
+
Start the Socket.IO server.
|
|
158
|
+
|
|
159
|
+
WHY: Provides a unified way to start Socket.IO servers with proper error handling,
|
|
160
|
+
port management, and process lifecycle control.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
port: Optional specific port to use. If None, finds available port.
|
|
164
|
+
timeout: Maximum time to wait for server startup
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
Tuple of (success, ServerInfo)
|
|
168
|
+
"""
|
|
169
|
+
with self._lock:
|
|
170
|
+
# Determine port to use
|
|
171
|
+
target_port = port if port else self.find_available_port()
|
|
172
|
+
|
|
173
|
+
# Check if server already running on this port
|
|
174
|
+
if self.is_server_running(target_port):
|
|
175
|
+
self.logger.info(
|
|
176
|
+
f"Socket.IO server already running on port {target_port}"
|
|
177
|
+
)
|
|
178
|
+
return True, self.get_server_info(target_port)
|
|
179
|
+
|
|
180
|
+
# Ensure dependencies are available
|
|
181
|
+
deps_ok, error_msg = self.ensure_dependencies()
|
|
182
|
+
if not deps_ok:
|
|
183
|
+
self.logger.error(f"Socket.IO dependencies not available: {error_msg}")
|
|
184
|
+
return False, ServerInfo(
|
|
185
|
+
port=target_port,
|
|
186
|
+
pid=None,
|
|
187
|
+
is_running=False,
|
|
188
|
+
launch_time=None,
|
|
189
|
+
url=f"http://localhost:{target_port}",
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
# Start the server
|
|
193
|
+
try:
|
|
194
|
+
self.logger.info(f"Starting Socket.IO server on port {target_port}")
|
|
195
|
+
|
|
196
|
+
# Get the socketio daemon script path
|
|
197
|
+
scripts_dir = get_scripts_dir()
|
|
198
|
+
daemon_script = scripts_dir / "socketio_daemon_wrapper.py"
|
|
199
|
+
|
|
200
|
+
if not daemon_script.exists():
|
|
201
|
+
self.logger.error(
|
|
202
|
+
f"Socket.IO daemon script not found: {daemon_script}"
|
|
203
|
+
)
|
|
204
|
+
return False, ServerInfo(
|
|
205
|
+
port=target_port,
|
|
206
|
+
pid=None,
|
|
207
|
+
is_running=False,
|
|
208
|
+
launch_time=None,
|
|
209
|
+
url=f"http://localhost:{target_port}",
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
# Start the server process
|
|
213
|
+
env = os.environ.copy()
|
|
214
|
+
env["PYTHONPATH"] = str(get_package_root())
|
|
215
|
+
|
|
216
|
+
process = subprocess.Popen(
|
|
217
|
+
[sys.executable, str(daemon_script), "--port", str(target_port)],
|
|
218
|
+
env=env,
|
|
219
|
+
stdout=subprocess.PIPE,
|
|
220
|
+
stderr=subprocess.PIPE,
|
|
221
|
+
start_new_session=True, # Detach from parent process group
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
# Store process reference
|
|
225
|
+
self._server_processes[target_port] = process
|
|
226
|
+
|
|
227
|
+
# Wait for server to be ready
|
|
228
|
+
if self.wait_for_server(target_port, timeout):
|
|
229
|
+
self.logger.info(
|
|
230
|
+
f"Socket.IO server started successfully on port {target_port}"
|
|
231
|
+
)
|
|
232
|
+
return True, self.get_server_info(target_port)
|
|
233
|
+
self.logger.error(
|
|
234
|
+
f"Socket.IO server failed to start within {timeout} seconds"
|
|
235
|
+
)
|
|
236
|
+
# Clean up failed process
|
|
237
|
+
self._cleanup_process(target_port)
|
|
238
|
+
return False, ServerInfo(
|
|
239
|
+
port=target_port,
|
|
240
|
+
pid=None,
|
|
241
|
+
is_running=False,
|
|
242
|
+
launch_time=None,
|
|
243
|
+
url=f"http://localhost:{target_port}",
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
except Exception as e:
|
|
247
|
+
self.logger.error(f"Failed to start Socket.IO server: {e}")
|
|
248
|
+
return False, ServerInfo(
|
|
249
|
+
port=target_port,
|
|
250
|
+
pid=None,
|
|
251
|
+
is_running=False,
|
|
252
|
+
launch_time=None,
|
|
253
|
+
url=f"http://localhost:{target_port}",
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
def stop_server(self, port: Optional[int] = None, timeout: int = 10) -> bool:
|
|
257
|
+
"""
|
|
258
|
+
Stop the Socket.IO server.
|
|
259
|
+
|
|
260
|
+
WHY: Provides graceful shutdown of Socket.IO servers with proper cleanup
|
|
261
|
+
and process termination.
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
port: Optional specific port to stop. If None, stops all servers.
|
|
265
|
+
timeout: Maximum time to wait for graceful shutdown
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
True if server was stopped successfully
|
|
269
|
+
"""
|
|
270
|
+
with self._lock:
|
|
271
|
+
if port:
|
|
272
|
+
return self._stop_server_on_port(port, timeout)
|
|
273
|
+
# Stop all managed servers
|
|
274
|
+
success = True
|
|
275
|
+
for server_port in list(self._server_processes.keys()):
|
|
276
|
+
if not self._stop_server_on_port(server_port, timeout):
|
|
277
|
+
success = False
|
|
278
|
+
return success
|
|
279
|
+
|
|
280
|
+
def _stop_server_on_port(self, port: int, timeout: int) -> bool:
|
|
281
|
+
"""Stop server on specific port."""
|
|
282
|
+
try:
|
|
283
|
+
# Check if we have a process reference
|
|
284
|
+
if port in self._server_processes:
|
|
285
|
+
process = self._server_processes[port]
|
|
286
|
+
|
|
287
|
+
# Send graceful shutdown signal
|
|
288
|
+
if process.poll() is None: # Process is still running
|
|
289
|
+
self.logger.info(f"Stopping Socket.IO server on port {port}")
|
|
290
|
+
process.terminate()
|
|
291
|
+
|
|
292
|
+
# Wait for graceful shutdown
|
|
293
|
+
try:
|
|
294
|
+
process.wait(timeout=timeout)
|
|
295
|
+
self.logger.info(
|
|
296
|
+
f"Socket.IO server on port {port} stopped gracefully"
|
|
297
|
+
)
|
|
298
|
+
except subprocess.TimeoutExpired:
|
|
299
|
+
# Force kill if graceful shutdown failed
|
|
300
|
+
self.logger.warning(
|
|
301
|
+
f"Force killing Socket.IO server on port {port}"
|
|
302
|
+
)
|
|
303
|
+
process.kill()
|
|
304
|
+
process.wait()
|
|
305
|
+
|
|
306
|
+
# Clean up process reference
|
|
307
|
+
del self._server_processes[port]
|
|
308
|
+
return True
|
|
309
|
+
|
|
310
|
+
# Try to stop server by port (external process)
|
|
311
|
+
process_info = self.port_manager.get_process_on_port(port)
|
|
312
|
+
if process_info and process_info.is_ours:
|
|
313
|
+
try:
|
|
314
|
+
os.kill(process_info.pid, signal.SIGTERM)
|
|
315
|
+
time.sleep(2) # Give it time to shut down
|
|
316
|
+
|
|
317
|
+
# Check if still running
|
|
318
|
+
try:
|
|
319
|
+
os.kill(process_info.pid, 0) # Check if process exists
|
|
320
|
+
# Still running, force kill
|
|
321
|
+
os.kill(process_info.pid, signal.SIGKILL)
|
|
322
|
+
except ProcessLookupError:
|
|
323
|
+
pass # Process already terminated
|
|
324
|
+
|
|
325
|
+
self.logger.info(
|
|
326
|
+
f"Stopped Socket.IO server (PID: {process_info.pid}) on port {port}"
|
|
327
|
+
)
|
|
328
|
+
return True
|
|
329
|
+
except Exception as e:
|
|
330
|
+
self.logger.error(f"Failed to stop server on port {port}: {e}")
|
|
331
|
+
return False
|
|
332
|
+
|
|
333
|
+
return True # No server to stop
|
|
334
|
+
|
|
335
|
+
except Exception as e:
|
|
336
|
+
self.logger.error(f"Error stopping server on port {port}: {e}")
|
|
337
|
+
return False
|
|
338
|
+
|
|
339
|
+
def _cleanup_process(self, port: int):
|
|
340
|
+
"""Clean up failed or terminated process."""
|
|
341
|
+
if port in self._server_processes:
|
|
342
|
+
process = self._server_processes[port]
|
|
343
|
+
try:
|
|
344
|
+
if process.poll() is None:
|
|
345
|
+
process.kill()
|
|
346
|
+
process.wait()
|
|
347
|
+
except:
|
|
348
|
+
pass
|
|
349
|
+
del self._server_processes[port]
|
|
350
|
+
|
|
351
|
+
def is_server_running(self, port: int) -> bool:
|
|
352
|
+
"""
|
|
353
|
+
Check if a Socket.IO server is running on the specified port.
|
|
354
|
+
|
|
355
|
+
WHY: Quick check to determine if a server is already running to avoid
|
|
356
|
+
conflicts and duplicates.
|
|
357
|
+
|
|
358
|
+
Args:
|
|
359
|
+
port: Port to check
|
|
360
|
+
|
|
361
|
+
Returns:
|
|
362
|
+
True if server is running on the port
|
|
363
|
+
"""
|
|
364
|
+
# First check our managed processes
|
|
365
|
+
if port in self._server_processes:
|
|
366
|
+
process = self._server_processes[port]
|
|
367
|
+
if process.poll() is None:
|
|
368
|
+
return True
|
|
369
|
+
# Process terminated, clean up
|
|
370
|
+
self._cleanup_process(port)
|
|
371
|
+
|
|
372
|
+
# Check if port is in use (by any process)
|
|
373
|
+
process_info = self.port_manager.get_process_on_port(port)
|
|
374
|
+
if process_info:
|
|
375
|
+
# Check if it's a Socket.IO server
|
|
376
|
+
return (
|
|
377
|
+
"socketio" in process_info.cmdline.lower()
|
|
378
|
+
or "socket-io" in process_info.cmdline.lower()
|
|
379
|
+
or process_info.is_daemon
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
return False
|
|
383
|
+
|
|
384
|
+
def get_server_info(self, port: int) -> ServerInfo:
|
|
385
|
+
"""
|
|
386
|
+
Get information about a server on the specified port.
|
|
387
|
+
|
|
388
|
+
Args:
|
|
389
|
+
port: Port to check
|
|
390
|
+
|
|
391
|
+
Returns:
|
|
392
|
+
ServerInfo object with server details
|
|
393
|
+
"""
|
|
394
|
+
is_running = self.is_server_running(port)
|
|
395
|
+
pid = None
|
|
396
|
+
launch_time = None
|
|
397
|
+
|
|
398
|
+
if is_running:
|
|
399
|
+
# Get process info
|
|
400
|
+
if port in self._server_processes:
|
|
401
|
+
process = self._server_processes[port]
|
|
402
|
+
pid = process.pid
|
|
403
|
+
launch_time = time.time() # Approximate
|
|
404
|
+
else:
|
|
405
|
+
process_info = self.port_manager.get_process_on_port(port)
|
|
406
|
+
if process_info:
|
|
407
|
+
pid = process_info.pid
|
|
408
|
+
|
|
409
|
+
return ServerInfo(
|
|
410
|
+
port=port,
|
|
411
|
+
pid=pid,
|
|
412
|
+
is_running=is_running,
|
|
413
|
+
launch_time=launch_time,
|
|
414
|
+
url=f"http://localhost:{port}",
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
def wait_for_server(self, port: int, timeout: int = 30) -> bool:
|
|
418
|
+
"""
|
|
419
|
+
Wait for a server to be ready on the specified port.
|
|
420
|
+
|
|
421
|
+
WHY: Ensures the server is fully started and ready to accept connections
|
|
422
|
+
before proceeding with other operations.
|
|
423
|
+
|
|
424
|
+
Args:
|
|
425
|
+
port: Port to wait for
|
|
426
|
+
timeout: Maximum time to wait in seconds
|
|
427
|
+
|
|
428
|
+
Returns:
|
|
429
|
+
True if server became ready within timeout
|
|
430
|
+
"""
|
|
431
|
+
start_time = time.time()
|
|
432
|
+
|
|
433
|
+
while time.time() - start_time < timeout:
|
|
434
|
+
# Try to connect to the server
|
|
435
|
+
try:
|
|
436
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
|
437
|
+
sock.settimeout(1)
|
|
438
|
+
result = sock.connect_ex(("localhost", port))
|
|
439
|
+
if result == 0:
|
|
440
|
+
self.logger.debug(f"Socket.IO server ready on port {port}")
|
|
441
|
+
return True
|
|
442
|
+
except Exception:
|
|
443
|
+
pass
|
|
444
|
+
|
|
445
|
+
time.sleep(0.5)
|
|
446
|
+
|
|
447
|
+
return False
|
|
448
|
+
|
|
449
|
+
def find_available_port(self, preferred_port: int = 8765) -> int:
|
|
450
|
+
"""
|
|
451
|
+
Find an available port for the Socket.IO server.
|
|
452
|
+
|
|
453
|
+
WHY: Automatically finds an available port to avoid conflicts when the
|
|
454
|
+
preferred port is already in use.
|
|
455
|
+
|
|
456
|
+
Args:
|
|
457
|
+
preferred_port: Preferred port to try first
|
|
458
|
+
|
|
459
|
+
Returns:
|
|
460
|
+
Available port number
|
|
461
|
+
"""
|
|
462
|
+
# Try preferred port first
|
|
463
|
+
if self.port_manager.is_port_available(preferred_port):
|
|
464
|
+
return preferred_port
|
|
465
|
+
|
|
466
|
+
# Find alternative port
|
|
467
|
+
available_port = self.port_manager.get_available_port(preferred_port)
|
|
468
|
+
self.logger.info(f"Port {preferred_port} unavailable, using {available_port}")
|
|
469
|
+
return available_port
|
|
470
|
+
|
|
471
|
+
def ensure_dependencies(self) -> Tuple[bool, Optional[str]]:
|
|
472
|
+
"""
|
|
473
|
+
Ensure Socket.IO dependencies are installed.
|
|
474
|
+
|
|
475
|
+
WHY: Verifies that required Socket.IO packages are available before
|
|
476
|
+
attempting to start the server.
|
|
477
|
+
|
|
478
|
+
Returns:
|
|
479
|
+
Tuple of (success, error_message)
|
|
480
|
+
"""
|
|
481
|
+
try:
|
|
482
|
+
# Use existing dependency manager
|
|
483
|
+
success, error_msg = ensure_socketio_dependencies(self.logger)
|
|
484
|
+
|
|
485
|
+
if not success:
|
|
486
|
+
# Provide helpful error message
|
|
487
|
+
if error_msg:
|
|
488
|
+
return False, error_msg
|
|
489
|
+
return False, (
|
|
490
|
+
"Socket.IO dependencies not installed. "
|
|
491
|
+
"Install with: pip install python-socketio aiohttp python-engineio "
|
|
492
|
+
"or pip install claude-mpm[monitor]"
|
|
493
|
+
)
|
|
494
|
+
|
|
495
|
+
return True, None
|
|
496
|
+
|
|
497
|
+
except Exception as e:
|
|
498
|
+
return False, str(e)
|