claude-mpm 4.2.39__py3-none-any.whl → 4.2.40__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 CHANGED
@@ -1 +1 @@
1
- 4.2.39
1
+ 4.2.40
@@ -106,11 +106,9 @@ def main(argv: Optional[list] = None):
106
106
  # Check for MCP auto-configuration (pipx installations)
107
107
  _check_mcp_auto_configuration()
108
108
 
109
- # DISABLED: MCP pre-warming causes fork() issues with monitor daemon (v4.2.39)
110
- # The pre-warming creates background threads that interfere with the monitor's
111
- # daemonization process, causing race conditions and port binding errors.
112
- # Accepting the 11.9s delay on first MCP use is better than a broken monitor.
113
- # _verify_mcp_gateway_startup()
109
+ # Re-enabled: MCP pre-warming is safe with subprocess daemon (v4.2.40)
110
+ # The subprocess approach avoids fork() issues entirely
111
+ _verify_mcp_gateway_startup()
114
112
 
115
113
  # Set up logging
116
114
  # Special case: For MCP start command, we need minimal logging to avoid stdout interference
@@ -133,7 +133,7 @@ class UnifiedDashboardManager(IUnifiedDashboardManager):
133
133
  if background:
134
134
  # The daemon.start() method will handle cleanup when force_restart=True
135
135
  # We don't need pre-emptive cleanup here as it causes race conditions
136
-
136
+
137
137
  # Try to start daemon with retry on port conflicts
138
138
  max_retries = 3
139
139
  retry_count = 0
@@ -157,35 +157,67 @@ class UnifiedMonitorDaemon:
157
157
  )
158
158
  return False
159
159
 
160
- # Wait for any pre-warming threads to complete before forking
161
- self._wait_for_prewarm_completion()
162
-
163
- # Use daemon manager's daemonize which includes cleanup
164
- # DO NOT reset startup_status_file - it's needed for parent-child communication!
165
- # self.daemon_manager.startup_status_file = None # BUG: This breaks communication
166
- success = self.daemon_manager.daemonize()
167
- if not success:
168
- return False
160
+ # Use subprocess approach for clean daemon startup (v4.2.40)
161
+ # This avoids all fork() + threading issues by starting in a fresh process
162
+ # The daemon_manager.use_subprocess_daemon() now checks for CLAUDE_MPM_SUBPROCESS_DAEMON
163
+ # environment variable to prevent infinite recursion
164
+ if self.daemon_manager.use_subprocess_daemon():
165
+ # Start using subprocess - this returns immediately in parent
166
+ success = self.daemon_manager.start_daemon_subprocess()
167
+ return success
168
+
169
+ # Check if we're in subprocess mode (environment variable set)
170
+ if os.environ.get("CLAUDE_MPM_SUBPROCESS_DAEMON") == "1":
171
+ # We're in a subprocess started by start_daemon_subprocess
172
+ # We need to write the PID file ourselves since parent didn't
173
+ self.logger.info("Running in subprocess daemon mode, writing PID file")
174
+ self.daemon_manager.write_pid_file()
175
+
176
+ # Setup signal handlers for graceful shutdown
177
+ self._setup_signal_handlers()
178
+
179
+ # Start the server (this will run until shutdown)
180
+ try:
181
+ result = self._run_server()
182
+ if not result:
183
+ self.logger.error("Failed to start server in subprocess mode")
184
+ return result
185
+ except Exception as e:
186
+ self.logger.error(f"Server startup exception in subprocess: {e}")
187
+ raise
188
+ else:
189
+ # Legacy fork approach (kept for compatibility but not used by default)
190
+ # Wait for any pre-warming threads to complete before forking
191
+ self._wait_for_prewarm_completion()
192
+
193
+ # Use daemon manager's daemonize which includes cleanup
194
+ # DO NOT reset startup_status_file - it's needed for parent-child communication!
195
+ # self.daemon_manager.startup_status_file = None # BUG: This breaks communication
196
+ success = self.daemon_manager.daemonize()
197
+ if not success:
198
+ return False
169
199
 
