claude-mpm 4.0.32__py3-none-any.whl → 4.1.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 (82) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/INSTRUCTIONS.md +70 -2
  3. claude_mpm/agents/OUTPUT_STYLE.md +0 -11
  4. claude_mpm/agents/WORKFLOW.md +14 -2
  5. claude_mpm/agents/templates/documentation.json +51 -34
  6. claude_mpm/agents/templates/research.json +0 -11
  7. claude_mpm/cli/__init__.py +111 -33
  8. claude_mpm/cli/commands/agent_manager.py +10 -8
  9. claude_mpm/cli/commands/agents.py +82 -0
  10. claude_mpm/cli/commands/cleanup_orphaned_agents.py +150 -0
  11. claude_mpm/cli/commands/mcp_pipx_config.py +199 -0
  12. claude_mpm/cli/parsers/agents_parser.py +27 -0
  13. claude_mpm/cli/parsers/base_parser.py +6 -0
  14. claude_mpm/cli/startup_logging.py +75 -0
  15. claude_mpm/core/framework_loader.py +173 -84
  16. claude_mpm/dashboard/static/css/dashboard.css +449 -0
  17. claude_mpm/dashboard/static/dist/components/agent-inference.js +1 -1
  18. claude_mpm/dashboard/static/dist/components/event-viewer.js +1 -1
  19. claude_mpm/dashboard/static/dist/components/file-tool-tracker.js +1 -1
  20. claude_mpm/dashboard/static/dist/components/module-viewer.js +1 -1
  21. claude_mpm/dashboard/static/dist/components/session-manager.js +1 -1
  22. claude_mpm/dashboard/static/dist/dashboard.js +1 -1
  23. claude_mpm/dashboard/static/dist/socket-client.js +1 -1
  24. claude_mpm/dashboard/static/js/components/agent-hierarchy.js +774 -0
  25. claude_mpm/dashboard/static/js/components/agent-inference.js +257 -3
  26. claude_mpm/dashboard/static/js/components/build-tracker.js +323 -0
  27. claude_mpm/dashboard/static/js/components/event-viewer.js +168 -39
  28. claude_mpm/dashboard/static/js/components/file-tool-tracker.js +17 -0
  29. claude_mpm/dashboard/static/js/components/session-manager.js +23 -3
  30. claude_mpm/dashboard/static/js/components/socket-manager.js +2 -0
  31. claude_mpm/dashboard/static/js/dashboard.js +207 -31
  32. claude_mpm/dashboard/static/js/socket-client.js +92 -11
  33. claude_mpm/dashboard/templates/index.html +1 -0
  34. claude_mpm/hooks/claude_hooks/connection_pool.py +25 -4
  35. claude_mpm/hooks/claude_hooks/event_handlers.py +81 -19
  36. claude_mpm/hooks/claude_hooks/hook_handler.py +125 -163
  37. claude_mpm/hooks/claude_hooks/hook_handler_eventbus.py +398 -0
  38. claude_mpm/hooks/claude_hooks/response_tracking.py +10 -0
  39. claude_mpm/services/agents/deployment/agent_deployment.py +34 -48
  40. claude_mpm/services/agents/deployment/agent_discovery_service.py +4 -1
  41. claude_mpm/services/agents/deployment/agent_template_builder.py +20 -11
  42. claude_mpm/services/agents/deployment/agent_version_manager.py +4 -1
  43. claude_mpm/services/agents/deployment/agents_directory_resolver.py +10 -25
  44. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +396 -13
  45. claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +3 -2
  46. claude_mpm/services/agents/deployment/strategies/system_strategy.py +10 -3
  47. claude_mpm/services/agents/deployment/strategies/user_strategy.py +10 -14
  48. claude_mpm/services/agents/deployment/system_instructions_deployer.py +8 -85
  49. claude_mpm/services/agents/memory/content_manager.py +98 -105
  50. claude_mpm/services/event_bus/__init__.py +18 -0
  51. claude_mpm/services/event_bus/config.py +165 -0
  52. claude_mpm/services/event_bus/event_bus.py +349 -0
  53. claude_mpm/services/event_bus/relay.py +297 -0
  54. claude_mpm/services/events/__init__.py +44 -0
  55. claude_mpm/services/events/consumers/__init__.py +18 -0
  56. claude_mpm/services/events/consumers/dead_letter.py +296 -0
  57. claude_mpm/services/events/consumers/logging.py +183 -0
  58. claude_mpm/services/events/consumers/metrics.py +242 -0
  59. claude_mpm/services/events/consumers/socketio.py +376 -0
  60. claude_mpm/services/events/core.py +470 -0
  61. claude_mpm/services/events/interfaces.py +230 -0
  62. claude_mpm/services/events/producers/__init__.py +14 -0
  63. claude_mpm/services/events/producers/hook.py +269 -0
  64. claude_mpm/services/events/producers/system.py +327 -0
  65. claude_mpm/services/mcp_gateway/auto_configure.py +372 -0
  66. claude_mpm/services/mcp_gateway/core/process_pool.py +411 -0
  67. claude_mpm/services/mcp_gateway/server/stdio_server.py +13 -0
  68. claude_mpm/services/monitor_build_service.py +345 -0
  69. claude_mpm/services/socketio/event_normalizer.py +667 -0
  70. claude_mpm/services/socketio/handlers/connection.py +81 -23
  71. claude_mpm/services/socketio/handlers/hook.py +14 -5
  72. claude_mpm/services/socketio/migration_utils.py +329 -0
  73. claude_mpm/services/socketio/server/broadcaster.py +26 -33
  74. claude_mpm/services/socketio/server/core.py +29 -5
  75. claude_mpm/services/socketio/server/eventbus_integration.py +189 -0
  76. claude_mpm/services/socketio/server/main.py +25 -0
  77. {claude_mpm-4.0.32.dist-info → claude_mpm-4.1.0.dist-info}/METADATA +28 -9
  78. {claude_mpm-4.0.32.dist-info → claude_mpm-4.1.0.dist-info}/RECORD +82 -56
  79. {claude_mpm-4.0.32.dist-info → claude_mpm-4.1.0.dist-info}/WHEEL +0 -0
  80. {claude_mpm-4.0.32.dist-info → claude_mpm-4.1.0.dist-info}/entry_points.txt +0 -0
  81. {claude_mpm-4.0.32.dist-info → claude_mpm-4.1.0.dist-info}/licenses/LICENSE +0 -0
  82. {claude_mpm-4.0.32.dist-info → claude_mpm-4.1.0.dist-info}/top_level.txt +0 -0
