claude-mpm 4.1.5__py3-none-any.whl → 4.1.7__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/templates/agent-manager.json +1 -1
- claude_mpm/agents/templates/agent-manager.md +111 -34
- claude_mpm/agents/templates/research.json +39 -13
- claude_mpm/cli/__init__.py +2 -0
- claude_mpm/cli/commands/__init__.py +2 -0
- claude_mpm/cli/commands/configure.py +1221 -0
- claude_mpm/cli/commands/configure_tui.py +1921 -0
- claude_mpm/cli/parsers/base_parser.py +7 -0
- claude_mpm/cli/parsers/configure_parser.py +119 -0
- claude_mpm/cli/startup_logging.py +39 -12
- claude_mpm/config/socketio_config.py +33 -4
- claude_mpm/constants.py +1 -0
- claude_mpm/core/socketio_pool.py +35 -3
- claude_mpm/dashboard/static/css/connection-status.css +370 -0
- claude_mpm/dashboard/static/js/components/connection-debug.js +654 -0
- claude_mpm/dashboard/static/js/connection-manager.js +536 -0
- claude_mpm/dashboard/static/js/socket-client.js +40 -16
- claude_mpm/dashboard/templates/index.html +11 -0
- claude_mpm/hooks/claude_hooks/services/__init__.py +3 -1
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +17 -0
- claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +190 -0
- claude_mpm/services/diagnostics/checks/__init__.py +2 -0
- claude_mpm/services/diagnostics/checks/instructions_check.py +418 -0
- claude_mpm/services/diagnostics/diagnostic_runner.py +15 -2
- claude_mpm/services/event_bus/direct_relay.py +230 -0
- claude_mpm/services/socketio/handlers/connection_handler.py +330 -0
- claude_mpm/services/socketio/server/broadcaster.py +32 -1
- claude_mpm/services/socketio/server/connection_manager.py +547 -0
- claude_mpm/services/socketio/server/core.py +78 -7
- claude_mpm/services/socketio/server/eventbus_integration.py +20 -9
- claude_mpm/services/socketio/server/main.py +74 -19
- {claude_mpm-4.1.5.dist-info → claude_mpm-4.1.7.dist-info}/METADATA +3 -1
- {claude_mpm-4.1.5.dist-info → claude_mpm-4.1.7.dist-info}/RECORD +38 -41
- claude_mpm/agents/OUTPUT_STYLE.md +0 -73
- claude_mpm/agents/backups/INSTRUCTIONS.md +0 -352
- claude_mpm/agents/templates/OPTIMIZATION_REPORT.md +0 -156
- claude_mpm/agents/templates/backup/data_engineer_agent_20250726_234551.json +0 -79
- claude_mpm/agents/templates/backup/documentation_agent_20250726_234551.json +0 -68
- claude_mpm/agents/templates/backup/engineer_agent_20250726_234551.json +0 -77
- claude_mpm/agents/templates/backup/ops_agent_20250726_234551.json +0 -78
- claude_mpm/agents/templates/backup/qa_agent_20250726_234551.json +0 -67
- claude_mpm/agents/templates/backup/research_agent_2025011_234551.json +0 -88
- claude_mpm/agents/templates/backup/research_agent_20250726_234551.json +0 -72
- claude_mpm/agents/templates/backup/research_memory_efficient.json +0 -88
- claude_mpm/agents/templates/backup/security_agent_20250726_234551.json +0 -78
- claude_mpm/agents/templates/backup/version_control_agent_20250726_234551.json +0 -62
- claude_mpm/agents/templates/vercel_ops_instructions.md +0 -582
- {claude_mpm-4.1.5.dist-info → claude_mpm-4.1.7.dist-info}/WHEEL +0 -0
- {claude_mpm-4.1.5.dist-info → claude_mpm-4.1.7.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.1.5.dist-info → claude_mpm-4.1.7.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.1.5.dist-info → claude_mpm-4.1.7.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,536 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enhanced Connection Manager for Dashboard
|
|
3
|
+
*
|
|
4
|
+
* Provides robust connection management with:
|
|
5
|
+
* - Persistent client ID across reconnections
|
|
6
|
+
* - Event sequence tracking and replay
|
|
7
|
+
* - Exponential backoff for reconnection
|
|
8
|
+
* - Connection health monitoring
|
|
9
|
+
* - Visual status indicators
|
|
10
|
+
* - Local event buffering
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
class EnhancedConnectionManager {
|
|
14
|
+
constructor(socketClient) {
|
|
15
|
+
this.socketClient = socketClient;
|
|
16
|
+
this.socket = null;
|
|
17
|
+
this.clientId = this.loadClientId();
|
|
18
|
+
this.lastSequence = this.loadLastSequence();
|
|
19
|
+
this.connectionState = 'disconnected';
|
|
20
|
+
this.connectionQuality = 1.0;
|
|
21
|
+
this.reconnectAttempts = 0;
|
|
22
|
+
this.maxReconnectAttempts = 10;
|
|
23
|
+
this.baseReconnectDelay = 1000; // 1 second
|
|
24
|
+
this.maxReconnectDelay = 30000; // 30 seconds
|
|
25
|
+
this.heartbeatInterval = 30000; // 30 seconds
|
|
26
|
+
this.heartbeatTimer = null;
|
|
27
|
+
this.pingTimer = null;
|
|
28
|
+
this.lastPingTime = null;
|
|
29
|
+
this.lastPongTime = null;
|
|
30
|
+
this.missedHeartbeats = 0;
|
|
31
|
+
this.maxMissedHeartbeats = 3;
|
|
32
|
+
|
|
33
|
+
// Event buffering for offline mode
|
|
34
|
+
this.eventBuffer = [];
|
|
35
|
+
this.maxEventBuffer = 100;
|
|
36
|
+
|
|
37
|
+
// Connection metrics
|
|
38
|
+
this.metrics = {
|
|
39
|
+
connectTime: null,
|
|
40
|
+
disconnectTime: null,
|
|
41
|
+
totalConnections: 0,
|
|
42
|
+
totalReconnections: 0,
|
|
43
|
+
totalEvents: 0,
|
|
44
|
+
eventsAcked: 0,
|
|
45
|
+
lastActivity: null
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// Status update callbacks
|
|
49
|
+
this.statusCallbacks = new Set();
|
|
50
|
+
this.qualityCallbacks = new Set();
|
|
51
|
+
|
|
52
|
+
// Initialize
|
|
53
|
+
this.setupEventHandlers();
|
|
54
|
+
this.startHealthMonitoring();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Load or generate client ID for persistent identification
|
|
59
|
+
*/
|
|
60
|
+
loadClientId() {
|
|
61
|
+
let clientId = localStorage.getItem('claude_mpm_client_id');
|
|
62
|
+
if (!clientId) {
|
|
63
|
+
clientId = 'client_' + Math.random().toString(36).substr(2, 9);
|
|
64
|
+
localStorage.setItem('claude_mpm_client_id', clientId);
|
|
65
|
+
}
|
|
66
|
+
return clientId;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Load last received event sequence for replay
|
|
71
|
+
*/
|
|
72
|
+
loadLastSequence() {
|
|
73
|
+
const sequence = localStorage.getItem('claude_mpm_last_sequence');
|
|
74
|
+
return sequence ? parseInt(sequence, 10) : 0;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Save last received event sequence
|
|
79
|
+
*/
|
|
80
|
+
saveLastSequence(sequence) {
|
|
81
|
+
this.lastSequence = sequence;
|
|
82
|
+
localStorage.setItem('claude_mpm_last_sequence', sequence.toString());
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Connect with enhanced options and authentication
|
|
87
|
+
*/
|
|
88
|
+
connect(port = '8765') {
|
|
89
|
+
const url = `http://localhost:${port}`;
|
|
90
|
+
|
|
91
|
+
console.log(`[ConnectionManager] Connecting to ${url} with client ID: ${this.clientId}`);
|
|
92
|
+
this.updateConnectionState('connecting');
|
|
93
|
+
|
|
94
|
+
// Create socket with enhanced options
|
|
95
|
+
this.socket = io(url, {
|
|
96
|
+
auth: {
|
|
97
|
+
client_id: this.clientId,
|
|
98
|
+
last_sequence: this.lastSequence
|
|
99
|
+
},
|
|
100
|
+
reconnection: true,
|
|
101
|
+
reconnectionDelay: this.calculateReconnectDelay(),
|
|
102
|
+
reconnectionDelayMax: this.maxReconnectDelay,
|
|
103
|
+
reconnectionAttempts: this.maxReconnectAttempts,
|
|
104
|
+
timeout: 20000,
|
|
105
|
+
transports: ['websocket', 'polling'],
|
|
106
|
+
pingInterval: 25000,
|
|
107
|
+
pingTimeout: 60000
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
this.setupSocketHandlers();
|
|
111
|
+
this.socketClient.socket = this.socket;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Calculate exponential backoff delay for reconnection
|
|
116
|
+
*/
|
|
117
|
+
calculateReconnectDelay() {
|
|
118
|
+
const delay = Math.min(
|
|
119
|
+
this.baseReconnectDelay * Math.pow(2, this.reconnectAttempts),
|
|
120
|
+
this.maxReconnectDelay
|
|
121
|
+
);
|
|
122
|
+
return delay + Math.random() * 1000; // Add jitter
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Setup socket event handlers
|
|
127
|
+
*/
|
|
128
|
+
setupSocketHandlers() {
|
|
129
|
+
if (!this.socket) return;
|
|
130
|
+
|
|
131
|
+
// Connection established
|
|
132
|
+
this.socket.on('connection_established', (data) => {
|
|
133
|
+
console.log('[ConnectionManager] Connection established:', data);
|
|
134
|
+
this.clientId = data.client_id;
|
|
135
|
+
this.metrics.connectTime = Date.now();
|
|
136
|
+
this.metrics.totalConnections++;
|
|
137
|
+
this.reconnectAttempts = 0;
|
|
138
|
+
this.missedHeartbeats = 0;
|
|
139
|
+
this.updateConnectionState('connected');
|
|
140
|
+
this.startHeartbeat();
|
|
141
|
+
|
|
142
|
+
// Flush buffered events if any
|
|
143
|
+
this.flushEventBuffer();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// Event replay after reconnection
|
|
147
|
+
this.socket.on('event_replay', (data) => {
|
|
148
|
+
console.log(`[ConnectionManager] Replaying ${data.count} events from sequence ${data.from_sequence}`);
|
|
149
|
+
|
|
150
|
+
if (data.events && data.events.length > 0) {
|
|
151
|
+
data.events.forEach(event => {
|
|
152
|
+
// Update sequence
|
|
153
|
+
if (event.sequence) {
|
|
154
|
+
this.saveLastSequence(event.sequence);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Process replayed event
|
|
158
|
+
this.socketClient.handleEvent('claude_event', event);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
this.showNotification(`Replayed ${data.count} missed events`, 'info');
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// Normal event with sequence tracking
|
|
166
|
+
this.socket.on('claude_event', (event) => {
|
|
167
|
+
if (event.sequence) {
|
|
168
|
+
this.saveLastSequence(event.sequence);
|
|
169
|
+
|
|
170
|
+
// Send acknowledgment
|
|
171
|
+
this.socket.emit('acknowledge_event', {
|
|
172
|
+
sequence: event.sequence
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
this.metrics.eventsAcked++;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
this.metrics.totalEvents++;
|
|
179
|
+
this.metrics.lastActivity = Date.now();
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// Heartbeat response
|
|
183
|
+
this.socket.on('heartbeat_response', (data) => {
|
|
184
|
+
this.missedHeartbeats = 0;
|
|
185
|
+
this.updateConnectionQuality(1.0);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// Pong response
|
|
189
|
+
this.socket.on('pong', (data) => {
|
|
190
|
+
this.lastPongTime = Date.now();
|
|
191
|
+
const latency = this.lastPongTime - this.lastPingTime;
|
|
192
|
+
this.updateLatency(latency);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// Connection stats response
|
|
196
|
+
this.socket.on('connection_stats', (data) => {
|
|
197
|
+
console.log('[ConnectionManager] Connection stats:', data);
|
|
198
|
+
if (data.connection) {
|
|
199
|
+
this.updateConnectionQuality(data.connection.quality);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// Standard Socket.IO events
|
|
204
|
+
this.socket.on('connect', () => {
|
|
205
|
+
console.log('[ConnectionManager] Socket connected');
|
|
206
|
+
if (this.metrics.disconnectTime) {
|
|
207
|
+
const downtime = Date.now() - this.metrics.disconnectTime;
|
|
208
|
+
console.log(`[ConnectionManager] Reconnected after ${(downtime / 1000).toFixed(1)}s`);
|
|
209
|
+
this.metrics.totalReconnections++;
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
this.socket.on('disconnect', (reason) => {
|
|
214
|
+
console.log('[ConnectionManager] Socket disconnected:', reason);
|
|
215
|
+
this.metrics.disconnectTime = Date.now();
|
|
216
|
+
this.updateConnectionState('disconnected');
|
|
217
|
+
this.stopHeartbeat();
|
|
218
|
+
|
|
219
|
+
// Handle different disconnect reasons
|
|
220
|
+
if (reason === 'io server disconnect') {
|
|
221
|
+
// Server initiated disconnect
|
|
222
|
+
this.showNotification('Server disconnected the connection', 'warning');
|
|
223
|
+
} else if (reason === 'ping timeout') {
|
|
224
|
+
// Connection timeout
|
|
225
|
+
this.showNotification('Connection timeout - attempting to reconnect', 'warning');
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
this.socket.on('connect_error', (error) => {
|
|
230
|
+
console.error('[ConnectionManager] Connection error:', error.message);
|
|
231
|
+
this.reconnectAttempts++;
|
|
232
|
+
|
|
233
|
+
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
|
234
|
+
this.updateConnectionState('failed');
|
|
235
|
+
this.showNotification('Failed to connect after multiple attempts', 'error');
|
|
236
|
+
} else {
|
|
237
|
+
const delay = this.calculateReconnectDelay();
|
|
238
|
+
this.showNotification(
|
|
239
|
+
`Connection failed, retrying in ${(delay / 1000).toFixed(1)}s...`,
|
|
240
|
+
'warning'
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
this.socket.on('reconnect', (attemptNumber) => {
|
|
246
|
+
console.log(`[ConnectionManager] Reconnected after ${attemptNumber} attempts`);
|
|
247
|
+
this.showNotification('Reconnected successfully', 'success');
|
|
248
|
+
|
|
249
|
+
// Request event replay
|
|
250
|
+
this.socket.emit('request_replay', {
|
|
251
|
+
last_sequence: this.lastSequence
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
this.socket.on('reconnect_attempt', (attemptNumber) => {
|
|
256
|
+
console.log(`[ConnectionManager] Reconnection attempt ${attemptNumber}`);
|
|
257
|
+
this.updateConnectionState('reconnecting');
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Start heartbeat monitoring
|
|
263
|
+
*/
|
|
264
|
+
startHeartbeat() {
|
|
265
|
+
this.stopHeartbeat();
|
|
266
|
+
|
|
267
|
+
this.heartbeatTimer = setInterval(() => {
|
|
268
|
+
if (this.socket && this.socket.connected) {
|
|
269
|
+
this.socket.emit('heartbeat');
|
|
270
|
+
this.missedHeartbeats++;
|
|
271
|
+
|
|
272
|
+
if (this.missedHeartbeats >= this.maxMissedHeartbeats) {
|
|
273
|
+
console.warn('[ConnectionManager] Too many missed heartbeats, connection may be stale');
|
|
274
|
+
this.updateConnectionQuality(0.3);
|
|
275
|
+
this.updateConnectionState('stale');
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}, this.heartbeatInterval);
|
|
279
|
+
|
|
280
|
+
// Also start ping monitoring for latency
|
|
281
|
+
this.pingTimer = setInterval(() => {
|
|
282
|
+
if (this.socket && this.socket.connected) {
|
|
283
|
+
this.lastPingTime = Date.now();
|
|
284
|
+
this.socket.emit('ping');
|
|
285
|
+
}
|
|
286
|
+
}, 10000); // Every 10 seconds
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Stop heartbeat monitoring
|
|
291
|
+
*/
|
|
292
|
+
stopHeartbeat() {
|
|
293
|
+
if (this.heartbeatTimer) {
|
|
294
|
+
clearInterval(this.heartbeatTimer);
|
|
295
|
+
this.heartbeatTimer = null;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (this.pingTimer) {
|
|
299
|
+
clearInterval(this.pingTimer);
|
|
300
|
+
this.pingTimer = null;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Start health monitoring
|
|
306
|
+
*/
|
|
307
|
+
startHealthMonitoring() {
|
|
308
|
+
// Periodic connection stats request
|
|
309
|
+
setInterval(() => {
|
|
310
|
+
if (this.socket && this.socket.connected) {
|
|
311
|
+
this.socket.emit('get_connection_stats');
|
|
312
|
+
}
|
|
313
|
+
}, 60000); // Every minute
|
|
314
|
+
|
|
315
|
+
// Activity timeout detection
|
|
316
|
+
setInterval(() => {
|
|
317
|
+
if (this.connectionState === 'connected' && this.metrics.lastActivity) {
|
|
318
|
+
const timeSinceActivity = Date.now() - this.metrics.lastActivity;
|
|
319
|
+
if (timeSinceActivity > 120000) { // 2 minutes
|
|
320
|
+
console.warn('[ConnectionManager] No activity for 2 minutes');
|
|
321
|
+
this.updateConnectionQuality(0.5);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}, 30000); // Check every 30 seconds
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Update connection state and notify listeners
|
|
329
|
+
*/
|
|
330
|
+
updateConnectionState(state) {
|
|
331
|
+
const previousState = this.connectionState;
|
|
332
|
+
this.connectionState = state;
|
|
333
|
+
|
|
334
|
+
if (previousState !== state) {
|
|
335
|
+
console.log(`[ConnectionManager] State change: ${previousState} -> ${state}`);
|
|
336
|
+
|
|
337
|
+
// Update UI
|
|
338
|
+
this.updateConnectionUI(state);
|
|
339
|
+
|
|
340
|
+
// Notify callbacks
|
|
341
|
+
this.statusCallbacks.forEach(callback => {
|
|
342
|
+
try {
|
|
343
|
+
callback(state, previousState);
|
|
344
|
+
} catch (error) {
|
|
345
|
+
console.error('Error in status callback:', error);
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Update connection quality score
|
|
353
|
+
*/
|
|
354
|
+
updateConnectionQuality(quality) {
|
|
355
|
+
this.connectionQuality = Math.max(0, Math.min(1, quality));
|
|
356
|
+
|
|
357
|
+
// Notify callbacks
|
|
358
|
+
this.qualityCallbacks.forEach(callback => {
|
|
359
|
+
try {
|
|
360
|
+
callback(this.connectionQuality);
|
|
361
|
+
} catch (error) {
|
|
362
|
+
console.error('Error in quality callback:', error);
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
// Update UI indicator
|
|
367
|
+
this.updateQualityUI(this.connectionQuality);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Update latency display
|
|
372
|
+
*/
|
|
373
|
+
updateLatency(latency) {
|
|
374
|
+
const latencyElement = document.getElementById('connection-latency');
|
|
375
|
+
if (latencyElement) {
|
|
376
|
+
latencyElement.textContent = `${latency}ms`;
|
|
377
|
+
|
|
378
|
+
// Color code based on latency
|
|
379
|
+
if (latency < 50) {
|
|
380
|
+
latencyElement.className = 'latency-good';
|
|
381
|
+
} else if (latency < 150) {
|
|
382
|
+
latencyElement.className = 'latency-moderate';
|
|
383
|
+
} else {
|
|
384
|
+
latencyElement.className = 'latency-poor';
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Update connection UI based on state
|
|
391
|
+
*/
|
|
392
|
+
updateConnectionUI(state) {
|
|
393
|
+
const statusElement = document.getElementById('connection-status');
|
|
394
|
+
if (!statusElement) return;
|
|
395
|
+
|
|
396
|
+
const stateConfig = {
|
|
397
|
+
'connecting': { text: 'Connecting...', class: 'status-connecting', icon: '⟳' },
|
|
398
|
+
'connected': { text: 'Connected', class: 'status-connected', icon: '●' },
|
|
399
|
+
'reconnecting': { text: 'Reconnecting...', class: 'status-reconnecting', icon: '⟳' },
|
|
400
|
+
'disconnected': { text: 'Disconnected', class: 'status-disconnected', icon: '●' },
|
|
401
|
+
'stale': { text: 'Connection Stale', class: 'status-stale', icon: '⚠' },
|
|
402
|
+
'failed': { text: 'Connection Failed', class: 'status-failed', icon: '✕' }
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
const config = stateConfig[state] || stateConfig['disconnected'];
|
|
406
|
+
statusElement.innerHTML = `<span>${config.icon}</span> ${config.text}`;
|
|
407
|
+
statusElement.className = `status-badge ${config.class}`;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Update connection quality UI
|
|
412
|
+
*/
|
|
413
|
+
updateQualityUI(quality) {
|
|
414
|
+
const qualityElement = document.getElementById('connection-quality');
|
|
415
|
+
if (!qualityElement) return;
|
|
416
|
+
|
|
417
|
+
const percentage = Math.round(quality * 100);
|
|
418
|
+
let qualityClass = 'quality-good';
|
|
419
|
+
let qualityText = 'Excellent';
|
|
420
|
+
|
|
421
|
+
if (quality < 0.3) {
|
|
422
|
+
qualityClass = 'quality-poor';
|
|
423
|
+
qualityText = 'Poor';
|
|
424
|
+
} else if (quality < 0.7) {
|
|
425
|
+
qualityClass = 'quality-moderate';
|
|
426
|
+
qualityText = 'Fair';
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
qualityElement.innerHTML = `
|
|
430
|
+
<div class="quality-bar ${qualityClass}">
|
|
431
|
+
<div class="quality-fill" style="width: ${percentage}%"></div>
|
|
432
|
+
</div>
|
|
433
|
+
<span class="quality-text">${qualityText} (${percentage}%)</span>
|
|
434
|
+
`;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Buffer events when disconnected
|
|
439
|
+
*/
|
|
440
|
+
bufferEvent(event) {
|
|
441
|
+
if (this.eventBuffer.length >= this.maxEventBuffer) {
|
|
442
|
+
this.eventBuffer.shift(); // Remove oldest
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
this.eventBuffer.push({
|
|
446
|
+
...event,
|
|
447
|
+
buffered_at: Date.now()
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
// Save to localStorage for persistence
|
|
451
|
+
localStorage.setItem('claude_mpm_event_buffer', JSON.stringify(this.eventBuffer));
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Flush buffered events after reconnection
|
|
456
|
+
*/
|
|
457
|
+
flushEventBuffer() {
|
|
458
|
+
if (this.eventBuffer.length === 0) return;
|
|
459
|
+
|
|
460
|
+
console.log(`[ConnectionManager] Flushing ${this.eventBuffer.length} buffered events`);
|
|
461
|
+
|
|
462
|
+
// Process buffered events
|
|
463
|
+
this.eventBuffer.forEach(event => {
|
|
464
|
+
this.socketClient.handleEvent('claude_event', event);
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
// Clear buffer
|
|
468
|
+
this.eventBuffer = [];
|
|
469
|
+
localStorage.removeItem('claude_mpm_event_buffer');
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Show notification to user
|
|
474
|
+
*/
|
|
475
|
+
showNotification(message, type = 'info') {
|
|
476
|
+
const notificationArea = document.getElementById('connection-notifications');
|
|
477
|
+
if (!notificationArea) return;
|
|
478
|
+
|
|
479
|
+
const notification = document.createElement('div');
|
|
480
|
+
notification.className = `notification notification-${type}`;
|
|
481
|
+
notification.textContent = message;
|
|
482
|
+
|
|
483
|
+
notificationArea.appendChild(notification);
|
|
484
|
+
|
|
485
|
+
// Auto-remove after 5 seconds
|
|
486
|
+
setTimeout(() => {
|
|
487
|
+
notification.style.opacity = '0';
|
|
488
|
+
setTimeout(() => notification.remove(), 300);
|
|
489
|
+
}, 5000);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Register status change callback
|
|
494
|
+
*/
|
|
495
|
+
onStatusChange(callback) {
|
|
496
|
+
this.statusCallbacks.add(callback);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Register quality change callback
|
|
501
|
+
*/
|
|
502
|
+
onQualityChange(callback) {
|
|
503
|
+
this.qualityCallbacks.add(callback);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* Get connection metrics
|
|
508
|
+
*/
|
|
509
|
+
getMetrics() {
|
|
510
|
+
return {
|
|
511
|
+
...this.metrics,
|
|
512
|
+
connectionState: this.connectionState,
|
|
513
|
+
connectionQuality: this.connectionQuality,
|
|
514
|
+
clientId: this.clientId,
|
|
515
|
+
lastSequence: this.lastSequence,
|
|
516
|
+
bufferedEvents: this.eventBuffer.length
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Disconnect and cleanup
|
|
522
|
+
*/
|
|
523
|
+
disconnect() {
|
|
524
|
+
this.stopHeartbeat();
|
|
525
|
+
|
|
526
|
+
if (this.socket) {
|
|
527
|
+
this.socket.disconnect();
|
|
528
|
+
this.socket = null;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
this.updateConnectionState('disconnected');
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// Export for use in other modules
|
|
536
|
+
export { EnhancedConnectionManager };
|
|
@@ -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 });
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
|
|
26
26
|
<!-- Stylesheets -->
|
|
27
27
|
<link rel="stylesheet" href="/static/css/dashboard.css">
|
|
28
|
+
<link rel="stylesheet" href="/static/css/connection-status.css">
|
|
28
29
|
|
|
29
30
|
<!-- Additional styles for file operations -->
|
|
30
31
|
<style>
|
|
@@ -131,6 +132,9 @@
|
|
|
131
132
|
</style>
|
|
132
133
|
</head>
|
|
133
134
|
<body>
|
|
135
|
+
<!-- Connection Notifications Area -->
|
|
136
|
+
<div id="connection-notifications" class="connection-notifications"></div>
|
|
137
|
+
|
|
134
138
|
<div class="container">
|
|
135
139
|
<!-- Header Section -->
|
|
136
140
|
<div class="header">
|
|
@@ -141,6 +145,13 @@
|
|
|
141
145
|
<div id="connection-status" class="status-badge status-disconnected">
|
|
142
146
|
<span>●</span> Disconnected
|
|
143
147
|
</div>
|
|
148
|
+
<div id="connection-quality" class="connection-quality" style="display: none;">
|
|
149
|
+
<div class="quality-bar">
|
|
150
|
+
<div class="quality-fill"></div>
|
|
151
|
+
</div>
|
|
152
|
+
<span class="quality-text">--</span>
|
|
153
|
+
</div>
|
|
154
|
+
<span id="connection-latency" class="connection-latency" style="display: none;">--ms</span>
|
|
144
155
|
<!-- Version display will be inserted here by BuildTracker component -->
|
|
145
156
|
</div>
|
|
146
157
|
<div class="metrics-widget">
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"""Hook handler services for modular functionality."""
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
# Use HTTP-based connection manager for stable dashboard communication
|
|
4
|
+
# from .connection_manager import ConnectionManagerService # Old SocketIO-based
|
|
5
|
+
from .connection_manager_http import ConnectionManagerService # New HTTP-based
|
|
4
6
|
from .duplicate_detector import DuplicateEventDetector
|
|
5
7
|
from .state_manager import StateManagerService
|
|
6
8
|
from .subagent_processor import SubagentResponseProcessor
|