170
- # We're now in the daemon process
171
- # Update our PID references and status file
172
- self.lifecycle.pid_file = self.daemon_manager.pid_file
173
- self.lifecycle.startup_status_file = self.daemon_manager.startup_status_file
200
+ # We're now in the daemon process
201
+ # Update our PID references and status file
202
+ self.lifecycle.pid_file = self.daemon_manager.pid_file
203
+ self.lifecycle.startup_status_file = self.daemon_manager.startup_status_file
174
204
 
175
- # Start the server in daemon mode
176
- try:
177
- result = self._run_server()
178
- if not result:
179
- # Report failure before exiting
180
- self.daemon_manager._report_startup_error("Failed to start server")
181
- else:
182
- # Report success
183
- self.daemon_manager._report_startup_success()
184
- return result
185
- except Exception as e:
186
- # Report any exceptions during startup
187
- self.daemon_manager._report_startup_error(f"Server startup exception: {e}")
188
- raise
205
+ # Start the server in daemon mode
206
+ try:
207
+ result = self._run_server()
208
+ if not result:
209
+ # Report failure before exiting
210
+ self.daemon_manager._report_startup_error("Failed to start server")
211
+ else:
212
+ # Report success
213
+ self.daemon_manager._report_startup_success()
214
+ return result
215
+ except Exception as e:
216
+ # Report any exceptions during startup
217
+ self.daemon_manager._report_startup_error(
218
+ f"Server startup exception: {e}"
219
+ )
220
+ raise
189
221
 
190
222
  def _start_foreground(self, force_restart: bool = False) -> bool:
191
223
  """Start in foreground mode.
@@ -602,29 +634,30 @@ class UnifiedMonitorDaemon:
602
634
  self.logger.info(
603
635
  f"Waiting for {len(active_threads)} background threads to complete before forking"
604
636
  )
605
-
637
+
606
638
  # List thread names for debugging
607
639
  thread_names = [t.name for t in active_threads]
608
640
  self.logger.debug(f"Active threads: {thread_names}")
609
641
 
610
642
  # Wait for threads to complete or timeout
611
643
  while time.time() - start_time < timeout:
612
- remaining_threads = [
613
- t for t in active_threads if t.is_alive()
614
- ]
644
+ remaining_threads = [t for t in active_threads if t.is_alive()]
615
645
  if not remaining_threads:
616
646
  self.logger.debug("All threads completed")
617
647
  break
618
-
648
+
619
649
  # Log remaining threads periodically
620
650
  if int(time.time() - start_time) % 1 == 0:
621
- self.logger.debug(f"{len(remaining_threads)} threads still active")
622
-
651
+ self.logger.debug(
652
+ f"{len(remaining_threads)} threads still active"
653
+ )
654
+
623
655
  time.sleep(0.1)
624
-
656
+
625
657
  # Final check
626
658
  final_threads = [
627
- t for t in threading.enumerate()
659
+ t
660
+ for t in threading.enumerate()
628
661
  if t.is_alive() and t != threading.current_thread()
629
662
  ]
630
663
  if final_threads:
@@ -149,7 +149,7 @@ class DaemonManager:
149
149
  # Try IPv4 first (most common)
150
150
  test_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
151
151
  test_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
152
-
152
+
153
153
  # Use 127.0.0.1 for localhost to match what the server does
154
154
  bind_host = "127.0.0.1" if self.host == "localhost" else self.host
155
155
  test_sock.bind((bind_host, self.port))
@@ -201,7 +201,10 @@ class DaemonManager:
201
201
  try:
202
202
  # Find processes using the port
203
203
  result = subprocess.run(
204
- ["lsof", "-ti", f":{self.port}"], capture_output=True, text=True, check=False
204
+ ["lsof", "-ti", f":{self.port}"],
205
+ capture_output=True,
206
+ text=True,
207
+ check=False,
205
208
  )
206
209
 
207
210
  if result.returncode != 0 or not result.stdout.strip():
@@ -220,35 +223,41 @@ class DaemonManager:
220
223
  process_info = subprocess.run(
221
224
  ["ps", "-p", str(pid), "-o", "comm="],
222
225
  capture_output=True,
223
- text=True, check=False,
226
+ text=True,
227
+ check=False,
224
228
  )
225
229
 
226
230
  # Get full command to check if it's our monitor process
227
231
  cmd_info = subprocess.run(
228
232
  ["ps", "-p", str(pid), "-o", "command="],
229
233
  capture_output=True,
230
- text=True, check=False,
234
+ text=True,
235
+ check=False,
231
236
  )
232
-
237
+
233
238
  if cmd_info.returncode != 0:
234
239
  continue
235
-
240
+
236
241
  full_command = cmd_info.stdout.strip().lower()
237
242
  process_name = process_info.stdout.strip().lower()
238
-
243
+
239
244
  # Check if this is our monitor/socketio process specifically
240
245
  # Look for monitor, socketio, dashboard, or our specific port
241
- is_monitor = any([
242
- "monitor" in full_command,
243
- "socketio" in full_command,
244
- "dashboard" in full_command,
245
- f"port={self.port}" in full_command,
246
- f":{self.port}" in full_command,
247
- "unified_monitor" in full_command,
248
- ])
249
-
246
+ is_monitor = any(
247
+ [
248
+ "monitor" in full_command,
249
+ "socketio" in full_command,
250
+ "dashboard" in full_command,
251
+ f"port={self.port}" in full_command,
252
+ f":{self.port}" in full_command,
253
+ "unified_monitor" in full_command,
254
+ ]
255
+ )
256
+
250
257
  if is_monitor and "python" in process_name:
251
- self.logger.info(f"Killing monitor process {pid}: {full_command[:100]}")
258
+ self.logger.info(
259
+ f"Killing monitor process {pid}: {full_command[:100]}"
260
+ )
252
261
  os.kill(pid, signal.SIGTERM)
253
262
 
254
263
  # Wait briefly for graceful shutdown
@@ -329,7 +338,7 @@ class DaemonManager:
329
338
 
330
339
  def _kill_claude_mpm_processes(self) -> bool:
331
340
  """Kill any claude-mpm monitor processes specifically.