@@ -13,13 +13,25 @@ import sys
13
13
  from datetime import datetime
14
14
  from typing import Any, Dict, Optional
15
15
 
16
- from .tool_analysis import (
17
- assess_security_risk,
18
- calculate_duration,
19
- classify_tool_operation,
20
- extract_tool_parameters,
21
- extract_tool_results,
22
- )
16
+ # Import tool analysis with fallback for direct execution
17
+ try:
18
+ # Try relative import first (when imported as module)
19
+ from .tool_analysis import (
20
+ assess_security_risk,
21
+ calculate_duration,
22
+ classify_tool_operation,
23
+ extract_tool_parameters,
24
+ extract_tool_results,
25
+ )
26
+ except ImportError:
27
+ # Fall back to direct import (when parent script is run directly)
28
+ from tool_analysis import (
29
+ assess_security_risk,
30
+ calculate_duration,
31
+ classify_tool_operation,
32
+ extract_tool_parameters,
33
+ extract_tool_results,
34
+ )
23
35
 
24
36
  # Debug mode
25
37
  DEBUG = os.environ.get("CLAUDE_MPM_HOOK_DEBUG", "true").lower() != "false"
@@ -99,8 +111,8 @@ class EventHandlers:
99
111
  file=sys.stderr,
100
112
  )
101
113
 
