claude-mpm 4.2.26__py3-none-any.whl → 4.2.27__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/services/cli/unified_dashboard_manager.py +111 -5
- claude_mpm/services/monitor/daemon.py +9 -2
- {claude_mpm-4.2.26.dist-info → claude_mpm-4.2.27.dist-info}/METADATA +1 -1
- {claude_mpm-4.2.26.dist-info → claude_mpm-4.2.27.dist-info}/RECORD +9 -9
- {claude_mpm-4.2.26.dist-info → claude_mpm-4.2.27.dist-info}/WHEEL +0 -0
- {claude_mpm-4.2.26.dist-info → claude_mpm-4.2.27.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.2.26.dist-info → claude_mpm-4.2.27.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.2.26.dist-info → claude_mpm-4.2.27.dist-info}/top_level.txt +0 -0
claude_mpm/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
4.2.
|
1
|
+
4.2.27
|
@@ -115,18 +115,46 @@ class UnifiedDashboardManager(IUnifiedDashboardManager):
|
|
115
115
|
self.logger.info(
|
116
116
|
f"Force restarting our dashboard on port {port} (PID: {pid})"
|
117
117
|
)
|
118
|
+
# Clean up the existing service before restart
|
119
|
+
self._cleanup_port_conflicts(port)
|
118
120
|
elif self.is_dashboard_running(port) and not force_restart:
|
119
|
-
# Different service is using the port
|
120
|
-
self.logger.warning(f"Port {port} is in use by a different service")
|
121
|
-
|
121
|
+
# Different service is using the port - try to clean it up
|
122
|
+
self.logger.warning(f"Port {port} is in use by a different service, attempting cleanup")
|
123
|
+
self._cleanup_port_conflicts(port)
|
124
|
+
# Brief pause to ensure cleanup is complete
|
125
|
+
import time
|
126
|
+
time.sleep(1)
|
122
127
|
|
123
128
|
self.logger.info(
|
124
129
|
f"Starting unified dashboard on port {port} (background: {background}, force_restart: {force_restart})"
|
125
130
|
)
|
126
131
|
|
127
132
|
if background:
|
128
|
-
#
|
129
|
-
|
133
|
+
# Try to start daemon with retry on port conflicts
|
134
|
+
max_retries = 3
|
135
|
+
retry_count = 0
|
136
|
+
success = False
|
137
|
+
|
138
|
+
while retry_count < max_retries and not success:
|
139
|
+
if retry_count > 0:
|
140
|
+
self.logger.info(f"Retry {retry_count}/{max_retries}: Cleaning up port {port}")
|
141
|
+
self._cleanup_port_conflicts(port)
|
142
|
+
time.sleep(2) # Wait for cleanup to complete
|
143
|
+
|
144
|
+
# Start daemon in background mode with force restart if needed
|
145
|
+
success = daemon.start(force_restart=force_restart or retry_count > 0)
|
146
|
+
|
147
|
+
if not success and retry_count < max_retries - 1:
|
148
|
+
# Check if it's a port conflict
|
149
|
+
if not self.port_manager.is_port_available(port):
|
150
|
+
self.logger.warning(f"Port {port} still in use, will retry cleanup")
|
151
|
+
retry_count += 1
|
152
|
+
else:
|
153
|
+
# Different kind of failure, don't retry
|
154
|
+
break
|
155
|
+
else:
|
156
|
+
break
|
157
|
+
|
130
158
|
if success:
|
131
159
|
with self._lock:
|
132
160
|
self._background_daemons[port] = daemon
|
@@ -311,6 +339,84 @@ class UnifiedDashboardManager(IUnifiedDashboardManager):
|
|
311
339
|
"""
|
312
340
|
return self.port_manager.find_available_port(preferred_port)
|
313
341
|
|
342
|
+
def _cleanup_port_conflicts(self, port: int) -> bool:
|
343
|
+
"""
|
344
|
+
Try to clean up any processes using our port.
|
345
|
+
|
346
|
+
Args:
|
347
|
+
port: Port to clean up
|
348
|
+
|
349
|
+
Returns:
|
350
|
+
True if cleanup was successful or not needed
|
351
|
+
"""
|
352
|
+
try:
|
353
|
+
import subprocess
|
354
|
+
import signal
|
355
|
+
import time
|
356
|
+
|
357
|
+
# Find processes using the port
|
358
|
+
result = subprocess.run(
|
359
|
+
["lsof", "-ti", f":{port}"],
|
360
|
+
capture_output=True,
|
361
|
+
text=True
|
362
|
+
)
|
363
|
+
|
364
|
+
if result.returncode == 0 and result.stdout.strip():
|
365
|
+
pids = result.stdout.strip().split('\n')
|
366
|
+
self.logger.info(f"Found processes using port {port}: {pids}")
|
367
|
+
|
368
|
+
for pid_str in pids:
|
369
|
+
try:
|
370
|
+
pid = int(pid_str.strip())
|
371
|
+
# Try graceful termination first
|
372
|
+
import os
|
373
|
+
os.kill(pid, signal.SIGTERM)
|
374
|
+
self.logger.info(f"Sent SIGTERM to process {pid}")
|
375
|
+
except (ValueError, ProcessLookupError) as e:
|
376
|
+
self.logger.debug(f"Could not terminate process {pid_str}: {e}")
|
377
|
+
continue
|
378
|
+
|
379
|
+
# Give processes time to shut down gracefully
|
380
|
+
time.sleep(3)
|
381
|
+
|
382
|
+
# Check if port is still in use and force kill if needed
|
383
|
+
result = subprocess.run(
|
384
|
+
["lsof", "-ti", f":{port}"],
|
385
|
+
capture_output=True,
|
386
|
+
text=True
|
387
|
+
)
|
388
|
+
|
389
|
+
if result.returncode == 0 and result.stdout.strip():
|
390
|
+
remaining_pids = result.stdout.strip().split('\n')
|
391
|
+
self.logger.warning(f"Processes still using port {port}: {remaining_pids}, force killing")
|
392
|
+
|
393
|
+
for pid_str in remaining_pids:
|
394
|
+
try:
|
395
|
+
pid = int(pid_str.strip())
|
396
|
+
os.kill(pid, signal.SIGKILL)
|
397
|
+
self.logger.info(f"Force killed process {pid}")
|
398
|
+
except (ValueError, ProcessLookupError) as e:
|
399
|
+
self.logger.debug(f"Could not force kill process {pid_str}: {e}")
|
400
|
+
continue
|
401
|
+
|
402
|
+
# Brief pause after force kill to ensure port is released
|
403
|
+
time.sleep(2)
|
404
|
+
|
405
|
+
self.logger.info(f"Successfully cleaned up processes on port {port}")
|
406
|
+
return True
|
407
|
+
else:
|
408
|
+
self.logger.debug(f"No processes found using port {port}")
|
409
|
+
return True
|
410
|
+
|
411
|
+
except FileNotFoundError:
|
412
|
+
# lsof not available, try alternative approach
|
413
|
+
self.logger.debug("lsof not available, skipping port cleanup")
|
414
|
+
return True
|
415
|
+
except Exception as e:
|
416
|
+
self.logger.warning(f"Error during port cleanup: {e}")
|
417
|
+
# Continue anyway - the port check will catch actual conflicts
|
418
|
+
return True
|
419
|
+
|
314
420
|
def start_server(
|
315
421
|
self, port: Optional[int] = None, timeout: int = 30, force_restart: bool = True
|
316
422
|
) -> Tuple[bool, DashboardInfo]:
|
@@ -263,8 +263,8 @@ class UnifiedMonitorDaemon:
|
|
263
263
|
cleaned = self._cleanup_port_conflicts()
|
264
264
|
|
265
265
|
if cleaned:
|
266
|
-
# Wait
|
267
|
-
time.sleep(
|
266
|
+
# Wait longer for port to be released to avoid race conditions
|
267
|
+
time.sleep(3)
|
268
268
|
# Check again
|
269
269
|
port_available, error_msg = self.lifecycle.verify_port_available(self.host)
|
270
270
|
|
@@ -302,6 +302,13 @@ class UnifiedMonitorDaemon:
|
|
302
302
|
force_restart: If True, restart existing service if it's ours
|
303
303
|
"""
|
304
304
|
self.logger.info(f"Starting unified monitor daemon on {self.host}:{self.port}")
|
305
|
+
|
306
|
+
# Clean up any processes on the port before checking service status
|
307
|
+
# This helps with race conditions where old processes haven't fully released the port
|
308
|
+
if force_restart:
|
309
|
+
self.logger.info("Force restart requested, cleaning up port conflicts...")
|
310
|
+
self._cleanup_port_conflicts()
|
311
|
+
time.sleep(1) # Brief pause to ensure port is released
|
305
312
|
|
306
313
|
# Check if already running (check PID file even in foreground mode)
|
307
314
|
if self.lifecycle.is_running():
|
@@ -1,5 +1,5 @@
|
|
1
1
|
claude_mpm/BUILD_NUMBER,sha256=toytnNjkIKPgQaGwDqQdC1rpNTAdSEc6Vja50d7Ovug,4
|
2
|
-
claude_mpm/VERSION,sha256=
|
2
|
+
claude_mpm/VERSION,sha256=lg0--TYI2YjVY4MKeldwdDKp-mvjjCP5p4dJWnMfDLw,7
|
3
3
|
claude_mpm/__init__.py,sha256=lyTZAYGH4DTaFGLRNWJKk5Q5oTjzN5I6AXmfVX-Jff0,1512
|
4
4
|
claude_mpm/__main__.py,sha256=Ro5UBWBoQaSAIoSqWAr7zkbLyvi4sSy28WShqAhKJG0,723
|
5
5
|
claude_mpm/constants.py,sha256=I946iCQzIIPRZVVJ8aO7lA4euiyDnNw2IX7EelAOkIE,5915
|
@@ -434,7 +434,7 @@ claude_mpm/services/cli/memory_crud_service.py,sha256=ciN9Pl_12iDAqF9zPBWOzu-iXi
|
|
434
434
|
claude_mpm/services/cli/memory_output_formatter.py,sha256=nbf7VsjGvH4e9fLv9c7PzjuO9COZhbK5P2fNZ79055w,24783
|
435
435
|
claude_mpm/services/cli/session_manager.py,sha256=rla_Stbcvt93wa9G9MCMu9UqB3FLGqlPt_eN5lQb3Gg,16599
|
436
436
|
claude_mpm/services/cli/startup_checker.py,sha256=efhuvu8ns5G16jcQ0nQZKVddmD2AktUEdlvjNcXjAuk,12232
|
437
|
-
claude_mpm/services/cli/unified_dashboard_manager.py,sha256=
|
437
|
+
claude_mpm/services/cli/unified_dashboard_manager.py,sha256=OqHMovIDnMJXa4Ys70uULB37kgaoMWhoAiDvAEKHA7U,17374
|
438
438
|
claude_mpm/services/communication/__init__.py,sha256=b4qc7_Rqy4DE9q7BAUlfUZjoYG4uimAyUnE0irPcXyU,560
|
439
439
|
claude_mpm/services/core/__init__.py,sha256=evEayLlBqJvxMZhrhuK6aagXmNrKGSj8Jm9OOxKzqvU,2195
|
440
440
|
claude_mpm/services/core/base.py,sha256=iA-F7DgGp-FJIMvQTiHQ68RkG_k-AtUWlArJPMw6ZPk,7297
|
@@ -550,7 +550,7 @@ claude_mpm/services/memory/cache/__init__.py,sha256=6M6-P8ParyxX8vOgp_IxHgLMvacr
|
|
550
550
|
claude_mpm/services/memory/cache/shared_prompt_cache.py,sha256=crnYPUT8zcS7TvoE1vW7pyaf4T77N5rJ1wUf_YQ2vvo,28704
|
551
551
|
claude_mpm/services/memory/cache/simple_cache.py,sha256=qsTjbcsPxj-kNfaod9VN_uE5NioIwpfkUin_mMVUJCg,10218
|
552
552
|
claude_mpm/services/monitor/__init__.py,sha256=X7gxSLUm9Fg_zEsX6LtCHP2ipF0qj6Emkun20h2So7g,745
|
553
|
-
claude_mpm/services/monitor/daemon.py,sha256=
|
553
|
+
claude_mpm/services/monitor/daemon.py,sha256=LGKn9LG2RKL4Of6c3DWZSxRFWqJJRUZFnxT4ochTPjc,28157
|
554
554
|
claude_mpm/services/monitor/event_emitter.py,sha256=JzRLNg8PUJ5s3ulNnq_D4yqCPItvidJzu8DmFxriieQ,12224
|
555
555
|
claude_mpm/services/monitor/server.py,sha256=m98Eyv9caxRywJ4JtAdOuv5EB__z7vd2hYRZPwcqFLg,28498
|
556
556
|
claude_mpm/services/monitor/handlers/__init__.py,sha256=jgPIf4IJVERm_tAeD9834tfx9IcxtlHj5r9rhEWpkfM,701
|
@@ -641,9 +641,9 @@ claude_mpm/utils/subprocess_utils.py,sha256=zgiwLqh_17WxHpySvUPH65pb4bzIeUGOAYUJ
|
|
641
641
|
claude_mpm/validation/__init__.py,sha256=YZhwE3mhit-lslvRLuwfX82xJ_k4haZeKmh4IWaVwtk,156
|
642
642
|
claude_mpm/validation/agent_validator.py,sha256=3Lo6LK-Mw9IdnL_bd3zl_R6FkgSVDYKUUM7EeVVD3jc,20865
|
643
643
|
claude_mpm/validation/frontmatter_validator.py,sha256=u8g4Eyd_9O6ugj7Un47oSGh3kqv4wMkuks2i_CtWRvM,7028
|
644
|
-
claude_mpm-4.2.
|
645
|
-
claude_mpm-4.2.
|
646
|
-
claude_mpm-4.2.
|
647
|
-
claude_mpm-4.2.
|
648
|
-
claude_mpm-4.2.
|
649
|
-
claude_mpm-4.2.
|
644
|
+
claude_mpm-4.2.27.dist-info/licenses/LICENSE,sha256=lpaivOlPuBZW1ds05uQLJJswy8Rp_HMNieJEbFlqvLk,1072
|
645
|
+
claude_mpm-4.2.27.dist-info/METADATA,sha256=TdXXStRCBxj-tjx9H9gISHrS9luzBtF7Ccj1If2HpT8,14451
|
646
|
+
claude_mpm-4.2.27.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
647
|
+
claude_mpm-4.2.27.dist-info/entry_points.txt,sha256=FDPZgz8JOvD-6iuXY2l9Zbo9zYVRuE4uz4Qr0vLeGOk,471
|
648
|
+
claude_mpm-4.2.27.dist-info/top_level.txt,sha256=1nUg3FEaBySgm8t-s54jK5zoPnu3_eY6EP6IOlekyHA,11
|
649
|
+
claude_mpm-4.2.27.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|