claude-mpm 3.4.26__py3-none-any.whl → 3.5.0__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.
Files changed (123) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/INSTRUCTIONS.md +182 -299
  3. claude_mpm/agents/agent_loader.py +283 -57
  4. claude_mpm/agents/agent_loader_integration.py +6 -9
  5. claude_mpm/agents/base_agent.json +2 -1
  6. claude_mpm/agents/base_agent_loader.py +1 -1
  7. claude_mpm/cli/__init__.py +6 -10
  8. claude_mpm/cli/commands/__init__.py +0 -2
  9. claude_mpm/cli/commands/agents.py +1 -1
  10. claude_mpm/cli/commands/memory.py +1 -1
  11. claude_mpm/cli/commands/run.py +12 -0
  12. claude_mpm/cli/parser.py +0 -13
  13. claude_mpm/cli/utils.py +1 -1
  14. claude_mpm/config/__init__.py +44 -2
  15. claude_mpm/config/agent_config.py +348 -0
  16. claude_mpm/config/paths.py +322 -0
  17. claude_mpm/constants.py +0 -1
  18. claude_mpm/core/__init__.py +2 -5
  19. claude_mpm/core/agent_registry.py +63 -17
  20. claude_mpm/core/claude_runner.py +354 -43
  21. claude_mpm/core/config.py +7 -1
  22. claude_mpm/core/config_aliases.py +4 -3
  23. claude_mpm/core/config_paths.py +151 -0
  24. claude_mpm/core/factories.py +4 -50
  25. claude_mpm/core/logger.py +11 -13
  26. claude_mpm/core/service_registry.py +2 -2
  27. claude_mpm/dashboard/static/js/components/agent-inference.js +101 -25
  28. claude_mpm/dashboard/static/js/components/event-processor.js +3 -2
  29. claude_mpm/hooks/claude_hooks/hook_handler.py +343 -83
  30. claude_mpm/hooks/memory_integration_hook.py +1 -1
  31. claude_mpm/init.py +37 -6
  32. claude_mpm/scripts/socketio_daemon.py +6 -2
  33. claude_mpm/services/__init__.py +71 -3
  34. claude_mpm/services/agents/__init__.py +85 -0
  35. claude_mpm/services/agents/deployment/__init__.py +21 -0
  36. claude_mpm/services/{agent_deployment.py → agents/deployment/agent_deployment.py} +192 -41
  37. claude_mpm/services/{agent_lifecycle_manager.py → agents/deployment/agent_lifecycle_manager.py} +11 -10
  38. claude_mpm/services/agents/loading/__init__.py +11 -0
  39. claude_mpm/services/{agent_profile_loader.py → agents/loading/agent_profile_loader.py} +9 -8
  40. claude_mpm/services/{base_agent_manager.py → agents/loading/base_agent_manager.py} +2 -2
  41. claude_mpm/services/{framework_agent_loader.py → agents/loading/framework_agent_loader.py} +116 -40
  42. claude_mpm/services/agents/management/__init__.py +9 -0
  43. claude_mpm/services/{agent_management_service.py → agents/management/agent_management_service.py} +6 -5
  44. claude_mpm/services/agents/memory/__init__.py +21 -0
  45. claude_mpm/services/{agent_memory_manager.py → agents/memory/agent_memory_manager.py} +3 -3
  46. claude_mpm/services/agents/registry/__init__.py +29 -0
  47. claude_mpm/services/{agent_registry.py → agents/registry/agent_registry.py} +101 -16
  48. claude_mpm/services/{deployed_agent_discovery.py → agents/registry/deployed_agent_discovery.py} +12 -2
  49. claude_mpm/services/{agent_modification_tracker.py → agents/registry/modification_tracker.py} +6 -5
  50. claude_mpm/services/async_session_logger.py +584 -0
  51. claude_mpm/services/claude_session_logger.py +299 -0
  52. claude_mpm/services/framework_claude_md_generator/content_assembler.py +2 -2
  53. claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +17 -17
  54. claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +3 -3
  55. claude_mpm/services/framework_claude_md_generator/section_generators/core_responsibilities.py +1 -1
  56. claude_mpm/services/framework_claude_md_generator/section_generators/orchestration_principles.py +1 -1
  57. claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +19 -24
  58. claude_mpm/services/framework_claude_md_generator/section_generators/troubleshooting.py +1 -1
  59. claude_mpm/services/framework_claude_md_generator.py +4 -2
  60. claude_mpm/services/memory/__init__.py +17 -0
  61. claude_mpm/services/{memory_builder.py → memory/builder.py} +3 -3
  62. claude_mpm/services/memory/cache/__init__.py +14 -0
  63. claude_mpm/services/{shared_prompt_cache.py → memory/cache/shared_prompt_cache.py} +1 -1
  64. claude_mpm/services/memory/cache/simple_cache.py +317 -0
  65. claude_mpm/services/{memory_optimizer.py → memory/optimizer.py} +1 -1
  66. claude_mpm/services/{memory_router.py → memory/router.py} +1 -1
  67. claude_mpm/services/optimized_hook_service.py +542 -0
  68. claude_mpm/services/project_registry.py +14 -8
  69. claude_mpm/services/response_tracker.py +237 -0
  70. claude_mpm/services/ticketing_service_original.py +4 -2
  71. claude_mpm/services/version_control/branch_strategy.py +3 -1
  72. claude_mpm/utils/paths.py +12 -10
  73. claude_mpm/utils/session_logging.py +114 -0
  74. claude_mpm/validation/agent_validator.py +2 -1
  75. {claude_mpm-3.4.26.dist-info → claude_mpm-3.5.0.dist-info}/METADATA +26 -20
  76. {claude_mpm-3.4.26.dist-info → claude_mpm-3.5.0.dist-info}/RECORD +83 -106
  77. claude_mpm/cli/commands/ui.py +0 -57
  78. claude_mpm/core/simple_runner.py +0 -1046
  79. claude_mpm/hooks/builtin/__init__.py +0 -1
  80. claude_mpm/hooks/builtin/logging_hook_example.py +0 -165
  81. claude_mpm/hooks/builtin/memory_hooks_example.py +0 -67
  82. claude_mpm/hooks/builtin/mpm_command_hook.py +0 -125
  83. claude_mpm/hooks/builtin/post_delegation_hook_example.py +0 -124
  84. claude_mpm/hooks/builtin/pre_delegation_hook_example.py +0 -125
  85. claude_mpm/hooks/builtin/submit_hook_example.py +0 -100
  86. claude_mpm/hooks/builtin/ticket_extraction_hook_example.py +0 -237
  87. claude_mpm/hooks/builtin/todo_agent_prefix_hook.py +0 -240
  88. claude_mpm/hooks/builtin/workflow_start_hook.py +0 -181
  89. claude_mpm/orchestration/__init__.py +0 -6
  90. claude_mpm/orchestration/archive/direct_orchestrator.py +0 -195
  91. claude_mpm/orchestration/archive/factory.py +0 -215
  92. claude_mpm/orchestration/archive/hook_enabled_orchestrator.py +0 -188
  93. claude_mpm/orchestration/archive/hook_integration_example.py +0 -178
  94. claude_mpm/orchestration/archive/interactive_subprocess_orchestrator.py +0 -826
  95. claude_mpm/orchestration/archive/orchestrator.py +0 -501
  96. claude_mpm/orchestration/archive/pexpect_orchestrator.py +0 -252
  97. claude_mpm/orchestration/archive/pty_orchestrator.py +0 -270
  98. claude_mpm/orchestration/archive/simple_orchestrator.py +0 -82
  99. claude_mpm/orchestration/archive/subprocess_orchestrator.py +0 -801
  100. claude_mpm/orchestration/archive/system_prompt_orchestrator.py +0 -278
  101. claude_mpm/orchestration/archive/wrapper_orchestrator.py +0 -187
  102. claude_mpm/schemas/workflow_validator.py +0 -411
  103. claude_mpm/services/parent_directory_manager/__init__.py +0 -577
  104. claude_mpm/services/parent_directory_manager/backup_manager.py +0 -258
  105. claude_mpm/services/parent_directory_manager/config_manager.py +0 -210
  106. claude_mpm/services/parent_directory_manager/deduplication_manager.py +0 -279
  107. claude_mpm/services/parent_directory_manager/framework_protector.py +0 -143
  108. claude_mpm/services/parent_directory_manager/operations.py +0 -186
  109. claude_mpm/services/parent_directory_manager/state_manager.py +0 -624
  110. claude_mpm/services/parent_directory_manager/template_deployer.py +0 -579
  111. claude_mpm/services/parent_directory_manager/validation_manager.py +0 -378
  112. claude_mpm/services/parent_directory_manager/version_control_helper.py +0 -339
  113. claude_mpm/services/parent_directory_manager/version_manager.py +0 -222
  114. claude_mpm/ui/__init__.py +0 -1
  115. claude_mpm/ui/rich_terminal_ui.py +0 -295
  116. claude_mpm/ui/terminal_ui.py +0 -328
  117. /claude_mpm/services/{agent_versioning.py → agents/deployment/agent_versioning.py} +0 -0
  118. /claude_mpm/services/{agent_capabilities_generator.py → agents/management/agent_capabilities_generator.py} +0 -0
  119. /claude_mpm/services/{agent_persistence_service.py → agents/memory/agent_persistence_service.py} +0 -0
  120. {claude_mpm-3.4.26.dist-info → claude_mpm-3.5.0.dist-info}/WHEEL +0 -0
  121. {claude_mpm-3.4.26.dist-info → claude_mpm-3.5.0.dist-info}/entry_points.txt +0 -0
  122. {claude_mpm-3.4.26.dist-info → claude_mpm-3.5.0.dist-info}/licenses/LICENSE +0 -0
  123. {claude_mpm-3.4.26.dist-info → claude_mpm-3.5.0.dist-info}/top_level.txt +0 -0
