claude-mpm 5.1.9__py3-none-any.whl → 5.4.14__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.

Potentially problematic release.


This version of claude-mpm might be problematic. Click here for more details.

Files changed (162) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/__init__.py +4 -0
  3. claude_mpm/agents/PM_INSTRUCTIONS.md +85 -0
  4. claude_mpm/agents/agent_loader.py +13 -44
  5. claude_mpm/agents/templates/circuit-breakers.md +138 -1
  6. claude_mpm/cli/__main__.py +4 -0
  7. claude_mpm/cli/commands/agent_state_manager.py +8 -17
  8. claude_mpm/cli/commands/auto_configure.py +210 -25
  9. claude_mpm/cli/commands/config.py +88 -2
  10. claude_mpm/cli/commands/configure.py +1097 -158
  11. claude_mpm/cli/commands/configure_agent_display.py +15 -6
  12. claude_mpm/cli/commands/mpm_init/core.py +160 -46
  13. claude_mpm/cli/commands/mpm_init/knowledge_extractor.py +481 -0
  14. claude_mpm/cli/commands/mpm_init/prompts.py +280 -0
  15. claude_mpm/cli/commands/skills.py +21 -2
  16. claude_mpm/cli/commands/summarize.py +413 -0
  17. claude_mpm/cli/executor.py +11 -3
  18. claude_mpm/cli/parsers/base_parser.py +5 -0
  19. claude_mpm/cli/parsers/config_parser.py +153 -83
  20. claude_mpm/cli/parsers/skills_parser.py +3 -2
  21. claude_mpm/cli/startup.py +333 -89
  22. claude_mpm/commands/mpm-config.md +266 -0
  23. claude_mpm/commands/{mpm-ticket-organize.md → mpm-organize.md} +4 -5
  24. claude_mpm/config/agent_sources.py +27 -0
  25. claude_mpm/core/framework/formatters/content_formatter.py +3 -13
  26. claude_mpm/core/framework/loaders/agent_loader.py +8 -5
  27. claude_mpm/core/framework_loader.py +4 -2
  28. claude_mpm/core/logger.py +13 -0
  29. claude_mpm/core/socketio_pool.py +3 -3
  30. claude_mpm/core/unified_agent_registry.py +5 -15
  31. claude_mpm/hooks/claude_hooks/correlation_manager.py +60 -0
  32. claude_mpm/hooks/claude_hooks/event_handlers.py +206 -78
  33. claude_mpm/hooks/claude_hooks/hook_handler.py +6 -0
  34. claude_mpm/hooks/claude_hooks/installer.py +33 -10
  35. claude_mpm/hooks/claude_hooks/memory_integration.py +26 -9
  36. claude_mpm/hooks/claude_hooks/response_tracking.py +2 -3
  37. claude_mpm/hooks/claude_hooks/services/connection_manager.py +4 -0
  38. claude_mpm/hooks/memory_integration_hook.py +46 -1
  39. claude_mpm/init.py +0 -19
  40. claude_mpm/scripts/claude-hook-handler.sh +58 -18
  41. claude_mpm/scripts/launch_monitor.py +93 -13
  42. claude_mpm/services/agents/agent_recommendation_service.py +278 -0
  43. claude_mpm/services/agents/agent_review_service.py +280 -0
  44. claude_mpm/services/agents/deployment/agent_discovery_service.py +2 -3
  45. claude_mpm/services/agents/deployment/agent_template_builder.py +4 -2
  46. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +78 -9
  47. claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +335 -53
  48. claude_mpm/services/agents/git_source_manager.py +34 -0
  49. claude_mpm/services/agents/loading/base_agent_manager.py +1 -13
  50. claude_mpm/services/agents/sources/git_source_sync_service.py +8 -1
  51. claude_mpm/services/agents/toolchain_detector.py +10 -6
  52. claude_mpm/services/analysis/__init__.py +11 -1
  53. claude_mpm/services/analysis/clone_detector.py +1030 -0
  54. claude_mpm/services/command_deployment_service.py +71 -10
  55. claude_mpm/services/event_bus/config.py +3 -1
  56. claude_mpm/services/git/git_operations_service.py +93 -8
  57. claude_mpm/services/monitor/daemon.py +9 -2
  58. claude_mpm/services/monitor/daemon_manager.py +39 -3
  59. claude_mpm/services/monitor/server.py +225 -19
  60. claude_mpm/services/self_upgrade_service.py +120 -12
  61. claude_mpm/services/skills/__init__.py +3 -0
  62. claude_mpm/services/skills/git_skill_source_manager.py +32 -2
  63. claude_mpm/services/skills/selective_skill_deployer.py +230 -0
  64. claude_mpm/services/skills/skill_to_agent_mapper.py +406 -0
  65. claude_mpm/services/skills_deployer.py +64 -3
  66. claude_mpm/services/socketio/event_normalizer.py +15 -1
  67. claude_mpm/services/socketio/server/core.py +160 -21
  68. claude_mpm/services/version_control/git_operations.py +103 -0
  69. claude_mpm/utils/agent_filters.py +17 -44
  70. {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.14.dist-info}/METADATA +47 -84
  71. {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.14.dist-info}/RECORD +76 -150
  72. claude_mpm-5.4.14.dist-info/entry_points.txt +5 -0
  73. claude_mpm-5.4.14.dist-info/licenses/LICENSE +94 -0
  74. claude_mpm-5.4.14.dist-info/licenses/LICENSE-FAQ.md +153 -0
  75. claude_mpm/agents/BASE_AGENT_TEMPLATE.md +0 -292
  76. claude_mpm/agents/BASE_DOCUMENTATION.md +0 -53
  77. claude_mpm/agents/BASE_ENGINEER.md +0 -658
  78. claude_mpm/agents/BASE_OPS.md +0 -219
  79. claude_mpm/agents/BASE_PM.md +0 -480
  80. claude_mpm/agents/BASE_PROMPT_ENGINEER.md +0 -787
  81. claude_mpm/agents/BASE_QA.md +0 -167
  82. claude_mpm/agents/BASE_RESEARCH.md +0 -53
  83. claude_mpm/agents/base_agent.json +0 -31
  84. claude_mpm/agents/base_agent_loader.py +0 -601
  85. claude_mpm/cli/ticket_cli.py +0 -35
  86. claude_mpm/commands/mpm-config-view.md +0 -150
  87. claude_mpm/dashboard/analysis_runner.py +0 -455
  88. claude_mpm/dashboard/index.html +0 -13
  89. claude_mpm/dashboard/open_dashboard.py +0 -66
  90. claude_mpm/dashboard/static/css/activity.css +0 -1958
  91. claude_mpm/dashboard/static/css/connection-status.css +0 -370
  92. claude_mpm/dashboard/static/css/dashboard.css +0 -4701
  93. claude_mpm/dashboard/static/js/components/activity-tree.js +0 -1871
  94. claude_mpm/dashboard/static/js/components/agent-hierarchy.js +0 -777
  95. claude_mpm/dashboard/static/js/components/agent-inference.js +0 -956
  96. claude_mpm/dashboard/static/js/components/build-tracker.js +0 -333
  97. claude_mpm/dashboard/static/js/components/code-simple.js +0 -857
  98. claude_mpm/dashboard/static/js/components/connection-debug.js +0 -654
  99. claude_mpm/dashboard/static/js/components/diff-viewer.js +0 -891
  100. claude_mpm/dashboard/static/js/components/event-processor.js +0 -542
  101. claude_mpm/dashboard/static/js/components/event-viewer.js +0 -1155
  102. claude_mpm/dashboard/static/js/components/export-manager.js +0 -368
  103. claude_mpm/dashboard/static/js/components/file-change-tracker.js +0 -443
  104. claude_mpm/dashboard/static/js/components/file-change-viewer.js +0 -690
  105. claude_mpm/dashboard/static/js/components/file-tool-tracker.js +0 -724
  106. claude_mpm/dashboard/static/js/components/file-viewer.js +0 -580
  107. claude_mpm/dashboard/static/js/components/hud-library-loader.js +0 -211
  108. claude_mpm/dashboard/static/js/components/hud-manager.js +0 -671
  109. claude_mpm/dashboard/static/js/components/hud-visualizer.js +0 -1718
  110. claude_mpm/dashboard/static/js/components/module-viewer.js +0 -2764
  111. claude_mpm/dashboard/static/js/components/session-manager.js +0 -579
  112. claude_mpm/dashboard/static/js/components/socket-manager.js +0 -368
  113. claude_mpm/dashboard/static/js/components/ui-state-manager.js +0 -749
  114. claude_mpm/dashboard/static/js/components/unified-data-viewer.js +0 -1824
  115. claude_mpm/dashboard/static/js/components/working-directory.js +0 -920
  116. claude_mpm/dashboard/static/js/connection-manager.js +0 -536
  117. claude_mpm/dashboard/static/js/dashboard.js +0 -1914
  118. claude_mpm/dashboard/static/js/extension-error-handler.js +0 -164
  119. claude_mpm/dashboard/static/js/socket-client.js +0 -1474
  120. claude_mpm/dashboard/static/js/tab-isolation-fix.js +0 -185
  121. claude_mpm/dashboard/static/socket.io.min.js +0 -7
  122. claude_mpm/dashboard/static/socket.io.v4.8.1.backup.js +0 -7
  123. claude_mpm/dashboard/templates/code_simple.html +0 -153
  124. claude_mpm/dashboard/templates/index.html +0 -606
  125. claude_mpm/dashboard/test_dashboard.html +0 -372
  126. claude_mpm/scripts/mcp_server.py +0 -75
  127. claude_mpm/scripts/mcp_wrapper.py +0 -39
  128. claude_mpm/services/mcp_gateway/__init__.py +0 -159
  129. claude_mpm/services/mcp_gateway/auto_configure.py +0 -369
  130. claude_mpm/services/mcp_gateway/config/__init__.py +0 -17
  131. claude_mpm/services/mcp_gateway/config/config_loader.py +0 -296
  132. claude_mpm/services/mcp_gateway/config/config_schema.py +0 -243
  133. claude_mpm/services/mcp_gateway/config/configuration.py +0 -429
  134. claude_mpm/services/mcp_gateway/core/__init__.py +0 -43
  135. claude_mpm/services/mcp_gateway/core/base.py +0 -312
  136. claude_mpm/services/mcp_gateway/core/exceptions.py +0 -253
  137. claude_mpm/services/mcp_gateway/core/interfaces.py +0 -443
  138. claude_mpm/services/mcp_gateway/core/process_pool.py +0 -977
  139. claude_mpm/services/mcp_gateway/core/singleton_manager.py +0 -315
  140. claude_mpm/services/mcp_gateway/core/startup_verification.py +0 -316
  141. claude_mpm/services/mcp_gateway/main.py +0 -589
  142. claude_mpm/services/mcp_gateway/registry/__init__.py +0 -12
  143. claude_mpm/services/mcp_gateway/registry/service_registry.py +0 -412
  144. claude_mpm/services/mcp_gateway/registry/tool_registry.py +0 -489
  145. claude_mpm/services/mcp_gateway/server/__init__.py +0 -15
  146. claude_mpm/services/mcp_gateway/server/mcp_gateway.py +0 -414
  147. claude_mpm/services/mcp_gateway/server/stdio_handler.py +0 -372
  148. claude_mpm/services/mcp_gateway/server/stdio_server.py +0 -712
  149. claude_mpm/services/mcp_gateway/tools/__init__.py +0 -36
  150. claude_mpm/services/mcp_gateway/tools/base_adapter.py +0 -485
  151. claude_mpm/services/mcp_gateway/tools/document_summarizer.py +0 -789
  152. claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +0 -654
  153. claude_mpm/services/mcp_gateway/tools/health_check_tool.py +0 -456
  154. claude_mpm/services/mcp_gateway/tools/hello_world.py +0 -551
  155. claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +0 -555
  156. claude_mpm/services/mcp_gateway/utils/__init__.py +0 -14
  157. claude_mpm/services/mcp_gateway/utils/package_version_checker.py +0 -160
  158. claude_mpm/services/mcp_gateway/utils/update_preferences.py +0 -170
  159. claude_mpm-5.1.9.dist-info/entry_points.txt +0 -10
  160. claude_mpm-5.1.9.dist-info/licenses/LICENSE +0 -21
  161. {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.14.dist-info}/WHEEL +0 -0
  162. {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.14.dist-info}/top_level.txt +0 -0
