claude-mpm 4.0.34__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/cli/__init__.py +48 -7
- 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/dashboard/static/js/components/build-tracker.js +35 -1
- claude_mpm/dashboard/static/js/socket-client.js +7 -5
- claude_mpm/hooks/claude_hooks/connection_pool.py +13 -2
- claude_mpm/hooks/claude_hooks/hook_handler.py +67 -167
- claude_mpm/services/agents/deployment/agent_discovery_service.py +4 -1
- claude_mpm/services/agents/deployment/agent_template_builder.py +2 -1
- claude_mpm/services/agents/deployment/agent_version_manager.py +4 -1
- claude_mpm/services/agents/deployment/multi_source_deployment_service.py +207 -10
- claude_mpm/services/event_bus/config.py +165 -0
- claude_mpm/services/event_bus/event_bus.py +35 -20
- claude_mpm/services/event_bus/relay.py +8 -12
- claude_mpm/services/mcp_gateway/auto_configure.py +372 -0
- claude_mpm/services/socketio/handlers/connection.py +3 -3
- claude_mpm/services/socketio/server/core.py +25 -2
- claude_mpm/services/socketio/server/eventbus_integration.py +189 -0
- claude_mpm/services/socketio/server/main.py +25 -0
- {claude_mpm-4.0.34.dist-info → claude_mpm-4.1.0.dist-info}/METADATA +25 -7
- {claude_mpm-4.0.34.dist-info → claude_mpm-4.1.0.dist-info}/RECORD +33 -28
- {claude_mpm-4.0.34.dist-info → claude_mpm-4.1.0.dist-info}/WHEEL +0 -0
- {claude_mpm-4.0.34.dist-info → claude_mpm-4.1.0.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.0.34.dist-info → claude_mpm-4.1.0.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.0.34.dist-info → claude_mpm-4.1.0.dist-info}/top_level.txt +0 -0
|
@@ -338,6 +338,12 @@ def create_parser(
|
|
|
338
338
|
from ..commands.cleanup import add_cleanup_parser
|
|
339
339
|
|
|
340
340
|
add_cleanup_parser(subparsers)
|
|
341
|
+
|
|
342
|
+
# MCP pipx configuration command
|
|
343
|
+
if hasattr(CLICommands, "MCP_PIPX_CONFIG") or True: # Always add for now
|
|
344
|
+
from ..commands.mcp_pipx_config import add_parser as add_mcp_pipx_parser
|
|
345
|
+
|
|
346
|
+
add_mcp_pipx_parser(subparsers)
|
|
341
347
|
|
|
342
348
|
from ..commands.doctor import add_doctor_parser
|
|
343
349
|
|
|
@@ -66,8 +66,10 @@ class StartupStatusLogger:
|
|
|
66
66
|
self.logger.info(f"MCP Server: {config_status['servers_count']} server(s) configured")
|
|
67
67
|
else:
|
|
68
68
|
self.logger.info("MCP Server: No servers configured")
|
|
69
|
+
self._log_mcp_setup_hint()
|
|
69
70
|
else:
|
|
70
71
|
self.logger.info("MCP Server: No configuration found in ~/.claude.json")
|
|
72
|
+
self._log_mcp_setup_hint()
|
|
71
73
|
|
|
72
74
|
# Check for claude-mpm MCP gateway status
|
|
73
75
|
gateway_status = self._check_mcp_gateway_status()
|
|
@@ -75,6 +77,9 @@ class StartupStatusLogger:
|
|
|
75
77
|
self.logger.info("MCP Gateway: Claude MPM gateway configured")
|
|
76
78
|
else:
|
|
77
79
|
self.logger.info("MCP Gateway: Claude MPM gateway not configured")
|
|
80
|
+
# Check if this is a pipx installation that could benefit from auto-config
|
|
81
|
+
if self._is_pipx_installation() and not self._has_auto_config_preference():
|
|
82
|
+
self.logger.info("MCP Gateway: Auto-configuration available for pipx users")
|
|
78
83
|
|
|
79
84
|
except Exception as e:
|
|
80
85
|
self.logger.warning(f"MCP Server: Status check failed - {e}")
|
|
@@ -293,6 +298,76 @@ class StartupStatusLogger:
|
|
|
293
298
|
result["error"] = str(e)
|
|
294
299
|
|
|
295
300
|
return result
|
|
301
|
+
|
|
302
|
+
def _is_pipx_installation(self) -> bool:
|
|
303
|
+
"""Check if this is a pipx installation."""
|
|
304
|
+
try:
|
|
305
|
+
# Check if running from pipx
|
|
306
|
+
if "pipx" in sys.executable.lower():
|
|
307
|
+
return True
|
|
308
|
+
|
|
309
|
+
# Check module path
|
|
310
|
+
import claude_mpm
|
|
311
|
+
module_path = Path(claude_mpm.__file__).parent
|
|
312
|
+
if "pipx" in str(module_path):
|
|
313
|
+
return True
|
|
314
|
+
except Exception:
|
|
315
|
+
pass
|
|
316
|
+
|
|
317
|
+
return False
|
|
318
|
+
|
|
319
|
+
def _has_auto_config_preference(self) -> bool:
|
|
320
|
+
"""Check if user has already been asked about auto-configuration."""
|
|
321
|
+
try:
|
|
322
|
+
from ..config.paths import paths
|
|
323
|
+
preference_file = paths.claude_mpm_dir_hidden / "mcp_auto_config_preference.json"
|
|
324
|
+
return preference_file.exists()
|
|
325
|
+
except Exception:
|
|
326
|
+
return False
|
|
327
|
+
|
|
328
|
+
def _log_mcp_setup_hint(self) -> None:
|
|
329
|
+
"""Log helpful hints for MCP setup."""
|
|
330
|
+
# Check if installed via pipx
|
|
331
|
+
is_pipx = self._check_pipx_installation()
|
|
332
|
+
|
|
333
|
+
if is_pipx:
|
|
334
|
+
self.logger.info("💡 TIP: It looks like you installed claude-mpm via pipx")
|
|
335
|
+
self.logger.info(" To configure MCP for Claude Code with pipx:")
|
|
336
|
+
self.logger.info(" 1. Run: python3 scripts/configure_mcp_pipx.py")
|
|
337
|
+
self.logger.info(" 2. Or see: docs/MCP_PIPX_SETUP.md for manual setup")
|
|
338
|
+
self.logger.info(" 3. Restart Claude Code after configuration")
|
|
339
|
+
else:
|
|
340
|
+
self.logger.info("💡 TIP: To enable MCP integration with Claude Code:")
|
|
341
|
+
self.logger.info(" 1. See docs/MCP_SETUP.md for setup instructions")
|
|
342
|
+
self.logger.info(" 2. Run: claude-mpm doctor --check mcp to verify")
|
|
343
|
+
self.logger.info(" 3. Restart Claude Code after configuration")
|
|
344
|
+
|
|
345
|
+
def _check_pipx_installation(self) -> bool:
|
|
346
|
+
"""Check if claude-mpm was installed via pipx."""
|
|
347
|
+
try:
|
|
348
|
+
# Check if running from a pipx venv
|
|
349
|
+
if "pipx" in sys.executable.lower():
|
|
350
|
+
return True
|
|
351
|
+
|
|
352
|
+
# Check if claude-mpm-mcp command exists and is from pipx
|
|
353
|
+
mcp_cmd = shutil.which("claude-mpm-mcp")
|
|
354
|
+
if mcp_cmd and "pipx" in mcp_cmd.lower():
|
|
355
|
+
return True
|
|
356
|
+
|
|
357
|
+
# Try to check pipx list
|
|
358
|
+
result = subprocess.run(
|
|
359
|
+
["pipx", "list"],
|
|
360
|
+
capture_output=True,
|
|
361
|
+
text=True,
|
|
362
|
+
timeout=2
|
|
363
|
+
)
|
|
364
|
+
if result.returncode == 0 and "claude-mpm" in result.stdout:
|
|
365
|
+
return True
|
|
366
|
+
|
|
367
|
+
except Exception:
|
|
368
|
+
pass
|
|
369
|
+
|
|
370
|
+
return False
|
|
296
371
|
|
|
297
372
|
|
|
298
373
|
def setup_startup_logging(project_root: Optional[Path] = None) -> Path:
|
|
@@ -35,12 +35,46 @@ export class BuildTracker {
|
|
|
35
35
|
/**
|
|
36
36
|
* Initialize the build tracker component
|
|
37
37
|
*/
|
|
38
|
-
init() {
|
|
38
|
+
async init() {
|
|
39
39
|
console.log('Initializing BuildTracker component');
|
|
40
|
+
|
|
41
|
+
// Try to load version.json for dashboard version
|
|
42
|
+
await this.loadDashboardVersion();
|
|
43
|
+
|
|
40
44
|
this.createElements();
|
|
41
45
|
this.setupEventListeners();
|
|
42
46
|
}
|
|
43
47
|
|
|
48
|
+
/**
|
|
49
|
+
* Load dashboard version from version.json if available
|
|
50
|
+
*
|
|
51
|
+
* WHY: Attempts to load the actual dashboard version from the
|
|
52
|
+
* version.json file created by the version management script.
|
|
53
|
+
* Falls back to defaults if file is not available.
|
|
54
|
+
*/
|
|
55
|
+
async loadDashboardVersion() {
|
|
56
|
+
try {
|
|
57
|
+
// Try to fetch version.json from the dashboard root
|
|
58
|
+
const response = await fetch('/version.json');
|
|
59
|
+
if (response.ok) {
|
|
60
|
+
const versionData = await response.json();
|
|
61
|
+
|
|
62
|
+
// Update monitor build info with loaded data
|
|
63
|
+
this.buildInfo.monitor = {
|
|
64
|
+
version: versionData.version || "1.0.0",
|
|
65
|
+
build: versionData.build || 1,
|
|
66
|
+
formatted_build: versionData.formatted_build || "0001",
|
|
67
|
+
full_version: versionData.full_version || "v1.0.0-0001"
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
console.log('Loaded dashboard version:', this.buildInfo.monitor);
|
|
71
|
+
}
|
|
72
|
+
} catch (error) {
|
|
73
|
+
// Silently fall back to defaults if version.json not available
|
|
74
|
+
console.debug('Dashboard version.json not available, using defaults');
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
44
78
|
/**
|
|
45
79
|
* Create the DOM elements for version display
|
|
46
80
|
*
|
|
@@ -47,7 +47,7 @@ class SocketClient {
|
|
|
47
47
|
// Health monitoring
|
|
48
48
|
this.lastPingTime = null;
|
|
49
49
|
this.lastPongTime = null;
|
|
50
|
-
this.pingTimeout =
|
|
50
|
+
this.pingTimeout = 90000; // 90 seconds for health check (more lenient than Socket.IO timeout)
|
|
51
51
|
this.healthCheckInterval = null;
|
|
52
52
|
|
|
53
53
|
// Start periodic status check as fallback mechanism
|
|
@@ -97,11 +97,13 @@ class SocketClient {
|
|
|
97
97
|
autoConnect: true,
|
|
98
98
|
reconnection: true,
|
|
99
99
|
reconnectionDelay: 1000,
|
|
100
|
-
reconnectionDelayMax:
|
|
101
|
-
|
|
102
|
-
timeout:
|
|
100
|
+
reconnectionDelayMax: 5000,
|
|
101
|
+
reconnectionAttempts: Infinity, // Keep trying indefinitely
|
|
102
|
+
timeout: 20000, // Increase connection timeout
|
|
103
103
|
forceNew: true,
|
|
104
|
-
transports: ['websocket', 'polling']
|
|
104
|
+
transports: ['websocket', 'polling'],
|
|
105
|
+
pingInterval: 25000, // Match server setting
|
|
106
|
+
pingTimeout: 60000 // Match server setting
|
|
105
107
|
});
|
|
106
108
|
|
|
107
109
|
this.setupSocketHandlers();
|
|
@@ -1,7 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""Socket.IO connection pool for Claude Code hook handler.
|
|
2
|
+
"""[DEPRECATED] Socket.IO connection pool for Claude Code hook handler.
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
DEPRECATION NOTICE: As of v4.0.35, this module is deprecated.
|
|
5
|
+
All event emission now goes through the EventBus, which handles
|
|
6
|
+
Socket.IO connections via its relay component. This provides:
|
|
7
|
+
- Single event path (no duplicates)
|
|
8
|
+
- Better separation of concerns
|
|
9
|
+
- Centralized connection management
|
|
10
|
+
- More resilient architecture
|
|
11
|
+
|
|
12
|
+
This module is kept for backward compatibility but will be removed in v5.0.0.
|
|
13
|
+
Please use EventBus.publish() instead of direct Socket.IO emission.
|
|
14
|
+
|
|
15
|
+
Original purpose: Provided connection pooling for Socket.IO clients to reduce
|
|
5
16
|
connection overhead and implement circuit breaker patterns.
|
|
6
17
|
"""
|
|
7
18
|
|
|
@@ -60,6 +60,14 @@ except ImportError:
|
|
|
60
60
|
}
|
|
61
61
|
})
|
|
62
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
|
|
70
|
+
|
|
63
71
|
# Import constants for configuration
|
|
64
72
|
try:
|
|
65
73
|
from claude_mpm.core.constants import NetworkConfig, RetryConfig, TimeoutConfig
|
|
@@ -111,13 +119,23 @@ class ClaudeHookHandler:
|
|
|
111
119
|
"""
|
|
112
120
|
|
|
113
121
|
def __init__(self):
|
|
114
|
-
# Socket.IO client (persistent if possible)
|
|
115
|
-
self.connection_pool = SocketIOConnectionPool(max_connections=3)
|
|
116
122
|
# Track events for periodic cleanup
|
|
117
123
|
self.events_processed = 0
|
|
118
124
|
self.last_cleanup = time.time()
|
|
119
125
|
# Event normalizer for consistent event schema
|
|
120
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
|
|
121
139
|
|
|
122
140
|
# Maximum sizes for tracking
|
|
123
141
|
self.MAX_DELEGATION_TRACKING = 200
|
|
@@ -511,176 +529,61 @@ class ClaudeHookHandler:
|
|
|
511
529
|
"""
|
|
512
530
|
print(json.dumps({"action": "continue"}))
|
|
513
531
|
|
|
514
|
-
def _discover_socketio_port(self) -> int:
|
|
515
|
-
"""Discover the port of the running SocketIO server."""
|
|
516
|
-
try:
|
|
517
|
-
# Try to import port manager
|
|
518
|
-
from claude_mpm.services.port_manager import PortManager
|
|
519
|
-
|
|
520
|
-
port_manager = PortManager()
|
|
521
|
-
instances = port_manager.list_active_instances()
|
|
522
|
-
|
|
523
|
-
if instances:
|
|
524
|
-
# Prefer port 8765 if available
|
|
525
|
-
for instance in instances:
|
|
526
|
-
if instance.get("port") == 8765:
|
|
527
|
-
return 8765
|
|
528
|
-
# Otherwise use the first active instance
|
|
529
|
-
return instances[0].get("port", 8765)
|
|
530
|
-
else:
|
|
531
|
-
# No active instances, use default
|
|
532
|
-
return 8765
|
|
533
|
-
except Exception:
|
|
534
|
-
# Fallback to environment variable or default
|
|
535
|
-
return int(os.environ.get("CLAUDE_MPM_SOCKETIO_PORT", "8765"))
|
|
536
532
|
|
|
537
533
|
def _emit_socketio_event(self, namespace: str, event: str, data: dict):
|
|
538
|
-
"""Emit
|
|
539
|
-
|
|
540
|
-
WHY
|
|
541
|
-
-
|
|
542
|
-
-
|
|
543
|
-
- Better
|
|
544
|
-
-
|
|
545
|
-
-
|
|
546
|
-
- All events normalized to standard schema 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
|
|
547
542
|
"""
|
|
548
|
-
#
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
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
|
+
}
|
|
554
552
|
|
|
555
|
-
#
|
|
556
|
-
|
|
557
|
-
|
|
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")
|
|
558
561
|
print(
|
|
559
|
-
f"Hook handler:
|
|
562
|
+
f"Hook handler: Publishing SubagentStop for agent '{agent_type}'",
|
|
560
563
|
file=sys.stderr,
|
|
561
564
|
)
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
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,
|
|
568
571
|
)
|
|
569
|
-
|
|
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)
|
|
570
579
|
if DEBUG:
|
|
571
|
-
print(
|
|
572
|
-
|
|
573
|
-
file=sys.stderr,
|
|
574
|
-
)
|
|
575
|
-
return
|
|
576
|
-
|
|
577
|
-
try:
|
|
578
|
-
# Verify connection is alive before emitting
|
|
579
|
-
if not client.connected:
|
|
580
|
+
print(f"✅ Published to EventBus: {topic}", file=sys.stderr)
|
|
581
|
+
except Exception as e:
|
|
580
582
|
if DEBUG:
|
|
581
|
-
print(
|
|
582
|
-
|
|
583
|
-
file=sys.stderr,
|
|
584
|
-
)
|
|
585
|
-
# Try to reconnect
|
|
586
|
-
try:
|
|
587
|
-
client.connect(
|
|
588
|
-
f"http://localhost:{port}",
|
|
589
|
-
wait=True,
|
|
590
|
-
wait_timeout=1.0,
|
|
591
|
-
transports=['websocket', 'polling'],
|
|
592
|
-
)
|
|
593
|
-
except:
|
|
594
|
-
# If reconnection fails, get a fresh client
|
|
595
|
-
client = self.connection_pool._create_connection(port)
|
|
596
|
-
if not client:
|
|
597
|
-
if DEBUG:
|
|
598
|
-
print(
|
|
599
|
-
f"Hook handler: Reconnection failed for event: hook.{event}",
|
|
600
|
-
file=sys.stderr,
|
|
601
|
-
)
|
|
602
|
-
return
|
|
603
|
-
|
|
604
|
-
# Create event data for normalization
|
|
605
|
-
raw_event = {
|
|
606
|
-
"type": "hook",
|
|
607
|
-
"subtype": event, # e.g., "user_prompt", "pre_tool", "subagent_stop"
|
|
608
|
-
"timestamp": datetime.now().isoformat(),
|
|
609
|
-
"data": data,
|
|
610
|
-
"source": "claude_hooks", # Identify the source
|
|
611
|
-
"session_id": data.get("sessionId"), # Include session if available
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
# Normalize the event using EventNormalizer for consistent schema
|
|
615
|
-
# Pass source explicitly to ensure it's set correctly
|
|
616
|
-
normalized_event = self.event_normalizer.normalize(raw_event, source="hook")
|
|
617
|
-
claude_event_data = normalized_event.to_dict()
|
|
618
|
-
|
|
619
|
-
# Log important events for debugging
|
|
620
|
-
if DEBUG and event in ["subagent_stop", "pre_tool"]:
|
|
621
|
-
if event == "subagent_stop":
|
|
622
|
-
agent_type = data.get("agent_type", "unknown")
|
|
623
|
-
print(
|
|
624
|
-
f"Hook handler: Emitting SubagentStop for agent '{agent_type}'",
|
|
625
|
-
file=sys.stderr,
|
|
626
|
-
)
|
|
627
|
-
elif event == "pre_tool" and data.get("tool_name") == "Task":
|
|
628
|
-
delegation = data.get("delegation_details", {})
|
|
629
|
-
agent_type = delegation.get("agent_type", "unknown")
|
|
630
|
-
print(
|
|
631
|
-
f"Hook handler: Emitting Task delegation to agent '{agent_type}'",
|
|
632
|
-
file=sys.stderr,
|
|
633
|
-
)
|
|
634
|
-
|
|
635
|
-
# Emit synchronously
|
|
636
|
-
client.emit("claude_event", claude_event_data)
|
|
637
|
-
|
|
638
|
-
# For critical events, wait a moment to ensure delivery
|
|
639
|
-
if event in ["subagent_stop", "pre_tool"]:
|
|
640
|
-
time.sleep(0.01) # Small delay to ensure event is sent
|
|
641
|
-
|
|
642
|
-
# Verify emission for critical events
|
|
643
|
-
if event in ["subagent_stop", "pre_tool"] and DEBUG:
|
|
644
|
-
if client.connected:
|
|
645
|
-
print(
|
|
646
|
-
f"✅ Successfully emitted Socket.IO event: hook.{event} (connection still active)",
|
|
647
|
-
file=sys.stderr,
|
|
648
|
-
)
|
|
649
|
-
else:
|
|
650
|
-
print(
|
|
651
|
-
f"⚠️ Event emitted but connection closed after: hook.{event}",
|
|
652
|
-
file=sys.stderr,
|
|
653
|
-
)
|
|
654
|
-
|
|
655
|
-
except Exception as e:
|
|
583
|
+
print(f"⚠️ Failed to publish to EventBus: {e}", file=sys.stderr)
|
|
584
|
+
else:
|
|
656
585
|
if DEBUG:
|
|
657
|
-
print(f"
|
|
658
|
-
|
|
659
|
-
# Try to reconnect immediately for critical events
|
|
660
|
-
if event in ["subagent_stop", "pre_tool"]:
|
|
661
|
-
if DEBUG:
|
|
662
|
-
print(
|
|
663
|
-
f"Hook handler: Attempting immediate reconnection for critical event: hook.{event}",
|
|
664
|
-
file=sys.stderr,
|
|
665
|
-
)
|
|
666
|
-
# Force get a new client and emit again
|
|
667
|
-
self.connection_pool._cleanup_dead_connections()
|
|
668
|
-
retry_client = self.connection_pool._create_connection(port)
|
|
669
|
-
if retry_client:
|
|
670
|
-
try:
|
|
671
|
-
retry_client.emit("claude_event", claude_event_data)
|
|
672
|
-
# Add to pool for future use
|
|
673
|
-
self.connection_pool.connections.append(
|
|
674
|
-
{"port": port, "client": retry_client, "created": time.time()}
|
|
675
|
-
)
|
|
676
|
-
if DEBUG:
|
|
677
|
-
print(
|
|
678
|
-
f"✅ Successfully re-emitted event after reconnection: hook.{event}",
|
|
679
|
-
file=sys.stderr,
|
|
680
|
-
)
|
|
681
|
-
except Exception as retry_e:
|
|
682
|
-
if DEBUG:
|
|
683
|
-
print(f"❌ Re-emission failed: {retry_e}", file=sys.stderr)
|
|
586
|
+
print(f"⚠️ EventBus not available for event: hook.{event}", file=sys.stderr)
|
|
684
587
|
|
|
685
588
|
def handle_subagent_stop(self, event: dict):
|
|
686
589
|
"""Handle subagent stop events with improved agent type detection.
|
|
@@ -1012,12 +915,9 @@ class ClaudeHookHandler:
|
|
|
1012
915
|
self._emit_socketio_event("/hook", "subagent_stop", subagent_stop_data)
|
|
1013
916
|
|
|
1014
917
|
def __del__(self):
|
|
1015
|
-
"""Cleanup
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
self.connection_pool.close_all()
|
|
1019
|
-
except:
|
|
1020
|
-
pass
|
|
918
|
+
"""Cleanup on handler destruction."""
|
|
919
|
+
# Connection pool no longer used - EventBus handles cleanup
|
|
920
|
+
pass
|
|
1021
921
|
|
|
1022
922
|
|
|
1023
923
|
def main():
|
|
@@ -198,7 +198,10 @@ class AgentDiscoveryService:
|
|
|
198
198
|
"name": metadata.get("name", template_file.stem),
|
|
199
199
|
"description": metadata.get("description", "No description available"),
|
|
200
200
|
"version": template_data.get(
|
|
201
|
-
"agent_version",
|
|
201
|
+
"agent_version",
|
|
202
|
+
template_data.get("version",
|
|
203
|
+
metadata.get("version", "1.0.0")
|
|
204
|
+
)
|
|
202
205
|
),
|
|
203
206
|
"tools": capabilities.get("tools", []),
|
|
204
207
|
"specializations": metadata.get(
|
|
@@ -136,7 +136,8 @@ class AgentTemplateBuilder:
|
|
|
136
136
|
)
|
|
137
137
|
|
|
138
138
|
# Extract custom metadata fields
|
|
139
|
-
|
|
139
|
+
metadata = template_data.get("metadata", {})
|
|
140
|
+
agent_version = template_data.get("agent_version") or template_data.get("version") or metadata.get("version", "1.0.0")
|
|
140
141
|
agent_type = template_data.get("agent_type", "general")
|
|
141
142
|
# Use the capabilities_model we already extracted earlier
|
|
142
143
|
model_type = capabilities_model or "sonnet"
|
|
@@ -254,8 +254,11 @@ class AgentVersionManager:
|
|
|
254
254
|
template_data = json.loads(template_file.read_text())
|
|
255
255
|
|
|
256
256
|
# Extract agent version from template
|
|
257
|
+
metadata = template_data.get("metadata", {})
|
|
257
258
|
current_agent_version = self.parse_version(
|
|
258
|
-
template_data.get("agent_version") or
|
|
259
|
+
template_data.get("agent_version") or
|
|
260
|
+
template_data.get("version") or
|
|
261
|
+
metadata.get("version", 0)
|
|
259
262
|
)
|
|
260
263
|
|
|
261
264
|
# If old format detected, always trigger update for migration
|