claude-mpm 4.2.44__py3-none-any.whl → 4.2.51__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/BASE_PM.md +43 -1
- claude_mpm/agents/INSTRUCTIONS.md +75 -1
- claude_mpm/agents/WORKFLOW.md +46 -1
- claude_mpm/agents/frontmatter_validator.py +20 -12
- claude_mpm/agents/templates/nextjs_engineer.json +277 -0
- claude_mpm/agents/templates/python_engineer.json +289 -0
- claude_mpm/agents/templates/react_engineer.json +11 -3
- claude_mpm/agents/templates/security.json +50 -9
- claude_mpm/cli/commands/agents.py +2 -2
- claude_mpm/cli/commands/uninstall.py +1 -2
- claude_mpm/cli/interactive/agent_wizard.py +3 -3
- claude_mpm/cli/parsers/agent_manager_parser.py +3 -3
- claude_mpm/cli/parsers/agents_parser.py +1 -1
- claude_mpm/constants.py +1 -1
- claude_mpm/core/error_handler.py +2 -4
- claude_mpm/core/file_utils.py +4 -12
- claude_mpm/core/log_manager.py +8 -5
- claude_mpm/core/logger.py +1 -1
- claude_mpm/core/logging_utils.py +6 -6
- claude_mpm/core/unified_agent_registry.py +18 -4
- claude_mpm/dashboard/react/components/DataInspector/DataInspector.module.css +188 -0
- claude_mpm/dashboard/react/components/EventViewer/EventViewer.module.css +156 -0
- claude_mpm/dashboard/react/components/shared/ConnectionStatus.module.css +38 -0
- claude_mpm/dashboard/react/components/shared/FilterBar.module.css +92 -0
- claude_mpm/dashboard/static/archive/activity_dashboard_fixed.html +248 -0
- claude_mpm/dashboard/static/archive/activity_dashboard_test.html +61 -0
- claude_mpm/dashboard/static/archive/test_activity_connection.html +179 -0
- claude_mpm/dashboard/static/archive/test_claude_tree_tab.html +68 -0
- claude_mpm/dashboard/static/archive/test_dashboard.html +409 -0
- claude_mpm/dashboard/static/archive/test_dashboard_fixed.html +519 -0
- claude_mpm/dashboard/static/archive/test_dashboard_verification.html +181 -0
- claude_mpm/dashboard/static/archive/test_file_data.html +315 -0
- claude_mpm/dashboard/static/archive/test_file_tree_empty_state.html +243 -0
- claude_mpm/dashboard/static/archive/test_file_tree_fix.html +234 -0
- claude_mpm/dashboard/static/archive/test_file_tree_rename.html +117 -0
- claude_mpm/dashboard/static/archive/test_file_tree_tab.html +115 -0
- claude_mpm/dashboard/static/archive/test_file_viewer.html +224 -0
- claude_mpm/dashboard/static/archive/test_final_activity.html +220 -0
- claude_mpm/dashboard/static/archive/test_tab_fix.html +139 -0
- claude_mpm/dashboard/static/built/assets/events.DjpNxWNo.css +1 -0
- claude_mpm/dashboard/static/built/components/activity-tree.js +1 -1
- claude_mpm/dashboard/static/built/components/agent-hierarchy.js +777 -0
- claude_mpm/dashboard/static/built/components/agent-inference.js +1 -1
- claude_mpm/dashboard/static/built/components/build-tracker.js +333 -0
- claude_mpm/dashboard/static/built/components/code-simple.js +857 -0
- claude_mpm/dashboard/static/built/components/code-tree/tree-breadcrumb.js +353 -0
- claude_mpm/dashboard/static/built/components/code-tree/tree-constants.js +235 -0
- claude_mpm/dashboard/static/built/components/code-tree/tree-search.js +409 -0
- claude_mpm/dashboard/static/built/components/code-tree/tree-utils.js +435 -0
- claude_mpm/dashboard/static/built/components/code-viewer.js +2 -1076
- claude_mpm/dashboard/static/built/components/connection-debug.js +654 -0
- claude_mpm/dashboard/static/built/components/diff-viewer.js +891 -0
- claude_mpm/dashboard/static/built/components/event-processor.js +1 -1
- claude_mpm/dashboard/static/built/components/event-viewer.js +1 -1
- claude_mpm/dashboard/static/built/components/export-manager.js +1 -1
- claude_mpm/dashboard/static/built/components/file-change-tracker.js +443 -0
- claude_mpm/dashboard/static/built/components/file-change-viewer.js +690 -0
- claude_mpm/dashboard/static/built/components/file-tool-tracker.js +1 -1
- claude_mpm/dashboard/static/built/components/module-viewer.js +1 -1
- claude_mpm/dashboard/static/built/components/nav-bar.js +145 -0
- claude_mpm/dashboard/static/built/components/page-structure.js +429 -0
- claude_mpm/dashboard/static/built/components/session-manager.js +1 -1
- claude_mpm/dashboard/static/built/components/ui-state-manager.js +2 -465
- claude_mpm/dashboard/static/built/components/working-directory.js +1 -1
- claude_mpm/dashboard/static/built/connection-manager.js +536 -0
- claude_mpm/dashboard/static/built/dashboard.js +1 -1
- claude_mpm/dashboard/static/built/extension-error-handler.js +164 -0
- claude_mpm/dashboard/static/built/react/events.js +30 -0
- claude_mpm/dashboard/static/built/shared/dom-helpers.js +396 -0
- claude_mpm/dashboard/static/built/shared/event-bus.js +330 -0
- claude_mpm/dashboard/static/built/shared/event-filter-service.js +540 -0
- claude_mpm/dashboard/static/built/shared/logger.js +385 -0
- claude_mpm/dashboard/static/built/shared/page-structure.js +251 -0
- claude_mpm/dashboard/static/built/shared/tooltip-service.js +253 -0
- claude_mpm/dashboard/static/built/socket-client.js +1 -1
- claude_mpm/dashboard/static/built/tab-isolation-fix.js +185 -0
- claude_mpm/dashboard/static/css/dashboard.css +28 -5
- claude_mpm/dashboard/static/dist/assets/events.DjpNxWNo.css +1 -0
- claude_mpm/dashboard/static/dist/components/activity-tree.js +1 -1
- claude_mpm/dashboard/static/dist/components/agent-inference.js +1 -1
- claude_mpm/dashboard/static/dist/components/code-viewer.js +2 -0
- claude_mpm/dashboard/static/dist/components/event-processor.js +1 -1
- claude_mpm/dashboard/static/dist/components/event-viewer.js +1 -1
- claude_mpm/dashboard/static/dist/components/export-manager.js +1 -1
- claude_mpm/dashboard/static/dist/components/file-tool-tracker.js +1 -1
- claude_mpm/dashboard/static/dist/components/module-viewer.js +1 -1
- claude_mpm/dashboard/static/dist/components/session-manager.js +1 -1
- claude_mpm/dashboard/static/dist/components/working-directory.js +1 -1
- claude_mpm/dashboard/static/dist/dashboard.js +1 -1
- claude_mpm/dashboard/static/dist/react/events.js +30 -0
- claude_mpm/dashboard/static/dist/socket-client.js +1 -1
- claude_mpm/dashboard/static/events.html +607 -0
- claude_mpm/dashboard/static/index.html +713 -0
- claude_mpm/dashboard/static/js/components/activity-tree.js +3 -17
- claude_mpm/dashboard/static/js/components/agent-hierarchy.js +4 -1
- claude_mpm/dashboard/static/js/components/agent-inference.js +3 -0
- claude_mpm/dashboard/static/js/components/build-tracker.js +8 -0
- claude_mpm/dashboard/static/js/components/code-viewer.js +306 -66
- claude_mpm/dashboard/static/js/components/event-processor.js +3 -0
- claude_mpm/dashboard/static/js/components/event-viewer.js +39 -2
- claude_mpm/dashboard/static/js/components/export-manager.js +3 -0
- claude_mpm/dashboard/static/js/components/file-tool-tracker.js +30 -10
- claude_mpm/dashboard/static/js/components/socket-manager.js +4 -0
- claude_mpm/dashboard/static/js/components/ui-state-manager.js +285 -85
- claude_mpm/dashboard/static/js/components/working-directory.js +3 -0
- claude_mpm/dashboard/static/js/dashboard.js +61 -33
- claude_mpm/dashboard/static/js/socket-client.js +12 -8
- claude_mpm/dashboard/static/js/stores/dashboard-store.js +562 -0
- claude_mpm/dashboard/static/js/tab-isolation-fix.js +185 -0
- claude_mpm/dashboard/static/legacy/activity.html +736 -0
- claude_mpm/dashboard/static/legacy/agents.html +786 -0
- claude_mpm/dashboard/static/legacy/files.html +747 -0
- claude_mpm/dashboard/static/legacy/tools.html +831 -0
- claude_mpm/dashboard/static/monitors-index.html +218 -0
- claude_mpm/dashboard/static/monitors.html +431 -0
- claude_mpm/dashboard/static/production/events.html +659 -0
- claude_mpm/dashboard/static/production/main.html +715 -0
- claude_mpm/dashboard/static/production/monitors.html +483 -0
- claude_mpm/dashboard/static/socket.io.min.js +7 -0
- claude_mpm/dashboard/static/socket.io.v4.8.1.backup.js +7 -0
- claude_mpm/dashboard/static/test-archive/dashboard.html +635 -0
- claude_mpm/dashboard/static/test-archive/debug-events.html +147 -0
- claude_mpm/dashboard/static/test-archive/test-navigation.html +256 -0
- claude_mpm/dashboard/static/test-archive/test-react-exports.html +180 -0
- claude_mpm/dashboard/templates/index.html +79 -9
- claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +1 -1
- claude_mpm/services/agents/deployment/agent_discovery_service.py +3 -0
- claude_mpm/services/agents/deployment/agent_template_builder.py +25 -8
- claude_mpm/services/agents/deployment/agent_validator.py +3 -0
- claude_mpm/services/agents/deployment/validation/template_validator.py +13 -4
- claude_mpm/services/agents/local_template_manager.py +2 -6
- claude_mpm/services/monitor/daemon.py +1 -2
- claude_mpm/services/monitor/daemon_manager.py +2 -5
- claude_mpm/services/monitor/event_emitter.py +2 -2
- claude_mpm/services/monitor/handlers/code_analysis.py +4 -6
- claude_mpm/services/monitor/handlers/hooks.py +2 -4
- claude_mpm/services/monitor/server.py +27 -4
- claude_mpm/tools/code_tree_analyzer.py +2 -2
- {claude_mpm-4.2.44.dist-info → claude_mpm-4.2.51.dist-info}/METADATA +1 -1
- {claude_mpm-4.2.44.dist-info → claude_mpm-4.2.51.dist-info}/RECORD +146 -81
- claude_mpm/dashboard/static/test-browser-monitor.html +0 -470
- claude_mpm/dashboard/static/test-simple.html +0 -97
- /claude_mpm/dashboard/static/{test_debug.html → test-archive/test_debug.html} +0 -0
- {claude_mpm-4.2.44.dist-info → claude_mpm-4.2.51.dist-info}/WHEEL +0 -0
- {claude_mpm-4.2.44.dist-info → claude_mpm-4.2.51.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.2.44.dist-info → claude_mpm-4.2.51.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.2.44.dist-info → claude_mpm-4.2.51.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: 20000
|
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 };
|