332
-
341
+
333
342
  This targets monitor/dashboard/socketio processes only,
334
343
  NOT general Claude instances.
335
344
 
@@ -338,7 +347,9 @@ class DaemonManager:
338
347
  """
339
348
  try:
340
349
  # Look for monitor-specific processes
341
- result = subprocess.run(["ps", "aux"], capture_output=True, text=True, check=False)
350
+ result = subprocess.run(
351
+ ["ps", "aux"], capture_output=True, text=True, check=False
352
+ )
342
353
 
343
354
  if result.returncode != 0:
344
355
  return False
@@ -349,10 +360,14 @@ class DaemonManager:
349
360
  for line in lines:
350
361
  line_lower = line.lower()
351
362
  # Only target monitor/dashboard/socketio processes
352
- if any(["monitor" in line_lower and "claude" in line_lower,
363
+ if any(
364
+ [
365
+ "monitor" in line_lower and "claude" in line_lower,
353
366
  "dashboard" in line_lower and "claude" in line_lower,
354
367
  "socketio" in line_lower,
355
- f":{self.port}" in line_lower and "python" in line_lower]):
368
+ f":{self.port}" in line_lower and "python" in line_lower,
369
+ ]
370
+ ):
356
371
  parts = line.split()
357
372
  if len(parts) > 1:
358
373
  try:
@@ -395,7 +410,8 @@ class DaemonManager:
395
410
  process_info = subprocess.run(
396
411
  ["ps", "-p", str(pid), "-o", "comm="],
397
412
  capture_output=True,
398
- text=True, check=False,
413
+ text=True,
414
+ check=False,
399
415
  )
400
416
 
401
417
  if "python" in process_info.stdout.lower():
@@ -441,7 +457,10 @@ class DaemonManager:
441
457
  """
442
458
  try:
443
459
  result = subprocess.run(
444
- ["lsof", "-ti", f":{self.port}"], capture_output=True, text=True, check=False
460
+ ["lsof", "-ti", f":{self.port}"],
461
+ capture_output=True,
462
+ text=True,
463
+ check=False,
445
464
  )
446
465
 
447
466
  if result.returncode == 0 and result.stdout.strip():