102
- # Emit to /hook namespace
103
- self.hook_handler._emit_socketio_event("/hook", "user_prompt", prompt_data)
114
+ # Emit normalized event (namespace no longer needed with normalized events)
115
+ self.hook_handler._emit_socketio_event("", "user_prompt", prompt_data)
104
116
 
105
117
  def handle_pre_tool_fast(self, event):
106
118
  """Handle pre-tool use with comprehensive data capture.
@@ -152,7 +164,7 @@ class EventHandlers:
152
164
  if tool_name == "Task" and isinstance(tool_input, dict):
153
165
  self._handle_task_delegation(tool_input, pre_tool_data, session_id)
154
166
 
155
- self.hook_handler._emit_socketio_event("/hook", "pre_tool", pre_tool_data)
167
+ self.hook_handler._emit_socketio_event("", "pre_tool", pre_tool_data)
156
168
 
157
169
  def _handle_task_delegation(
158
170
  self, tool_input: dict, pre_tool_data: dict, session_id: str
@@ -242,7 +254,7 @@ class EventHandlers:
242
254
  "hook_event_name": "SubagentStart", # For dashboard compatibility
243
255
  }
244
256
  self.hook_handler._emit_socketio_event(
245
- "/hook", "subagent_start", subagent_start_data
257
+ "", "subagent_start", subagent_start_data
246
258
  )
247
259
 
248
260
  def _get_git_branch(self, working_dir: str = None) -> str:
@@ -362,7 +374,7 @@ class EventHandlers:
362
374
  session_id, agent_type, event, self.hook_handler.delegation_requests
363
375
  )
364
376
 
365
- self.hook_handler._emit_socketio_event("/hook", "post_tool", post_tool_data)
377
+ self.hook_handler._emit_socketio_event("", "post_tool", post_tool_data)
366
378
 
367
379
  def handle_notification_fast(self, event):
368
380
  """Handle notification events from Claude.
@@ -399,9 +411,9 @@ class EventHandlers:
399
411
  ),
400
412
  }
401
413
 
402
- # Emit to /hook namespace
414
+ # Emit normalized event
403
415
  self.hook_handler._emit_socketio_event(
404
- "/hook", "notification", notification_data
416
+ "", "notification", notification_data
405
417
  )
406
418
 
407
419
  def handle_stop_fast(self, event):
@@ -481,8 +493,8 @@ class EventHandlers:
481
493
  "has_output": bool(event.get("final_output")),
482
494
  }
483
495
 
484
- # Emit to /hook namespace
485
- self.hook_handler._emit_socketio_event("/hook", "stop", stop_data)
496
+ # Emit normalized event
497
+ self.hook_handler._emit_socketio_event("", "stop", stop_data)
486
498
 
487
499
  def handle_subagent_stop_fast(self, event):
488
500
  """Handle subagent stop events with improved agent type detection."""
@@ -599,9 +611,9 @@ class EventHandlers:
599
611
  file=sys.stderr,
600
612
  )
601
613
 
602
- # Emit to /hook namespace with high priority
614
+ # Emit normalized event with high priority
603
615
  self.hook_handler._emit_socketio_event(
604
- "/hook", "subagent_stop", subagent_stop_data
616
+ "", "subagent_stop", subagent_stop_data
605
617
  )
606
618
 
607
619
  def _handle_subagent_response_tracking(
@@ -737,7 +749,57 @@ class EventHandlers:
737
749
  )
738
750
 
739
751
  def handle_assistant_response(self, event):
740
- """Handle assistant response events for comprehensive response tracking."""
752
+ """Handle assistant response events for comprehensive response tracking.
753
+
754
+ WHY emit assistant response events:
755
+ - Provides visibility into Claude's responses to user prompts
756
+ - Captures response content and metadata for analysis
757
+ - Enables tracking of conversation flow and response patterns
758
+ - Essential for comprehensive monitoring of Claude interactions
759
+ """
760
+ # Track the response for logging
741
761
  self.hook_handler.response_tracking_manager.track_assistant_response(
742
762
  event, self.hook_handler.pending_prompts
743
763
  )
