claude-mpm 4.1.8__py3-none-any.whl → 4.1.11__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 (111) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/INSTRUCTIONS.md +26 -1
  3. claude_mpm/agents/agents_metadata.py +57 -0
  4. claude_mpm/agents/templates/.claude-mpm/memories/README.md +17 -0
  5. claude_mpm/agents/templates/.claude-mpm/memories/engineer_memories.md +3 -0
  6. claude_mpm/agents/templates/agent-manager.json +263 -17
  7. claude_mpm/agents/templates/agentic_coder_optimizer.json +222 -0
  8. claude_mpm/agents/templates/code_analyzer.json +18 -8
  9. claude_mpm/agents/templates/engineer.json +1 -1
  10. claude_mpm/agents/templates/logs/prompts/agent_engineer_20250826_014258_728.md +39 -0
  11. claude_mpm/agents/templates/qa.json +1 -1
  12. claude_mpm/agents/templates/research.json +1 -1
  13. claude_mpm/cli/__init__.py +15 -0
  14. claude_mpm/cli/commands/__init__.py +6 -0
  15. claude_mpm/cli/commands/analyze.py +548 -0
  16. claude_mpm/cli/commands/analyze_code.py +524 -0
  17. claude_mpm/cli/commands/configure.py +78 -28
  18. claude_mpm/cli/commands/configure_tui.py +62 -60
  19. claude_mpm/cli/commands/dashboard.py +288 -0
  20. claude_mpm/cli/commands/debug.py +1386 -0
  21. claude_mpm/cli/commands/mpm_init.py +427 -0
  22. claude_mpm/cli/commands/mpm_init_handler.py +83 -0
  23. claude_mpm/cli/parsers/analyze_code_parser.py +170 -0
  24. claude_mpm/cli/parsers/analyze_parser.py +135 -0
  25. claude_mpm/cli/parsers/base_parser.py +44 -0
  26. claude_mpm/cli/parsers/dashboard_parser.py +113 -0
  27. claude_mpm/cli/parsers/debug_parser.py +319 -0
  28. claude_mpm/cli/parsers/mpm_init_parser.py +122 -0
  29. claude_mpm/constants.py +13 -1
  30. claude_mpm/core/framework_loader.py +148 -6
  31. claude_mpm/core/log_manager.py +16 -13
  32. claude_mpm/core/logger.py +1 -1
  33. claude_mpm/core/unified_agent_registry.py +1 -1
  34. claude_mpm/dashboard/.claude-mpm/socketio-instances.json +1 -0
  35. claude_mpm/dashboard/analysis_runner.py +455 -0
  36. claude_mpm/dashboard/static/built/components/activity-tree.js +2 -0
  37. claude_mpm/dashboard/static/built/components/agent-inference.js +1 -1
  38. claude_mpm/dashboard/static/built/components/code-tree.js +2 -0
  39. claude_mpm/dashboard/static/built/components/code-viewer.js +2 -0
  40. claude_mpm/dashboard/static/built/components/event-viewer.js +1 -1
  41. claude_mpm/dashboard/static/built/components/file-tool-tracker.js +1 -1
  42. claude_mpm/dashboard/static/built/components/module-viewer.js +1 -1
  43. claude_mpm/dashboard/static/built/components/session-manager.js +1 -1
  44. claude_mpm/dashboard/static/built/components/working-directory.js +1 -1
  45. claude_mpm/dashboard/static/built/dashboard.js +1 -1
  46. claude_mpm/dashboard/static/built/socket-client.js +1 -1
  47. claude_mpm/dashboard/static/css/activity.css +549 -0
  48. claude_mpm/dashboard/static/css/code-tree.css +1175 -0
  49. claude_mpm/dashboard/static/css/dashboard.css +245 -0
  50. claude_mpm/dashboard/static/dist/components/activity-tree.js +2 -0
  51. claude_mpm/dashboard/static/dist/components/code-tree.js +2 -0
  52. claude_mpm/dashboard/static/dist/components/code-viewer.js +2 -0
  53. claude_mpm/dashboard/static/dist/components/event-viewer.js +1 -1
  54. claude_mpm/dashboard/static/dist/components/session-manager.js +1 -1
  55. claude_mpm/dashboard/static/dist/components/working-directory.js +1 -1
  56. claude_mpm/dashboard/static/dist/dashboard.js +1 -1
  57. claude_mpm/dashboard/static/dist/socket-client.js +1 -1
  58. claude_mpm/dashboard/static/js/components/activity-tree.js +1338 -0
  59. claude_mpm/dashboard/static/js/components/code-tree.js +2535 -0
  60. claude_mpm/dashboard/static/js/components/code-viewer.js +480 -0
  61. claude_mpm/dashboard/static/js/components/event-viewer.js +59 -9
  62. claude_mpm/dashboard/static/js/components/session-manager.js +40 -4
  63. claude_mpm/dashboard/static/js/components/socket-manager.js +12 -0
  64. claude_mpm/dashboard/static/js/components/ui-state-manager.js +4 -0
  65. claude_mpm/dashboard/static/js/components/working-directory.js +17 -1
  66. claude_mpm/dashboard/static/js/dashboard.js +51 -0
  67. claude_mpm/dashboard/static/js/socket-client.js +465 -29
  68. claude_mpm/dashboard/templates/index.html +182 -4
  69. claude_mpm/hooks/claude_hooks/hook_handler.py +182 -5
  70. claude_mpm/hooks/claude_hooks/installer.py +386 -113
  71. claude_mpm/scripts/claude-hook-handler.sh +161 -0
  72. claude_mpm/scripts/socketio_daemon.py +121 -8
  73. claude_mpm/services/agents/deployment/agent_lifecycle_manager_refactored.py +2 -2
  74. claude_mpm/services/agents/deployment/agent_record_service.py +1 -2
  75. claude_mpm/services/agents/memory/memory_format_service.py +1 -3
  76. claude_mpm/services/cli/agent_cleanup_service.py +1 -5
  77. claude_mpm/services/cli/agent_dependency_service.py +1 -1
  78. claude_mpm/services/cli/agent_validation_service.py +3 -4
  79. claude_mpm/services/cli/dashboard_launcher.py +2 -3
  80. claude_mpm/services/cli/startup_checker.py +0 -11
  81. claude_mpm/services/core/cache_manager.py +1 -3
  82. claude_mpm/services/core/path_resolver.py +1 -4
  83. claude_mpm/services/core/service_container.py +2 -2
  84. claude_mpm/services/diagnostics/checks/instructions_check.py +1 -2
  85. claude_mpm/services/infrastructure/monitoring/__init__.py +11 -11
  86. claude_mpm/services/infrastructure/monitoring.py +11 -11
  87. claude_mpm/services/project/architecture_analyzer.py +1 -1
  88. claude_mpm/services/project/dependency_analyzer.py +4 -4
  89. claude_mpm/services/project/language_analyzer.py +3 -3
  90. claude_mpm/services/project/metrics_collector.py +3 -6
  91. claude_mpm/services/socketio/event_normalizer.py +64 -0
  92. claude_mpm/services/socketio/handlers/__init__.py +2 -0
  93. claude_mpm/services/socketio/handlers/code_analysis.py +672 -0
  94. claude_mpm/services/socketio/handlers/registry.py +2 -0
  95. claude_mpm/services/socketio/server/connection_manager.py +6 -4
  96. claude_mpm/services/socketio/server/core.py +100 -11
  97. claude_mpm/services/socketio/server/main.py +8 -2
  98. claude_mpm/services/visualization/__init__.py +19 -0
  99. claude_mpm/services/visualization/mermaid_generator.py +938 -0
  100. claude_mpm/tools/__main__.py +208 -0
  101. claude_mpm/tools/code_tree_analyzer.py +1596 -0
  102. claude_mpm/tools/code_tree_builder.py +631 -0
  103. claude_mpm/tools/code_tree_events.py +416 -0
  104. claude_mpm/tools/socketio_debug.py +671 -0
  105. {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.11.dist-info}/METADATA +2 -1
  106. {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.11.dist-info}/RECORD +110 -74
  107. claude_mpm/agents/schema/agent_schema.json +0 -314
  108. {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.11.dist-info}/WHEEL +0 -0
  109. {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.11.dist-info}/entry_points.txt +0 -0
  110. {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.11.dist-info}/licenses/LICENSE +0 -0
  111. {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.11.dist-info}/top_level.txt +0 -0