@@ -9,6 +9,7 @@ import os
9
9
  import re
10
10
  import subprocess
11
11
  import sys
12
+ import uuid
12
13
  from datetime import datetime, timezone
13
14
  from pathlib import Path
14
15
  from typing import Optional
@@ -94,22 +95,29 @@ class EventHandlers:
94
95
  }
95
96
 
96
97
  # Store prompt for comprehensive response tracking if enabled
97
- if (
98
- self.hook_handler.response_tracking_manager.response_tracking_enabled
99
- and self.hook_handler.response_tracking_manager.track_all_interactions
100
- ):
101
- session_id = event.get("session_id", "")
102
- if session_id:
103
- self.hook_handler.pending_prompts[session_id] = {
104
- "prompt": prompt,
105
- "timestamp": datetime.now(timezone.utc).isoformat(),
106
- "working_directory": working_dir,
107
- }
108
- if DEBUG:
109
- print(
110
- f"Stored prompt for comprehensive tracking: session {session_id[:8]}...",
111
- file=sys.stderr,
112
- )
98
+ try:
99
+ rtm = getattr(self.hook_handler, "response_tracking_manager", None)
100
+ if (
101
+ rtm
102
+ and getattr(rtm, "response_tracking_enabled", False)
103
+ and getattr(rtm, "track_all_interactions", False)
104
+ ):
105
+ session_id = event.get("session_id", "")
106
+ if session_id:
107
+ pending_prompts = getattr(self.hook_handler, "pending_prompts", {})
108
+ pending_prompts[session_id] = {
109
+ "prompt": prompt,
110
+ "timestamp": datetime.now(timezone.utc).isoformat(),
111
+ "working_directory": working_dir,
112
+ }
113
+ if DEBUG:
114
+ print(
115
+ f"Stored prompt for comprehensive tracking: session {session_id[:8]}...",
116
+ file=sys.stderr,
117
+ )
118
+ except Exception:
119
+ # Response tracking is optional - silently continue if it fails
120
+ pass
113
121
 