@@ -485,9 +504,145 @@ class DaemonManager:
485
504
  self.logger.error(f"Cannot start daemon - port {self.port} is in use")
486
505
  return False
487
506
 
488
- # Daemonize the process
507
+ # Use subprocess for clean daemon startup (v4.2.40)
508
+ # This avoids fork() issues with Python threading
509
+ if self.use_subprocess_daemon():
510
+ return self.start_daemon_subprocess()
511
+ # Fallback to traditional fork (kept for compatibility)
489
512
  return self.daemonize()
490
513
 
514
+ def use_subprocess_daemon(self) -> bool:
515
+ """Check if we should use subprocess instead of fork for daemonization.
516
+
517
+ Returns:
518
+ True to use subprocess (safer), False to use traditional fork
519
+ """
520
+ # Check if we're already in a subprocess to prevent infinite recursion
521
+ if os.environ.get("CLAUDE_MPM_SUBPROCESS_DAEMON") == "1":
522
+ # We're already in a subprocess, use traditional fork
523
+ return False
524
+
525
+ # Otherwise, use subprocess for monitor daemon to avoid threading issues
526
+ return True
527
+
528
+ def start_daemon_subprocess(self) -> bool:
529
+ """Start daemon using subprocess.Popen for clean process isolation.
530
+
531
+ This avoids all the fork() + threading issues by starting the monitor
532
+ in a completely fresh process with no inherited threads or locks.
533
+
534
+ Returns:
535
+ True if daemon started successfully
536
+ """
537
+ try:
538
+ # Build command to run monitor in foreground mode in subprocess
539
+ import sys
540
+
541
+ python_exe = sys.executable
542
+
543
+ # Run 'claude-mpm monitor start' in subprocess with environment variable
544
+ # to indicate we're already in a subprocess (prevents infinite recursion)
545
+ cmd = [
546
+ python_exe,
547
+ "-m",
548
+ "claude_mpm.cli",
549
+ "monitor",
550
+ "start",
551
+ "--background", # Run as daemon
552
+ "--port",
553
+ str(self.port),
554
+ "--host",
555
+ self.host,
556
+ ]
557
+
558
+ # Set environment variable to prevent recursive subprocess creation
559
+ env = os.environ.copy()
560
+ env["CLAUDE_MPM_SUBPROCESS_DAEMON"] = "1"
561
+
562
+ self.logger.info(f"Starting monitor daemon via subprocess: {' '.join(cmd)}")
563
+
564
+ # Open log file for output redirection
565
+ log_file_handle = None
566
+ if self.log_file:
567
+ log_file_handle = open(self.log_file, "a")
568
+ log_file = log_file_handle
569
+ else:
570
+ log_file = subprocess.DEVNULL
571
+
572
+ try:
573
+ # Start the subprocess detached from parent
574
+ # Redirect stdout/stderr to log file to capture output
575
+ process = subprocess.Popen(
576
+ cmd,
577
+ stdin=subprocess.DEVNULL,
578
+ stdout=log_file,
579
+ stderr=subprocess.STDOUT if self.log_file else subprocess.DEVNULL,
580
+ start_new_session=True, # Create new process group
581
+ close_fds=(
582
+ False if self.log_file else True
583
+ ), # Keep log file open if redirecting
584
+ env=env, # Pass modified environment
585
+ )
586
+
587
+ # Close the log file handle now that subprocess has it
588
+ if log_file_handle:
589
+ log_file_handle.close()
590
+
591
+ # Get the process PID
592
+ pid = process.pid
593
+ self.logger.info(f"Monitor subprocess started with PID {pid}")
594
+
595
+ # Wait for the subprocess to write its PID file
596
+ # The subprocess will write the PID file after it starts successfully
597
+ max_wait = 10 # seconds
598
+ start_time = time.time()
599
+
600
+ while time.time() - start_time < max_wait:
601
+ # Check if process is still running
602
+ if process.poll() is not None:
603
+ # Process exited
604
+ self.logger.error(
605
+ f"Monitor daemon exited with code {process.returncode}"
606
+ )
607
+ return False
608
+
609
+ # Check if PID file was written
610
+ if self.pid_file.exists():
611
+ try:
612
+ with open(self.pid_file) as f:
613
+ written_pid = int(f.read().strip())
614
+ if written_pid == pid:
615
+ # PID file written correctly, check port
616
+ if (
617
+ not self._is_port_available()
618
+ ): # Port NOT available means it's in use (good!)
619
+ self.logger.info(
620
+ f"Monitor daemon successfully started on port {self.port}"
621
+ )
622
+ return True
623
+ except:
624
+ pass # PID file not ready yet
625
+
626
+ time.sleep(0.5)
627
+
628
+ # Timeout waiting for daemon to start
629
+ self.logger.error("Timeout waiting for monitor daemon to start")
630
+ # Try to kill the process if it's still running
631
+ if process.poll() is None:
632
+ process.terminate()
633
+ time.sleep(1)
634
+ if process.poll() is None:
635
+ process.kill()
636
+ return False
637
+ finally:
638
+ # Clean up log file handle if still open
639
+ if log_file_handle and not log_file_handle.closed:
640
+ log_file_handle.close()
641
+
642
+ except Exception as e:
643
+ self.logger.error(f"Failed to start daemon via subprocess: {e}")
644
+ return False
645
+
491
646
  def daemonize(self) -> bool:
