claude-mpm 5.4.90__py3-none-any.whl → 5.4.94__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/monitor.py +2 -2
- claude_mpm/cli/startup_display.py +72 -5
- claude_mpm/cli/startup_logging.py +2 -2
- claude_mpm/commands/mpm-session-resume.md +1 -1
- claude_mpm/core/unified_config.py +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.C33zOoyM.css +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.CW1J-YuA.css +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BS0ej2w8.js → 1WZnGYqX.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{7ZAeO_Uj.js → 67pF3qNn.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BCWDw8BF.js → 6RxdMKe4.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BVFqgd56.js → 8cZrfX0h.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CDNOxKrg.js → 9a6T2nm-.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{MJf6AOIJ.js → B443AUzu.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/B8AwtY2H.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DNI1jw9S.js → BF15LAsF.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DOeJfApz.js → BRcwIQNr.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BfXd4Xj4.js → BV6nKitt.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DWDi9IaK.js → BViJ8lZt.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BnFPFynJ.js → BcQ-Q0FE.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B_fnSNFx.js → Bpyvgze_.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BzTRqg-z.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C0Fr8dve.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{EKp_wsKE.js → C3rbW_a-.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C_l0vq62.js → C8WYN38h.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CDi5wzaD.js → C9I8FlXH.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BwpSELyW.js → CIQcWgO2.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CQ94FMOU.js → CIctN7YN.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CzkNB1Vu.js → CKrS_JZW.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{D0Fj1OdD.js → CR6P9C4A.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{NsEh4Ivo.js → CRRR9MD_.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CRcR2DqT.js +334 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DdIDcQsD.js → CSXtMOf0.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C5Dg_JxJ.js → CT-sbxSk.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CyrTH56Q.js → CWm6DJsp.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C86quetY.js → CpqQ1Kzn.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Dqtg3hb8.js → D9iCMida.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DJN4AVXS.js → D9ykgMoY.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{14Ru8gxt.js → DL2Ldur1.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DLeM8wSV.js → DPfltzjH.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Vzk33B_K.js → DR8nis88.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CvWciI1W.js → DUliQN2b.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DOViuQX_.js → DXlhR01x.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{s04HIjWg.js → D_lyTybS.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{WiqB4NUY.js → DngoTTgh.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CT5eAo1x.js → DqkmHtDC.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DJuK4-OP.js → DsDh8EYs.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C7i47te_.js → DypDmXgd.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BfMC7wDI.js → IPYC-LnN.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{URSAF6IJ.js → JTLiF7dt.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B06ALsCS.js → JpevfAFt.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DY1XQ8fi.js → R8CEIRAd.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{D1ARDjz0.js → Zxy7qc-l.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/q9Hm6zAU.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{vJiSSdpk.js → qtd3IeO4.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BXs4CVzO.js → ulBFON_C.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DTgfNBV9.js → wQVh1CoA.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/{app.CnXU_fEX.js → app.Dr7t0z2J.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.BGhZHUS3.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/{0.BuxSUm_s.js → 0.RgBboRvH.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/{1.znyTz9u3.js → 1.DG-KkbDf.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.D_jnf-x6.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/version.json +1 -1
- claude_mpm/dashboard/static/svelte-build/index.html +8 -8
- claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/event_handlers.py +63 -10
- claude_mpm/hooks/claude_hooks/hook_handler.py +3 -1
- claude_mpm/hooks/claude_hooks/response_tracking.py +2 -16
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +20 -0
- claude_mpm/services/agents/cache_git_manager.py +1 -1
- claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +3 -0
- claude_mpm/services/diagnostics/checks/agent_sources_check.py +30 -0
- claude_mpm/services/diagnostics/checks/configuration_check.py +24 -0
- claude_mpm/services/diagnostics/checks/installation_check.py +22 -0
- claude_mpm/services/diagnostics/checks/mcp_services_check.py +23 -0
- claude_mpm/services/diagnostics/doctor_reporter.py +31 -1
- claude_mpm/services/diagnostics/models.py +14 -1
- claude_mpm/services/monitor/daemon_manager.py +15 -4
- claude_mpm/services/monitor/management/lifecycle.py +8 -2
- claude_mpm/services/monitor/server.py +106 -16
- claude_mpm/services/skills/selective_skill_deployer.py +114 -16
- {claude_mpm-5.4.90.dist-info → claude_mpm-5.4.94.dist-info}/METADATA +1 -1
- {claude_mpm-5.4.90.dist-info → claude_mpm-5.4.94.dist-info}/RECORD +92 -90
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.B7S5qgOx.css +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.D3t4z6uz.css +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/4TdZjIqw.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/AeivYILh.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Cn4nXAfg.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/D_vpdI7l.js +0 -325
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DnL7ky1O.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.CUaAfoQJ.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.CLVHDDxl.js +0 -1
- {claude_mpm-5.4.90.dist-info → claude_mpm-5.4.94.dist-info}/WHEEL +0 -0
- {claude_mpm-5.4.90.dist-info → claude_mpm-5.4.94.dist-info}/entry_points.txt +0 -0
- {claude_mpm-5.4.90.dist-info → claude_mpm-5.4.94.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-5.4.90.dist-info → claude_mpm-5.4.94.dist-info}/licenses/LICENSE-FAQ.md +0 -0
- {claude_mpm-5.4.90.dist-info → claude_mpm-5.4.94.dist-info}/top_level.txt +0 -0
|
@@ -115,7 +115,7 @@ class EventHandlers:
|
|
|
115
115
|
f"Stored prompt for comprehensive tracking: session {session_id[:8]}...",
|
|
116
116
|
file=sys.stderr,
|
|
117
117
|
)
|
|
118
|
-
except Exception:
|
|
118
|
+
except Exception: # nosec B110
|
|
119
119
|
# Response tracking is optional - silently continue if it fails
|
|
120
120
|
pass
|
|
121
121
|
|
|
@@ -200,6 +200,23 @@ class EventHandlers:
|
|
|
200
200
|
|
|
201
201
|
self.hook_handler._emit_socketio_event("", "pre_tool", pre_tool_data)
|
|
202
202
|
|
|
203
|
+
# Handle TodoWrite specially - emit dedicated todo_updated event
|
|
204
|
+
# WHY: Frontend expects todo_updated events for dashboard display
|
|
205
|
+
# The broadcaster.todo_updated() method exists but was never called
|
|
206
|
+
if tool_name == "TodoWrite" and tool_params.get("todos"):
|
|
207
|
+
todo_data = {
|
|
208
|
+
"todos": tool_params["todos"],
|
|
209
|
+
"total_count": len(tool_params["todos"]),
|
|
210
|
+
"session_id": session_id,
|
|
211
|
+
"timestamp": timestamp,
|
|
212
|
+
}
|
|
213
|
+
self.hook_handler._emit_socketio_event("", "todo_updated", todo_data)
|
|
214
|
+
if DEBUG:
|
|
215
|
+
print(
|
|
216
|
+
f" - Emitted todo_updated event with {len(tool_params['todos'])} todos for session {session_id[:8]}...",
|
|
217
|
+
file=sys.stderr,
|
|
218
|
+
)
|
|
219
|
+
|
|
203
220
|
def _handle_task_delegation(
|
|
204
221
|
self, tool_input: dict, pre_tool_data: dict, session_id: str
|
|
205
222
|
):
|
|
@@ -280,7 +297,7 @@ class EventHandlers:
|
|
|
280
297
|
mhm = getattr(self.hook_handler, "memory_hook_manager", None)
|
|
281
298
|
if mhm and hasattr(mhm, "trigger_pre_delegation_hook"):
|
|
282
299
|
mhm.trigger_pre_delegation_hook(agent_type, tool_input, session_id)
|
|
283
|
-
except Exception:
|
|
300
|
+
except Exception: # nosec B110
|
|
284
301
|
# Memory hooks are optional
|
|
285
302
|
pass
|
|
286
303
|
|
|
@@ -373,7 +390,7 @@ class EventHandlers:
|
|
|
373
390
|
os.chdir(working_dir)
|
|
374
391
|
|
|
375
392
|
# Run git command to get current branch
|
|
376
|
-
result = subprocess.run(
|
|
393
|
+
result = subprocess.run( # nosec B603 B607
|
|
377
394
|
["git", "branch", "--show-current"],
|
|
378
395
|
capture_output=True,
|
|
379
396
|
text=True,
|
|
@@ -483,7 +500,7 @@ class EventHandlers:
|
|
|
483
500
|
mhm = getattr(self.hook_handler, "memory_hook_manager", None)
|
|
484
501
|
if mhm and hasattr(mhm, "trigger_post_delegation_hook"):
|
|
485
502
|
mhm.trigger_post_delegation_hook(agent_type, event, session_id)
|
|
486
|
-
except Exception:
|
|
503
|
+
except Exception: # nosec B110
|
|
487
504
|
# Memory hooks are optional
|
|
488
505
|
pass
|
|
489
506
|
|
|
@@ -497,7 +514,7 @@ class EventHandlers:
|
|
|
497
514
|
rtm.track_agent_response(
|
|
498
515
|
session_id, agent_type, event, delegation_requests
|
|
499
516
|
)
|
|
500
|
-
except Exception:
|
|
517
|
+
except Exception: # nosec B110
|
|
501
518
|
# Response tracking is optional
|
|
502
519
|
pass
|
|
503
520
|
|
|
@@ -559,13 +576,49 @@ class EventHandlers:
|
|
|
559
576
|
if DEBUG:
|
|
560
577
|
self._log_stop_event_debug(event, session_id, metadata)
|
|
561
578
|
|
|
579
|
+
# Auto-pause integration (independent of response tracking)
|
|
580
|
+
# WHY HERE: Auto-pause must work even when response_tracking is disabled
|
|
581
|
+
# Extract usage data directly from event and trigger auto-pause if thresholds crossed
|
|
582
|
+
if "usage" in event:
|
|
583
|
+
auto_pause = getattr(self.hook_handler, "auto_pause_handler", None)
|
|
584
|
+
if auto_pause:
|
|
585
|
+
try:
|
|
586
|
+
usage_data = event["usage"]
|
|
587
|
+
metadata["usage"] = {
|
|
588
|
+
"input_tokens": usage_data.get("input_tokens", 0),
|
|
589
|
+
"output_tokens": usage_data.get("output_tokens", 0),
|
|
590
|
+
"cache_creation_input_tokens": usage_data.get(
|
|
591
|
+
"cache_creation_input_tokens", 0
|
|
592
|
+
),
|
|
593
|
+
"cache_read_input_tokens": usage_data.get(
|
|
594
|
+
"cache_read_input_tokens", 0
|
|
595
|
+
),
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
threshold_crossed = auto_pause.on_usage_update(metadata["usage"])
|
|
599
|
+
if threshold_crossed:
|
|
600
|
+
warning = auto_pause.emit_threshold_warning(threshold_crossed)
|
|
601
|
+
print(f"\n⚠️ {warning}", file=sys.stderr)
|
|
602
|
+
|
|
603
|
+
if DEBUG:
|
|
604
|
+
print(
|
|
605
|
+
f" - Auto-pause threshold crossed: {threshold_crossed}",
|
|
606
|
+
file=sys.stderr,
|
|
607
|
+
)
|
|
608
|
+
except Exception as e:
|
|
609
|
+
if DEBUG:
|
|
610
|
+
print(
|
|
611
|
+
f"Auto-pause error in handle_stop_fast: {e}",
|
|
612
|
+
file=sys.stderr,
|
|
613
|
+
)
|
|
614
|
+
|
|
562
615
|
# Track response if enabled
|
|
563
616
|
try:
|
|
564
617
|
rtm = getattr(self.hook_handler, "response_tracking_manager", None)
|
|
565
618
|
if rtm and hasattr(rtm, "track_stop_response"):
|
|
566
619
|
pending_prompts = getattr(self.hook_handler, "pending_prompts", {})
|
|
567
620
|
rtm.track_stop_response(event, session_id, metadata, pending_prompts)
|
|
568
|
-
except Exception:
|
|
621
|
+
except Exception: # nosec B110
|
|
569
622
|
# Response tracking is optional
|
|
570
623
|
pass
|
|
571
624
|
|
|
@@ -607,7 +660,7 @@ class EventHandlers:
|
|
|
607
660
|
f" - response_tracker exists: {tracker_exists}",
|
|
608
661
|
file=sys.stderr,
|
|
609
662
|
)
|
|
610
|
-
except Exception:
|
|
663
|
+
except Exception: # nosec B110
|
|
611
664
|
# If debug logging fails, just skip it
|
|
612
665
|
pass
|
|
613
666
|
|
|
@@ -673,7 +726,7 @@ class EventHandlers:
|
|
|
673
726
|
try:
|
|
674
727
|
# Get the original request data (with fuzzy matching fallback)
|
|
675
728
|
delegation_requests = getattr(self.hook_handler, "delegation_requests", {})
|
|
676
|
-
request_info = delegation_requests.get(session_id)
|
|
729
|
+
request_info = delegation_requests.get(session_id) # nosec B113
|
|
677
730
|
|
|
678
731
|
# If exact match fails, try partial matching
|
|
679
732
|
if not request_info and session_id:
|
|
@@ -698,7 +751,7 @@ class EventHandlers:
|
|
|
698
751
|
f" - ✅ Fuzzy match found: {stored_sid[:16]}...",
|
|
699
752
|
file=sys.stderr,
|
|
700
753
|
)
|
|
701
|
-
request_info = delegation_requests.get(stored_sid)
|
|
754
|
+
request_info = delegation_requests.get(stored_sid) # nosec B113
|
|
702
755
|
# Update the key to use the current session_id for consistency
|
|
703
756
|
if request_info:
|
|
704
757
|
delegation_requests[session_id] = request_info
|
|
@@ -805,7 +858,7 @@ class EventHandlers:
|
|
|
805
858
|
if rtm and hasattr(rtm, "track_assistant_response"):
|
|
806
859
|
pending_prompts = getattr(self.hook_handler, "pending_prompts", {})
|
|
807
860
|
rtm.track_assistant_response(event, pending_prompts)
|
|
808
|
-
except Exception:
|
|
861
|
+
except Exception: # nosec B110
|
|
809
862
|
# Response tracking is optional
|
|
810
863
|
pass
|
|
811
864
|
|
|
@@ -550,13 +550,15 @@ class ClaudeHookHandler:
|
|
|
550
550
|
# Build hook execution data
|
|
551
551
|
hook_data = {
|
|
552
552
|
"hook_name": hook_type,
|
|
553
|
-
"hook_type": hook_type,
|
|
553
|
+
"hook_type": hook_type, # Actual hook type (PreToolUse, UserPromptSubmit, etc.)
|
|
554
|
+
"hook_event_type": hook_type, # Additional field for clarity
|
|
554
555
|
"session_id": session_id,
|
|
555
556
|
"working_directory": working_dir,
|
|
556
557
|
"success": success,
|
|
557
558
|
"duration_ms": duration_ms,
|
|
558
559
|
"result_summary": summary,
|
|
559
560
|
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
561
|
+
"source": "claude_hook_handler", # Explicit source identification
|
|
560
562
|
}
|
|
561
563
|
|
|
562
564
|
# Add error information if present
|
|
@@ -306,6 +306,8 @@ class ResponseTrackingManager:
|
|
|
306
306
|
)
|
|
307
307
|
|
|
308
308
|
# Capture Claude API usage data if available
|
|
309
|
+
# NOTE: Usage data is already captured in metadata by handle_stop_fast()
|
|
310
|
+
# which also handles auto-pause triggering (even when response tracking disabled)
|
|
309
311
|
if "usage" in event:
|
|
310
312
|
usage_data = event["usage"]
|
|
311
313
|
metadata["usage"] = {
|
|
@@ -327,22 +329,6 @@ class ResponseTrackingManager:
|
|
|
327
329
|
file=sys.stderr,
|
|
328
330
|
)
|
|
329
331
|
|
|
330
|
-
# Auto-pause integration
|
|
331
|
-
auto_pause = getattr(self, "auto_pause_handler", None)
|
|
332
|
-
if auto_pause and metadata.get("usage"):
|
|
333
|
-
try:
|
|
334
|
-
threshold_crossed = auto_pause.on_usage_update(
|
|
335
|
-
metadata["usage"]
|
|
336
|
-
)
|
|
337
|
-
if threshold_crossed:
|
|
338
|
-
warning = auto_pause.emit_threshold_warning(
|
|
339
|
-
threshold_crossed
|
|
340
|
-
)
|
|
341
|
-
print(f"\n⚠️ {warning}", file=sys.stderr)
|
|
342
|
-
except Exception as e:
|
|
343
|
-
if DEBUG:
|
|
344
|
-
print(f"Auto-pause error: {e}", file=sys.stderr)
|
|
345
|
-
|
|
346
332
|
# Track the main Claude response
|
|
347
333
|
file_path = self.response_tracker.track_response(
|
|
348
334
|
agent_name="claude_main",
|
|
Binary file
|
|
@@ -134,6 +134,26 @@ class ConnectionManagerService:
|
|
|
134
134
|
# Otherwise use "hook" as the type
|
|
135
135
|
if event == "hook_execution":
|
|
136
136
|
hook_type = data.get("hook_type", "unknown")
|
|
137
|
+
|
|
138
|
+
# BUGFIX: Validate hook_type is meaningful (not generic/invalid values)
|
|
139
|
+
# Problem: Dashboard shows "hook hook" instead of "PreToolUse", "UserPromptSubmit", etc.
|
|
140
|
+
# Root cause: hook_type defaults to "hook" or "unknown", providing no useful information
|
|
141
|
+
# Solution: Fallback to hook_name, then to descriptive "hook_execution_untyped"
|
|
142
|
+
if hook_type in ("hook", "unknown", "", None):
|
|
143
|
+
# Try fallback to hook_name field (set by _emit_hook_execution_event)
|
|
144
|
+
hook_type = data.get("hook_name", "unknown_hook")
|
|
145
|
+
|
|
146
|
+
# Final fallback if still generic - use descriptive name
|
|
147
|
+
if hook_type in ("hook", "unknown", "", None):
|
|
148
|
+
hook_type = "hook_execution_untyped"
|
|
149
|
+
|
|
150
|
+
# Debug log when we detect invalid hook_type for troubleshooting
|
|
151
|
+
if DEBUG:
|
|
152
|
+
print(
|
|
153
|
+
f"⚠️ Invalid hook_type detected, using fallback: {hook_type}",
|
|
154
|
+
file=sys.stderr,
|
|
155
|
+
)
|
|
156
|
+
|
|
137
157
|
event_type = hook_type
|
|
138
158
|
else:
|
|
139
159
|
event_type = "hook"
|
|
@@ -88,7 +88,7 @@ class CacheGitManager:
|
|
|
88
88
|
if self.repo_path:
|
|
89
89
|
logger.debug(f"Initialized CacheGitManager for repo: {self.repo_path}")
|
|
90
90
|
else:
|
|
91
|
-
logger.
|
|
91
|
+
logger.debug(f"Cache path is not a git repository: {cache_path}")
|
|
92
92
|
|
|
93
93
|
def _find_git_root(self) -> Optional[Path]:
|
|
94
94
|
"""
|
|
@@ -491,6 +491,9 @@ class RemoteAgentDiscoveryService:
|
|
|
491
491
|
"SKILL.md",
|
|
492
492
|
"SKILLS.md",
|
|
493
493
|
"skill-template.md",
|
|
494
|
+
# Legacy agents superseded by newer versions
|
|
495
|
+
# TODO: Remove after bobmatnyc/claude-mpm-agents#XXX is merged
|
|
496
|
+
"memory-manager.md", # Superseded by memory-manager-agent.md (v1.2.0)
|
|
494
497
|
}
|
|
495
498
|
md_files = [f for f in md_files if f.name not in excluded_files]
|
|
496
499
|
|
|
@@ -69,6 +69,13 @@ class AgentSourcesCheck(BaseDiagnosticCheck):
|
|
|
69
69
|
fix_command="claude-mpm agent-source add https://github.com/bobmatnyc/claude-mpm-agents",
|
|
70
70
|
fix_description="Add default system repository",
|
|
71
71
|
sub_results=sub_results if self.verbose else [],
|
|
72
|
+
explanation=(
|
|
73
|
+
"Agent sources define where Claude MPM discovers specialized agents. "
|
|
74
|
+
"Without configured sources, no agents can be deployed or delegated to. "
|
|
75
|
+
"This is a critical component for multi-agent workflows."
|
|
76
|
+
),
|
|
77
|
+
severity="critical",
|
|
78
|
+
doc_link="https://github.com/bobmatnyc/claude-mpm/blob/main/docs/guides/agent-sources.md",
|
|
72
79
|
)
|
|
73
80
|
|
|
74
81
|
# Check 2: Configuration is valid YAML
|
|
@@ -141,16 +148,30 @@ class AgentSourcesCheck(BaseDiagnosticCheck):
|
|
|
141
148
|
r for r in sub_results if r.status == ValidationSeverity.WARNING
|
|
142
149
|
]
|
|
143
150
|
|
|
151
|
+
# Determine status and enhanced troubleshooting info (issue #125)
|
|
144
152
|
if error_results:
|
|
145
153
|
status = ValidationSeverity.ERROR
|
|
146
154
|
message = f"Agent sources have {len(error_results)} critical issue(s)"
|
|
147
155
|
fix_command = None
|
|
148
156
|
fix_description = None
|
|
157
|
+
severity = "critical"
|
|
158
|
+
explanation = (
|
|
159
|
+
"Agent sources are the foundation of Claude MPM's delegation system. "
|
|
160
|
+
"Critical errors prevent agent discovery and deployment, blocking "
|
|
161
|
+
"multi-agent workflows entirely."
|
|
162
|
+
)
|
|
163
|
+
doc_link = "https://github.com/bobmatnyc/claude-mpm/blob/main/docs/guides/agent-sources.md"
|
|
149
164
|
elif warning_results:
|
|
150
165
|
status = ValidationSeverity.WARNING
|
|
151
166
|
message = f"Agent sources have {len(warning_results)} minor issue(s)"
|
|
152
167
|
fix_command = "claude-mpm agent-source update"
|
|
153
168
|
fix_description = "Update all sources to refresh cache"
|
|
169
|
+
severity = "medium"
|
|
170
|
+
explanation = (
|
|
171
|
+
"Some agent sources have issues but the system can still function. "
|
|
172
|
+
"You may have limited agent availability or outdated cache."
|
|
173
|
+
)
|
|
174
|
+
doc_link = "https://github.com/bobmatnyc/claude-mpm/blob/main/docs/guides/agent-sources.md"
|
|
154
175
|
else:
|
|
155
176
|
status = OperationResult.SUCCESS
|
|
156
177
|
enabled_count = details["enabled_sources"]
|
|
@@ -158,6 +179,12 @@ class AgentSourcesCheck(BaseDiagnosticCheck):
|
|
|
158
179
|
message = f"All checks passed ({enabled_count} source(s), {agents_count} agent(s))"
|
|
159
180
|
fix_command = None
|
|
160
181
|
fix_description = None
|
|
182
|
+
severity = "info"
|
|
183
|
+
explanation = (
|
|
184
|
+
"Agent sources are properly configured and agents are discoverable. "
|
|
185
|
+
"You can delegate tasks to specialized agents."
|
|
186
|
+
)
|
|
187
|
+
doc_link = ""
|
|
161
188
|
|
|
162
189
|
return DiagnosticResult(
|
|
163
190
|
category=self.category,
|
|
@@ -167,6 +194,9 @@ class AgentSourcesCheck(BaseDiagnosticCheck):
|
|
|
167
194
|
fix_command=fix_command,
|
|
168
195
|
fix_description=fix_description,
|
|
169
196
|
sub_results=sub_results if self.verbose else [],
|
|
197
|
+
explanation=explanation,
|
|
198
|
+
severity=severity,
|
|
199
|
+
doc_link=doc_link,
|
|
170
200
|
)
|
|
171
201
|
|
|
172
202
|
except Exception as e:
|
|
@@ -63,12 +63,36 @@ class ConfigurationCheck(BaseDiagnosticCheck):
|
|
|
63
63
|
status = OperationResult.SUCCESS
|
|
64
64
|
message = "Configuration is valid"
|
|
65
65
|
|
|
66
|
+
# Add enhanced troubleshooting info (issue #125)
|
|
67
|
+
severity = "medium"
|
|
68
|
+
explanation = ""
|
|
69
|
+
doc_link = ""
|
|
70
|
+
|
|
71
|
+
if status == ValidationSeverity.ERROR:
|
|
72
|
+
severity = "high"
|
|
73
|
+
explanation = (
|
|
74
|
+
"Configuration files control how Claude MPM behaves. Critical errors "
|
|
75
|
+
"in configuration may prevent features from working correctly or cause "
|
|
76
|
+
"unexpected behavior."
|
|
77
|
+
)
|
|
78
|
+
doc_link = "https://github.com/bobmatnyc/claude-mpm/blob/main/docs/configuration.md"
|
|
79
|
+
elif status == ValidationSeverity.WARNING:
|
|
80
|
+
severity = "low"
|
|
81
|
+
explanation = (
|
|
82
|
+
"Configuration has minor issues that may affect optional features. "
|
|
83
|
+
"Core functionality should still work."
|
|
84
|
+
)
|
|
85
|
+
doc_link = "https://github.com/bobmatnyc/claude-mpm/blob/main/docs/configuration.md"
|
|
86
|
+
|
|
66
87
|
return DiagnosticResult(
|
|
67
88
|
category=self.category,
|
|
68
89
|
status=status,
|
|
69
90
|
message=message,
|
|
70
91
|
details=details,
|
|
71
92
|
sub_results=sub_results if self.verbose else [],
|
|
93
|
+
explanation=explanation,
|
|
94
|
+
severity=severity,
|
|
95
|
+
doc_link=doc_link,
|
|
72
96
|
)
|
|
73
97
|
|
|
74
98
|
except Exception as e:
|
|
@@ -65,12 +65,34 @@ class InstallationCheck(BaseDiagnosticCheck):
|
|
|
65
65
|
status = OperationResult.SUCCESS
|
|
66
66
|
message = "Installation is healthy"
|
|
67
67
|
|
|
68
|
+
# Determine severity and explanation based on status (issue #125)
|
|
69
|
+
severity = "medium"
|
|
70
|
+
explanation = ""
|
|
71
|
+
doc_link = ""
|
|
72
|
+
|
|
73
|
+
if status == ValidationSeverity.ERROR:
|
|
74
|
+
severity = "high"
|
|
75
|
+
explanation = (
|
|
76
|
+
"Claude MPM installation verification failed. Critical components are missing "
|
|
77
|
+
"or misconfigured, which will prevent the system from functioning properly."
|
|
78
|
+
)
|
|
79
|
+
doc_link = "https://github.com/bobmatnyc/claude-mpm/blob/main/docs/installation.md"
|
|
80
|
+
elif status == ValidationSeverity.WARNING:
|
|
81
|
+
severity = "medium"
|
|
82
|
+
explanation = (
|
|
83
|
+
"Installation is functional but has minor issues. These may affect "
|
|
84
|
+
"performance or features but won't prevent basic operation."
|
|
85
|
+
)
|
|
86
|
+
|
|
68
87
|
return DiagnosticResult(
|
|
69
88
|
category=self.category,
|
|
70
89
|
status=status,
|
|
71
90
|
message=message,
|
|
72
91
|
details=details,
|
|
73
92
|
sub_results=sub_results if self.verbose else [],
|
|
93
|
+
explanation=explanation,
|
|
94
|
+
severity=severity,
|
|
95
|
+
doc_link=doc_link,
|
|
74
96
|
)
|
|
75
97
|
|
|
76
98
|
except Exception as e:
|
|
@@ -216,12 +216,35 @@ class MCPServicesCheck(BaseDiagnosticCheck):
|
|
|
216
216
|
status = ValidationSeverity.WARNING
|
|
217
217
|
message = f"All {total_services} MCP services installed but connections not tested"
|
|
218
218
|
|
|
219
|
+
# Enhanced troubleshooting info (issue #125)
|
|
220
|
+
severity = "medium"
|
|
221
|
+
explanation = ""
|
|
222
|
+
doc_link = ""
|
|
223
|
+
|
|
224
|
+
if status == ValidationSeverity.ERROR:
|
|
225
|
+
severity = "high"
|
|
226
|
+
explanation = (
|
|
227
|
+
"MCP services provide enhanced capabilities like vector search, browser automation, "
|
|
228
|
+
"and ticket management. Critical errors prevent these services from functioning."
|
|
229
|
+
)
|
|
230
|
+
doc_link = "https://github.com/bobmatnyc/claude-mpm/blob/main/docs/mcp-services.md"
|
|
231
|
+
elif status == ValidationSeverity.WARNING:
|
|
232
|
+
severity = "low"
|
|
233
|
+
explanation = (
|
|
234
|
+
"MCP services are optional but provide powerful features. "
|
|
235
|
+
"Some services may not be installed or configured properly."
|
|
236
|
+
)
|
|
237
|
+
doc_link = "https://github.com/bobmatnyc/claude-mpm/blob/main/docs/mcp-services.md"
|
|
238
|
+
|
|
219
239
|
return DiagnosticResult(
|
|
220
240
|
category=self.category,
|
|
221
241
|
status=status,
|
|
222
242
|
message=message,
|
|
223
243
|
details=details,
|
|
224
244
|
sub_results=sub_results if self.verbose else [],
|
|
245
|
+
explanation=explanation,
|
|
246
|
+
severity=severity,
|
|
247
|
+
doc_link=doc_link,
|
|
225
248
|
)
|
|
226
249
|
|
|
227
250
|
except Exception as e:
|
|
@@ -28,6 +28,15 @@ class DoctorReporter:
|
|
|
28
28
|
OperationResult.SKIPPED: "⏭️ ",
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
# Severity level emojis (issue #125)
|
|
32
|
+
SEVERITY_SYMBOLS = {
|
|
33
|
+
"critical": "🔴",
|
|
34
|
+
"high": "🟠",
|
|
35
|
+
"medium": "🟡",
|
|
36
|
+
"low": "🟢",
|
|
37
|
+
"info": "🔵",
|
|
38
|
+
}
|
|
39
|
+
|
|
31
40
|
# ANSI color codes
|
|
32
41
|
COLORS = {
|
|
33
42
|
"reset": "\033[0m",
|
|
@@ -93,8 +102,15 @@ class DoctorReporter:
|
|
|
93
102
|
symbol = self.STATUS_SYMBOLS.get(result.status, "?")
|
|
94
103
|
color = self._get_status_color(result.status)
|
|
95
104
|
|
|
105
|
+
# Add severity indicator if present (issue #125)
|
|
106
|
+
severity_prefix = ""
|
|
107
|
+
if result.severity and result.severity != "medium":
|
|
108
|
+
severity_symbol = self.SEVERITY_SYMBOLS.get(result.severity, "")
|
|
109
|
+
if severity_symbol:
|
|
110
|
+
severity_prefix = f"{severity_symbol} {result.severity.upper()}: "
|
|
111
|
+
|
|
96
112
|
# Main result line
|
|
97
|
-
line = f"{indent_str}{symbol} {result.category}: "
|
|
113
|
+
line = f"{indent_str}{severity_prefix}{symbol} {result.category}: "
|
|
98
114
|
|
|
99
115
|
if result.status == OperationResult.SUCCESS:
|
|
100
116
|
line += self._color("OK", color)
|
|
@@ -111,6 +127,15 @@ class DoctorReporter:
|
|
|
111
127
|
message_indent = " " + indent_str
|
|
112
128
|
print(f"{message_indent}{result.message}")
|
|
113
129
|
|
|
130
|
+
# Explanation (issue #125)
|
|
131
|
+
if result.explanation:
|
|
132
|
+
# Format multi-line explanations with proper indentation
|
|
133
|
+
explanation_lines = result.explanation.split("\n")
|
|
134
|
+
for explanation_line in explanation_lines:
|
|
135
|
+
print(
|
|
136
|
+
f"{message_indent}{self._color(explanation_line.strip(), 'gray')}"
|
|
137
|
+
)
|
|
138
|
+
|
|
114
139
|
# Details (in verbose mode)
|
|
115
140
|
if self.verbose and result.details:
|
|
116
141
|
for key, value in result.details.items():
|
|
@@ -126,6 +151,11 @@ class DoctorReporter:
|
|
|
126
151
|
if result.fix_description:
|
|
127
152
|
print(f"{fix_indent} {self._color(result.fix_description, 'gray')}")
|
|
128
153
|
|
|
154
|
+
# Documentation link (issue #125)
|
|
155
|
+
if result.doc_link:
|
|
156
|
+
doc_indent = " " + indent_str
|
|
157
|
+
print(f"{doc_indent}{self._color('📖 Docs:', 'blue')} {result.doc_link}")
|
|
158
|
+
|
|
129
159
|
# Sub-results (in verbose mode)
|
|
130
160
|
if self.verbose and result.sub_results:
|
|
131
161
|
for sub_result in result.sub_results:
|
|
@@ -51,9 +51,14 @@ class DiagnosticResult:
|
|
|
51
51
|
fix_description: Optional[str] = None
|
|
52
52
|
sub_results: List["DiagnosticResult"] = field(default_factory=list)
|
|
53
53
|
|
|
54
|
+
# Enhanced troubleshooting fields (issue #125)
|
|
55
|
+
explanation: str = "" # What this check means and why it matters
|
|
56
|
+
severity: str = "medium" # critical, high, medium, low, info
|
|
57
|
+
doc_link: str = "" # Link to relevant documentation
|
|
58
|
+
|
|
54
59
|
def to_dict(self) -> Dict[str, Any]:
|
|
55
60
|
"""Convert to dictionary for JSON serialization."""
|
|
56
|
-
|
|
61
|
+
result = {
|
|
57
62
|
"category": self.category,
|
|
58
63
|
"status": self.status.value,
|
|
59
64
|
"message": self.message,
|
|
@@ -62,6 +67,14 @@ class DiagnosticResult:
|
|
|
62
67
|
"fix_description": self.fix_description,
|
|
63
68
|
"sub_results": [r.to_dict() for r in self.sub_results],
|
|
64
69
|
}
|
|
70
|
+
# Include enhanced fields if present
|
|
71
|
+
if self.explanation:
|
|
72
|
+
result["explanation"] = self.explanation
|
|
73
|
+
if self.severity != "medium":
|
|
74
|
+
result["severity"] = self.severity
|
|
75
|
+
if self.doc_link:
|
|
76
|
+
result["doc_link"] = self.doc_link
|
|
77
|
+
return result
|
|
65
78
|
|
|
66
79
|
@property
|
|
67
80
|
def has_issues(self) -> bool:
|
|
@@ -95,10 +95,10 @@ class DaemonManager:
|
|
|
95
95
|
def _get_default_log_file(self) -> Path:
|
|
96
96
|
"""Get default log file path with port number to support multiple daemons."""
|
|
97
97
|
project_root = Path.cwd()
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
logs_dir = project_root / ".claude-mpm" / "logs"
|
|
99
|
+
logs_dir.mkdir(parents=True, exist_ok=True)
|
|
100
100
|
# Include port in filename to support multiple daemon instances
|
|
101
|
-
return
|
|
101
|
+
return logs_dir / f"monitor-daemon-{self.port}.log"
|
|
102
102
|
|
|
103
103
|
def cleanup_port_conflicts(self, max_retries: int = 3) -> bool:
|
|
104
104
|
"""Clean up any processes using the daemon port.
|
|
@@ -649,14 +649,24 @@ class DaemonManager:
|
|
|
649
649
|
|
|
650
650
|
# Wait for the subprocess to write its PID file and bind to port
|
|
651
651
|
# The subprocess will write the PID file after it starts successfully
|
|
652
|
-
|
|
652
|
+
# Allow configuration via environment variable (default 30s to account for agent/skill sync)
|
|
653
|
+
max_wait = int(os.environ.get("CLAUDE_MPM_MONITOR_TIMEOUT", "30"))
|
|
653
654
|
start_time = time.time()
|
|
654
655
|
pid_file_found = False
|
|
655
656
|
port_bound = False
|
|
657
|
+
last_progress_log = 0.0
|
|
656
658
|
|
|
657
659
|
self.logger.debug(f"Waiting up to {max_wait}s for daemon to start...")
|
|
658
660
|
|
|
659
661
|
while time.time() - start_time < max_wait:
|
|
662
|
+
# Log progress every 5 seconds to show we're waiting
|
|
663
|
+
elapsed = time.time() - start_time
|
|
664
|
+
if elapsed - last_progress_log >= 5.0:
|
|
665
|
+
self.logger.info(
|
|
666
|
+
f"Waiting for monitor daemon... ({int(elapsed)}s elapsed, syncing agents/skills)"
|
|
667
|
+
)
|
|
668
|
+
last_progress_log = elapsed
|
|
669
|
+
|
|
660
670
|
# Check if process is still running
|
|
661
671
|
returncode = process.poll()
|
|
662
672
|
if returncode is not None:
|
|
@@ -976,6 +986,7 @@ class DaemonManager:
|
|
|
976
986
|
os.dup2(null_in.fileno(), sys.stdin.fileno())
|
|
977
987
|
|
|
978
988
|
# Redirect stdout and stderr to log file
|
|
989
|
+
# Ensure logs directory exists
|
|
979
990
|
self.log_file.parent.mkdir(parents=True, exist_ok=True)
|
|
980
991
|
with self.log_file.open("a") as log_out:
|
|
981
992
|
os.dup2(log_out.fileno(), sys.stdout.fileno())
|
|
@@ -128,12 +128,16 @@ class DaemonLifecycle:
|
|
|
128
128
|
# Redirect stdout and stderr
|
|
129
129
|
if self.log_file:
|
|
130
130
|
# Redirect to log file
|
|
131
|
+
# Ensure logs directory exists
|
|
132
|
+
self.log_file.parent.mkdir(parents=True, exist_ok=True)
|
|
131
133
|
with self.log_file.open("a") as log_out:
|
|
132
134
|
os.dup2(log_out.fileno(), sys.stdout.fileno())
|
|
133
135
|
os.dup2(log_out.fileno(), sys.stderr.fileno())
|
|
134
136
|
else:
|
|
135
137
|
# Default to a daemon log file instead of /dev/null for errors
|
|
136
|
-
default_log =
|
|
138
|
+
default_log = (
|
|
139
|
+
Path.home() / ".claude-mpm" / "logs" / "monitor-daemon.log"
|
|
140
|
+
)
|
|
137
141
|
default_log.parent.mkdir(parents=True, exist_ok=True)
|
|
138
142
|
with default_log.open("a") as log_out:
|
|
139
143
|
os.dup2(log_out.fileno(), sys.stdout.fileno())
|
|
@@ -475,7 +479,9 @@ class DaemonLifecycle:
|
|
|
475
479
|
try:
|
|
476
480
|
# If no log file specified, create a default one
|
|
477
481
|
if not self.log_file:
|
|
478
|
-
default_log =
|
|
482
|
+
default_log = (
|
|
483
|
+
Path.home() / ".claude-mpm" / "logs" / "monitor-daemon.log"
|
|
484
|
+
)
|
|
479
485
|
default_log.parent.mkdir(parents=True, exist_ok=True)
|
|
480
486
|
self.log_file = default_log
|
|
481
487
|
|