114
122
  # Emit normalized event (namespace no longer needed with normalized events)
115
123
  self.hook_handler._emit_socketio_event("", "user_prompt", prompt_data)
@@ -134,6 +142,9 @@ class EventHandlers:
134
142
  tool_name = event.get("tool_name", "")
135
143
  tool_input = event.get("tool_input", {})
136
144
 
145
+ # Generate unique tool call ID for correlation with post_tool event
146
+ tool_call_id = str(uuid.uuid4())
147
+
137
148
  # Extract key parameters based on tool type
138
149
  tool_params = extract_tool_parameters(tool_name, tool_input)
139
150
 
@@ -144,6 +155,8 @@ class EventHandlers:
144
155
  working_dir = event.get("cwd", "")
145
156
  git_branch = self._get_git_branch(working_dir) if working_dir else "Unknown"
146
157
 
158
+ timestamp = datetime.now(timezone.utc).isoformat()
159
+
147
160
  pre_tool_data = {
148
161
  "tool_name": tool_name,
149
162
  "operation_type": operation_type,
@@ -151,15 +164,27 @@ class EventHandlers:
151
164
  "session_id": event.get("session_id", ""),
152
165
  "working_directory": working_dir,
153
166
  "git_branch": git_branch,
154
- "timestamp": datetime.now(timezone.utc).isoformat(),
167
+ "timestamp": timestamp,
155
168
  "parameter_count": len(tool_input) if isinstance(tool_input, dict) else 0,
156
169
  "is_file_operation": tool_name
157
170
  in ["Write", "Edit", "MultiEdit", "Read", "LS", "Glob"],
158
171
  "is_execution": tool_name in ["Bash", "NotebookEdit"],
159
172
  "is_delegation": tool_name == "Task",
160
173
  "security_risk": assess_security_risk(tool_name, tool_input),
174
+ "correlation_id": tool_call_id, # Add correlation_id for pre/post correlation
161
175
  }