764
+
765
+ # Get working directory and git branch
766
+ working_dir = event.get("cwd", "")
767
+ git_branch = self._get_git_branch(working_dir) if working_dir else "Unknown"
768
+
769
+ # Extract response data
770
+ response_text = event.get("response", "")
771
+ session_id = event.get("session_id", "")
772
+
773
+ # Prepare assistant response data for Socket.IO emission
774
+ assistant_response_data = {
775
+ "response_text": response_text,
776
+ "response_preview": response_text[:500] if len(response_text) > 500 else response_text,
777
+ "response_length": len(response_text),
778
+ "session_id": session_id,
779
+ "working_directory": working_dir,
780
+ "git_branch": git_branch,
781
+ "timestamp": datetime.now().isoformat(),
782
+ "contains_code": "```" in response_text,
783
+ "contains_json": "```json" in response_text,
784
+ "hook_event_name": "AssistantResponse", # Explicitly set for dashboard
785
+ "has_structured_response": bool(re.search(r"```json\s*\{.*?\}\s*```", response_text, re.DOTALL)),
786
+ }
787
+
788
+ # Check if this is a response to a tracked prompt
789
+ if session_id in self.hook_handler.pending_prompts:
790
+ prompt_data = self.hook_handler.pending_prompts[session_id]
791
+ assistant_response_data["original_prompt"] = prompt_data.get("prompt", "")[:200]
792
+ assistant_response_data["prompt_timestamp"] = prompt_data.get("timestamp", "")
793
+ assistant_response_data["is_tracked_response"] = True
794
+ else:
795
+ assistant_response_data["is_tracked_response"] = False
796
+
797
+ # Debug logging
798
+ if DEBUG:
799
+ print(
800
+ f"Hook handler: Processing AssistantResponse - session: '{session_id}', response_length: {len(response_text)}",
801
+ file=sys.stderr,
802
+ )
803
+
804
+ # Emit normalized event
805
+ self.hook_handler._emit_socketio_event("", "assistant_response", assistant_response_data)
@@ -24,11 +24,49 @@ import time
24
24
  from collections import deque
25
25
  from datetime import datetime
26
26
 
27
- # Import extracted modules
28
- from .connection_pool import SocketIOConnectionPool
29
- from .event_handlers import EventHandlers
30
- from .memory_integration import MemoryHookManager
31
- from .response_tracking import ResponseTrackingManager
27
+ # Import extracted modules with fallback for direct execution
28
+ try:
29
+ # Try relative imports first (when imported as module)
30
+ from .connection_pool import SocketIOConnectionPool
31
+ from .event_handlers import EventHandlers
32
+ from .memory_integration import MemoryHookManager
33
+ from .response_tracking import ResponseTrackingManager
34
+ except ImportError:
35
+ # Fall back to absolute imports (when run directly)
36
+ import sys
37
+ from pathlib import Path
38
+ # Add parent directory to path
39
+ sys.path.insert(0, str(Path(__file__).parent))
40
+ from connection_pool import SocketIOConnectionPool
41
+ from event_handlers import EventHandlers
42
+ from memory_integration import MemoryHookManager
43
+ from response_tracking import ResponseTrackingManager
44
+
45
+ # Import EventNormalizer for consistent event formatting
46
+ try:
47
+ from claude_mpm.services.socketio.event_normalizer import EventNormalizer
48
+ except ImportError:
49
+ # Create a simple fallback EventNormalizer if import fails
50
+ class EventNormalizer:
51
+ def normalize(self, event_data):
52
+ """Simple fallback normalizer that returns event as-is."""
53
+ return type('NormalizedEvent', (), {
54
+ 'to_dict': lambda: {
55
+ 'event': 'claude_event',
56
+ 'type': event_data.get('type', 'unknown'),
57
+ 'subtype': event_data.get('subtype', 'generic'),
58
+ 'timestamp': event_data.get('timestamp', datetime.now().isoformat()),
59
+ 'data': event_data.get('data', event_data)
60
+ }
61
+ })
62
+
63
+ # Import EventBus for decoupled event distribution
64
+ try:
65
+ from claude_mpm.services.event_bus import EventBus
66
+ EVENTBUS_AVAILABLE = True
67
+ except ImportError:
68
+ EVENTBUS_AVAILABLE = False
69
+ EventBus = None
32
70
 