@@ -17,6 +17,7 @@ import sys
17
17
  import os
18
18
  import subprocess
19
19
  from datetime import datetime
20
+ import time
20
21
  from pathlib import Path
21
22
  from collections import deque
22
23
 
@@ -26,12 +27,9 @@ DEBUG = os.environ.get('CLAUDE_MPM_HOOK_DEBUG', '').lower() == 'true'
26
27
  # Add imports for memory hook integration with comprehensive error handling
27
28
  MEMORY_HOOKS_AVAILABLE = False
28
29
  try:
29
- # Try to add src to path if not already there (fallback for missing PYTHONPATH)
30
- import sys
31
- from pathlib import Path
32
- src_path = Path(__file__).parent.parent.parent.parent
33
- if src_path.exists() and str(src_path) not in sys.path:
34
- sys.path.insert(0, str(src_path))
30
+ # Use centralized path management for adding src to path
31
+ from claude_mpm.config.paths import paths
32
+ paths.ensure_in_path()
35
33
 
36
34
  from claude_mpm.services.hook_service import HookService
37
35
  from claude_mpm.hooks.memory_integration_hook import (
@@ -47,6 +45,16 @@ except Exception as e:
47
45
  print(f"Memory hooks not available: {e}", file=sys.stderr)
48
46
  MEMORY_HOOKS_AVAILABLE = False
49
47
 
48
+ # Response tracking integration
49
+ RESPONSE_TRACKING_AVAILABLE = False
50
+ try:
51
+ from claude_mpm.services.response_tracker import ResponseTracker
52
+ RESPONSE_TRACKING_AVAILABLE = True
53
+ except Exception as e:
54
+ if DEBUG:
55
+ print(f"Response tracking not available: {e}", file=sys.stderr)
56
+ RESPONSE_TRACKING_AVAILABLE = False
57
+
50
58
  # Socket.IO import
51
59
  try:
52
60
  import socketio
@@ -78,6 +86,8 @@ class ClaudeHookHandler:
78
86
  self.active_delegations = {}
79
87
  # Use deque to limit memory usage (keep last 100 delegations)
80
88
  self.delegation_history = deque(maxlen=100)
89
+ # Store delegation request data for response correlation: session_id -> request_data
90
+ self.delegation_requests = {}
81
91
 
82
92
  # Git branch cache (to avoid repeated subprocess calls)
83
93
  self._git_branch_cache = {}
@@ -90,15 +100,29 @@ class ClaudeHookHandler:
90
100
  if MEMORY_HOOKS_AVAILABLE:
91
101
  self._initialize_memory_hooks()
92
102
 
103
+ # Initialize response tracking if available and enabled
104
+ self.response_tracker = None
105
+ self.response_tracking_enabled = False
106
+ if RESPONSE_TRACKING_AVAILABLE:
107
+ self._initialize_response_tracking()
108
+
93
109
  # No fallback server needed - we only use Socket.IO now
94
110
 
95
- def _track_delegation(self, session_id: str, agent_type: str):
96
- """Track a new agent delegation."""
111
+ def _track_delegation(self, session_id: str, agent_type: str, request_data: dict = None):
112
+ """Track a new agent delegation with optional request data for response correlation."""
97
113
  if session_id and agent_type and agent_type != 'unknown':
98
114
  self.active_delegations[session_id] = agent_type
99
115
  key = f"{session_id}:{datetime.now().timestamp()}"
100
116
  self.delegation_history.append((key, agent_type))
101
117
 
118
+ # Store request data for response tracking correlation
119
+ if request_data:
120
+ self.delegation_requests[session_id] = {
121
+ 'agent_type': agent_type,
122
+ 'request': request_data,
123
+ 'timestamp': datetime.now().isoformat()
124
+ }
125
+
102
126
  # Clean up old delegations (older than 5 minutes)
103
127
  cutoff_time = datetime.now().timestamp() - 300
104
128
  keys_to_remove = []
@@ -115,7 +139,10 @@ class ClaudeHookHandler:
115
139
  keys_to_remove.append(sid)
116
140
 
117
141
  for key in keys_to_remove:
118
- del self.active_delegations[key]
142
+ if key in self.active_delegations:
143
+ del self.active_delegations[key]
144
+ if key in self.delegation_requests:
145
+ del self.delegation_requests[key]
119
146
 
120
147
  def _get_delegation_agent_type(self, session_id: str) -> str:
121
148
  """Get the agent type for a session's active delegation."""
@@ -174,6 +201,129 @@ class ClaudeHookHandler:
174
201
  print(f"❌ Failed to initialize memory hooks: {e}", file=sys.stderr)
175
202
  # Don't fail the entire handler - memory system is optional
176
203
 
204
+ def _initialize_response_tracking(self):
205
+ """Initialize response tracking if enabled in configuration.
206
+
207
+ WHY: This enables automatic capture and storage of agent responses
208
+ for analysis, debugging, and learning purposes. Integration into the
209
+ existing hook handler avoids duplicate event capture.
210
+
211
+ DESIGN DECISION: Check configuration to allow enabling/disabling
212
+ response tracking without code changes.
213
+ """
214
+ try:
215
+ # Create configuration with optional config file
216
+ config_file = os.environ.get('CLAUDE_PM_CONFIG_FILE')
217
+ config = Config(config_file=config_file) if config_file else Config()
218
+
219
+ # Check if response tracking is enabled (check both sections for compatibility)
220
+ response_tracking_enabled = config.get('response_tracking.enabled', False)
221
+ response_logging_enabled = config.get('response_logging.enabled', False)
222
+
223
+ if not (response_tracking_enabled or response_logging_enabled):
224
+ if DEBUG:
225
+ print("Response tracking disabled - skipping initialization", file=sys.stderr)
226
+ return
227
+
228
+ # Initialize response tracker with config
229
+ self.response_tracker = ResponseTracker(config=config)
230
+ self.response_tracking_enabled = self.response_tracker.is_enabled()
231
+
232
+ if DEBUG:
233
+ print("✅ Response tracking initialized", file=sys.stderr)
234
+
235
+ except Exception as e:
236
+ if DEBUG:
237
+ print(f"❌ Failed to initialize response tracking: {e}", file=sys.stderr)
238
+ # Don't fail the entire handler - response tracking is optional
239
+
240
+ def _track_agent_response(self, session_id: str, agent_type: str, event: dict):
241
+ """Track agent response by correlating with original request and saving response.
242
+
243
+ WHY: This integrates response tracking into the existing hook flow,
244
+ capturing agent responses when Task delegations complete. It correlates
245
+ the response with the original request stored during pre-tool processing.
246
+
247
+ DESIGN DECISION: Only track responses if response tracking is enabled
248
+ and we have the original request data. Graceful error handling ensures
249
+ response tracking failures don't break hook processing.
250
+ """
251
+ if not self.response_tracking_enabled or not self.response_tracker:
252
+ return
253
+
254
+ try:
255
+ # Get the original request data stored during pre-tool
256
+ request_info = self.delegation_requests.get(session_id)
257
+ if not request_info:
258
+ if DEBUG:
259
+ print(f"No request data found for session {session_id}, skipping response tracking", file=sys.stderr)
260
+ return
261
+
262
+ # Extract response from event output
263
+ response = event.get('output', '')
264
+ if not response:
265
+ # If no output, use error or construct a basic response
266
+ error = event.get('error', '')
267
+ exit_code = event.get('exit_code', 0)
268
+ if error:
269
+ response = f"Error: {error}"
270
+ else:
271
+ response = f"Task completed with exit code: {exit_code}"
272
+
273
+ # Convert response to string if it's not already
274
+ response_text = str(response)
275
+
276
+ # Get the original request (prompt + description)
277
+ original_request = request_info.get('request', {})
278
+ prompt = original_request.get('prompt', '')
279
+ description = original_request.get('description', '')
280
+
281
+ # Combine prompt and description for the full request
282
+ full_request = prompt
283
+ if description and description != prompt:
284
+ if full_request:
285
+ full_request += f"\n\nDescription: {description}"
286
+ else:
287
+ full_request = description
288
+
289
+ if not full_request:
290
+ full_request = f"Task delegation to {agent_type} agent"
291
+
292
+ # Prepare metadata
293
+ metadata = {
294
+ 'exit_code': event.get('exit_code', 0),
295
+ 'success': event.get('exit_code', 0) == 0,
296
+ 'has_error': bool(event.get('error')),
297
+ 'duration_ms': event.get('duration_ms'),
298
+ 'working_directory': event.get('cwd', ''),
299
+ 'timestamp': datetime.now().isoformat(),
300
+ 'tool_name': 'Task',
301
+ 'original_request_timestamp': request_info.get('timestamp')
302
+ }
303
+
304
+ # Track the response
305
+ file_path = self.response_tracker.track_response(
306
+ agent_name=agent_type,
307
+ request=full_request,
308
+ response=response_text,
309
+ session_id=session_id,
310
+ metadata=metadata
311
+ )
312
+
313
+ if file_path and DEBUG:
314
+ print(f"✅ Tracked response for {agent_type} agent in session {session_id}: {file_path.name}", file=sys.stderr)
315
+ elif DEBUG and not file_path:
316
+ print(f"Response tracking returned None for {agent_type} agent (might be excluded or disabled)", file=sys.stderr)
317
+
318
+ # Clean up the request data after successful tracking
319
+ if session_id in self.delegation_requests:
320
+ del self.delegation_requests[session_id]
321
+
322
+ except Exception as e:
323
+ if DEBUG:
324
+ print(f"❌ Failed to track agent response: {e}", file=sys.stderr)
325
+ # Don't fail the hook processing - response tracking is optional
326
+
177
327
  def _get_git_branch(self, working_dir: str = None) -> str:
178
328
  """Get git branch for the given directory with caching.
179
329
 
@@ -232,12 +382,13 @@ class ClaudeHookHandler:
232
382
  return 'Unknown'
233
383
 
234
384
  def _get_socketio_client(self):
235
- """Get or create Socket.IO client.
385
+ """Get or create Socket.IO client with improved reliability.
236
386
 
237
- WHY this approach:
238
- - Reuses existing connection when possible
239
- - Creates new connection only when needed
240
- - Handles connection failures gracefully
387
+ WHY improved approach:
388
+ - Implements retry logic with exponential backoff
389
+ - Properly tests connection before returning
390
+ - Ensures connection persists across events
391
+ - Better error handling and recovery
241
392
  """
242
393
  if not SOCKETIO_AVAILABLE:
243
394
  return None
@@ -248,33 +399,68 @@ class ClaudeHookHandler:
248
399
  # Test if still connected
249
400
  if self.sio_client.connected:
250
401
  return self.sio_client
402
+ else:
403
+ # Connection lost, clear it
404
+ if DEBUG:
405
+ print("Hook handler: Socket.IO connection lost, reconnecting...", file=sys.stderr)
406
+ self.sio_connected = False
251
407
  except:
252
- pass
408
+ self.sio_connected = False
253
409
 
254
- # Need to create new client
255
- try:
256
- port = int(os.environ.get('CLAUDE_MPM_SOCKETIO_PORT', '8765'))
257
- self.sio_client = socketio.Client(
258
- reconnection=False, # Don't auto-reconnect in hooks
259
- logger=False,
260
- engineio_logger=False
261
- )
262
-
263
- # Try to connect with short timeout
264
- self.sio_client.connect(f'http://localhost:{port}', wait_timeout=1)
265
- self.sio_connected = True
266
-
267
- if DEBUG:
268
- print(f"Hook handler: Connected to Socket.IO server on port {port}", file=sys.stderr)
269
-
270
- return self.sio_client
271
-
272
- except Exception as e:
273
- if DEBUG:
274
- print(f"Hook handler: Failed to connect to Socket.IO: {e}", file=sys.stderr)
275
- self.sio_client = None
276
- self.sio_connected = False
277
- return None
410
+ # Need to create or reconnect client
411
+ port = int(os.environ.get('CLAUDE_MPM_SOCKETIO_PORT', '8765'))
412
+ max_retries = 3
413
+ retry_delay = 0.1 # Start with 100ms
414
+
415
+ for attempt in range(max_retries):
416
+ try:
417
+ # Clean up old client if exists
418
+ if self.sio_client and not self.sio_connected:
419
+ try:
420
+ self.sio_client.disconnect()
421
+ except:
422
+ pass
423
+ self.sio_client = None
424
+
425
+ # Create new client
426
+ self.sio_client = socketio.Client(
427
+ reconnection=True, # Enable auto-reconnection
428
+ reconnection_attempts=3,
429
+ reconnection_delay=0.5,
430
+ reconnection_delay_max=2,
431
+ logger=False,
432
+ engineio_logger=False
433
+ )
434
+
435
+ # Try to connect with proper wait
436
+ self.sio_client.connect(
437
+ f'http://localhost:{port}',
438
+ wait=True,
439
+ wait_timeout=1.0 # Reasonable timeout
440
+ )
441
+
442
+ # Verify connection
443
+ if self.sio_client.connected:
444
+ self.sio_connected = True
445
+ if DEBUG:
446
+ print(f"Hook handler: Successfully connected to Socket.IO server on port {port} (attempt {attempt + 1})", file=sys.stderr)
447
+ return self.sio_client
448
+
449
+ except Exception as e:
450
+ if DEBUG and attempt == max_retries - 1:
451
+ print(f"Hook handler: Failed to connect to Socket.IO after {max_retries} attempts: {e}", file=sys.stderr)
452
+ elif DEBUG:
453
+ print(f"Hook handler: Connection attempt {attempt + 1} failed, retrying...", file=sys.stderr)
454
+
455
+ # Exponential backoff
456
+ if attempt < max_retries - 1:
457
+ time.sleep(retry_delay)
458
+ retry_delay *= 2 # Double the delay for next attempt
459
+
460
+ # All attempts failed
461
+ self.sio_client = None
462
+ self.sio_connected = False
463
+ return None
278
464
 
279
465
  def handle(self):
280
466
  """Process hook event with minimal overhead and zero blocking delays.
@@ -318,40 +504,75 @@ class ClaudeHookHandler:
318
504
  print(json.dumps({"action": "continue"}))
319
505
 
320
506
  def _emit_socketio_event(self, namespace: str, event: str, data: dict):
321
- """Emit Socket.IO event using direct client.
507
+ """Emit Socket.IO event with improved reliability and logging.
322
508
 
323
- WHY direct client approach:
324
- - Simple synchronous emission
325
- - No threading complexity
326
- - Reliable delivery
327
- - Fast when connection is reused
509
+ WHY improved approach:
510
+ - Better error handling and recovery
511
+ - Comprehensive event logging for debugging
512
+ - Automatic reconnection on failure
513
+ - Validates data before emission
328
514
  """
329
- # Fast path: Skip all Socket.IO operations unless configured
330
- if not os.environ.get('CLAUDE_MPM_SOCKETIO_PORT') and not DEBUG:
331
- return
515
+ # Always try to emit Socket.IO events if available
516
+ # The daemon should be running when manager is active
332
517
 
333
518
  # Get Socket.IO client
334
519
  client = self._get_socketio_client()
335
- if client:
336
- try:
337
- # Format event for Socket.IO server
338
- claude_event_data = {
339
- 'type': f'hook.{event}',
340
- 'timestamp': datetime.now().isoformat(),
341
- 'data': data
342
- }
343
-
344
- # Emit synchronously
345
- client.emit('claude_event', claude_event_data)
346
-
347
- if DEBUG:
348
- print(f"Emitted Socket.IO event: hook.{event}", file=sys.stderr)
520
+ if not client:
521
+ if DEBUG:
522
+ print(f"Hook handler: No Socket.IO client available for event: hook.{event}", file=sys.stderr)
523
+ return
524
+
525
+ try:
526
+ # Format event for Socket.IO server
527
+ claude_event_data = {
528
+ 'type': f'hook.{event}', # Dashboard expects 'hook.' prefix
529
+ 'timestamp': datetime.now().isoformat(),
530
+ 'data': data
531
+ }
532
+
533
+ # Log important events for debugging
534
+ if DEBUG and event in ['subagent_stop', 'pre_tool']:
535
+ if event == 'subagent_stop':
536
+ agent_type = data.get('agent_type', 'unknown')
537
+ print(f"Hook handler: Emitting SubagentStop for agent '{agent_type}'", file=sys.stderr)
538
+ elif event == 'pre_tool' and data.get('tool_name') == 'Task':
539
+ delegation = data.get('delegation_details', {})
540
+ agent_type = delegation.get('agent_type', 'unknown')
541
+ print(f"Hook handler: Emitting Task delegation to agent '{agent_type}'", file=sys.stderr)
542
+
543
+ # Emit synchronously with verification
544
+ client.emit('claude_event', claude_event_data)
545
+
546
+ # Verify emission for critical events
547
+ if event in ['subagent_stop', 'pre_tool'] and DEBUG:
548
+ if client.connected:
549
+ print(f"✅ Successfully emitted Socket.IO event: hook.{event}", file=sys.stderr)
550
+ else:
551
+ print(f"⚠️ Event emitted but connection status uncertain: hook.{event}", file=sys.stderr)
552
+ self.sio_connected = False # Force reconnection next time
349
553
 
350
- except Exception as e:
554
+ except Exception as e:
555
+ if DEBUG:
556
+ print(f"❌ Socket.IO emit failed for hook.{event}: {e}", file=sys.stderr)
557
+ # Mark as disconnected so next call will reconnect
558
+ self.sio_connected = False
559
+
560
+ # Try to reconnect immediately for critical events
561
+ if event in ['subagent_stop', 'pre_tool']:
351
562
  if DEBUG:
352
- print(f"Socket.IO emit failed: {e}", file=sys.stderr)
353
- # Mark as disconnected so next call will reconnect
354
- self.sio_connected = False
563
+ print(f"Hook handler: Attempting immediate reconnection for critical event: hook.{event}", file=sys.stderr)
564
+ # Clear the client to force reconnection
565
+ self.sio_client = None
566
+ # Try to get a new client and emit again
567
+ retry_client = self._get_socketio_client()
568
+ if retry_client:
569
+ try:
570
+ retry_client.emit('claude_event', claude_event_data)
571
+ if DEBUG:
572
+ print(f"✅ Successfully re-emitted event after reconnection: hook.{event}", file=sys.stderr)
573
+ except Exception as retry_e:
574
+ if DEBUG:
575
+ print(f"❌ Re-emission failed: {retry_e}", file=sys.stderr)
355
576
 
356
577
  def _handle_user_prompt_fast(self, event):
357
578
  """Handle user prompt with comprehensive data capture.
@@ -373,7 +594,6 @@ class ClaudeHookHandler:
373
594
 
374
595
  # Extract comprehensive prompt data
375
596
  prompt_data = {
376
- 'event_type': 'user_prompt',
377
597
  'prompt_text': prompt,
378
598
  'prompt_preview': prompt[:200] if len(prompt) > 200 else prompt,
379
599
  'prompt_length': len(prompt),
@@ -411,7 +631,6 @@ class ClaudeHookHandler:
411
631
  git_branch = self._get_git_branch(working_dir) if working_dir else 'Unknown'
412
632
 
413
633
  pre_tool_data = {
414
- 'event_type': 'pre_tool',
415
634
  'tool_name': tool_name,
416
635
  'operation_type': operation_type,
417
636
  'tool_parameters': tool_params,
@@ -428,21 +647,56 @@ class ClaudeHookHandler:
428
647
 
429
648
  # Add delegation-specific data if this is a Task tool
430
649
  if tool_name == 'Task' and isinstance(tool_input, dict):
431
- agent_type = tool_input.get('subagent_type', 'unknown')
650
+ # Normalize agent type to handle capitalized names like "Research", "Engineer", etc.
651
+ raw_agent_type = tool_input.get('subagent_type', 'unknown')
652
+
653
+ # Use AgentNameNormalizer if available, otherwise simple lowercase normalization
654
+ try:
655
+ from claude_mpm.core.agent_name_normalizer import AgentNameNormalizer
656
+ normalizer = AgentNameNormalizer()
657
+ # Convert to Task format (lowercase with hyphens)
658
+ agent_type = normalizer.to_task_format(raw_agent_type) if raw_agent_type != 'unknown' else 'unknown'
659
+ except ImportError:
660
+ # Fallback to simple normalization
661
+ agent_type = raw_agent_type.lower().replace('_', '-') if raw_agent_type != 'unknown' else 'unknown'
662
+
432
663
  pre_tool_data['delegation_details'] = {
433
664
  'agent_type': agent_type,
665
+ 'original_agent_type': raw_agent_type, # Keep original for debugging
434
666
  'prompt': tool_input.get('prompt', ''),
435
667
  'description': tool_input.get('description', ''),
436
668
  'task_preview': (tool_input.get('prompt', '') or tool_input.get('description', ''))[:100]
437
669
  }
438
670
 
439
- # Track this delegation for SubagentStop correlation
671
+ # Track this delegation for SubagentStop correlation and response tracking
440
672
  session_id = event.get('session_id', '')
441
673
  if session_id and agent_type != 'unknown':
442
- self._track_delegation(session_id, agent_type)
674
+ # Prepare request data for response tracking correlation
675
+ request_data = {
676
+ 'prompt': tool_input.get('prompt', ''),
677
+ 'description': tool_input.get('description', ''),
678
+ 'agent_type': agent_type
679
+ }
680
+ self._track_delegation(session_id, agent_type, request_data)
681
+
682
+ # Log important delegations for debugging
683
+ if DEBUG or agent_type in ['research', 'engineer', 'qa', 'documentation']:
684
+ print(f"Hook handler: Task delegation started - agent: '{agent_type}', session: '{session_id}'", file=sys.stderr)
443
685
 
444
686
  # Trigger memory pre-delegation hook
445
687
  self._trigger_memory_pre_delegation_hook(agent_type, tool_input, session_id)
688
+
689
+ # Emit a subagent_start event for better tracking
690
+ subagent_start_data = {
691
+ 'agent_type': agent_type,
692
+ 'agent_id': f"{agent_type}_{session_id}",
693
+ 'session_id': session_id,
694
+ 'prompt': tool_input.get('prompt', ''),
695
+ 'description': tool_input.get('description', ''),
696
+ 'timestamp': datetime.now().isoformat(),
697
+ 'hook_event_name': 'SubagentStart' # For dashboard compatibility
698
+ }
699
+ self._emit_socketio_event('/hook', 'subagent_start', subagent_start_data)
446
700
 
447
701
  self._emit_socketio_event('/hook', 'pre_tool', pre_tool_data)
448
702
 
@@ -468,7 +722,6 @@ class ClaudeHookHandler:
468
722
  git_branch = self._get_git_branch(working_dir) if working_dir else 'Unknown'
469
723
 
470
724
  post_tool_data = {
471
- 'event_type': 'post_tool',
472
725
  'tool_name': tool_name,
473
726
  'exit_code': exit_code,
474
727
  'success': exit_code == 0,
@@ -484,11 +737,16 @@ class ClaudeHookHandler:
484
737
  'output_size': len(str(result_data.get('output', ''))) if result_data.get('output') else 0
485
738
  }
486
739
 
487
- # Handle Task delegation completion for memory hooks
740
+ # Handle Task delegation completion for memory hooks and response tracking
488
741
  if tool_name == 'Task':
489
742
  session_id = event.get('session_id', '')
490
743
  agent_type = self._get_delegation_agent_type(session_id)
744
+
745
+ # Trigger memory post-delegation hook
491
746
  self._trigger_memory_post_delegation_hook(agent_type, event, session_id)
747
+
748
+ # Track agent response if response tracking is enabled
749
+ self._track_agent_response(session_id, agent_type, event)
492
750
 
493
751
  self._emit_socketio_event('/hook', 'post_tool', post_tool_data)
494
752
 
@@ -687,7 +945,6 @@ class ClaudeHookHandler:
687
945
  git_branch = self._get_git_branch(working_dir) if working_dir else 'Unknown'
688
946
 
689
947
  notification_data = {
690
- 'event_type': 'notification',
691
948
  'notification_type': notification_type,
692
949
  'message': message,
693
950
  'message_preview': message[:200] if len(message) > 200 else message,
@@ -721,7 +978,6 @@ class ClaudeHookHandler:
721
978
  git_branch = self._get_git_branch(working_dir) if working_dir else 'Unknown'
722
979
 
723
980
  stop_data = {
724
- 'event_type': 'stop',
725
981
  'reason': reason,
726
982
  'stop_type': stop_type,
727
983
  'session_id': event.get('session_id', ''),
@@ -738,7 +994,7 @@ class ClaudeHookHandler:
738
994
  self._emit_socketio_event('/hook', 'stop', stop_data)
739
995
 
740
996
  def _handle_subagent_stop_fast(self, event):
741
- """Handle subagent stop events when subagent processing stops.
997
+ """Handle subagent stop events with improved agent type detection.
742
998
 
743
999
  WHY comprehensive subagent stop capture:
744
1000
  - Provides visibility into subagent lifecycle and delegation patterns
@@ -767,31 +1023,35 @@ class ClaudeHookHandler:
767
1023
  elif 'pm' in task_desc or 'project' in task_desc:
768
1024
  agent_type = 'pm'
769
1025
 
1026
+ # Always log SubagentStop events for debugging
1027
+ if DEBUG or agent_type != 'unknown':
1028
+ print(f"Hook handler: Processing SubagentStop - agent: '{agent_type}', session: '{session_id}', reason: '{reason}'", file=sys.stderr)
1029
+
770
1030
  # Get working directory and git branch
771
1031
  working_dir = event.get('cwd', '')
772
1032
  git_branch = self._get_git_branch(working_dir) if working_dir else 'Unknown'
773
1033
 
774
1034
  subagent_stop_data = {
775
- 'event_type': 'subagent_stop',
776
1035
  'agent_type': agent_type,
777
1036
  'agent_id': agent_id,
778
1037
  'reason': reason,
779
- 'session_id': event.get('session_id', ''),
1038
+ 'session_id': session_id,
780
1039
  'working_directory': working_dir,
781
1040
  'git_branch': git_branch,
782
1041
  'timestamp': datetime.now().isoformat(),
783
1042
  'is_successful_completion': reason in ['completed', 'finished', 'done'],
784
1043
  'is_error_termination': reason in ['error', 'timeout', 'failed', 'blocked'],
785
- 'is_delegation_related': agent_type in ['research', 'engineer', 'pm', 'ops'],
1044
+ 'is_delegation_related': agent_type in ['research', 'engineer', 'pm', 'ops', 'qa', 'documentation', 'security'],
786
1045
  'has_results': bool(event.get('results') or event.get('output')),
787
- 'duration_context': event.get('duration_ms')
1046
+ 'duration_context': event.get('duration_ms'),
1047
+ 'hook_event_name': 'SubagentStop' # Explicitly set for dashboard
788
1048
  }
789
1049
 
790
- # Debug log the raw event data
1050
+ # Debug log the processed data
791
1051
  if DEBUG:
792
- print(f"SubagentStop raw event data: {json.dumps(event, indent=2)}", file=sys.stderr)
1052
+ print(f"SubagentStop processed data: agent_type='{agent_type}', session_id='{session_id}'", file=sys.stderr)
793
1053
 
794
- # Emit to /hook namespace
1054
+ # Emit to /hook namespace with high priority
795
1055
  self._emit_socketio_event('/hook', 'subagent_stop', subagent_stop_data)
796
1056
 
797
1057
  def _trigger_memory_pre_delegation_hook(self, agent_type: str, tool_input: dict, session_id: str):
@@ -22,7 +22,7 @@ logger = get_logger(__name__)
22
22
 
23
23
  # Try to import memory manager with fallback handling
24
24
  try:
25
- from claude_mpm.services.agent_memory_manager import AgentMemoryManager
25
+ from claude_mpm.services.agents.memory import AgentMemoryManager
26
26
  MEMORY_MANAGER_AVAILABLE = True
27
27
  except ImportError as e:
28
28
  logger.warning(f"AgentMemoryManager not available: {e}")