162
176
 
177
+ # Store tool_call_id using CorrelationManager for cross-process retrieval
178
+ if session_id:
179
+ from .correlation_manager import CorrelationManager
180
+
181
+ CorrelationManager.store(session_id, tool_call_id, tool_name)
182
+ if DEBUG:
183
+ print(
184
+ f" - Generated tool_call_id: {tool_call_id[:8]}... for session {session_id[:8]}...",
185
+ file=sys.stderr,
186
+ )
187
+
163
188
  # Add delegation-specific data if this is a Task tool
164
189
  if tool_name == "Task" and isinstance(tool_input, dict):
165
190
  self._handle_task_delegation(tool_input, pre_tool_data, session_id)
@@ -226,8 +251,11 @@ class EventHandlers:
226
251
  f" - Request data keys: {list(request_data.keys())}",
227
252
  file=sys.stderr,
228
253
  )
254
+ delegation_requests = getattr(
255
+ self.hook_handler, "delegation_requests", {}
256
+ )
229
257
  print(
230
- f" - delegation_requests size: {len(self.hook_handler.delegation_requests)}",
258
+ f" - delegation_requests size: {len(delegation_requests)}",
231
259
  file=sys.stderr,
232
260
  )
233
261
 
@@ -239,9 +267,13 @@ class EventHandlers:
239
267
  )
240
268
 
241
269
  # Trigger memory pre-delegation hook
242
- self.hook_handler.memory_hook_manager.trigger_pre_delegation_hook(
243
- agent_type, tool_input, session_id
244
- )
270
+ try:
271
+ mhm = getattr(self.hook_handler, "memory_hook_manager", None)
272
+ if mhm and hasattr(mhm, "trigger_pre_delegation_hook"):
273
+ mhm.trigger_pre_delegation_hook(agent_type, tool_input, session_id)
274
+ except Exception:
275
+ # Memory hooks are optional
276
+ pass
245
277
 
246
278
  # Emit a subagent_start event for better tracking