@@ -1,74 +1,303 @@
1
1
  /**
2
2
  * Socket.IO Client for Claude MPM Dashboard
3
- * Handles WebSocket connections and event processing
3
+ *
4
+ * This module provides real-time WebSocket communication between the Claude MPM dashboard
5
+ * and the backend Socket.IO server. It handles connection management, event processing,
6
+ * retry logic, and health monitoring.
7
+ *
8
+ * Architecture:
9
+ * - Maintains persistent WebSocket connection to Claude MPM backend
10
+ * - Implements robust retry logic with exponential backoff
11
+ * - Provides event queuing during disconnections
12
+ * - Validates event schemas for data integrity
13
+ * - Monitors connection health with ping/pong mechanisms
14
+ *
15
+ * Event Flow:
16
+ * 1. Events from Claude Code hooks → Socket.IO server → Dashboard client
17
+ * 2. Dashboard requests → Socket.IO server → Backend services
18
+ * 3. Status updates → Socket.IO server → All connected clients
19
+ *
20
+ * Thread Safety:
21
+ * - Single-threaded JavaScript execution model ensures safety
22
+ * - Event callbacks are queued and executed sequentially
23
+ * - Connection state changes are atomic
24
+ *
25
+ * Performance Considerations:
26
+ * - Event queue limited to 100 items to prevent memory leaks
27
+ * - Health checks run every 45s to match server ping interval
28
+ * - Exponential backoff prevents connection spam
29
+ * - Lazy event validation reduces overhead
30
+ *
31
+ * Security:
32
+ * - Connects only to localhost to prevent external access
33
+ * - Event schema validation prevents malformed data processing
34
+ * - Connection timeout prevents hanging connections
35
+ *
36
+ * @author Claude MPM Team
37
+ * @version 1.0
38
+ * @since v4.0.25
4
39
  */