33
71
  # Import constants for configuration
34
72
  try:
@@ -81,11 +119,23 @@ class ClaudeHookHandler:
81
119
  """
82
120
 
83
121
  def __init__(self):
84
- # Socket.IO client (persistent if possible)
85
- self.connection_pool = SocketIOConnectionPool(max_connections=3)
86
122
  # Track events for periodic cleanup
87
123
  self.events_processed = 0
88
124
  self.last_cleanup = time.time()
125
+ # Event normalizer for consistent event schema
126
+ self.event_normalizer = EventNormalizer()
127
+
128
+ # Initialize EventBus for decoupled event distribution
129
+ self.event_bus = None
130
+ if EVENTBUS_AVAILABLE:
131
+ try:
132
+ self.event_bus = EventBus.get_instance()
133
+ if DEBUG:
134
+ print("✅ EventBus initialized for hook handler", file=sys.stderr)
135
+ except Exception as e:
136
+ if DEBUG:
137
+ print(f"⚠️ Failed to initialize EventBus: {e}", file=sys.stderr)
138
+ self.event_bus = None
89
139
 
90
140
  # Maximum sizes for tracking
91
141
  self.MAX_DELEGATION_TRACKING = 200
@@ -479,167 +529,61 @@ class ClaudeHookHandler:
479
529
  """
480
530
  print(json.dumps({"action": "continue"}))
481
531
 
482
- def _discover_socketio_port(self) -> int:
483
- """Discover the port of the running SocketIO server."""
484
- try:
485
- # Try to import port manager
486
- from claude_mpm.services.port_manager import PortManager
487
-
488
- port_manager = PortManager()
489
- instances = port_manager.list_active_instances()
490
-
491
- if instances:
492
- # Prefer port 8765 if available
493
- for instance in instances:
494
- if instance.get("port") == 8765:
495
- return 8765
496
- # Otherwise use the first active instance
497
- return instances[0].get("port", 8765)
498
- else:
499
- # No active instances, use default
500
- return 8765
501
- except Exception:
502
- # Fallback to environment variable or default
503
- return int(os.environ.get("CLAUDE_MPM_SOCKETIO_PORT", "8765"))
504
532
 
505
533
  def _emit_socketio_event(self, namespace: str, event: str, data: dict):