247
279
  subagent_start_data = {
@@ -375,6 +407,7 @@ class EventHandlers:
375
407
  """
376
408
  tool_name = event.get("tool_name", "")
377
409
  exit_code = event.get("exit_code", 0)
410
+ session_id = event.get("session_id", "")
378
411
 
379
412
  # Extract result data
380
413
  result_data = extract_tool_results(event)
@@ -386,6 +419,16 @@ class EventHandlers:
386
419
  working_dir = event.get("cwd", "")
387
420
  git_branch = self._get_git_branch(working_dir) if working_dir else "Unknown"
388
421
 
422
+ # Retrieve tool_call_id using CorrelationManager for cross-process correlation
423
+ from .correlation_manager import CorrelationManager
424
+
425
+ tool_call_id = CorrelationManager.retrieve(session_id) if session_id else None
426
+ if DEBUG and tool_call_id:
427
+ print(
428
+ f" - Retrieved tool_call_id: {tool_call_id[:8]}... for session {session_id[:8]}...",
429
+ file=sys.stderr,
430
+ )
431
+
389
432
  post_tool_data = {
390
433
  "tool_name": tool_name,
391
434
  "exit_code": exit_code,
@@ -399,7 +442,7 @@ class EventHandlers:
399
442
  ),
400
443
  "duration_ms": duration,
401
444
  "result_summary": result_data,
402
- "session_id": event.get("session_id", ""),
445
+ "session_id": session_id,
403
446
  "working_directory": working_dir,
404
447
  "git_branch": git_branch,
405
448
  "timestamp": datetime.now(timezone.utc).isoformat(),
@@ -412,20 +455,37 @@ class EventHandlers:
412
455
  ),
413
456
  }
414
457
 
458
+ # Add correlation_id if available for correlation with pre_tool
459
+ if tool_call_id:
460
+ post_tool_data["correlation_id"] = tool_call_id
461
+
415
462
  # Handle Task delegation completion for memory hooks and response tracking
416
463
  if tool_name == "Task":
417
464
  session_id = event.get("session_id", "")
418
465
  agent_type = self.hook_handler._get_delegation_agent_type(session_id)
419
466
 
420
467
  # Trigger memory post-delegation hook
421
- self.hook_handler.memory_hook_manager.trigger_post_delegation_hook(
422
- agent_type, event, session_id
423
- )
468
+ try:
469
+ mhm = getattr(self.hook_handler, "memory_hook_manager", None)
470
+ if mhm and hasattr(mhm, "trigger_post_delegation_hook"):
471
+ mhm.trigger_post_delegation_hook(agent_type, event, session_id)
472
+ except Exception:
473
+ # Memory hooks are optional
474
+ pass
424
475
 
425
476
  # Track agent response if response tracking is enabled
426
- self.hook_handler.response_tracking_manager.track_agent_response(
427
- session_id, agent_type, event, self.hook_handler.delegation_requests
428
- )
477
+ try:
478
+ rtm = getattr(self.hook_handler, "response_tracking_manager", None)
479
+ if rtm and hasattr(rtm, "track_agent_response"):
480
+ delegation_requests = getattr(
481
+ self.hook_handler, "delegation_requests", {}
482
+ )
483
+ rtm.track_agent_response(
484
+ session_id, agent_type, event, delegation_requests
485
+ )
486
+ except Exception:
487
+ # Response tracking is optional
488
+ pass
429
489
 
430
490
  self.hook_handler._emit_socketio_event("", "post_tool", post_tool_data)
431
491
 
@@ -486,9 +546,14 @@ class EventHandlers:
486
546
  self._log_stop_event_debug(event, session_id, metadata)
487
547
 
488
548
  # Track response if enabled
489
- self.hook_handler.response_tracking_manager.track_stop_response(
490
- event, session_id, metadata, self.hook_handler.pending_prompts
491
- )
549
+ try:
550
+ rtm = getattr(self.hook_handler, "response_tracking_manager", None)
551
+ if rtm and hasattr(rtm, "track_stop_response"):
552
+ pending_prompts = getattr(self.hook_handler, "pending_prompts", {})
553
+ rtm.track_stop_response(event, session_id, metadata, pending_prompts)
554
+ except Exception:
555
+ # Response tracking is optional
556
+ pass
492
557
 
493
558
  # Emit stop event to Socket.IO
494
559
  self._emit_stop_event(event, session_id, metadata)
@@ -511,15 +576,27 @@ class EventHandlers:
511
576
  self, event: dict, session_id: str, metadata: dict
512
577
  ) -> None:
513
578
  """Log debug information for stop events."""
579
+ try:
580
+ rtm = getattr(self.hook_handler, "response_tracking_manager", None)
581
+ tracking_enabled = (
582
+ getattr(rtm, "response_tracking_enabled", False) if rtm else False
583
+ )
584
+ tracker_exists = (
585
+ getattr(rtm, "response_tracker", None) is not None if rtm else False
586
+ )
587
+
588
+ print(
589
+ f" - response_tracking_enabled: {tracking_enabled}",
590
+ file=sys.stderr,
591
+ )
592
+ print(
593
+ f" - response_tracker exists: {tracker_exists}",
594
+ file=sys.stderr,
595
+ )
596
+ except Exception:
597
+ # If debug logging fails, just skip it
598
+ pass
514
599
 
515
- print(
516
- f" - response_tracking_enabled: {self.hook_handler.response_tracking_manager.response_tracking_enabled}",
517
- file=sys.stderr,
518
- )
519
- print(
520
- f" - response_tracker exists: {self.hook_handler.response_tracking_manager.response_tracker is not None}",
521
- file=sys.stderr,
522
- )
523
600
  print(
524
601
  f" - session_id: {session_id[:8] if session_id else 'None'}...",
525
602
  file=sys.stderr,
@@ -567,15 +644,22 @@ class EventHandlers:
567
644
  git_branch: str,
568
645
  ):
569
646
  """Handle response tracking for subagent stop events with fuzzy matching."""
570
- if not (
571
- self.hook_handler.response_tracking_manager.response_tracking_enabled
572
- and self.hook_handler.response_tracking_manager.response_tracker
573
- ):
647
+ try:
648
+ rtm = getattr(self.hook_handler, "response_tracking_manager", None)
649
+ if not (
650
+ rtm
651
+ and getattr(rtm, "response_tracking_enabled", False)
652
+ and getattr(rtm, "response_tracker", None)
653
+ ):
654
+ return
655
+ except Exception:
656
+ # Response tracking is optional
574
657
  return
575
658
 
576
659
  try:
577
660
  # Get the original request data (with fuzzy matching fallback)
578
- request_info = self.hook_handler.delegation_requests.get(session_id)
661
+ delegation_requests = getattr(self.hook_handler, "delegation_requests", {})
662
+ request_info = delegation_requests.get(session_id)
579
663
 
580
664
  # If exact match fails, try partial matching
581
665
  if not request_info and session_id:
@@ -585,7 +669,7 @@ class EventHandlers:
585
669
  file=sys.stderr,
586
670
  )
587
671
  # Try to find a session that matches the first 8-16 characters
588
- for stored_sid in list(self.hook_handler.delegation_requests.keys()):
672
+ for stored_sid in list(delegation_requests.keys()):
589
673
  if (
590
674
  stored_sid.startswith(session_id[:8])
591
675
  or session_id.startswith(stored_sid[:8])
@@ -600,17 +684,13 @@ class EventHandlers:
600
684
  f" - ✅ Fuzzy match found: {stored_sid[:16]}...",
601
685
  file=sys.stderr,
602
686
  )
603
- request_info = self.hook_handler.delegation_requests.get(
604
- stored_sid
605
- )
687
+ request_info = delegation_requests.get(stored_sid)
606
688
  # Update the key to use the current session_id for consistency
607
689
  if request_info:
608
- self.hook_handler.delegation_requests[session_id] = (
609
- request_info
610
- )
690
+ delegation_requests[session_id] = request_info
611
691
  # Optionally remove the old key to avoid duplicates
612
692
  if stored_sid != session_id:
613
- del self.hook_handler.delegation_requests[stored_sid]
693
+ del delegation_requests[stored_sid]
614
694
  break
615
695
 
616
696
  if request_info:
@@ -658,23 +738,31 @@ class EventHandlers:
658
738
  )
659
739
 
660
740
  # Track the response
661
- file_path = self.hook_handler.response_tracking_manager.response_tracker.track_response(
662
- agent_name=agent_type,
663
- request=full_request,
664
- response=response_text,
665
- session_id=session_id,
666
- metadata=metadata,
741
+ rtm = getattr(self.hook_handler, "response_tracking_manager", None)
742
+ response_tracker = (
743
+ getattr(rtm, "response_tracker", None) if rtm else None
667
744
  )
668
-
669
- if file_path and DEBUG:
670
- print(
671
- f"✅ Tracked {agent_type} agent response on SubagentStop: {file_path.name}",
672
- file=sys.stderr,
745
+ if response_tracker and hasattr(response_tracker, "track_response"):
746
+ file_path = response_tracker.track_response(
747
+ agent_name=agent_type,
748
+ request=full_request,
749
+ response=response_text,
750
+ session_id=session_id,
751
+ metadata=metadata,
673
752
  )
674
753
 
754
+ if file_path and DEBUG:
755
+ print(
756
+ f"✅ Tracked {agent_type} agent response on SubagentStop: {file_path.name}",
757
+ file=sys.stderr,
758
+ )
759
+
675
760
  # Clean up the request data
676
- if session_id in self.hook_handler.delegation_requests:
677
- del self.hook_handler.delegation_requests[session_id]
761
+ delegation_requests = getattr(
762
+ self.hook_handler, "delegation_requests", {}
763
+ )
764
+ if session_id in delegation_requests:
765
+ del delegation_requests[session_id]
678
766
 
679
767
  elif DEBUG:
680
768
  print(
@@ -698,9 +786,14 @@ class EventHandlers:
698
786
  - Essential for comprehensive monitoring of Claude interactions
699
787
  """
700
788
  # Track the response for logging
701
- self.hook_handler.response_tracking_manager.track_assistant_response(
702
- event, self.hook_handler.pending_prompts
703
- )
789
+ try:
790
+ rtm = getattr(self.hook_handler, "response_tracking_manager", None)
791
+ if rtm and hasattr(rtm, "track_assistant_response"):
792
+ pending_prompts = getattr(self.hook_handler, "pending_prompts", {})
793
+ rtm.track_assistant_response(event, pending_prompts)
794
+ except Exception:
795
+ # Response tracking is optional
796
+ pass
704
797
 
705
798
  # Get working directory and git branch
706
799
  working_dir = event.get("cwd", "")
@@ -730,16 +823,21 @@ class EventHandlers:
730
823
  }
