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.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/INSTRUCTIONS.md +70 -2
- claude_mpm/agents/OUTPUT_STYLE.md +0 -11
- claude_mpm/agents/WORKFLOW.md +14 -2
- claude_mpm/agents/templates/documentation.json +51 -34
- claude_mpm/agents/templates/research.json +0 -11
- claude_mpm/cli/__init__.py +111 -33
- claude_mpm/cli/commands/agent_manager.py +10 -8
- claude_mpm/cli/commands/agents.py +82 -0
- claude_mpm/cli/commands/cleanup_orphaned_agents.py +150 -0
- claude_mpm/cli/commands/mcp_pipx_config.py +199 -0
- claude_mpm/cli/parsers/agents_parser.py +27 -0
- claude_mpm/cli/parsers/base_parser.py +6 -0
- claude_mpm/cli/startup_logging.py +75 -0
- claude_mpm/core/framework_loader.py +173 -84
- claude_mpm/dashboard/static/css/dashboard.css +449 -0
- claude_mpm/dashboard/static/dist/components/agent-inference.js +1 -1
- claude_mpm/dashboard/static/dist/components/event-viewer.js +1 -1
- claude_mpm/dashboard/static/dist/components/file-tool-tracker.js +1 -1
- claude_mpm/dashboard/static/dist/components/module-viewer.js +1 -1
- claude_mpm/dashboard/static/dist/components/session-manager.js +1 -1
- claude_mpm/dashboard/static/dist/dashboard.js +1 -1
- claude_mpm/dashboard/static/dist/socket-client.js +1 -1
- claude_mpm/dashboard/static/js/components/agent-hierarchy.js +774 -0
- claude_mpm/dashboard/static/js/components/agent-inference.js +257 -3
- claude_mpm/dashboard/static/js/components/build-tracker.js +323 -0
- claude_mpm/dashboard/static/js/components/event-viewer.js +168 -39
- claude_mpm/dashboard/static/js/components/file-tool-tracker.js +17 -0
- claude_mpm/dashboard/static/js/components/session-manager.js +23 -3
- claude_mpm/dashboard/static/js/components/socket-manager.js +2 -0
- claude_mpm/dashboard/static/js/dashboard.js +207 -31
- claude_mpm/dashboard/static/js/socket-client.js +92 -11
- claude_mpm/dashboard/templates/index.html +1 -0
- claude_mpm/hooks/claude_hooks/connection_pool.py +25 -4
- claude_mpm/hooks/claude_hooks/event_handlers.py +81 -19
- claude_mpm/hooks/claude_hooks/hook_handler.py +125 -163
- claude_mpm/hooks/claude_hooks/hook_handler_eventbus.py +398 -0
- claude_mpm/hooks/claude_hooks/response_tracking.py +10 -0
- claude_mpm/services/agents/deployment/agent_deployment.py +34 -48
- claude_mpm/services/agents/deployment/agent_discovery_service.py +4 -1
- claude_mpm/services/agents/deployment/agent_template_builder.py +20 -11
- claude_mpm/services/agents/deployment/agent_version_manager.py +4 -1
- claude_mpm/services/agents/deployment/agents_directory_resolver.py +10 -25
- claude_mpm/services/agents/deployment/multi_source_deployment_service.py +396 -13
- claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +3 -2
- claude_mpm/services/agents/deployment/strategies/system_strategy.py +10 -3
- claude_mpm/services/agents/deployment/strategies/user_strategy.py +10 -14
- claude_mpm/services/agents/deployment/system_instructions_deployer.py +8 -85
- claude_mpm/services/agents/memory/content_manager.py +98 -105
- claude_mpm/services/event_bus/__init__.py +18 -0
- claude_mpm/services/event_bus/config.py +165 -0
- claude_mpm/services/event_bus/event_bus.py +349 -0
- claude_mpm/services/event_bus/relay.py +297 -0
- claude_mpm/services/events/__init__.py +44 -0
- claude_mpm/services/events/consumers/__init__.py +18 -0
- claude_mpm/services/events/consumers/dead_letter.py +296 -0
- claude_mpm/services/events/consumers/logging.py +183 -0
- claude_mpm/services/events/consumers/metrics.py +242 -0
- claude_mpm/services/events/consumers/socketio.py +376 -0
- claude_mpm/services/events/core.py +470 -0
- claude_mpm/services/events/interfaces.py +230 -0
- claude_mpm/services/events/producers/__init__.py +14 -0
- claude_mpm/services/events/producers/hook.py +269 -0
- claude_mpm/services/events/producers/system.py +327 -0
- claude_mpm/services/mcp_gateway/auto_configure.py +372 -0
- claude_mpm/services/mcp_gateway/core/process_pool.py +411 -0
- claude_mpm/services/mcp_gateway/server/stdio_server.py +13 -0
- claude_mpm/services/monitor_build_service.py +345 -0
- claude_mpm/services/socketio/event_normalizer.py +667 -0
- claude_mpm/services/socketio/handlers/connection.py +81 -23
- claude_mpm/services/socketio/handlers/hook.py +14 -5
- claude_mpm/services/socketio/migration_utils.py +329 -0
- claude_mpm/services/socketio/server/broadcaster.py +26 -33
- claude_mpm/services/socketio/server/core.py +29 -5
- claude_mpm/services/socketio/server/eventbus_integration.py +189 -0
- claude_mpm/services/socketio/server/main.py +25 -0
- {claude_mpm-4.0.32.dist-info → claude_mpm-4.1.0.dist-info}/METADATA +28 -9
- {claude_mpm-4.0.32.dist-info → claude_mpm-4.1.0.dist-info}/RECORD +82 -56
- {claude_mpm-4.0.32.dist-info → claude_mpm-4.1.0.dist-info}/WHEEL +0 -0
- {claude_mpm-4.0.32.dist-info → claude_mpm-4.1.0.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.0.32.dist-info → claude_mpm-4.1.0.dist-info}/licenses/LICENSE +0 -0
- {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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
|
103
|
-
self.hook_handler._emit_socketio_event("
|
|
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("
|
|
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
|
-
"
|
|
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("
|
|
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
|
|
414
|
+
# Emit normalized event
|
|
403
415
|
self.hook_handler._emit_socketio_event(
|
|
404
|
-
"
|
|
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
|
|
485
|
-
self.hook_handler._emit_socketio_event("
|
|
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
|
|
614
|
+
# Emit normalized event with high priority
|
|
603
615
|
self.hook_handler._emit_socketio_event(
|
|
604
|
-
"
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
from .
|
|
31
|
-
from .
|
|
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
|
|
507
|
-
|
|
508
|
-
WHY
|
|
509
|
-
-
|
|
510
|
-
-
|
|
511
|
-
-
|
|
512
|
-
-
|
|
513
|
-
-
|
|
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
|
-
#
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
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
|
-
#
|
|
523
|
-
|
|
524
|
-
|
|
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:
|
|
562
|
+
f"Hook handler: Publishing SubagentStop for agent '{agent_type}'",
|
|
527
563
|
file=sys.stderr,
|
|
528
564
|
)
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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"
|
|
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
|
|
954
|
-
|
|
955
|
-
|
|
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():
|