492
647
  """Daemonize the current process.
493
648
 
@@ -495,12 +650,14 @@ class DaemonManager:
495
650
  True if successful (in parent), doesn't return in child
496
651
  """
497
652
  # Guard against re-entrant execution after fork
498
- if hasattr(self, '_forking_in_progress'):
499
- self.logger.error("CRITICAL: Detected re-entrant daemonize call after fork!")
653
+ if hasattr(self, "_forking_in_progress"):
654
+ self.logger.error(
655
+ "CRITICAL: Detected re-entrant daemonize call after fork!"
656
+ )
500
657
  return False
501
-
658
+
502
659
  self._forking_in_progress = True
503
-
660
+
504
661
  try:
505
662
  # Clean up asyncio event loops before forking
506
663
  self._cleanup_event_loops()
@@ -787,7 +944,7 @@ class DaemonManager:
787
944
  f.write("success")
788
945
  f.flush() # Ensure it's written immediately
789
946
  os.fsync(f.fileno()) # Force write to disk
790
- except Exception as e:
947
+ except Exception:
791
948
  # Logging might not work in daemon process after fork
792
949
  pass
793
950
 
@@ -239,7 +239,11 @@ class UnifiedMonitorServer:
239
239
  except OSError as e:
240
240
  # Port binding error - make sure it's reported clearly
241
241
  # Check for common port binding errors
242
- if "Address already in use" in str(e) or "[Errno 48]" in str(e) or "[Errno 98]" in str(e):
242
+ if (
243
+ "Address already in use" in str(e)
244
+ or "[Errno 48]" in str(e)
245
+ or "[Errno 98]" in str(e)
246
+ ):
243
247
  error_msg = f"Port {self.port} is already in use. Another process may be using this port."
244
248
  self.logger.error(error_msg)
245
249
  self.startup_error = error_msg
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: claude-mpm
3
- Version: 4.2.39
3
+ Version: 4.2.40
4
4
  Summary: Claude Multi-Agent Project Manager - Orchestrate Claude with agent delegation and ticket tracking
5
5
  Author-email: Bob Matsuoka <bob@matsuoka.com>
6
6
  Maintainer: Claude MPM Team
@@ -1,5 +1,5 @@
1
1
  claude_mpm/BUILD_NUMBER,sha256=toytnNjkIKPgQaGwDqQdC1rpNTAdSEc6Vja50d7Ovug,4
2
- claude_mpm/VERSION,sha256=rPIlGse66RudpHHHZLBC5KxFCk-2bw29CGEogBG2IXc,7
2
+ claude_mpm/VERSION,sha256=Ytc6ZB74poWqcejFEJHapI9Tv44jcmBlZEH-GuEoUwY,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
@@ -56,7 +56,7 @@ claude_mpm/agents/templates/.claude-mpm/memories/README.md,sha256=vEiG7cPjHRZfwX
56
56
  claude_mpm/agents/templates/.claude-mpm/memories/engineer_memories.md,sha256=KMZSJrQi-wHOwfl2C0m3A4PpC4QuBtDolAtVybGahKc,77