731
824
 
732
825
  # Check if this is a response to a tracked prompt
733
- if session_id in self.hook_handler.pending_prompts:
734
- prompt_data = self.hook_handler.pending_prompts[session_id]
735
- assistant_response_data["original_prompt"] = prompt_data.get("prompt", "")[
736
- :200
737
- ]
738
- assistant_response_data["prompt_timestamp"] = prompt_data.get(
739
- "timestamp", ""
740
- )
741
- assistant_response_data["is_tracked_response"] = True
742
- else:
826
+ try:
827
+ pending_prompts = getattr(self.hook_handler, "pending_prompts", {})
828
+ if session_id in pending_prompts:
829
+ prompt_data = pending_prompts[session_id]
830
+ assistant_response_data["original_prompt"] = prompt_data.get(
831
+ "prompt", ""
832
+ )[:200]
833
+ assistant_response_data["prompt_timestamp"] = prompt_data.get(
834
+ "timestamp", ""
835
+ )
836
+ assistant_response_data["is_tracked_response"] = True
837
+ else:
838
+ assistant_response_data["is_tracked_response"] = False
839
+ except Exception:
840
+ # If prompt lookup fails, just mark as not tracked
743
841
  assistant_response_data["is_tracked_response"] = False
744
842
 
745
843
  # Debug logging