5
40
 
6
41
  // Access the global io from window object in ES6 module context
42
+ // WHY: Socket.IO is loaded via CDN in HTML, available as window.io
7
43
  const io = window.io;
8
44
 
45
+ /**
46
+ * Primary Socket.IO client for dashboard communication.
47
+ *
48
+ * Manages WebSocket connection lifecycle, event processing, and error handling.
49
+ * Implements connection resilience with automatic retry and health monitoring.
50
+ *
51
+ * Key Features:
52
+ * - Automatic connection retry with exponential backoff
53
+ * - Event queue management during disconnections
54
+ * - Schema validation for incoming events
55
+ * - Health monitoring with ping/pong
56
+ * - Session management and event history
57
+ *
58
+ * Connection States:
59
+ * - isConnected: Currently connected to server
60
+ * - isConnecting: Connection attempt in progress
61
+ * - disconnectTime: Timestamp of last disconnection
62
+ *
63
+ * Event Processing:
64
+ * - Validates against schema before processing
65
+ * - Queues events during disconnection (max 100)
66
+ * - Maintains event history and session tracking
67
+ *
68
+ * @class SocketClient
69
+ */
9
70
  class SocketClient {
71
+ /**
72
+ * Initialize Socket.IO client with default configuration.
73
+ *
74
+ * Sets up connection management, event processing, and health monitoring.
75
+ * Configures retry logic and event queue management.
76
+ *
77
+ * WHY this initialization approach:
78
+ * - Lazy socket creation allows for port specification
79
+ * - Event queue prevents data loss during reconnections
80
+ * - Health monitoring detects server issues early
81
+ * - Schema validation ensures data integrity
82
+ *
83
+ * @constructor
84
+ */
10
85
  constructor() {
86
+ /**
87
+ * Socket.IO connection instance.
88
+ * @type {Socket|null}
89
+ * @private
90
+ */
11
91
  this.socket = null;
92
+
93
+ /**
94
+ * Current connection port.
95
+ * @type {string|null}
96
+ * @private
97
+ */
12
98
  this.port = null; // Store the current port
99
+
100
+ /**
101
+ * Event callback registry for connection lifecycle events.
102
+ * WHY: Allows multiple components to register for connection events.
103
+ * @type {Object.<string, Function[]>}
104
+ * @private
105
+ */
13
106
  this.connectionCallbacks = {
14
- connect: [],
15
- disconnect: [],
16
- error: [],
17
- event: []
107
+ connect: [], // Called on successful connection
108
+ disconnect: [], // Called on disconnection
109
+ error: [], // Called on connection errors
110
+ event: [] // Called on incoming events
18
111
  };
19
112
 
20
- // Event schema validation
113
+ /**
114
+ * Event schema definition for validation.
115
+ * WHY: Ensures data integrity and prevents processing malformed events.
116
+ * @type {Object}
117
+ * @private
118
+ */
21
119
  this.eventSchema = {
22
120
  required: ['source', 'type', 'subtype', 'timestamp', 'data'],
23
121
  optional: ['event', 'session_id']
24
122
  };
25
123
 
26
- // Connection state
124
+ /**
125
+ * Current connection state.
126
+ * @type {boolean}
127
+ * @private
128
+ */
27
129
  this.isConnected = false;
130
+
131
+ /**
132
+ * Connection attempt in progress flag.
133
+ * WHY: Prevents multiple simultaneous connection attempts.
134
+ * @type {boolean}
135
+ * @private
136
+ */
28
137
  this.isConnecting = false;
138
+
139
+ /**
140
+ * Timestamp of last successful connection.
141
+ * @type {number|null}
142
+ * @private
143
+ */
29
144
  this.lastConnectTime = null;
145
+
146
+ /**
147
+ * Timestamp of last disconnection.
148
+ * WHY: Used to calculate downtime and trigger reconnection logic.
149
+ * @type {number|null}
150
+ * @private
151
+ */
30
152
  this.disconnectTime = null;
31
153
 
32
- // Event processing
154
+ /**
155
+ * Event history storage.
156
+ * WHY: Maintains event history for dashboard display and analysis.
157
+ * @type {Array.<Object>}
158
+ * @private
159
+ */
33
160
  this.events = [];
161
+
162
+ /**
163
+ * Session tracking map.
164
+ * WHY: Groups events by session for better organization.
165
+ * @type {Map<string, Object>}
166
+ * @private
167
+ */
34
168
  this.sessions = new Map();
169
+
170
+ /**
171
+ * Current active session identifier.
172
+ * @type {string|null}
173
+ * @private
174
+ */
35
175
  this.currentSessionId = null;
36
176
 
37
- // Event queue for disconnection periods
177
+ /**
178
+ * Event queue for disconnection periods.
179
+ * WHY: Prevents event loss during temporary disconnections.
180
+ * @type {Array.<Object>}
181
+ * @private
182
+ */
38
183
  this.eventQueue = [];
184
+
185
+ /**
186
+ * Maximum queue size to prevent memory leaks.
187
+ * WHY: Limits memory usage during extended disconnections.
188
+ * @type {number}
189
+ * @private
190
+ * @const
191
+ */
39
192
  this.maxQueueSize = 100;
40
193
 
41
- // Retry configuration - Match server settings
194
+ /**
195
+ * Current retry attempt counter.
196
+ * WHY: Tracks retry attempts for exponential backoff logic.
197
+ * @type {number}
198
+ * @private
199
+ */
42
200
  this.retryAttempts = 0;
201
+
202
+ /**
203
+ * Maximum retry attempts before giving up.
204
+ * WHY: Prevents infinite retry loops that could impact performance.
205
+ * @type {number}
206
+ * @private
207
+ * @const
208
+ */
43
209
  this.maxRetryAttempts = 5; // Increased from 3 to 5 for better stability
210
+
211
+ /**
212
+ * Retry delay intervals in milliseconds (exponential backoff).
213
+ * WHY: Prevents server overload during connection issues.
214
+ * @type {number[]}
215
+ * @private
216
+ * @const
217
+ */
44
218
  this.retryDelays = [1000, 2000, 3000, 4000, 5000]; // Exponential backoff with 5 attempts
219
+
220
+ /**
221
+ * Map of pending emissions for retry logic.
222
+ * WHY: Tracks failed emissions that need to be retried.
223
+ * @type {Map<string, Object>}
224
+ * @private
225
+ */
45
226
  this.pendingEmissions = new Map(); // Track pending emissions for retry
46
227
 
47
- // Health monitoring
228
+ /**
229
+ * Timestamp of last ping sent to server.
230
+ * WHY: Used for health monitoring and connection validation.
231
+ * @type {number|null}
232
+ * @private
233
+ */
48
234
  this.lastPingTime = null;
235
+
236
+ /**
237
+ * Timestamp of last pong received from server.
238
+ * WHY: Confirms server is responsive and connection is healthy.
239
+ * @type {number|null}
240
+ * @private
241
+ */
49
242
  this.lastPongTime = null;
243
+
244
+ /**
245
+ * Health check timeout in milliseconds.
246
+ * WHY: More lenient than Socket.IO timeout to prevent false positives.
247
+ * @type {number}
248
+ * @private
249
+ * @const
250
+ */
50
251
  this.pingTimeout = 90000; // 90 seconds for health check (more lenient than Socket.IO timeout)
252
+
253
+ /**
254
+ * Health check interval timer.
255
+ * @type {number|null}
256
+ * @private
257
+ */
51
258
  this.healthCheckInterval = null;
52
259
 
53
- // Start periodic status check as fallback mechanism
260
+ // Initialize background monitoring
54
261
  this.startStatusCheckFallback();
55
262
  this.startHealthMonitoring();
56
263
  }
57
264
 
58
265
  /**
59
- * Connect to Socket.IO server
60
- * @param {string} port - Port number to connect to
266
+ * Connect to Socket.IO server on specified port.
267
+ *
268
+ * Initiates WebSocket connection to the Claude MPM Socket.IO server.
269
+ * Handles connection conflicts and ensures clean state transitions.
270
+ *
271
+ * Connection Process:
272
+ * 1. Validates port and constructs localhost URL
273
+ * 2. Checks for existing connections and cleans up if needed
274
+ * 3. Delegates to doConnect() for actual connection logic
275
+ *
276
+ * Thread Safety:
277
+ * - Uses setTimeout for async cleanup to prevent race conditions
278
+ * - Connection state flags prevent multiple simultaneous attempts
279
+ *
280
+ * @param {string} [port='8765'] - Port number to connect to (defaults to 8765)
281
+ *
282
+ * @throws {Error} If Socket.IO library is not loaded
283
+ *
284
+ * @example
285
+ * // Connect to default port
286
+ * socketClient.connect();
287
+ *
288
+ * // Connect to specific port
289
+ * socketClient.connect('8766');
61
290
  */
62
291
  connect(port = '8765') {
63
- // Store the port for later use
292
+ // Store the port for later use in reconnections
64
293
  this.port = port;
65
294
  const url = `http://localhost:${port}`;
66
295
 
67
- // Prevent multiple simultaneous connections
296
+ // WHY this check: Prevents connection conflicts that can cause memory leaks
68
297
  if (this.socket && (this.socket.connected || this.socket.connecting)) {
69
298
  console.log('Already connected or connecting, disconnecting first...');
70
299
  this.socket.disconnect();
71
- // Wait a moment for cleanup
300
+ // WHY 100ms delay: Allows cleanup to complete before new connection
72
301
  setTimeout(() => this.doConnect(url), 100);
73
302
  return;
74
303
  }
@@ -77,8 +306,28 @@ class SocketClient {
77
306
  }
78
307
 
79
308
  /**
80
- * Perform the actual connection
81
- * @param {string} url - Socket.IO server URL
309
+ * Execute the actual Socket.IO connection with full configuration.
310
+ *
311
+ * Creates and configures Socket.IO client with appropriate timeouts,
312
+ * retry logic, and transport settings. Sets up event handlers for
313
+ * connection lifecycle management.
314
+ *
315
+ * Configuration Details:
316
+ * - autoConnect: true - Immediate connection attempt
317
+ * - reconnection: true - Built-in reconnection enabled
318
+ * - pingInterval: 45000ms - Matches server configuration
319
+ * - pingTimeout: 20000ms - Health check timeout
320
+ * - transports: ['websocket', 'polling'] - Fallback options
321
+ *
322
+ * WHY these settings:
323
+ * - Ping intervals must match server to prevent timeouts
324
+ * - Limited reconnection attempts prevent infinite loops
325
+ * - forceNew prevents socket reuse issues
326
+ *
327
+ * @param {string} url - Complete Socket.IO server URL (http://localhost:port)
328
+ * @private
329
+ *
330
+ * @throws {Error} If Socket.IO library is not available
82
331
  */
83
332
  doConnect(url) {
84
333
  console.log(`Connecting to Socket.IO server at ${url}`);
@@ -132,6 +381,10 @@ class SocketClient {
132
381
 
133
382
  this.notifyConnectionStatus('Connected', 'connected');
134
383
 
384
+ // Expose socket globally for components that need direct access
385
+ window.socket = this.socket;
386
+ console.log('SocketClient: Exposed socket globally as window.socket');
387
+
135
388
  // Emit connect callback
136
389
  this.connectionCallbacks.connect.forEach(callback =>
137
390
  callback(this.socket.id)
@@ -222,6 +475,12 @@ class SocketClient {
222
475
  return;
223
476
  }
224
477
 
478
+ // Code analysis events are now allowed to flow through to the events list for troubleshooting
479
+ // They will appear in both the Events tab and the Code tab
480
+ if (validatedEvent.type && validatedEvent.type.startsWith('code:')) {
481
+ console.log('Code analysis event received via claude_event, adding to events list for troubleshooting:', validatedEvent.type);
482
+ }
483
+
225
484
  // Transform event to match expected format (for backward compatibility)
226
485
  const transformedEvent = this.transformEvent(validatedEvent);
227
486
  console.log('Transformed event:', transformedEvent);
@@ -291,6 +550,57 @@ class SocketClient {
291
550
  this.addEvent({ type: 'log', subtype: 'entry', timestamp: new Date().toISOString(), data });
292
551
  });
293
552
 
553
+ // Code analysis events - now allowed to flow through for troubleshooting
554
+ // These are ALSO handled by the code-tree component and shown in the footer
555
+ // They will appear in both places: Events tab (for troubleshooting) and Code tab (for visualization)
556
+ this.socket.on('code:analysis:queued', (data) => {
557
+ // Add to events list for troubleshooting
558
+ console.log('Code analysis queued event received, adding to events list for troubleshooting');
559
+ this.addEvent({ type: 'code', subtype: 'analysis:queued', timestamp: new Date().toISOString(), data });
560
+ });
561
+
562
+ this.socket.on('code:analysis:accepted', (data) => {
563
+ // Add to events list for troubleshooting
564
+ console.log('Code analysis accepted event received, adding to events list for troubleshooting');
565
+ this.addEvent({ type: 'code', subtype: 'analysis:accepted', timestamp: new Date().toISOString(), data });
566
+ });
567
+
568
+ this.socket.on('code:analysis:start', (data) => {
569
+ // Add to events list for troubleshooting
570
+ console.log('Code analysis start event received, adding to events list for troubleshooting');
571
+ this.addEvent({ type: 'code', subtype: 'analysis:start', timestamp: new Date().toISOString(), data });
572
+ });
573
+
574
+ this.socket.on('code:analysis:complete', (data) => {
575
+ // Add to events list for troubleshooting
576
+ console.log('Code analysis complete event received, adding to events list for troubleshooting');
577
+ this.addEvent({ type: 'code', subtype: 'analysis:complete', timestamp: new Date().toISOString(), data });
578
+ });
579
+
580
+ this.socket.on('code:analysis:error', (data) => {
581
+ // Add to events list for troubleshooting
582
+ console.log('Code analysis error event received, adding to events list for troubleshooting');
583
+ this.addEvent({ type: 'code', subtype: 'analysis:error', timestamp: new Date().toISOString(), data });
584
+ });
585
+
586
+ this.socket.on('code:file:start', (data) => {
587
+ // Add to events list for troubleshooting
588
+ console.log('Code file start event received, adding to events list for troubleshooting');
589
+ this.addEvent({ type: 'code', subtype: 'file:start', timestamp: new Date().toISOString(), data });
590
+ });
591
+
592
+ this.socket.on('code:node:found', (data) => {
593
+ // Add to events list for troubleshooting
594
+ console.log('Code node found event received, adding to events list for troubleshooting');
595
+ this.addEvent({ type: 'code', subtype: 'node:found', timestamp: new Date().toISOString(), data });
596
+ });
597
+
598
+ this.socket.on('code:analysis:progress', (data) => {
599
+ // Add to events list for troubleshooting
600
+ console.log('Code analysis progress event received, adding to events list for troubleshooting');
601
+ this.addEvent({ type: 'code', subtype: 'analysis:progress', timestamp: new Date().toISOString(), data });
602
+ });
603
+
294
604
  this.socket.on('history', (data) => {
295
605
  console.log('Received event history:', data);
296
606
  if (data && Array.isArray(data.events)) {
@@ -528,12 +838,44 @@ class SocketClient {
528
838
  id: sessionId,
529
839
  startTime: eventData.timestamp,
530
840
  lastActivity: eventData.timestamp,
531
- eventCount: 0
841
+ eventCount: 0,
842
+ working_directory: null,
843
+ git_branch: null
532
844
  });
533
845
  }
534
846
  const session = this.sessions.get(sessionId);
535
847
  session.lastActivity = eventData.timestamp;
536
848
  session.eventCount++;
849
+
850
+ // Extract working directory from event data if available (prioritize newer data)
851
+ // Check multiple possible locations for working directory
852
+ const possiblePaths = [
853
+ eventData.data.cwd,
854
+ eventData.data.working_directory,
855
+ eventData.data.working_dir,
856
+ eventData.data.workingDirectory,
857
+ eventData.data.instance_info?.working_dir,
858
+ eventData.data.instance_info?.working_directory,
859
+ eventData.data.instance_info?.cwd,
860
+ eventData.cwd,
861
+ eventData.working_directory,
862
+ eventData.working_dir
863
+ ];
864
+
865
+ for (const path of possiblePaths) {
866
+ if (path && typeof path === 'string' && path.trim()) {
867
+ session.working_directory = path;
868
+ console.log(`[SOCKET-CLIENT] Found working directory for session ${sessionId}:`, path);
869
+ break;
870
+ }
871
+ }
872
+
873
+ // Extract git branch if available
874
+ if (eventData.data.git_branch) {
875
+ session.git_branch = eventData.data.git_branch;
876
+ } else if (eventData.data.instance_info && eventData.data.instance_info.git_branch) {
877
+ session.git_branch = eventData.data.instance_info.git_branch;
878
+ }
537
879
  }
538
880
 
539
881
  if (notify) {
@@ -604,6 +946,32 @@ class SocketClient {
604
946
  this.connectionCallbacks.event.push(callback);
605
947
  }
606
948
 
949
+ /**
950
+ * Subscribe to socket events (proxy to underlying socket)
951
+ * @param {string} event - Event name
952
+ * @param {Function} callback - Callback function
953
+ */
954
+ on(event, callback) {
955
+ if (this.socket) {
956
+ return this.socket.on(event, callback);
957
+ } else {
958
+ console.warn(`Cannot subscribe to '${event}': socket not initialized`);
959
+ }
960
+ }
961
+
962
+ /**
963
+ * Unsubscribe from socket events (proxy to underlying socket)
964
+ * @param {string} event - Event name
965
+ * @param {Function} callback - Callback function (optional)
966
+ */
967
+ off(event, callback) {
968
+ if (this.socket) {
969
+ return this.socket.off(event, callback);
970
+ } else {
971
+ console.warn(`Cannot unsubscribe from '${event}': socket not initialized`);
972
+ }
973
+ }
974
+
607
975
  /**
608
976
  * Notify connection status change
609
977
  * @param {string} status - Status message
@@ -722,6 +1090,7 @@ class SocketClient {
722
1090
  // 1. Hook events: { type: 'hook.pre_tool', timestamp: '...', data: {...} }
723
1091
  // 2. Legacy events: { event: 'TestStart', timestamp: '...', ... }
724
1092
  // 3. Standard events: { type: 'session', subtype: 'started', ... }
1093
+ // 4. Normalized events: { type: 'code', subtype: 'progress', ... } - already normalized, keep as-is
725
1094
 
726
1095
  if (!eventData) {
727
1096
  return eventData; // Return as-is if null/undefined
@@ -729,8 +1098,26 @@ class SocketClient {
729
1098
 
730
1099
  let transformedEvent = { ...eventData };
731
1100
 
1101
+ // Check if event is already normalized (has both type and subtype as separate fields)
1102
+ // This prevents double-transformation of events that were normalized on the backend
1103
+ const isAlreadyNormalized = eventData.type && eventData.subtype &&
1104
+ !eventData.type.includes('.') &&
1105
+ !eventData.type.includes(':');
1106
+
1107
+ if (isAlreadyNormalized) {
1108
+ // Event is already properly normalized from backend, just preserve it
1109
+ // Store a composite originalEventName for display if needed
1110
+ if (!transformedEvent.originalEventName) {
1111
+ if (eventData.subtype === 'generic' || eventData.type === eventData.subtype) {
1112
+ transformedEvent.originalEventName = eventData.type;
1113
+ } else {
1114
+ transformedEvent.originalEventName = `${eventData.type}.${eventData.subtype}`;
1115
+ }
1116
+ }
1117
+ // Return early to avoid further transformation
1118
+ }
732
1119
  // Handle legacy format with 'event' field but no 'type'
733
- if (!eventData.type && eventData.event) {
1120
+ else if (!eventData.type && eventData.event) {
734
1121
  // Map common event names to proper type/subtype
735
1122
  const eventName = eventData.event;
736
1123
 
@@ -761,8 +1148,10 @@ class SocketClient {
761
1148
 
762
1149
  // Remove the 'event' field to avoid confusion
763
1150
  delete transformedEvent.event;
1151
+ // Store original event name for display purposes
1152
+ transformedEvent.originalEventName = eventName;
764
1153
  }
765
- // Handle standard format with 'type' field
1154
+ // Handle standard format with 'type' field that needs transformation
766
1155
  else if (eventData.type) {
767
1156
  const type = eventData.type;
768
1157
 
@@ -771,25 +1160,43 @@ class SocketClient {
771
1160
  const subtype = type.substring(5); // Remove 'hook.' prefix
772
1161
  transformedEvent.type = 'hook';
773
1162
  transformedEvent.subtype = subtype;
1163
+ transformedEvent.originalEventName = type;
1164
+ }
1165
+ // Transform 'code:*' events to proper code type
1166
+ // Handle multi-level subtypes like 'code:analysis:queued'
1167
+ else if (type.startsWith('code:')) {
1168
+ transformedEvent.type = 'code';
1169
+ // Replace colons with underscores in subtype for consistency
1170
+ const subtypePart = type.substring(5); // Remove 'code:' prefix
1171
+ transformedEvent.subtype = subtypePart.replace(/:/g, '_');
1172
+ transformedEvent.originalEventName = type;
774
1173
  }
775
1174
  // Transform other dotted types like 'session.started' -> type: 'session', subtype: 'started'
776
1175
  else if (type.includes('.')) {
777
1176
  const [mainType, ...subtypeParts] = type.split('.');
778
1177
  transformedEvent.type = mainType;
779
1178
  transformedEvent.subtype = subtypeParts.join('.');
1179
+ transformedEvent.originalEventName = type;
1180
+ }
1181
+ // Transform any remaining colon-separated types generically
1182
+ else if (type.includes(':')) {
1183
+ const parts = type.split(':', 2); // Split into max 2 parts
1184
+ transformedEvent.type = parts[0];
1185
+ // Replace any remaining colons with underscores in subtype
1186
+ transformedEvent.subtype = parts.length > 1 ? parts[1].replace(/:/g, '_') : 'generic';
1187
+ transformedEvent.originalEventName = type;
1188
+ }
1189
+ // If type doesn't need transformation but has no subtype, set a default
1190
+ else if (!eventData.subtype) {
1191
+ transformedEvent.subtype = 'generic';
1192
+ transformedEvent.originalEventName = type;
780
1193
  }
781
1194
  }
782
1195
  // If no type and no event field, mark as unknown
783
1196
  else {
784
1197
  transformedEvent.type = 'unknown';
785
1198
  transformedEvent.subtype = '';
786
- }
787
-
788
- // Store original event name for display purposes (before any transformation)
789
- if (!eventData.type && eventData.event) {
790
- transformedEvent.originalEventName = eventData.event;
791
- } else if (eventData.type) {
792
- transformedEvent.originalEventName = eventData.type;
1199
+ transformedEvent.originalEventName = 'unknown';
793
1200
  }
794
1201
 
795
1202
  // Extract and flatten data fields to top level for dashboard compatibility
@@ -820,11 +1227,40 @@ class SocketClient {
820
1227
  transformedEvent.data = eventData.data;
821
1228
  }
822
1229
 
1230
+ // Add hook_event_name for ActivityTree compatibility
1231
+ // Map the type/subtype structure to the expected hook_event_name format
1232
+ if (transformedEvent.type === 'hook') {
1233
+ if (transformedEvent.subtype === 'pre_tool') {
1234
+ transformedEvent.hook_event_name = 'PreToolUse';
1235
+ } else if (transformedEvent.subtype === 'post_tool') {
1236
+ transformedEvent.hook_event_name = 'PostToolUse';
1237
+ } else if (transformedEvent.subtype === 'subagent_start') {
1238
+ transformedEvent.hook_event_name = 'SubagentStart';
1239
+ } else if (transformedEvent.subtype === 'subagent_stop') {
1240
+ transformedEvent.hook_event_name = 'SubagentStop';
1241
+ } else if (transformedEvent.subtype === 'todo_write') {
1242
+ transformedEvent.hook_event_name = 'TodoWrite';
1243
+ } else if (transformedEvent.subtype === 'start') {
1244
+ transformedEvent.hook_event_name = 'Start';
1245
+ } else if (transformedEvent.subtype === 'stop') {
1246
+ transformedEvent.hook_event_name = 'Stop';
1247
+ }
1248
+ } else if (transformedEvent.type === 'subagent') {
1249
+ if (transformedEvent.subtype === 'start') {
1250
+ transformedEvent.hook_event_name = 'SubagentStart';
1251
+ } else if (transformedEvent.subtype === 'stop') {
1252
+ transformedEvent.hook_event_name = 'SubagentStop';
1253
+ }
1254
+ } else if (transformedEvent.type === 'todo' && transformedEvent.subtype === 'updated') {
1255
+ transformedEvent.hook_event_name = 'TodoWrite';
1256
+ }
1257
+
823
1258
  // Debug logging for tool events
824
1259
  if (transformedEvent.type === 'hook' && (transformedEvent.subtype === 'pre_tool' || transformedEvent.subtype === 'post_tool')) {
825
1260
  console.log('Transformed tool event:', {
826
1261
  type: transformedEvent.type,
827
1262
  subtype: transformedEvent.subtype,
1263
+ hook_event_name: transformedEvent.hook_event_name,
828
1264
  tool_name: transformedEvent.tool_name,
829
1265
  has_tool_parameters: !!transformedEvent.tool_parameters,
830
1266
  tool_parameters: transformedEvent.tool_parameters,