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.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/INSTRUCTIONS.md +26 -1
- claude_mpm/agents/agents_metadata.py +57 -0
- claude_mpm/agents/templates/.claude-mpm/memories/README.md +17 -0
- claude_mpm/agents/templates/.claude-mpm/memories/engineer_memories.md +3 -0
- claude_mpm/agents/templates/agent-manager.json +263 -17
- claude_mpm/agents/templates/agentic_coder_optimizer.json +222 -0
- claude_mpm/agents/templates/code_analyzer.json +18 -8
- claude_mpm/agents/templates/engineer.json +1 -1
- claude_mpm/agents/templates/logs/prompts/agent_engineer_20250826_014258_728.md +39 -0
- claude_mpm/agents/templates/qa.json +1 -1
- claude_mpm/agents/templates/research.json +1 -1
- claude_mpm/cli/__init__.py +15 -0
- claude_mpm/cli/commands/__init__.py +6 -0
- claude_mpm/cli/commands/analyze.py +548 -0
- claude_mpm/cli/commands/analyze_code.py +524 -0
- claude_mpm/cli/commands/configure.py +78 -28
- claude_mpm/cli/commands/configure_tui.py +62 -60
- claude_mpm/cli/commands/dashboard.py +288 -0
- claude_mpm/cli/commands/debug.py +1386 -0
- claude_mpm/cli/commands/mpm_init.py +427 -0
- claude_mpm/cli/commands/mpm_init_handler.py +83 -0
- claude_mpm/cli/parsers/analyze_code_parser.py +170 -0
- claude_mpm/cli/parsers/analyze_parser.py +135 -0
- claude_mpm/cli/parsers/base_parser.py +44 -0
- claude_mpm/cli/parsers/dashboard_parser.py +113 -0
- claude_mpm/cli/parsers/debug_parser.py +319 -0
- claude_mpm/cli/parsers/mpm_init_parser.py +122 -0
- claude_mpm/constants.py +13 -1
- claude_mpm/core/framework_loader.py +148 -6
- claude_mpm/core/log_manager.py +16 -13
- claude_mpm/core/logger.py +1 -1
- claude_mpm/core/unified_agent_registry.py +1 -1
- claude_mpm/dashboard/.claude-mpm/socketio-instances.json +1 -0
- claude_mpm/dashboard/analysis_runner.py +455 -0
- claude_mpm/dashboard/static/built/components/activity-tree.js +2 -0
- claude_mpm/dashboard/static/built/components/agent-inference.js +1 -1
- claude_mpm/dashboard/static/built/components/code-tree.js +2 -0
- claude_mpm/dashboard/static/built/components/code-viewer.js +2 -0
- claude_mpm/dashboard/static/built/components/event-viewer.js +1 -1
- 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/session-manager.js +1 -1
- claude_mpm/dashboard/static/built/components/working-directory.js +1 -1
- claude_mpm/dashboard/static/built/dashboard.js +1 -1
- claude_mpm/dashboard/static/built/socket-client.js +1 -1
- claude_mpm/dashboard/static/css/activity.css +549 -0
- claude_mpm/dashboard/static/css/code-tree.css +1175 -0
- claude_mpm/dashboard/static/css/dashboard.css +245 -0
- claude_mpm/dashboard/static/dist/components/activity-tree.js +2 -0
- claude_mpm/dashboard/static/dist/components/code-tree.js +2 -0
- claude_mpm/dashboard/static/dist/components/code-viewer.js +2 -0
- claude_mpm/dashboard/static/dist/components/event-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/socket-client.js +1 -1
- claude_mpm/dashboard/static/js/components/activity-tree.js +1338 -0
- claude_mpm/dashboard/static/js/components/code-tree.js +2535 -0
- claude_mpm/dashboard/static/js/components/code-viewer.js +480 -0
- claude_mpm/dashboard/static/js/components/event-viewer.js +59 -9
- claude_mpm/dashboard/static/js/components/session-manager.js +40 -4
- claude_mpm/dashboard/static/js/components/socket-manager.js +12 -0
- claude_mpm/dashboard/static/js/components/ui-state-manager.js +4 -0
- claude_mpm/dashboard/static/js/components/working-directory.js +17 -1
- claude_mpm/dashboard/static/js/dashboard.js +51 -0
- claude_mpm/dashboard/static/js/socket-client.js +465 -29
- claude_mpm/dashboard/templates/index.html +182 -4
- claude_mpm/hooks/claude_hooks/hook_handler.py +182 -5
- claude_mpm/hooks/claude_hooks/installer.py +386 -113
- claude_mpm/scripts/claude-hook-handler.sh +161 -0
- claude_mpm/scripts/socketio_daemon.py +121 -8
- claude_mpm/services/agents/deployment/agent_lifecycle_manager_refactored.py +2 -2
- claude_mpm/services/agents/deployment/agent_record_service.py +1 -2
- claude_mpm/services/agents/memory/memory_format_service.py +1 -3
- claude_mpm/services/cli/agent_cleanup_service.py +1 -5
- claude_mpm/services/cli/agent_dependency_service.py +1 -1
- claude_mpm/services/cli/agent_validation_service.py +3 -4
- claude_mpm/services/cli/dashboard_launcher.py +2 -3
- claude_mpm/services/cli/startup_checker.py +0 -11
- claude_mpm/services/core/cache_manager.py +1 -3
- claude_mpm/services/core/path_resolver.py +1 -4
- claude_mpm/services/core/service_container.py +2 -2
- claude_mpm/services/diagnostics/checks/instructions_check.py +1 -2
- claude_mpm/services/infrastructure/monitoring/__init__.py +11 -11
- claude_mpm/services/infrastructure/monitoring.py +11 -11
- claude_mpm/services/project/architecture_analyzer.py +1 -1
- claude_mpm/services/project/dependency_analyzer.py +4 -4
- claude_mpm/services/project/language_analyzer.py +3 -3
- claude_mpm/services/project/metrics_collector.py +3 -6
- claude_mpm/services/socketio/event_normalizer.py +64 -0
- claude_mpm/services/socketio/handlers/__init__.py +2 -0
- claude_mpm/services/socketio/handlers/code_analysis.py +672 -0
- claude_mpm/services/socketio/handlers/registry.py +2 -0
- claude_mpm/services/socketio/server/connection_manager.py +6 -4
- claude_mpm/services/socketio/server/core.py +100 -11
- claude_mpm/services/socketio/server/main.py +8 -2
- claude_mpm/services/visualization/__init__.py +19 -0
- claude_mpm/services/visualization/mermaid_generator.py +938 -0
- claude_mpm/tools/__main__.py +208 -0
- claude_mpm/tools/code_tree_analyzer.py +1596 -0
- claude_mpm/tools/code_tree_builder.py +631 -0
- claude_mpm/tools/code_tree_events.py +416 -0
- claude_mpm/tools/socketio_debug.py +671 -0
- {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.11.dist-info}/METADATA +2 -1
- {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.11.dist-info}/RECORD +110 -74
- claude_mpm/agents/schema/agent_schema.json +0 -314
- {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.11.dist-info}/WHEEL +0 -0
- {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.11.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.11.dist-info}/licenses/LICENSE +0 -0
- {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
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
*
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
*
|
|
81
|
-
*
|
|
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,
|