@@ -753,3 +851,33 @@ class EventHandlers:
753
851
  self.hook_handler._emit_socketio_event(
754
852
  "", "assistant_response", assistant_response_data
755
853
  )
854
+
855
+ def handle_session_start_fast(self, event):
856
+ """Handle session start events for tracking conversation sessions.
857
+
858
+ WHY track session starts:
859
+ - Provides visibility into new conversation sessions
860
+ - Enables tracking of session lifecycle and duration
861
+ - Useful for monitoring concurrent sessions and resource usage
862
+ """
863
+ session_id = event.get("session_id", "")
864
+ working_dir = event.get("cwd", "")
865
+ git_branch = self._get_git_branch(working_dir) if working_dir else "Unknown"
866
+
867
+ session_start_data = {
868
+ "session_id": session_id,
869
+ "working_directory": working_dir,
870
+ "git_branch": git_branch,
871
+ "timestamp": datetime.now(timezone.utc).isoformat(),
872
+ "hook_event_name": "SessionStart",
873
+ }
874
+
875
+ # Debug logging
876
+ if DEBUG:
877
+ print(
878
+ f"Hook handler: Processing SessionStart - session: '{session_id}'",
879
+ file=sys.stderr,
880
+ )
881
+
882
+ # Emit normalized event
883
+ self.hook_handler._emit_socketio_event("", "session_start", session_start_data)
@@ -304,6 +304,10 @@ class ClaudeHookHandler:
304
304
  # Perform periodic cleanup if needed
305
305
  if self.state_manager.increment_events_processed():
306
306
  self.state_manager.cleanup_old_entries()
307
+ # Also cleanup old correlation files
308
+ from .correlation_manager import CorrelationManager
309
+
310
+ CorrelationManager.cleanup_old()
307
311
  if DEBUG:
308
312
  print(
309
313
  f"🧹 Performed cleanup after {self.state_manager.events_processed} events",
@@ -413,6 +417,8 @@ class ClaudeHookHandler:
413
417
  "Notification": self.event_handlers.handle_notification_fast,
414
418
  "Stop": self.event_handlers.handle_stop_fast,
415
419
  "SubagentStop": self.event_handlers.handle_subagent_stop_fast,
420
+ "SubagentStart": self.event_handlers.handle_session_start_fast,
421
+ "SessionStart": self.event_handlers.handle_session_start_fast,
416
422
  "AssistantResponse": self.event_handlers.handle_assistant_response,
417
423
  }
418
424
 
@@ -202,8 +202,9 @@ main "$@"
202
202
  self.hooks_dir = self.claude_dir / "hooks" # Kept for backward compatibility
203
203
  # Use settings.json for hooks (Claude Code reads from this file)
204
204
  self.settings_file = self.claude_dir / "settings.json"
205
- # Keep reference to old file for migration
206
- self.old_settings_file = self.claude_dir / "settings.json"
205
+ # There is no legacy settings file - this was a bug where both pointed to same file
206
+ # Setting to None to disable cleanup that was deleting freshly installed hooks
207
+ self.old_settings_file = None
207
208
  self._claude_version: Optional[str] = None
208
209
  self._hook_script_path: Optional[Path] = None
209
210
 
@@ -462,7 +463,9 @@ main "$@"
462
463
 
463
464
  def _cleanup_old_settings(self) -> None:
464
465
  """Remove hooks from old settings.json file if present."""
465
- if not self.old_settings_file.exists():
466
+ # No-op: old_settings_file was pointing to same file as settings_file (bug)
467
+ # This was causing freshly installed hooks to be immediately deleted
468
+ if self.old_settings_file is None or not self.old_settings_file.exists():
466
469
  return
467
470
 
468
471
  try:
@@ -518,15 +521,31 @@ main "$@"
518
521
  }
519
522
  ]
520
523
 
521
- # Non-tool events don't need a matcher
522
- non_tool_events = ["UserPromptSubmit", "Stop", "SubagentStop", "SubagentStart"]
523
- for event_type in non_tool_events:
524
+ # Simple events (no subtypes, no matcher needed)
525
+ simple_events = ["Stop", "SubagentStop", "SubagentStart"]
526
+ for event_type in simple_events:
524
527
  settings["hooks"][event_type] = [
525
528
  {
526
529
  "hooks": [hook_command],
527
530
  }
528
531
  ]
529
532
 
533
+ # SessionStart needs matcher for subtypes (startup, resume)
534
+ settings["hooks"]["SessionStart"] = [
535
+ {
536
+ "matcher": "*", # Match all SessionStart subtypes
537
+ "hooks": [hook_command],
538
+ }
539
+ ]
540
+
541
+ # UserPromptSubmit needs matcher for potential subtypes
542
+ settings["hooks"]["UserPromptSubmit"] = [
543
+ {
544
+ "matcher": "*",
545
+ "hooks": [hook_command],
546
+ }
547
+ ]
548
+
530
549
  # Write settings to settings.json
