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.
Files changed (52) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/templates/agent-manager.json +1 -1
  3. claude_mpm/agents/templates/agent-manager.md +111 -34
  4. claude_mpm/agents/templates/research.json +39 -13
  5. claude_mpm/cli/__init__.py +2 -0
  6. claude_mpm/cli/commands/__init__.py +2 -0
  7. claude_mpm/cli/commands/configure.py +1221 -0
  8. claude_mpm/cli/commands/configure_tui.py +1921 -0
  9. claude_mpm/cli/parsers/base_parser.py +7 -0
  10. claude_mpm/cli/parsers/configure_parser.py +119 -0
  11. claude_mpm/cli/startup_logging.py +39 -12
  12. claude_mpm/config/socketio_config.py +33 -4
  13. claude_mpm/constants.py +1 -0
  14. claude_mpm/core/socketio_pool.py +35 -3
  15. claude_mpm/dashboard/static/css/connection-status.css +370 -0
  16. claude_mpm/dashboard/static/js/components/connection-debug.js +654 -0
  17. claude_mpm/dashboard/static/js/connection-manager.js +536 -0
  18. claude_mpm/dashboard/static/js/socket-client.js +40 -16
  19. claude_mpm/dashboard/templates/index.html +11 -0
  20. claude_mpm/hooks/claude_hooks/services/__init__.py +3 -1
  21. claude_mpm/hooks/claude_hooks/services/connection_manager.py +17 -0
  22. claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +190 -0
  23. claude_mpm/services/diagnostics/checks/__init__.py +2 -0
  24. claude_mpm/services/diagnostics/checks/instructions_check.py +418 -0
  25. claude_mpm/services/diagnostics/diagnostic_runner.py +15 -2
  26. claude_mpm/services/event_bus/direct_relay.py +230 -0
  27. claude_mpm/services/socketio/handlers/connection_handler.py +330 -0
  28. claude_mpm/services/socketio/server/broadcaster.py +32 -1
  29. claude_mpm/services/socketio/server/connection_manager.py +547 -0
  30. claude_mpm/services/socketio/server/core.py +78 -7
  31. claude_mpm/services/socketio/server/eventbus_integration.py +20 -9
  32. claude_mpm/services/socketio/server/main.py +74 -19
  33. {claude_mpm-4.1.5.dist-info → claude_mpm-4.1.7.dist-info}/METADATA +3 -1
  34. {claude_mpm-4.1.5.dist-info → claude_mpm-4.1.7.dist-info}/RECORD +38 -41
  35. claude_mpm/agents/OUTPUT_STYLE.md +0 -73
  36. claude_mpm/agents/backups/INSTRUCTIONS.md +0 -352
  37. claude_mpm/agents/templates/OPTIMIZATION_REPORT.md +0 -156
  38. claude_mpm/agents/templates/backup/data_engineer_agent_20250726_234551.json +0 -79
  39. claude_mpm/agents/templates/backup/documentation_agent_20250726_234551.json +0 -68
  40. claude_mpm/agents/templates/backup/engineer_agent_20250726_234551.json +0 -77
  41. claude_mpm/agents/templates/backup/ops_agent_20250726_234551.json +0 -78
  42. claude_mpm/agents/templates/backup/qa_agent_20250726_234551.json +0 -67
  43. claude_mpm/agents/templates/backup/research_agent_2025011_234551.json +0 -88
  44. claude_mpm/agents/templates/backup/research_agent_20250726_234551.json +0 -72
  45. claude_mpm/agents/templates/backup/research_memory_efficient.json +0 -88
  46. claude_mpm/agents/templates/backup/security_agent_20250726_234551.json +0 -78
  47. claude_mpm/agents/templates/backup/version_control_agent_20250726_234551.json +0 -62
  48. claude_mpm/agents/templates/vercel_ops_instructions.md +0 -582
  49. {claude_mpm-4.1.5.dist-info → claude_mpm-4.1.7.dist-info}/WHEEL +0 -0
  50. {claude_mpm-4.1.5.dist-info → claude_mpm-4.1.7.dist-info}/entry_points.txt +0 -0
  51. {claude_mpm-4.1.5.dist-info → claude_mpm-4.1.7.dist-info}/licenses/LICENSE +0 -0
  52. {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: Infinity, // Keep trying indefinitely
102
- timeout: 20000, // Increase connection timeout
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: 25000, // Match server setting
106
- pingTimeout: 60000 // Match server setting
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
- console.log('Disconnected from server:', reason);
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
- // Start auto-reconnect if it was an unexpected disconnect
165
- if (reason === 'transport close' || reason === 'ping timeout') {
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
- from .connection_manager import ConnectionManagerService
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