57
57
  claude_mpm/agents/templates/logs/prompts/agent_engineer_20250826_014258_728.md,sha256=UBm4BycXtdaa-_l1VCh0alTGGOUSsnCbpKwbFuI-mUY,2219
58
58
  claude_mpm/agents/templates/logs/prompts/agent_engineer_20250901_010124_142.md,sha256=oPvFSYFnmJ4TkbTe4AZnNHWaJMJ-xqZP2WM6scUKQKo,13089
59
- claude_mpm/cli/__init__.py,sha256=5eW6ZPruxwWaHwIXyHByQkKUOhvMLwAax59iCU1MHVg,16863
59
+ claude_mpm/cli/__init__.py,sha256=hbDc2PSEJHNRMhAYVAk1qPNCL5bTDOvU808VYBJEJgs,16670
60
60
  claude_mpm/cli/__main__.py,sha256=WnVGBwe10InxuZjJRFdwuMF6Gh16aXox6zFgxr0sRXk,847
61
61
  claude_mpm/cli/parser.py,sha256=Vqx9n-6Xo1uNhXR4rThmgWpZXTr0nOtkgDf3oMS9b0g,5855
62
62
  claude_mpm/cli/startup_logging.py,sha256=CWu43ecTJLLT-YHRL94c783XljpR8GKydycGJ1JFuXI,23462
@@ -436,7 +436,7 @@ claude_mpm/services/cli/memory_crud_service.py,sha256=ciN9Pl_12iDAqF9zPBWOzu-iXi
436
436
  claude_mpm/services/cli/memory_output_formatter.py,sha256=nbf7VsjGvH4e9fLv9c7PzjuO9COZhbK5P2fNZ79055w,24783
437
437
  claude_mpm/services/cli/session_manager.py,sha256=rla_Stbcvt93wa9G9MCMu9UqB3FLGqlPt_eN5lQb3Gg,16599
438
438
  claude_mpm/services/cli/startup_checker.py,sha256=efhuvu8ns5G16jcQ0nQZKVddmD2AktUEdlvjNcXjAuk,12232
439
- claude_mpm/services/cli/unified_dashboard_manager.py,sha256=YXb3hbyukTk-yycqLAIUTYe54lt0GFI5OyH6VrLL_RI,15449
439
+ claude_mpm/services/cli/unified_dashboard_manager.py,sha256=XOXC7mlfmqwKrNDDbxu3q8qPn0oAUvPkQY7e_SwTTIA,15433
440
440
  claude_mpm/services/communication/__init__.py,sha256=b4qc7_Rqy4DE9q7BAUlfUZjoYG4uimAyUnE0irPcXyU,560
441
441
  claude_mpm/services/core/__init__.py,sha256=evEayLlBqJvxMZhrhuK6aagXmNrKGSj8Jm9OOxKzqvU,2195
442
442
  claude_mpm/services/core/base.py,sha256=iA-F7DgGp-FJIMvQTiHQ68RkG_k-AtUWlArJPMw6ZPk,7297
@@ -552,10 +552,10 @@ claude_mpm/services/memory/cache/__init__.py,sha256=6M6-P8ParyxX8vOgp_IxHgLMvacr
552
552
  claude_mpm/services/memory/cache/shared_prompt_cache.py,sha256=crnYPUT8zcS7TvoE1vW7pyaf4T77N5rJ1wUf_YQ2vvo,28704
553
553
  claude_mpm/services/memory/cache/simple_cache.py,sha256=qsTjbcsPxj-kNfaod9VN_uE5NioIwpfkUin_mMVUJCg,10218
554
554
  claude_mpm/services/monitor/__init__.py,sha256=X7gxSLUm9Fg_zEsX6LtCHP2ipF0qj6Emkun20h2So7g,745
