claude-mpm 3.4.10__py3-none-any.whl → 3.4.14__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 (29) hide show
  1. claude_mpm/cli/commands/run.py +10 -10
  2. claude_mpm/dashboard/index.html +13 -0
  3. claude_mpm/dashboard/static/css/dashboard.css +2722 -0
  4. claude_mpm/dashboard/static/js/components/agent-inference.js +619 -0
  5. claude_mpm/dashboard/static/js/components/event-processor.js +641 -0
  6. claude_mpm/dashboard/static/js/components/event-viewer.js +914 -0
  7. claude_mpm/dashboard/static/js/components/export-manager.js +362 -0
  8. claude_mpm/dashboard/static/js/components/file-tool-tracker.js +611 -0
  9. claude_mpm/dashboard/static/js/components/hud-library-loader.js +211 -0
  10. claude_mpm/dashboard/static/js/components/hud-manager.js +671 -0
  11. claude_mpm/dashboard/static/js/components/hud-visualizer.js +1718 -0
  12. claude_mpm/dashboard/static/js/components/module-viewer.js +2701 -0
  13. claude_mpm/dashboard/static/js/components/session-manager.js +520 -0
  14. claude_mpm/dashboard/static/js/components/socket-manager.js +343 -0
  15. claude_mpm/dashboard/static/js/components/ui-state-manager.js +427 -0
  16. claude_mpm/dashboard/static/js/components/working-directory.js +866 -0
  17. claude_mpm/dashboard/static/js/dashboard-original.js +4134 -0
  18. claude_mpm/dashboard/static/js/dashboard.js +1978 -0
  19. claude_mpm/dashboard/static/js/socket-client.js +537 -0
  20. claude_mpm/dashboard/templates/index.html +346 -0
  21. claude_mpm/dashboard/test_dashboard.html +372 -0
  22. claude_mpm/scripts/socketio_daemon.py +51 -6
  23. claude_mpm/services/socketio_server.py +41 -5
  24. {claude_mpm-3.4.10.dist-info → claude_mpm-3.4.14.dist-info}/METADATA +2 -1
  25. {claude_mpm-3.4.10.dist-info → claude_mpm-3.4.14.dist-info}/RECORD +29 -9
  26. {claude_mpm-3.4.10.dist-info → claude_mpm-3.4.14.dist-info}/WHEEL +0 -0
  27. {claude_mpm-3.4.10.dist-info → claude_mpm-3.4.14.dist-info}/entry_points.txt +0 -0
  28. {claude_mpm-3.4.10.dist-info → claude_mpm-3.4.14.dist-info}/licenses/LICENSE +0 -0
  29. {claude_mpm-3.4.10.dist-info → claude_mpm-3.4.14.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,537 @@
1
+ /**
2
+ * Socket.IO Client for Claude MPM Dashboard
3
+ * Handles WebSocket connections and event processing
4
+ */
5
+
6
+ class SocketClient {
7
+ constructor() {
8
+ this.socket = null;
9
+ this.port = null; // Store the current port
10
+ this.connectionCallbacks = {
11
+ connect: [],
12
+ disconnect: [],
13
+ error: [],
14
+ event: []
15
+ };
16
+
17
+ // Connection state
18
+ this.isConnected = false;
19
+ this.isConnecting = false;
20
+
21
+ // Event processing
22
+ this.events = [];
23
+ this.sessions = new Map();
24
+ this.currentSessionId = null;
25
+
26
+ // Start periodic status check as fallback mechanism
27
+ this.startStatusCheckFallback();
28
+ }
29
+
30
+ /**
31
+ * Connect to Socket.IO server
32
+ * @param {string} port - Port number to connect to
33
+ */
34
+ connect(port = '8765') {
35
+ // Store the port for later use
36
+ this.port = port;
37
+ const url = `http://localhost:${port}`;
38
+
39
+ // Prevent multiple simultaneous connections
40
+ if (this.socket && (this.socket.connected || this.socket.connecting)) {
41
+ console.log('Already connected or connecting, disconnecting first...');
42
+ this.socket.disconnect();
43
+ // Wait a moment for cleanup
44
+ setTimeout(() => this.doConnect(url), 100);
45
+ return;
46
+ }
47
+
48
+ this.doConnect(url);
49
+ }
50
+
51
+ /**
52
+ * Perform the actual connection
53
+ * @param {string} url - Socket.IO server URL
54
+ */
55
+ doConnect(url) {
56
+ console.log(`Connecting to Socket.IO server at ${url}`);
57
+ this.isConnecting = true;
58
+ this.notifyConnectionStatus('Connecting...', 'connecting');
59
+
60
+ this.socket = io(url, {
61
+ autoConnect: true,
62
+ reconnection: true,
63
+ reconnectionDelay: 1000,
64
+ reconnectionDelayMax: 10000,
65
+ maxReconnectionAttempts: 10,
66
+ timeout: 10000,
67
+ forceNew: true,
68
+ transports: ['websocket', 'polling']
69
+ });
70
+
71
+ this.setupSocketHandlers();
72
+ }
73
+
74
+ /**
75
+ * Setup Socket.IO event handlers
76
+ */
77
+ setupSocketHandlers() {
78
+ this.socket.on('connect', () => {
79
+ console.log('Connected to Socket.IO server');
80
+ this.isConnected = true;
81
+ this.isConnecting = false;
82
+ this.notifyConnectionStatus('Connected', 'connected');
83
+
84
+ // Emit connect callback
85
+ this.connectionCallbacks.connect.forEach(callback =>
86
+ callback(this.socket.id)
87
+ );
88
+
89
+ this.requestStatus();
90
+ // History is now automatically sent by server on connection
91
+ // No need to explicitly request it
92
+ });
93
+
94
+ this.socket.on('disconnect', (reason) => {
95
+ console.log('Disconnected from server:', reason);
96
+ this.isConnected = false;
97
+ this.isConnecting = false;
98
+ this.notifyConnectionStatus(`Disconnected: ${reason}`, 'disconnected');
99
+
100
+ // Emit disconnect callback
101
+ this.connectionCallbacks.disconnect.forEach(callback =>
102
+ callback(reason)
103
+ );
104
+ });
105
+
106
+ this.socket.on('connect_error', (error) => {
107
+ console.error('Connection error:', error);
108
+ this.isConnecting = false;
109
+ const errorMsg = error.message || error.description || 'Unknown error';
110
+ this.notifyConnectionStatus(`Connection Error: ${errorMsg}`, 'disconnected');
111
+
112
+ // Add error event
113
+ this.addEvent({
114
+ type: 'connection.error',
115
+ timestamp: new Date().toISOString(),
116
+ data: { error: errorMsg, url: this.socket.io.uri }
117
+ });
118
+
119
+ // Emit error callback
120
+ this.connectionCallbacks.error.forEach(callback =>
121
+ callback(errorMsg)
122
+ );
123
+ });
124
+
125
+ // Primary event handler - this is what the server actually emits
126
+ this.socket.on('claude_event', (data) => {
127
+ console.log('Received claude_event:', data);
128
+
129
+ // Transform event to match expected format
130
+ const transformedEvent = this.transformEvent(data);
131
+ console.log('Transformed event:', transformedEvent);
132
+ this.addEvent(transformedEvent);
133
+ });
134
+
135
+ // Session and event handlers (legacy/fallback)
136
+ this.socket.on('session.started', (data) => {
137
+ this.addEvent({ type: 'session', subtype: 'started', timestamp: new Date().toISOString(), data });
138
+ });
139
+
140
+ this.socket.on('session.ended', (data) => {
141
+ this.addEvent({ type: 'session', subtype: 'ended', timestamp: new Date().toISOString(), data });
142
+ });
143
+
144
+ this.socket.on('claude.request', (data) => {
145
+ this.addEvent({ type: 'claude', subtype: 'request', timestamp: new Date().toISOString(), data });
146
+ });
147
+
148
+ this.socket.on('claude.response', (data) => {
149
+ this.addEvent({ type: 'claude', subtype: 'response', timestamp: new Date().toISOString(), data });
150
+ });
151
+
152
+ this.socket.on('agent.loaded', (data) => {
153
+ this.addEvent({ type: 'agent', subtype: 'loaded', timestamp: new Date().toISOString(), data });
154
+ });
155
+
156
+ this.socket.on('agent.executed', (data) => {
157
+ this.addEvent({ type: 'agent', subtype: 'executed', timestamp: new Date().toISOString(), data });
158
+ });
159
+
160
+ this.socket.on('hook.pre', (data) => {
161
+ this.addEvent({ type: 'hook', subtype: 'pre', timestamp: new Date().toISOString(), data });
162
+ });
163
+
164
+ this.socket.on('hook.post', (data) => {
165
+ this.addEvent({ type: 'hook', subtype: 'post', timestamp: new Date().toISOString(), data });
166
+ });
167
+
168
+ this.socket.on('todo.updated', (data) => {
169
+ this.addEvent({ type: 'todo', subtype: 'updated', timestamp: new Date().toISOString(), data });
170
+ });
171
+
172
+ this.socket.on('memory.operation', (data) => {
173
+ this.addEvent({ type: 'memory', subtype: 'operation', timestamp: new Date().toISOString(), data });
174
+ });
175
+
176
+ this.socket.on('log.entry', (data) => {
177
+ this.addEvent({ type: 'log', subtype: 'entry', timestamp: new Date().toISOString(), data });
178
+ });
179
+
180
+ this.socket.on('history', (data) => {
181
+ console.log('Received event history:', data);
182
+ if (data && Array.isArray(data.events)) {
183
+ console.log(`Processing ${data.events.length} historical events (${data.count} sent, ${data.total_available} total available)`);
184
+ // Add events in the order received (should already be chronological - oldest first)
185
+ // Transform each historical event to match expected format
186
+ data.events.forEach(event => {
187
+ const transformedEvent = this.transformEvent(event);
188
+ this.addEvent(transformedEvent, false);
189
+ });
190
+ this.notifyEventUpdate();
191
+ console.log(`Event history loaded: ${data.events.length} events added to dashboard`);
192
+ } else if (Array.isArray(data)) {
193
+ // Handle legacy format for backward compatibility
194
+ console.log('Received legacy event history format:', data.length, 'events');
195
+ data.forEach(event => {
196
+ const transformedEvent = this.transformEvent(event);
197
+ this.addEvent(transformedEvent, false);
198
+ });
199
+ this.notifyEventUpdate();
200
+ }
201
+ });
202
+
203
+ this.socket.on('system.status', (data) => {
204
+ console.log('Received system status:', data);
205
+ if (data.sessions) {
206
+ this.updateSessions(data.sessions);
207
+ }
208
+ if (data.current_session) {
209
+ this.currentSessionId = data.current_session;
210
+ }
211
+ });
212
+ }
213
+
214
+ /**
215
+ * Disconnect from Socket.IO server
216
+ */
217
+ disconnect() {
218
+ if (this.socket) {
219
+ this.socket.disconnect();
220
+ this.socket = null;
221
+ }
222
+ this.port = null; // Clear the stored port
223
+ this.isConnected = false;
224
+ this.isConnecting = false;
225
+ }
226
+
227
+ /**
228
+ * Request server status
229
+ */
230
+ requestStatus() {
231
+ if (this.socket && this.socket.connected) {
232
+ console.log('Requesting server status...');
233
+ this.socket.emit('request.status');
234
+ }
235
+ }
236
+
237
+ /**
238
+ * Request event history from server
239
+ * @param {Object} options - History request options
240
+ * @param {number} options.limit - Maximum number of events to retrieve (default: 50)
241
+ * @param {Array<string>} options.event_types - Optional filter by event types
242
+ */
243
+ requestHistory(options = {}) {
244
+ if (this.socket && this.socket.connected) {
245
+ const params = {
246
+ limit: options.limit || 50,
247
+ event_types: options.event_types || []
248
+ };
249
+ console.log('Requesting event history...', params);
250
+ this.socket.emit('get_history', params);
251
+ } else {
252
+ console.warn('Cannot request history: not connected to server');
253
+ }
254
+ }
255
+
256
+ /**
257
+ * Add event to local storage and notify listeners
258
+ * @param {Object} eventData - Event data
259
+ * @param {boolean} notify - Whether to notify listeners (default: true)
260
+ */
261
+ addEvent(eventData, notify = true) {
262
+ // Ensure event has required fields
263
+ if (!eventData.timestamp) {
264
+ eventData.timestamp = new Date().toISOString();
265
+ }
266
+ if (!eventData.id) {
267
+ eventData.id = Date.now() + Math.random();
268
+ }
269
+
270
+ this.events.push(eventData);
271
+
272
+ // Update session tracking
273
+ if (eventData.data && eventData.data.session_id) {
274
+ const sessionId = eventData.data.session_id;
275
+ if (!this.sessions.has(sessionId)) {
276
+ this.sessions.set(sessionId, {
277
+ id: sessionId,
278
+ startTime: eventData.timestamp,
279
+ lastActivity: eventData.timestamp,
280
+ eventCount: 0
281
+ });
282
+ }
283
+ const session = this.sessions.get(sessionId);
284
+ session.lastActivity = eventData.timestamp;
285
+ session.eventCount++;
286
+ }
287
+
288
+ if (notify) {
289
+ this.notifyEventUpdate();
290
+ }
291
+ }
292
+
293
+ /**
294
+ * Update sessions from server data
295
+ * @param {Array} sessionsData - Sessions data from server
296
+ */
297
+ updateSessions(sessionsData) {
298
+ if (Array.isArray(sessionsData)) {
299
+ sessionsData.forEach(session => {
300
+ this.sessions.set(session.id, session);
301
+ });
302
+ }
303
+ }
304
+
305
+ /**
306
+ * Clear all events
307
+ */
308
+ clearEvents() {
309
+ this.events = [];
310
+ this.sessions.clear();
311
+ this.notifyEventUpdate();
312
+ }
313
+
314
+ /**
315
+ * Clear events and request fresh history from server
316
+ * @param {Object} options - History request options (same as requestHistory)
317
+ */
318
+ refreshHistory(options = {}) {
319
+ this.clearEvents();
320
+ this.requestHistory(options);
321
+ }
322
+
323
+ /**
324
+ * Get filtered events by session
325
+ * @param {string} sessionId - Session ID to filter by (null for all)
326
+ * @returns {Array} Filtered events
327
+ */
328
+ getEventsBySession(sessionId = null) {
329
+ if (!sessionId) {
330
+ return this.events;
331
+ }
332
+ return this.events.filter(event =>
333
+ event.data && event.data.session_id === sessionId
334
+ );
335
+ }
336
+
337
+ /**
338
+ * Register callback for connection events
339
+ * @param {string} eventType - Type of event (connect, disconnect, error)
340
+ * @param {Function} callback - Callback function
341
+ */
342
+ onConnection(eventType, callback) {
343
+ if (this.connectionCallbacks[eventType]) {
344
+ this.connectionCallbacks[eventType].push(callback);
345
+ }
346
+ }
347
+
348
+ /**
349
+ * Register callback for event updates
350
+ * @param {Function} callback - Callback function
351
+ */
352
+ onEventUpdate(callback) {
353
+ this.connectionCallbacks.event.push(callback);
354
+ }
355
+
356
+ /**
357
+ * Notify connection status change
358
+ * @param {string} status - Status message
359
+ * @param {string} type - Status type (connected, disconnected, connecting)
360
+ */
361
+ notifyConnectionStatus(status, type) {
362
+ console.log(`SocketClient: Connection status changed to '${status}' (${type})`);
363
+
364
+ // Direct DOM update - immediate and reliable
365
+ this.updateConnectionStatusDOM(status, type);
366
+
367
+ // Also dispatch custom event for other modules
368
+ document.dispatchEvent(new CustomEvent('socketConnectionStatus', {
369
+ detail: { status, type }
370
+ }));
371
+ }
372
+
373
+ /**
374
+ * Directly update the connection status DOM element
375
+ * @param {string} status - Status message
376
+ * @param {string} type - Status type (connected, disconnected, connecting)
377
+ */
378
+ updateConnectionStatusDOM(status, type) {
379
+ const statusElement = document.getElementById('connection-status');
380
+ if (statusElement) {
381
+ // Update the text content while preserving the indicator span
382
+ statusElement.innerHTML = `<span>●</span> ${status}`;
383
+
384
+ // Update the CSS class for styling
385
+ statusElement.className = `status-badge status-${type}`;
386
+
387
+ console.log(`SocketClient: Direct DOM update - status: '${status}' (${type})`);
388
+ } else {
389
+ console.warn('SocketClient: Could not find connection-status element in DOM');
390
+ }
391
+ }
392
+
393
+ /**
394
+ * Notify event update
395
+ */
396
+ notifyEventUpdate() {
397
+ this.connectionCallbacks.event.forEach(callback =>
398
+ callback(this.events, this.sessions)
399
+ );
400
+
401
+ // Also dispatch custom event
402
+ document.dispatchEvent(new CustomEvent('socketEventUpdate', {
403
+ detail: { events: this.events, sessions: this.sessions }
404
+ }));
405
+ }
406
+
407
+ /**
408
+ * Get connection state
409
+ * @returns {Object} Connection state
410
+ */
411
+ getConnectionState() {
412
+ return {
413
+ isConnected: this.isConnected,
414
+ isConnecting: this.isConnecting,
415
+ socketId: this.socket ? this.socket.id : null
416
+ };
417
+ }
418
+
419
+ /**
420
+ * Transform received event to match expected dashboard format
421
+ * @param {Object} eventData - Raw event data from server
422
+ * @returns {Object} Transformed event
423
+ */
424
+ transformEvent(eventData) {
425
+ // Handle the actual event structure sent from hook_handler.py:
426
+ // { type: 'hook.pre_tool', timestamp: '...', data: { tool_name: '...', agent_type: '...', etc } }
427
+
428
+ if (!eventData || !eventData.type) {
429
+ return eventData; // Return as-is if malformed
430
+ }
431
+
432
+ const type = eventData.type;
433
+ let transformedEvent = { ...eventData };
434
+
435
+ // Transform 'hook.subtype' format to separate type and subtype
436
+ if (type.startsWith('hook.')) {
437
+ const subtype = type.substring(5); // Remove 'hook.' prefix
438
+ transformedEvent.type = 'hook';
439
+ transformedEvent.subtype = subtype;
440
+ }
441
+ // Transform other dotted types like 'session.started' -> type: 'session', subtype: 'started'
442
+ else if (type.includes('.')) {
443
+ const [mainType, ...subtypeParts] = type.split('.');
444
+ transformedEvent.type = mainType;
445
+ transformedEvent.subtype = subtypeParts.join('.');
446
+ }
447
+
448
+ // Extract and flatten data fields to top level for dashboard compatibility
449
+ // The dashboard expects fields like tool_name, agent_type, etc. at the top level
450
+ if (eventData.data && typeof eventData.data === 'object') {
451
+ // Copy all data fields to the top level
452
+ Object.keys(eventData.data).forEach(key => {
453
+ // Only copy if the key doesn't already exist at top level
454
+ if (!(key in transformedEvent)) {
455
+ transformedEvent[key] = eventData.data[key];
456
+ }
457
+ });
458
+ }
459
+
460
+ return transformedEvent;
461
+ }
462
+
463
+ /**
464
+ * Get current events and sessions
465
+ * @returns {Object} Current state
466
+ */
467
+ getState() {
468
+ return {
469
+ events: this.events,
470
+ sessions: this.sessions,
471
+ currentSessionId: this.currentSessionId
472
+ };
473
+ }
474
+
475
+ /**
476
+ * Start periodic status check as fallback mechanism
477
+ * This ensures the UI stays in sync with actual socket state
478
+ */
479
+ startStatusCheckFallback() {
480
+ // Check status every 2 seconds
481
+ setInterval(() => {
482
+ this.checkAndUpdateStatus();
483
+ }, 2000);
484
+
485
+ // Initial check after DOM is ready
486
+ if (document.readyState === 'loading') {
487
+ document.addEventListener('DOMContentLoaded', () => {
488
+ setTimeout(() => this.checkAndUpdateStatus(), 100);
489
+ });
490
+ } else {
491
+ setTimeout(() => this.checkAndUpdateStatus(), 100);
492
+ }
493
+ }
494
+
495
+ /**
496
+ * Check actual socket state and update UI if necessary
497
+ */
498
+ checkAndUpdateStatus() {
499
+ let actualStatus = 'Disconnected';
500
+ let actualType = 'disconnected';
501
+
502
+ if (this.socket) {
503
+ if (this.socket.connected) {
504
+ actualStatus = 'Connected';
505
+ actualType = 'connected';
506
+ this.isConnected = true;
507
+ this.isConnecting = false;
508
+ } else if (this.socket.connecting || this.isConnecting) {
509
+ actualStatus = 'Connecting...';
510
+ actualType = 'connecting';
511
+ this.isConnected = false;
512
+ } else {
513
+ actualStatus = 'Disconnected';
514
+ actualType = 'disconnected';
515
+ this.isConnected = false;
516
+ this.isConnecting = false;
517
+ }
518
+ }
519
+
520
+ // Check if UI needs updating
521
+ const statusElement = document.getElementById('connection-status');
522
+ if (statusElement) {
523
+ const currentText = statusElement.textContent.replace('●', '').trim();
524
+ const currentClass = statusElement.className;
525
+ const expectedClass = `status-badge status-${actualType}`;
526
+
527
+ // Update if status text or class doesn't match
528
+ if (currentText !== actualStatus || currentClass !== expectedClass) {
529
+ console.log(`SocketClient: Fallback update - was '${currentText}' (${currentClass}), now '${actualStatus}' (${expectedClass})`);
530
+ this.updateConnectionStatusDOM(actualStatus, actualType);
531
+ }
532
+ }
533
+ }
534
+ }
535
+
536
+ // Export for use in other modules
537
+ window.SocketClient = SocketClient;