claude-mpm 4.1.6__py3-none-any.whl → 4.1.8__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/OUTPUT_STYLE.md +73 -0
- claude_mpm/agents/templates/agent-manager.json +1 -1
- claude_mpm/agents/templates/agent-manager.md +349 -34
- claude_mpm/cli/commands/configure.py +151 -2
- claude_mpm/cli/commands/configure_tui.py +5 -1
- claude_mpm/cli/parsers/configure_parser.py +23 -0
- claude_mpm/config/socketio_config.py +33 -4
- claude_mpm/dashboard/static/js/socket-client.js +40 -16
- claude_mpm/hooks/claude_hooks/installer.py +455 -0
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +17 -0
- claude_mpm/services/agents/deployment/agent_config_provider.py +127 -27
- claude_mpm/services/diagnostics/checks/instructions_check.py +1 -3
- claude_mpm/services/event_bus/direct_relay.py +146 -11
- claude_mpm/services/socketio/handlers/connection_handler.py +3 -18
- claude_mpm/services/socketio/server/connection_manager.py +124 -63
- claude_mpm/services/socketio/server/core.py +34 -7
- claude_mpm/services/socketio/server/main.py +83 -21
- {claude_mpm-4.1.6.dist-info → claude_mpm-4.1.8.dist-info}/METADATA +1 -1
- {claude_mpm-4.1.6.dist-info → claude_mpm-4.1.8.dist-info}/RECORD +24 -22
- {claude_mpm-4.1.6.dist-info → claude_mpm-4.1.8.dist-info}/WHEEL +0 -0
- {claude_mpm-4.1.6.dist-info → claude_mpm-4.1.8.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.1.6.dist-info → claude_mpm-4.1.8.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.1.6.dist-info → claude_mpm-4.1.8.dist-info}/top_level.txt +0 -0
|
@@ -222,6 +222,16 @@ class ConfigureCommand(BaseCommand):
|
|
|
222
222
|
if getattr(args, "version_info", False):
|
|
223
223
|
return self._show_version_info()
|
|
224
224
|
|
|
225
|
+
# Handle hook installation
|
|
226
|
+
if getattr(args, "install_hooks", False):
|
|
227
|
+
return self._install_hooks(force=getattr(args, "force", False))
|
|
228
|
+
|
|
229
|
+
if getattr(args, "verify_hooks", False):
|
|
230
|
+
return self._verify_hooks()
|
|
231
|
+
|
|
232
|
+
if getattr(args, "uninstall_hooks", False):
|
|
233
|
+
return self._uninstall_hooks()
|
|
234
|
+
|
|
225
235
|
# Handle direct navigation options
|
|
226
236
|
if getattr(args, "agents", False):
|
|
227
237
|
return self._run_agent_management()
|
|
@@ -1018,7 +1028,11 @@ class ConfigureCommand(BaseCommand):
|
|
|
1018
1028
|
import subprocess
|
|
1019
1029
|
|
|
1020
1030
|
result = subprocess.run(
|
|
1021
|
-
["claude", "--version"],
|
|
1031
|
+
["claude", "--version"],
|
|
1032
|
+
capture_output=True,
|
|
1033
|
+
text=True,
|
|
1034
|
+
timeout=5,
|
|
1035
|
+
check=False,
|
|
1022
1036
|
)
|
|
1023
1037
|
if result.returncode == 0:
|
|
1024
1038
|
claude_version = result.stdout.strip()
|
|
@@ -1157,7 +1171,11 @@ Directory: {self.project_dir}
|
|
|
1157
1171
|
import subprocess
|
|
1158
1172
|
|
|
1159
1173
|
result = subprocess.run(
|
|
1160
|
-
["claude", "--version"],
|
|
1174
|
+
["claude", "--version"],
|
|
1175
|
+
capture_output=True,
|
|
1176
|
+
text=True,
|
|
1177
|
+
timeout=5,
|
|
1178
|
+
check=False,
|
|
1161
1179
|
)
|
|
1162
1180
|
if result.returncode == 0:
|
|
1163
1181
|
data["claude_version"] = result.stdout.strip()
|
|
@@ -1175,6 +1193,137 @@ Directory: {self.project_dir}
|
|
|
1175
1193
|
|
|
1176
1194
|
return CommandResult.success_result("Version information displayed", data=data)
|
|
1177
1195
|
|
|
1196
|
+
def _install_hooks(self, force: bool = False) -> CommandResult:
|
|
1197
|
+
"""Install Claude MPM hooks for Claude Code integration."""
|
|
1198
|
+
try:
|
|
1199
|
+
from ...hooks.claude_hooks.installer import HookInstaller
|
|
1200
|
+
|
|
1201
|
+
installer = HookInstaller()
|
|
1202
|
+
|
|
1203
|
+
# Check current status first
|
|
1204
|
+
status = installer.get_status()
|
|
1205
|
+
if status["installed"] and not force:
|
|
1206
|
+
self.console.print("[yellow]Hooks are already installed.[/yellow]")
|
|
1207
|
+
self.console.print("Use --force to reinstall.")
|
|
1208
|
+
|
|
1209
|
+
if not status["valid"]:
|
|
1210
|
+
self.console.print("\n[red]However, there are issues:[/red]")
|
|
1211
|
+
for issue in status["issues"]:
|
|
1212
|
+
self.console.print(f" - {issue}")
|
|
1213
|
+
|
|
1214
|
+
return CommandResult.success_result(
|
|
1215
|
+
"Hooks already installed", data=status
|
|
1216
|
+
)
|
|
1217
|
+
|
|
1218
|
+
# Install hooks
|
|
1219
|
+
self.console.print("[cyan]Installing Claude MPM hooks...[/cyan]")
|
|
1220
|
+
success = installer.install_hooks(force=force)
|
|
1221
|
+
|
|
1222
|
+
if success:
|
|
1223
|
+
self.console.print("[green]✓ Hooks installed successfully![/green]")
|
|
1224
|
+
self.console.print("\nYou can now use /mpm commands in Claude Code:")
|
|
1225
|
+
self.console.print(" /mpm - Show help")
|
|
1226
|
+
self.console.print(" /mpm status - Show claude-mpm status")
|
|
1227
|
+
|
|
1228
|
+
# Verify installation
|
|
1229
|
+
is_valid, issues = installer.verify_hooks()
|
|
1230
|
+
if not is_valid:
|
|
1231
|
+
self.console.print(
|
|
1232
|
+
"\n[yellow]Warning: Installation completed but verification found issues:[/yellow]"
|
|
1233
|
+
)
|
|
1234
|
+
for issue in issues:
|
|
1235
|
+
self.console.print(f" - {issue}")
|
|
1236
|
+
|
|
1237
|
+
return CommandResult.success_result("Hooks installed successfully")
|
|
1238
|
+
self.console.print("[red]✗ Hook installation failed[/red]")
|
|
1239
|
+
return CommandResult.error_result("Hook installation failed")
|
|
1240
|
+
|
|
1241
|
+
except ImportError:
|
|
1242
|
+
self.console.print("[red]Error: HookInstaller module not found[/red]")
|
|
1243
|
+
self.console.print("Please ensure claude-mpm is properly installed.")
|
|
1244
|
+
return CommandResult.error_result("HookInstaller module not found")
|
|
1245
|
+
except Exception as e:
|
|
1246
|
+
self.logger.error(f"Hook installation error: {e}", exc_info=True)
|
|
1247
|
+
return CommandResult.error_result(f"Hook installation failed: {e}")
|
|
1248
|
+
|
|
1249
|
+
def _verify_hooks(self) -> CommandResult:
|
|
1250
|
+
"""Verify that Claude MPM hooks are properly installed."""
|
|
1251
|
+
try:
|
|
1252
|
+
from ...hooks.claude_hooks.installer import HookInstaller
|
|
1253
|
+
|
|
1254
|
+
installer = HookInstaller()
|
|
1255
|
+
status = installer.get_status()
|
|
1256
|
+
|
|
1257
|
+
self.console.print("[bold]Hook Installation Status[/bold]\n")
|
|
1258
|
+
|
|
1259
|
+
if status["installed"]:
|
|
1260
|
+
self.console.print(
|
|
1261
|
+
f"[green]✓[/green] Hooks installed at: {status['hook_script']}"
|
|
1262
|
+
)
|
|
1263
|
+
else:
|
|
1264
|
+
self.console.print("[red]✗[/red] Hooks not installed")
|
|
1265
|
+
|
|
1266
|
+
if status["settings_file"]:
|
|
1267
|
+
self.console.print(
|
|
1268
|
+
f"[green]✓[/green] Settings file: {status['settings_file']}"
|
|
1269
|
+
)
|
|
1270
|
+
else:
|
|
1271
|
+
self.console.print("[red]✗[/red] Settings file not found")
|
|
1272
|
+
|
|
1273
|
+
if status.get("configured_events"):
|
|
1274
|
+
self.console.print(
|
|
1275
|
+
f"[green]✓[/green] Configured events: {', '.join(status['configured_events'])}"
|
|
1276
|
+
)
|
|
1277
|
+
else:
|
|
1278
|
+
self.console.print("[red]✗[/red] No events configured")
|
|
1279
|
+
|
|
1280
|
+
if status["valid"]:
|
|
1281
|
+
self.console.print("\n[green]All checks passed![/green]")
|
|
1282
|
+
else:
|
|
1283
|
+
self.console.print("\n[red]Issues found:[/red]")
|
|
1284
|
+
for issue in status["issues"]:
|
|
1285
|
+
self.console.print(f" - {issue}")
|
|
1286
|
+
|
|
1287
|
+
return CommandResult.success_result(
|
|
1288
|
+
"Hook verification complete", data=status
|
|
1289
|
+
)
|
|
1290
|
+
|
|
1291
|
+
except ImportError:
|
|
1292
|
+
self.console.print("[red]Error: HookInstaller module not found[/red]")
|
|
1293
|
+
return CommandResult.error_result("HookInstaller module not found")
|
|
1294
|
+
except Exception as e:
|
|
1295
|
+
self.logger.error(f"Hook verification error: {e}", exc_info=True)
|
|
1296
|
+
return CommandResult.error_result(f"Hook verification failed: {e}")
|
|
1297
|
+
|
|
1298
|
+
def _uninstall_hooks(self) -> CommandResult:
|
|
1299
|
+
"""Uninstall Claude MPM hooks."""
|
|
1300
|
+
try:
|
|
1301
|
+
from ...hooks.claude_hooks.installer import HookInstaller
|
|
1302
|
+
|
|
1303
|
+
installer = HookInstaller()
|
|
1304
|
+
|
|
1305
|
+
# Confirm uninstallation
|
|
1306
|
+
if not Confirm.ask(
|
|
1307
|
+
"[yellow]Are you sure you want to uninstall Claude MPM hooks?[/yellow]"
|
|
1308
|
+
):
|
|
1309
|
+
return CommandResult.success_result("Uninstallation cancelled")
|
|
1310
|
+
|
|
1311
|
+
self.console.print("[cyan]Uninstalling Claude MPM hooks...[/cyan]")
|
|
1312
|
+
success = installer.uninstall_hooks()
|
|
1313
|
+
|
|
1314
|
+
if success:
|
|
1315
|
+
self.console.print("[green]✓ Hooks uninstalled successfully![/green]")
|
|
1316
|
+
return CommandResult.success_result("Hooks uninstalled successfully")
|
|
1317
|
+
self.console.print("[red]✗ Hook uninstallation failed[/red]")
|
|
1318
|
+
return CommandResult.error_result("Hook uninstallation failed")
|
|
1319
|
+
|
|
1320
|
+
except ImportError:
|
|
1321
|
+
self.console.print("[red]Error: HookInstaller module not found[/red]")
|
|
1322
|
+
return CommandResult.error_result("HookInstaller module not found")
|
|
1323
|
+
except Exception as e:
|
|
1324
|
+
self.logger.error(f"Hook uninstallation error: {e}", exc_info=True)
|
|
1325
|
+
return CommandResult.error_result(f"Hook uninstallation failed: {e}")
|
|
1326
|
+
|
|
1178
1327
|
def _run_agent_management(self) -> CommandResult:
|
|
1179
1328
|
"""Jump directly to agent management."""
|
|
1180
1329
|
try:
|
|
@@ -1200,7 +1200,11 @@ class SettingsScreen(Container):
|
|
|
1200
1200
|
import subprocess
|
|
1201
1201
|
|
|
1202
1202
|
result = subprocess.run(
|
|
1203
|
-
["claude", "--version"],
|
|
1203
|
+
["claude", "--version"],
|
|
1204
|
+
capture_output=True,
|
|
1205
|
+
text=True,
|
|
1206
|
+
timeout=5,
|
|
1207
|
+
check=False,
|
|
1204
1208
|
)
|
|
1205
1209
|
if result.returncode == 0:
|
|
1206
1210
|
claude_version = result.stdout.strip()
|
|
@@ -96,6 +96,29 @@ def add_configure_subparser(subparsers) -> argparse.ArgumentParser:
|
|
|
96
96
|
help="Import configuration from a file",
|
|
97
97
|
)
|
|
98
98
|
|
|
99
|
+
# Hook management options
|
|
100
|
+
hooks_group = configure_parser.add_argument_group("hook management")
|
|
101
|
+
hooks_group.add_argument(
|
|
102
|
+
"--install-hooks",
|
|
103
|
+
action="store_true",
|
|
104
|
+
help="Install Claude MPM hooks for Claude Code integration",
|
|
105
|
+
)
|
|
106
|
+
hooks_group.add_argument(
|
|
107
|
+
"--verify-hooks",
|
|
108
|
+
action="store_true",
|
|
109
|
+
help="Verify that Claude MPM hooks are properly installed",
|
|
110
|
+
)
|
|
111
|
+
hooks_group.add_argument(
|
|
112
|
+
"--uninstall-hooks",
|
|
113
|
+
action="store_true",
|
|
114
|
+
help="Uninstall Claude MPM hooks",
|
|
115
|
+
)
|
|
116
|
+
hooks_group.add_argument(
|
|
117
|
+
"--force",
|
|
118
|
+
action="store_true",
|
|
119
|
+
help="Force reinstallation of hooks even if they already exist",
|
|
120
|
+
)
|
|
121
|
+
|
|
99
122
|
# Display options
|
|
100
123
|
display_group = configure_parser.add_argument_group("display options")
|
|
101
124
|
display_group.add_argument(
|
|
@@ -10,6 +10,8 @@ WHY configuration management:
|
|
|
10
10
|
- Supports multiple deployment scenarios (local, PyPI, Docker, etc.)
|
|
11
11
|
- Provides environment-specific defaults
|
|
12
12
|
- Allows runtime configuration overrides
|
|
13
|
+
|
|
14
|
+
CRITICAL: Ping/pong settings MUST match between client and server to prevent disconnections!
|
|
13
15
|
"""
|
|
14
16
|
|
|
15
17
|
import os
|
|
@@ -19,6 +21,30 @@ from typing import Any, Dict, List, Optional
|
|
|
19
21
|
# Import constants for default values
|
|
20
22
|
from claude_mpm.core.constants import NetworkConfig, RetryConfig, SystemLimits
|
|
21
23
|
|
|
24
|
+
# Connection stability settings - MUST be consistent between client and server
|
|
25
|
+
CONNECTION_CONFIG = {
|
|
26
|
+
# Ping/pong intervals (milliseconds for client, seconds for server)
|
|
27
|
+
"ping_interval_ms": 25000, # 25 seconds (for client JavaScript)
|
|
28
|
+
"ping_interval": 25, # 25 seconds (for server Python) - reduced for stability
|
|
29
|
+
"ping_timeout_ms": 20000, # 20 seconds (for client JavaScript)
|
|
30
|
+
"ping_timeout": 20, # 20 seconds (for server Python)
|
|
31
|
+
# Connection management
|
|
32
|
+
"stale_timeout": 90, # 90 seconds before considering connection stale (was 180)
|
|
33
|
+
"health_check_interval": 30, # Health check every 30 seconds
|
|
34
|
+
"event_ttl": 300, # Keep events for 5 minutes for replay
|
|
35
|
+
"connection_timeout": 10, # 10 seconds for initial connection timeout
|
|
36
|
+
# Client reconnection settings
|
|
37
|
+
"reconnection_attempts": 5, # Number of reconnection attempts
|
|
38
|
+
"reconnection_delay": 1000, # Initial delay in ms
|
|
39
|
+
"reconnection_delay_max": 5000, # Maximum delay in ms
|
|
40
|
+
# Feature flags
|
|
41
|
+
"enable_extra_heartbeat": False, # Disable redundant heartbeats
|
|
42
|
+
"enable_health_monitoring": True, # Enable connection health monitoring
|
|
43
|
+
# Buffer settings
|
|
44
|
+
"max_events_buffer": 1000, # Maximum events to buffer per client
|
|
45
|
+
"max_http_buffer_size": 1e8, # 100MB max buffer for large payloads
|
|
46
|
+
}
|
|
47
|
+
|
|
22
48
|
|
|
23
49
|
@dataclass
|
|
24
50
|
class SocketIOConfig:
|
|
@@ -29,11 +55,14 @@ class SocketIOConfig:
|
|
|
29
55
|
port: int = NetworkConfig.DEFAULT_DASHBOARD_PORT
|
|
30
56
|
server_id: Optional[str] = None
|
|
31
57
|
|
|
32
|
-
# Connection settings
|
|
58
|
+
# Connection settings - Use centralized config for consistency
|
|
33
59
|
cors_allowed_origins: str = "*" # Configure properly for production
|
|
34
|
-
ping_timeout: int =
|
|
35
|
-
ping_interval: int =
|
|
36
|
-
max_http_buffer_size: int =
|
|
60
|
+
ping_timeout: int = CONNECTION_CONFIG["ping_timeout"] # 20 seconds
|
|
61
|
+
ping_interval: int = CONNECTION_CONFIG["ping_interval"] # 25 seconds (was 45)
|
|
62
|
+
max_http_buffer_size: int = int(CONNECTION_CONFIG["max_http_buffer_size"])
|
|
63
|
+
connection_timeout: int = CONNECTION_CONFIG.get(
|
|
64
|
+
"connection_timeout", 10
|
|
65
|
+
) # 10 seconds
|
|
37
66
|
|
|
38
67
|
# Compatibility settings
|
|
39
68
|
min_client_version: str = "0.7.0"
|
|
@@ -38,10 +38,10 @@ class SocketClient {
|
|
|
38
38
|
this.eventQueue = [];
|
|
39
39
|
this.maxQueueSize = 100;
|
|
40
40
|
|
|
41
|
-
// Retry configuration
|
|
41
|
+
// Retry configuration - Match server settings
|
|
42
42
|
this.retryAttempts = 0;
|
|
43
|
-
this.maxRetryAttempts = 3
|
|
44
|
-
this.retryDelays = [1000, 2000, 4000]; // Exponential backoff
|
|
43
|
+
this.maxRetryAttempts = 5; // Increased from 3 to 5 for better stability
|
|
44
|
+
this.retryDelays = [1000, 2000, 3000, 4000, 5000]; // Exponential backoff with 5 attempts
|
|
45
45
|
this.pendingEmissions = new Map(); // Track pending emissions for retry
|
|
46
46
|
|
|
47
47
|
// Health monitoring
|
|
@@ -98,12 +98,12 @@ class SocketClient {
|
|
|
98
98
|
reconnection: true,
|
|
99
99
|
reconnectionDelay: 1000,
|
|
100
100
|
reconnectionDelayMax: 5000,
|
|
101
|
-
reconnectionAttempts:
|
|
102
|
-
timeout: 20000, //
|
|
101
|
+
reconnectionAttempts: 5, // Try 5 times then stop (was Infinity which can cause issues)
|
|
102
|
+
timeout: 20000, // Connection timeout
|
|
103
103
|
forceNew: true,
|
|
104
104
|
transports: ['websocket', 'polling'],
|
|
105
|
-
pingInterval:
|
|
106
|
-
pingTimeout:
|
|
105
|
+
pingInterval: 45000, // CRITICAL: Must match server's 45 seconds
|
|
106
|
+
pingTimeout: 20000 // CRITICAL: Must match server's 20 seconds
|
|
107
107
|
});
|
|
108
108
|
|
|
109
109
|
this.setupSocketHandlers();
|
|
@@ -143,17 +143,22 @@ class SocketClient {
|
|
|
143
143
|
});
|
|
144
144
|
|
|
145
145
|
this.socket.on('disconnect', (reason) => {
|
|
146
|
-
|
|
146
|
+
// Enhanced logging for debugging disconnection issues
|
|
147
|
+
const disconnectInfo = {
|
|
148
|
+
reason: reason,
|
|
149
|
+
timestamp: new Date().toISOString(),
|
|
150
|
+
wasConnected: this.isConnected,
|
|
151
|
+
uptimeSeconds: this.lastConnectTime ? ((Date.now() - this.lastConnectTime) / 1000).toFixed(1) : 0,
|
|
152
|
+
lastPing: this.lastPingTime ? ((Date.now() - this.lastPingTime) / 1000).toFixed(1) + 's ago' : 'never',
|
|
153
|
+
lastPong: this.lastPongTime ? ((Date.now() - this.lastPongTime) / 1000).toFixed(1) + 's ago' : 'never'
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
console.log('Disconnected from server:', disconnectInfo);
|
|
157
|
+
|
|
147
158
|
this.isConnected = false;
|
|
148
159
|
this.isConnecting = false;
|
|
149
160
|
this.disconnectTime = Date.now();
|
|
150
161
|
|
|
151
|
-
// Calculate uptime
|
|
152
|
-
if (this.lastConnectTime) {
|
|
153
|
-
const uptime = (Date.now() - this.lastConnectTime) / 1000;
|
|
154
|
-
console.log(`Connection uptime was ${uptime.toFixed(1)}s`);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
162
|
this.notifyConnectionStatus(`Disconnected: ${reason}`, 'disconnected');
|
|
158
163
|
|
|
159
164
|
// Emit disconnect callback
|
|
@@ -161,8 +166,21 @@ class SocketClient {
|
|
|
161
166
|
callback(reason)
|
|
162
167
|
);
|
|
163
168
|
|
|
164
|
-
//
|
|
165
|
-
|
|
169
|
+
// Detailed reason analysis for auto-reconnect decision
|
|
170
|
+
const reconnectReasons = [
|
|
171
|
+
'transport close', // Network issue
|
|
172
|
+
'ping timeout', // Server not responding
|
|
173
|
+
'transport error', // Connection error
|
|
174
|
+
'io server disconnect', // Server initiated disconnect (might be restart)
|
|
175
|
+
];
|
|
176
|
+
|
|
177
|
+
if (reconnectReasons.includes(reason)) {
|
|
178
|
+
console.log(`Auto-reconnect triggered for reason: ${reason}`);
|
|
179
|
+
this.scheduleReconnect();
|
|
180
|
+
} else if (reason === 'io client disconnect') {
|
|
181
|
+
console.log('Client-initiated disconnect, not auto-reconnecting');
|
|
182
|
+
} else {
|
|
183
|
+
console.log(`Unknown disconnect reason: ${reason}, attempting reconnect anyway`);
|
|
166
184
|
this.scheduleReconnect();
|
|
167
185
|
}
|
|
168
186
|
});
|
|
@@ -222,6 +240,12 @@ class SocketClient {
|
|
|
222
240
|
});
|
|
223
241
|
});
|
|
224
242
|
|
|
243
|
+
// Track pong responses from server
|
|
244
|
+
this.socket.on('pong', (data) => {
|
|
245
|
+
this.lastPongTime = Date.now();
|
|
246
|
+
// console.log('Received pong from server');
|
|
247
|
+
});
|
|
248
|
+
|
|
225
249
|
// Session and event handlers (legacy/fallback)
|
|
226
250
|
this.socket.on('session.started', (data) => {
|
|
227
251
|
this.addEvent({ type: 'session', subtype: 'started', timestamp: new Date().toISOString(), data });
|