506
- """Emit Socket.IO event with improved reliability and persistent connections.
507
-
508
- WHY improved approach:
509
- - Maintains persistent connections throughout handler lifecycle
510
- - Better error handling and automatic recovery
511
- - Connection health monitoring before emission
512
- - Automatic reconnection for critical events
513
- - Validates data before emission
534
+ """Emit event through EventBus for Socket.IO relay.
535
+
536
+ WHY EventBus-only approach:
537
+ - Single event path prevents duplicates
538
+ - EventBus relay handles Socket.IO connection management
539
+ - Better separation of concerns
540
+ - More resilient with centralized failure handling
541
+ - Cleaner architecture and easier testing
514
542
  """
515
- # Always try to emit Socket.IO events if available
516
- # The daemon should be running when manager is active
517
-
518
- # Get Socket.IO client with dynamic port discovery
519
- port = self._discover_socketio_port()
520
- client = self.connection_pool.get_connection(port)
543
+ # Create event data for normalization
544
+ raw_event = {
545
+ "type": "hook",
546
+ "subtype": event, # e.g., "user_prompt", "pre_tool", "subagent_stop"
547
+ "timestamp": datetime.now().isoformat(),
548
+ "data": data,
549
+ "source": "claude_hooks", # Identify the source
550
+ "session_id": data.get("sessionId"), # Include session if available
551
+ }
521
552
 
522
- # If no client available, try to create one
523
- if not client:
524
- if DEBUG:
553
+ # Normalize the event using EventNormalizer for consistent schema
554
+ normalized_event = self.event_normalizer.normalize(raw_event, source="hook")
555
+ claude_event_data = normalized_event.to_dict()
556
+
557
+ # Log important events for debugging
558
+ if DEBUG and event in ["subagent_stop", "pre_tool"]:
559
+ if event == "subagent_stop":
560
+ agent_type = data.get("agent_type", "unknown")
525
561
  print(
526
- f"Hook handler: No Socket.IO client available, attempting to create connection for event: hook.{event}",
562
+ f"Hook handler: Publishing SubagentStop for agent '{agent_type}'",
527
563
  file=sys.stderr,
528
564
  )
529
- # Force creation of a new connection
530
- client = self.connection_pool._create_connection(port)
531
- if client:
532
- # Add to pool for future use
533
- self.connection_pool.connections.append(
534
- {"port": port, "client": client, "created": time.time()}
565
+ elif event == "pre_tool" and data.get("tool_name") == "Task":
566
+ delegation = data.get("delegation_details", {})
567
+ agent_type = delegation.get("agent_type", "unknown")
568
+ print(
569
+ f"Hook handler: Publishing Task delegation to agent '{agent_type}'",
570
+ file=sys.stderr,
535
571
  )
536
- else:
572
+
573
+ # Publish to EventBus for distribution through relay
574
+ if self.event_bus and EVENTBUS_AVAILABLE:
575
+ try:
576
+ # Publish to EventBus with topic format: hook.{event}
577
+ topic = f"hook.{event}"
578
+ self.event_bus.publish(topic, claude_event_data)
537
579
  if DEBUG:
538
- print(
539
- f"Hook handler: Failed to create Socket.IO connection for event: hook.{event}",
540
- file=sys.stderr,
541
- )
542
- return
543
-
544
- try:
545
- # Verify connection is alive before emitting
546
- if not client.connected:
580
+ print(f"✅ Published to EventBus: {topic}", file=sys.stderr)
581
+ except Exception as e:
547
582
  if DEBUG:
548
- print(
549
- f"Hook handler: Client not connected, attempting reconnection for event: hook.{event}",
550
- file=sys.stderr,
551
- )
552
- # Try to reconnect
553
- try:
554
- client.connect(
555
- f"http://localhost:{port}",
556
- wait=True,
557
- wait_timeout=1.0,
558
- transports=['websocket', 'polling'],
559
- )
560
- except:
561
- # If reconnection fails, get a fresh client
562
- client = self.connection_pool._create_connection(port)
563
- if not client:
564
- if DEBUG:
565
- print(
566
- f"Hook handler: Reconnection failed for event: hook.{event}",
567
- file=sys.stderr,
568
- )
569
- return
570
-
571
- # Format event for Socket.IO server
572
- claude_event_data = {
573
- "type": f"hook.{event}", # Dashboard expects 'hook.' prefix
574
- "timestamp": datetime.now().isoformat(),
575
- "data": data,
576
- }
577
-
578
- # Log important events for debugging
579
- if DEBUG and event in ["subagent_stop", "pre_tool"]:
580
- if event == "subagent_stop":
581
- agent_type = data.get("agent_type", "unknown")
582
- print(
583
- f"Hook handler: Emitting SubagentStop for agent '{agent_type}'",
584
- file=sys.stderr,
585
- )
586
- elif event == "pre_tool" and data.get("tool_name") == "Task":
587
- delegation = data.get("delegation_details", {})
588
- agent_type = delegation.get("agent_type", "unknown")
589
- print(
590
- f"Hook handler: Emitting Task delegation to agent '{agent_type}'",
591
- file=sys.stderr,
592
- )
593
-
594
- # Emit synchronously
595
- client.emit("claude_event", claude_event_data)
596
-
597
- # For critical events, wait a moment to ensure delivery
598
- if event in ["subagent_stop", "pre_tool"]:
599
- time.sleep(0.01) # Small delay to ensure event is sent
600
-
601
- # Verify emission for critical events
602
- if event in ["subagent_stop", "pre_tool"] and DEBUG:
603
- if client.connected:
604
- print(
605
- f"✅ Successfully emitted Socket.IO event: hook.{event} (connection still active)",
606
- file=sys.stderr,
607
- )
608
- else:
609
- print(
610
- f"⚠️ Event emitted but connection closed after: hook.{event}",
611
- file=sys.stderr,
612
- )
613
-
614
- except Exception as e:
583
+ print(f"⚠️ Failed to publish to EventBus: {e}", file=sys.stderr)
584
+ else:
615
585
  if DEBUG:
616
- print(f" Socket.IO emit failed for hook.{event}: {e}", file=sys.stderr)
617
-
618
- # Try to reconnect immediately for critical events
619
- if event in ["subagent_stop", "pre_tool"]:
620
- if DEBUG:
621
- print(
622
- f"Hook handler: Attempting immediate reconnection for critical event: hook.{event}",
623
- file=sys.stderr,
624
- )
625
- # Force get a new client and emit again
626
- self.connection_pool._cleanup_dead_connections()
627
- retry_client = self.connection_pool._create_connection(port)
628
- if retry_client:
629
- try:
630
- retry_client.emit("claude_event", claude_event_data)
631
- # Add to pool for future use
632
- self.connection_pool.connections.append(
633
- {"port": port, "client": retry_client, "created": time.time()}
634
- )
635
- if DEBUG:
636
- print(
637
- f"✅ Successfully re-emitted event after reconnection: hook.{event}",
638
- file=sys.stderr,
639
- )
640
- except Exception as retry_e:
641
- if DEBUG:
642
- print(f"❌ Re-emission failed: {retry_e}", file=sys.stderr)
586
+ print(f"⚠️ EventBus not available for event: hook.{event}", file=sys.stderr)
643
587
 
644
588
  def handle_subagent_stop(self, event: dict):
645
589
  """Handle subagent stop events with improved agent type detection.