555
- claude_mpm/services/monitor/daemon.py,sha256=YwwxMaqgcEzbP9eRUbmHodkhiNgn0oU2g7xpHDhd00w,24310
556
- claude_mpm/services/monitor/daemon_manager.py,sha256=0K0lhxNbPGIG6R_rS5B2r8OKJg2ZebVQuwSSDMN22bo,28237
555
+ claude_mpm/services/monitor/daemon.py,sha256=ZeFZPoLKd8PGShNA-mRpFkQkQBbsP7iaDDlNJGCbFe0,25964
556
+ claude_mpm/services/monitor/daemon_manager.py,sha256=fAcrRh-zBAHXP5_9s5WKCZ-V5EhTEijHPvrSJWwU7Yc,34238
557
557
  claude_mpm/services/monitor/event_emitter.py,sha256=JzRLNg8PUJ5s3ulNnq_D4yqCPItvidJzu8DmFxriieQ,12224
558
- claude_mpm/services/monitor/server.py,sha256=aKweXs3saNuPDaPwuoJT9g6kYYHefSiLcGmLdHD6FYM,28579
558
+ claude_mpm/services/monitor/server.py,sha256=A8gBPqna_Yx5WT06_8OjHQuchgsWRXhfIOBYYtOTsI8,28659
559
559
  claude_mpm/services/monitor/handlers/__init__.py,sha256=jgPIf4IJVERm_tAeD9834tfx9IcxtlHj5r9rhEWpkfM,701
560
560
  claude_mpm/services/monitor/handlers/code_analysis.py,sha256=mHyI27Wp6WVmUBc0m0i991ogyFZBTvkrfR7Kf3EAk5U,11474
561
561
  claude_mpm/services/monitor/handlers/dashboard.py,sha256=uGBhb-6RG6u4WLipUXgdx7RCW-vb_qek5dIfHIwAC7o,9805
@@ -644,9 +644,9 @@ claude_mpm/utils/subprocess_utils.py,sha256=zgiwLqh_17WxHpySvUPH65pb4bzIeUGOAYUJ
644
644
  claude_mpm/validation/__init__.py,sha256=YZhwE3mhit-lslvRLuwfX82xJ_k4haZeKmh4IWaVwtk,156
645
645
  claude_mpm/validation/agent_validator.py,sha256=3Lo6LK-Mw9IdnL_bd3zl_R6FkgSVDYKUUM7EeVVD3jc,20865
646
646
  claude_mpm/validation/frontmatter_validator.py,sha256=u8g4Eyd_9O6ugj7Un47oSGh3kqv4wMkuks2i_CtWRvM,7028
647
- claude_mpm-4.2.39.dist-info/licenses/LICENSE,sha256=lpaivOlPuBZW1ds05uQLJJswy8Rp_HMNieJEbFlqvLk,1072
648
- claude_mpm-4.2.39.dist-info/METADATA,sha256=mf2X8SRmS0EUaenCC01wANeEtZ-nZLLIFd-4m0c9v_4,14451
649
- claude_mpm-4.2.39.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
650
- claude_mpm-4.2.39.dist-info/entry_points.txt,sha256=FDPZgz8JOvD-6iuXY2l9Zbo9zYVRuE4uz4Qr0vLeGOk,471
651
- claude_mpm-4.2.39.dist-info/top_level.txt,sha256=1nUg3FEaBySgm8t-s54jK5zoPnu3_eY6EP6IOlekyHA,11
652
- claude_mpm-4.2.39.dist-info/RECORD,,
647
+ claude_mpm-4.2.40.dist-info/licenses/LICENSE,sha256=lpaivOlPuBZW1ds05uQLJJswy8Rp_HMNieJEbFlqvLk,1072
648
+ claude_mpm-4.2.40.dist-info/METADATA,sha256=vAL5h1M9CdSo3Po_0evvbLYZixbc_un0p70-FZqLD4g,14451
649
+ claude_mpm-4.2.40.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
650
+ claude_mpm-4.2.40.dist-info/entry_points.txt,sha256=FDPZgz8JOvD-6iuXY2l9Zbo9zYVRuE4uz4Qr0vLeGOk,471
651
+ claude_mpm-4.2.40.dist-info/top_level.txt,sha256=1nUg3FEaBySgm8t-s54jK5zoPnu3_eY6EP6IOlekyHA,11
652
+ claude_mpm-4.2.40.dist-info/RECORD,,