531
550
  with self.settings_file.open("w") as f:
532
551
  json.dump(settings, f, indent=2)
@@ -650,9 +669,13 @@ main "$@"
650
669
  old_script.unlink()
651
670
  self.logger.info(f"Removed old deployed script: {old_script}")
652
671
 
653
- # Remove from Claude settings (both old and new locations)
654
- for settings_path in [self.settings_file, self.old_settings_file]:
655
- if settings_path.exists():
672
+ # Remove from Claude settings
673
+ settings_paths = [self.settings_file]
674
+ if self.old_settings_file is not None:
675
+ settings_paths.append(self.old_settings_file)
676
+
677
+ for settings_path in settings_paths:
678
+ if settings_path and settings_path.exists():
656
679
  with settings_path.open() as f:
657
680
  settings = json.load(f)
658
681
 
@@ -763,7 +786,7 @@ main "$@"
763
786
  pass
764
787
 
765
788
  # Also check old settings file
766
- if self.old_settings_file.exists():
789
+ if self.old_settings_file is not None and self.old_settings_file.exists():
767
790
  try:
768
791
  with self.old_settings_file.open() as f:
769
792
  old_settings = json.load(f)
@@ -6,18 +6,35 @@ including pre and post delegation hooks.
6
6
  """
7
7
 
8
8
  import logging
9
+ import os
9
10
  import sys
10
11
 
11
- # Reconfigure logging to INFO level BEFORE kuzu-memory imports
12
+ # Install-type-aware logging configuration BEFORE kuzu-memory imports
12
13
  # This overrides kuzu-memory's WARNING-level basicConfig (fixes 1M-445)
13
- logging.basicConfig(
14
- level=logging.INFO,
15
- format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
16
- force=True, # Python 3.8+ - reconfigures root logger
17
- stream=sys.stderr,
18
- )
19
-
20
- import os
14
+ # but respects production install silence
15
+ try:
16
+ from claude_mpm.core.unified_paths import DeploymentContext, PathContext
17
+
18
+ context = PathContext.detect_deployment_context()
19
+
20
+ # Only configure verbose logging for development/editable installs
21
+ # Production installs remain silent by default
22
+ if context in (DeploymentContext.DEVELOPMENT, DeploymentContext.EDITABLE_INSTALL):
23
+ logging.basicConfig(
24
+ level=logging.INFO,
25
+ format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
26
+ force=True, # Python 3.8+ - reconfigures root logger
27
+ stream=sys.stderr,
28
+ )
29
+ except ImportError:
30
+ # Fallback: if unified_paths not available, configure logging
31
+ # This maintains backward compatibility
32
+ logging.basicConfig(
33
+ level=logging.INFO,
34
+ format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
35
+ force=True,
36
+ stream=sys.stderr,
37
+ )
21
38
  from datetime import datetime, timezone
22
39
  from typing import Optional
23
40
 
@@ -18,8 +18,7 @@ DEBUG = os.environ.get("CLAUDE_MPM_HOOK_DEBUG", "true").lower() != "false"
18
18
 
19
19
  # Response tracking integration
20
20
  # NOTE: ResponseTracker import moved to _initialize_response_tracking() for lazy loading
21
- # This prevents unnecessary import of base_agent_loader and other heavy dependencies
22
- # when hooks don't need response tracking
21
+ # This prevents unnecessary import of heavy dependencies when hooks don't need response tracking
23
22
  RESPONSE_TRACKING_AVAILABLE = (
24
23
  True # Assume available, will check on actual initialization
25
24
  )
@@ -51,7 +50,7 @@ class ResponseTrackingManager:
51
50
  response tracking without code changes.
52
51
 
53
52
  NOTE: ResponseTracker is imported lazily here to avoid loading
54
- base_agent_loader and other heavy dependencies unless actually needed.
53
+ heavy dependencies unless actually needed.
55
54
  """
56
55
  try:
57
56
  # Lazy import of ResponseTracker to avoid unnecessary dependency loading
@@ -115,6 +115,9 @@ class ConnectionManagerService:
115
115
  - Fallback: HTTP POST for reliability when direct connection fails
116
116
  - Eliminates duplicate events from multiple emission paths
117
117
  """
118
+ # Extract tool_call_id from data if present for correlation
119
+ tool_call_id = data.get("tool_call_id")
120
+
118
121
  # Create event data for normalization
119
122
  raw_event = {
120
123
  "type": "hook",
@@ -123,6 +126,7 @@ class ConnectionManagerService:
123
126
  "data": data,
124
127
  "source": "claude_hooks", # Identify the source
125
128
  "session_id": data.get("sessionId"), # Include session if available
129
+ "correlation_id": tool_call_id, # Set from tool_call_id for event correlation
126
130
  }
127
131
 
128
132
  # Normalize the event using EventNormalizer for consistent schema