@@ -876,6 +820,17 @@ class ClaudeHookHandler:
876
820
  metadata["task_completed"] = structured_response.get(
877
821
  "task_completed", False
878
822
  )
823
+
824
+ # Check for MEMORIES field and process if present
825
+ if "MEMORIES" in structured_response and structured_response["MEMORIES"]:
826
+ memories = structured_response["MEMORIES"]
827
+ if DEBUG:
828
+ print(
829
+ f"Found MEMORIES field in {agent_type} response with {len(memories)} items",
830
+ file=sys.stderr,
831
+ )
832
+ # The memory will be processed by extract_and_update_memory
833
+ # which is called by the memory hook service
879
834
 
880
835
  # Track the response
881
836
  file_path = (
@@ -937,7 +892,17 @@ class ClaudeHookHandler:
937
892
  "files_modified": structured_response.get("files_modified", []),
938
893
  "tools_used": structured_response.get("tools_used", []),
939
894
  "remember": structured_response.get("remember"),
895
+ "MEMORIES": structured_response.get("MEMORIES"), # Complete memory replacement
940
896
  }
897
+
898
+ # Log if MEMORIES field is present
899
+ if "MEMORIES" in structured_response and structured_response["MEMORIES"]:
900
+ if DEBUG:
901
+ memories_count = len(structured_response["MEMORIES"])
902
+ print(
903
+ f"Agent {agent_type} returned MEMORIES field with {memories_count} items",
904
+ file=sys.stderr,
905
+ )
941
906
 
942
907
  # Debug log the processed data
943
908
  if DEBUG:
@@ -950,12 +915,9 @@ class ClaudeHookHandler:
950
915
  self._emit_socketio_event("/hook", "subagent_stop", subagent_stop_data)
951
916
 
952
917
  def __del__(self):
953
- """Cleanup Socket.IO connections on handler destruction."""
954
- if hasattr(self, "connection_pool") and self.connection_pool:
955
- try:
956
- self.connection_pool.close_all()
957
- except:
958
- pass
918
+ """Cleanup on handler destruction."""
919
+ # Connection pool no longer used - EventBus handles cleanup
920
+ pass
959
921
 
960
922
 
961
923
  def main():