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.
- claude_mpm/cli/commands/run.py +10 -10
- claude_mpm/dashboard/index.html +13 -0
- claude_mpm/dashboard/static/css/dashboard.css +2722 -0
- claude_mpm/dashboard/static/js/components/agent-inference.js +619 -0
- claude_mpm/dashboard/static/js/components/event-processor.js +641 -0
- claude_mpm/dashboard/static/js/components/event-viewer.js +914 -0
- claude_mpm/dashboard/static/js/components/export-manager.js +362 -0
- claude_mpm/dashboard/static/js/components/file-tool-tracker.js +611 -0
- claude_mpm/dashboard/static/js/components/hud-library-loader.js +211 -0
- claude_mpm/dashboard/static/js/components/hud-manager.js +671 -0
- claude_mpm/dashboard/static/js/components/hud-visualizer.js +1718 -0
- claude_mpm/dashboard/static/js/components/module-viewer.js +2701 -0
- claude_mpm/dashboard/static/js/components/session-manager.js +520 -0
- claude_mpm/dashboard/static/js/components/socket-manager.js +343 -0
- claude_mpm/dashboard/static/js/components/ui-state-manager.js +427 -0
- claude_mpm/dashboard/static/js/components/working-directory.js +866 -0
- claude_mpm/dashboard/static/js/dashboard-original.js +4134 -0
- claude_mpm/dashboard/static/js/dashboard.js +1978 -0
- claude_mpm/dashboard/static/js/socket-client.js +537 -0
- claude_mpm/dashboard/templates/index.html +346 -0
- claude_mpm/dashboard/test_dashboard.html +372 -0
- claude_mpm/scripts/socketio_daemon.py +51 -6
- claude_mpm/services/socketio_server.py +41 -5
- {claude_mpm-3.4.10.dist-info → claude_mpm-3.4.14.dist-info}/METADATA +2 -1
- {claude_mpm-3.4.10.dist-info → claude_mpm-3.4.14.dist-info}/RECORD +29 -9
- {claude_mpm-3.4.10.dist-info → claude_mpm-3.4.14.dist-info}/WHEEL +0 -0
- {claude_mpm-3.4.10.dist-info → claude_mpm-3.4.14.dist-info}/entry_points.txt +0 -0
- {claude_mpm-3.4.10.dist-info → claude_mpm-3.4.14.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-3.4.10.dist-info → claude_mpm-3.4.14.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,611 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File and Tool Tracker Module
|
|
3
|
+
*
|
|
4
|
+
* Tracks file operations and tool calls by pairing pre/post events and maintaining
|
|
5
|
+
* organized collections for the files and tools tabs. Provides analysis of
|
|
6
|
+
* tool execution patterns and file operation history.
|
|
7
|
+
*
|
|
8
|
+
* WHY: Extracted from main dashboard to isolate complex event pairing logic
|
|
9
|
+
* that groups related events into meaningful operations. This provides better
|
|
10
|
+
* maintainability for the intricate logic of matching tool events with their results.
|
|
11
|
+
*
|
|
12
|
+
* DESIGN DECISION: Uses intelligent correlation strategy for tool calls that:
|
|
13
|
+
* - Separates pre_tool and post_tool events first
|
|
14
|
+
* - Correlates based on temporal proximity, parameter similarity, and context
|
|
15
|
+
* - Handles timing differences between pre/post events (tools can run for minutes)
|
|
16
|
+
* - Prevents duplicate tool entries by ensuring each tool call appears once
|
|
17
|
+
* - Supports both paired and orphaned events for comprehensive tracking
|
|
18
|
+
*/
|
|
19
|
+
class FileToolTracker {
|
|
20
|
+
constructor(agentInference, workingDirectoryManager) {
|
|
21
|
+
this.agentInference = agentInference;
|
|
22
|
+
this.workingDirectoryManager = workingDirectoryManager;
|
|
23
|
+
|
|
24
|
+
// File tracking for files tab
|
|
25
|
+
this.fileOperations = new Map(); // Map of file paths to operations
|
|
26
|
+
|
|
27
|
+
// Tool call tracking for tools tab
|
|
28
|
+
this.toolCalls = new Map(); // Map of tool call keys to paired pre/post events
|
|
29
|
+
|
|
30
|
+
console.log('File-tool tracker initialized');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Update file operations from events
|
|
35
|
+
* @param {Array} events - Events to process
|
|
36
|
+
*/
|
|
37
|
+
updateFileOperations(events) {
|
|
38
|
+
// Clear existing data
|
|
39
|
+
this.fileOperations.clear();
|
|
40
|
+
|
|
41
|
+
console.log('updateFileOperations - processing', events.length, 'events');
|
|
42
|
+
|
|
43
|
+
// Group events by session and timestamp to match pre/post pairs
|
|
44
|
+
const eventPairs = new Map(); // Key: session_id + timestamp + tool_name
|
|
45
|
+
let fileOperationCount = 0;
|
|
46
|
+
|
|
47
|
+
// First pass: collect all tool events and group them
|
|
48
|
+
events.forEach((event, index) => {
|
|
49
|
+
const isFileOp = this.isFileOperation(event);
|
|
50
|
+
if (isFileOp) fileOperationCount++;
|
|
51
|
+
|
|
52
|
+
if (index < 5) { // Debug first 5 events with more detail
|
|
53
|
+
console.log(`Event ${index}:`, {
|
|
54
|
+
type: event.type,
|
|
55
|
+
subtype: event.subtype,
|
|
56
|
+
tool_name: event.tool_name,
|
|
57
|
+
tool_parameters: event.tool_parameters,
|
|
58
|
+
isFileOp: isFileOp
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (isFileOp) {
|
|
63
|
+
const toolName = event.tool_name;
|
|
64
|
+
const sessionId = event.session_id || 'unknown';
|
|
65
|
+
const eventKey = `${sessionId}_${toolName}_${Math.floor(new Date(event.timestamp).getTime() / 1000)}`; // Group by second
|
|
66
|
+
|
|
67
|
+
if (!eventPairs.has(eventKey)) {
|
|
68
|
+
eventPairs.set(eventKey, {
|
|
69
|
+
pre_event: null,
|
|
70
|
+
post_event: null,
|
|
71
|
+
tool_name: toolName,
|
|
72
|
+
session_id: sessionId
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const pair = eventPairs.get(eventKey);
|
|
77
|
+
if (event.subtype === 'pre_tool' || event.type === 'hook' && !event.subtype.includes('post')) {
|
|
78
|
+
pair.pre_event = event;
|
|
79
|
+
} else if (event.subtype === 'post_tool' || event.subtype.includes('post')) {
|
|
80
|
+
pair.post_event = event;
|
|
81
|
+
} else {
|
|
82
|
+
// For events without clear pre/post distinction, treat as both
|
|
83
|
+
pair.pre_event = event;
|
|
84
|
+
pair.post_event = event;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
console.log('updateFileOperations - found', fileOperationCount, 'file operations in', eventPairs.size, 'event pairs');
|
|
90
|
+
|
|
91
|
+
// Second pass: extract file paths and operations from paired events
|
|
92
|
+
eventPairs.forEach((pair, key) => {
|
|
93
|
+
const filePath = this.extractFilePathFromPair(pair);
|
|
94
|
+
|
|
95
|
+
if (filePath) {
|
|
96
|
+
console.log('File operation detected for:', filePath, 'from pair:', key);
|
|
97
|
+
|
|
98
|
+
if (!this.fileOperations.has(filePath)) {
|
|
99
|
+
this.fileOperations.set(filePath, {
|
|
100
|
+
path: filePath,
|
|
101
|
+
operations: [],
|
|
102
|
+
lastOperation: null
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const fileData = this.fileOperations.get(filePath);
|
|
107
|
+
const operation = this.getFileOperationFromPair(pair);
|
|
108
|
+
const timestamp = pair.post_event?.timestamp || pair.pre_event?.timestamp;
|
|
109
|
+
|
|
110
|
+
const agentInfo = this.extractAgentFromPair(pair);
|
|
111
|
+
const workingDirectory = this.workingDirectoryManager.extractWorkingDirectoryFromPair(pair);
|
|
112
|
+
|
|
113
|
+
fileData.operations.push({
|
|
114
|
+
operation: operation,
|
|
115
|
+
timestamp: timestamp,
|
|
116
|
+
agent: agentInfo.name,
|
|
117
|
+
confidence: agentInfo.confidence,
|
|
118
|
+
sessionId: pair.session_id,
|
|
119
|
+
details: this.getFileOperationDetailsFromPair(pair),
|
|
120
|
+
workingDirectory: workingDirectory
|
|
121
|
+
});
|
|
122
|
+
fileData.lastOperation = timestamp;
|
|
123
|
+
} else {
|
|
124
|
+
console.log('No file path found for pair:', key, pair);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
console.log('updateFileOperations - final result:', this.fileOperations.size, 'file operations');
|
|
129
|
+
if (this.fileOperations.size > 0) {
|
|
130
|
+
console.log('File operations map:', Array.from(this.fileOperations.entries()));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Update tool calls from events - pairs pre/post tool events into complete tool calls
|
|
136
|
+
* @param {Array} events - Events to process
|
|
137
|
+
*/
|
|
138
|
+
updateToolCalls(events) {
|
|
139
|
+
// Clear existing data
|
|
140
|
+
this.toolCalls.clear();
|
|
141
|
+
|
|
142
|
+
console.log('updateToolCalls - processing', events.length, 'events');
|
|
143
|
+
|
|
144
|
+
// Improved correlation strategy: collect events first, then correlate intelligently
|
|
145
|
+
const preToolEvents = [];
|
|
146
|
+
const postToolEvents = [];
|
|
147
|
+
let toolOperationCount = 0;
|
|
148
|
+
|
|
149
|
+
// First pass: separate pre_tool and post_tool events
|
|
150
|
+
events.forEach((event, index) => {
|
|
151
|
+
const isToolOp = this.isToolOperation(event);
|
|
152
|
+
if (isToolOp) toolOperationCount++;
|
|
153
|
+
|
|
154
|
+
if (index < 5) { // Debug first 5 events with more detail
|
|
155
|
+
console.log(`Tool Event ${index}:`, {
|
|
156
|
+
type: event.type,
|
|
157
|
+
subtype: event.subtype,
|
|
158
|
+
tool_name: event.tool_name,
|
|
159
|
+
tool_parameters: event.tool_parameters,
|
|
160
|
+
isToolOp: isToolOp
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (isToolOp) {
|
|
165
|
+
if (event.subtype === 'pre_tool' || (event.type === 'hook' && !event.subtype.includes('post'))) {
|
|
166
|
+
preToolEvents.push(event);
|
|
167
|
+
} else if (event.subtype === 'post_tool' || event.subtype.includes('post')) {
|
|
168
|
+
postToolEvents.push(event);
|
|
169
|
+
} else {
|
|
170
|
+
// For events without clear pre/post distinction, treat as standalone
|
|
171
|
+
preToolEvents.push(event);
|
|
172
|
+
postToolEvents.push(event);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
console.log('updateToolCalls - found', toolOperationCount, 'tool operations:', preToolEvents.length, 'pre_tool,', postToolEvents.length, 'post_tool');
|
|
178
|
+
|
|
179
|
+
// Second pass: correlate pre_tool events with post_tool events
|
|
180
|
+
const toolCallPairs = new Map();
|
|
181
|
+
const usedPostEvents = new Set();
|
|
182
|
+
|
|
183
|
+
preToolEvents.forEach((preEvent, preIndex) => {
|
|
184
|
+
const toolName = preEvent.tool_name;
|
|
185
|
+
const sessionId = preEvent.session_id || 'unknown';
|
|
186
|
+
const preTimestamp = new Date(preEvent.timestamp).getTime();
|
|
187
|
+
|
|
188
|
+
// Create a base pair for this pre_tool event
|
|
189
|
+
const pairKey = `${sessionId}_${toolName}_${preIndex}_${preTimestamp}`;
|
|
190
|
+
const pair = {
|
|
191
|
+
pre_event: preEvent,
|
|
192
|
+
post_event: null,
|
|
193
|
+
tool_name: toolName,
|
|
194
|
+
session_id: sessionId,
|
|
195
|
+
operation_type: preEvent.operation_type || 'tool_execution',
|
|
196
|
+
timestamp: preEvent.timestamp,
|
|
197
|
+
duration_ms: null,
|
|
198
|
+
success: null,
|
|
199
|
+
exit_code: null,
|
|
200
|
+
result_summary: null,
|
|
201
|
+
agent_type: null,
|
|
202
|
+
agent_confidence: null
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
// Get agent info from pre_event
|
|
206
|
+
const agentInfo = this.extractAgentFromEvent(preEvent);
|
|
207
|
+
pair.agent_type = agentInfo.name;
|
|
208
|
+
pair.agent_confidence = agentInfo.confidence;
|
|
209
|
+
|
|
210
|
+
// Try to find matching post_tool event
|
|
211
|
+
let bestMatchIndex = -1;
|
|
212
|
+
let bestMatchScore = -1;
|
|
213
|
+
const maxTimeDiffMs = 300000; // 5 minutes max time difference
|
|
214
|
+
|
|
215
|
+
postToolEvents.forEach((postEvent, postIndex) => {
|
|
216
|
+
// Skip already used post events
|
|
217
|
+
if (usedPostEvents.has(postIndex)) return;
|
|
218
|
+
|
|
219
|
+
// Must match tool name and session
|
|
220
|
+
if (postEvent.tool_name !== toolName || postEvent.session_id !== sessionId) return;
|
|
221
|
+
|
|
222
|
+
const postTimestamp = new Date(postEvent.timestamp).getTime();
|
|
223
|
+
const timeDiff = Math.abs(postTimestamp - preTimestamp);
|
|
224
|
+
|
|
225
|
+
// Post event should generally come after pre event (or very close)
|
|
226
|
+
const isTemporallyValid = postTimestamp >= preTimestamp - 1000; // Allow 1s clock skew
|
|
227
|
+
|
|
228
|
+
// Calculate correlation score (higher is better)
|
|
229
|
+
let score = 0;
|
|
230
|
+
if (isTemporallyValid && timeDiff <= maxTimeDiffMs) {
|
|
231
|
+
score = 1000 - (timeDiff / 1000); // Prefer closer timestamps
|
|
232
|
+
|
|
233
|
+
// Boost score for parameter similarity (if available)
|
|
234
|
+
if (this.compareToolParameters(preEvent, postEvent)) {
|
|
235
|
+
score += 500;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Boost score for same working directory
|
|
239
|
+
if (preEvent.working_directory && postEvent.working_directory &&
|
|
240
|
+
preEvent.working_directory === postEvent.working_directory) {
|
|
241
|
+
score += 100;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (score > bestMatchScore) {
|
|
246
|
+
bestMatchScore = score;
|
|
247
|
+
bestMatchIndex = postIndex;
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// If we found a good match, pair them
|
|
252
|
+
if (bestMatchIndex >= 0 && bestMatchScore > 0) {
|
|
253
|
+
const postEvent = postToolEvents[bestMatchIndex];
|
|
254
|
+
pair.post_event = postEvent;
|
|
255
|
+
pair.duration_ms = postEvent.duration_ms;
|
|
256
|
+
pair.success = postEvent.success;
|
|
257
|
+
pair.exit_code = postEvent.exit_code;
|
|
258
|
+
pair.result_summary = postEvent.result_summary;
|
|
259
|
+
|
|
260
|
+
usedPostEvents.add(bestMatchIndex);
|
|
261
|
+
console.log(`Paired pre_tool ${toolName} at ${preEvent.timestamp} with post_tool at ${postEvent.timestamp} (score: ${bestMatchScore})`);
|
|
262
|
+
} else {
|
|
263
|
+
console.log(`No matching post_tool found for ${toolName} at ${preEvent.timestamp} (still running or orphaned)`);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
toolCallPairs.set(pairKey, pair);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
// Third pass: handle any orphaned post_tool events (shouldn't happen but be safe)
|
|
270
|
+
postToolEvents.forEach((postEvent, postIndex) => {
|
|
271
|
+
if (usedPostEvents.has(postIndex)) return;
|
|
272
|
+
|
|
273
|
+
console.log('Orphaned post_tool event found:', postEvent.tool_name, 'at', postEvent.timestamp);
|
|
274
|
+
|
|
275
|
+
const toolName = postEvent.tool_name;
|
|
276
|
+
const sessionId = postEvent.session_id || 'unknown';
|
|
277
|
+
const postTimestamp = new Date(postEvent.timestamp).getTime();
|
|
278
|
+
|
|
279
|
+
const pairKey = `orphaned_${sessionId}_${toolName}_${postIndex}_${postTimestamp}`;
|
|
280
|
+
const pair = {
|
|
281
|
+
pre_event: null,
|
|
282
|
+
post_event: postEvent,
|
|
283
|
+
tool_name: toolName,
|
|
284
|
+
session_id: sessionId,
|
|
285
|
+
operation_type: 'tool_execution',
|
|
286
|
+
timestamp: postEvent.timestamp,
|
|
287
|
+
duration_ms: postEvent.duration_ms,
|
|
288
|
+
success: postEvent.success,
|
|
289
|
+
exit_code: postEvent.exit_code,
|
|
290
|
+
result_summary: postEvent.result_summary,
|
|
291
|
+
agent_type: null,
|
|
292
|
+
agent_confidence: null
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
const agentInfo = this.extractAgentFromEvent(postEvent);
|
|
296
|
+
pair.agent_type = agentInfo.name;
|
|
297
|
+
pair.agent_confidence = agentInfo.confidence;
|
|
298
|
+
|
|
299
|
+
toolCallPairs.set(pairKey, pair);
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
// Store the correlated tool calls
|
|
303
|
+
this.toolCalls = toolCallPairs;
|
|
304
|
+
|
|
305
|
+
console.log('updateToolCalls - final result:', this.toolCalls.size, 'tool calls');
|
|
306
|
+
if (this.toolCalls.size > 0) {
|
|
307
|
+
console.log('Tool calls map keys:', Array.from(this.toolCalls.keys()));
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Check if event is a tool operation
|
|
313
|
+
* @param {Object} event - Event to check
|
|
314
|
+
* @returns {boolean} - True if tool operation
|
|
315
|
+
*/
|
|
316
|
+
isToolOperation(event) {
|
|
317
|
+
// Tool operations have tool_name and are hook events with pre_tool or post_tool subtype
|
|
318
|
+
return event.tool_name &&
|
|
319
|
+
event.type === 'hook' &&
|
|
320
|
+
(event.subtype === 'pre_tool' || event.subtype === 'post_tool' ||
|
|
321
|
+
event.subtype.includes('tool'));
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Check if event is a file operation
|
|
326
|
+
* @param {Object} event - Event to check
|
|
327
|
+
* @returns {boolean} - True if file operation
|
|
328
|
+
*/
|
|
329
|
+
isFileOperation(event) {
|
|
330
|
+
// File operations are tool events with tools that operate on files
|
|
331
|
+
const fileTools = ['Read', 'Write', 'Edit', 'Grep', 'MultiEdit'];
|
|
332
|
+
return event.tool_name && fileTools.includes(event.tool_name);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Extract file path from event
|
|
337
|
+
* @param {Object} event - Event to extract from
|
|
338
|
+
* @returns {string|null} - File path or null
|
|
339
|
+
*/
|
|
340
|
+
extractFilePath(event) {
|
|
341
|
+
// Try various locations where file path might be stored
|
|
342
|
+
if (event.tool_parameters?.file_path) return event.tool_parameters.file_path;
|
|
343
|
+
if (event.tool_parameters?.path) return event.tool_parameters.path;
|
|
344
|
+
if (event.data?.tool_parameters?.file_path) return event.data.tool_parameters.file_path;
|
|
345
|
+
if (event.data?.tool_parameters?.path) return event.data.tool_parameters.path;
|
|
346
|
+
if (event.file_path) return event.file_path;
|
|
347
|
+
if (event.path) return event.path;
|
|
348
|
+
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Extract file path from event pair
|
|
354
|
+
* @param {Object} pair - Event pair object
|
|
355
|
+
* @returns {string|null} - File path or null
|
|
356
|
+
*/
|
|
357
|
+
extractFilePathFromPair(pair) {
|
|
358
|
+
// Try pre_event first, then post_event
|
|
359
|
+
let filePath = null;
|
|
360
|
+
|
|
361
|
+
if (pair.pre_event) {
|
|
362
|
+
filePath = this.extractFilePath(pair.pre_event);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (!filePath && pair.post_event) {
|
|
366
|
+
filePath = this.extractFilePath(pair.post_event);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
return filePath;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Get file operation type from event
|
|
374
|
+
* @param {Object} event - Event to analyze
|
|
375
|
+
* @returns {string} - Operation type
|
|
376
|
+
*/
|
|
377
|
+
getFileOperation(event) {
|
|
378
|
+
if (!event.tool_name) return 'unknown';
|
|
379
|
+
|
|
380
|
+
const toolName = event.tool_name.toLowerCase();
|
|
381
|
+
switch (toolName) {
|
|
382
|
+
case 'read': return 'read';
|
|
383
|
+
case 'write': return 'write';
|
|
384
|
+
case 'edit': return 'edit';
|
|
385
|
+
case 'multiedit': return 'edit';
|
|
386
|
+
case 'grep': return 'search';
|
|
387
|
+
default: return toolName;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Get file operation from event pair
|
|
393
|
+
* @param {Object} pair - Event pair object
|
|
394
|
+
* @returns {string} - Operation type
|
|
395
|
+
*/
|
|
396
|
+
getFileOperationFromPair(pair) {
|
|
397
|
+
// Try pre_event first, then post_event
|
|
398
|
+
if (pair.pre_event) {
|
|
399
|
+
return this.getFileOperation(pair.pre_event);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (pair.post_event) {
|
|
403
|
+
return this.getFileOperation(pair.post_event);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return 'unknown';
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Extract agent information from event pair
|
|
411
|
+
* @param {Object} pair - Event pair object
|
|
412
|
+
* @returns {Object} - Agent info with name and confidence
|
|
413
|
+
*/
|
|
414
|
+
extractAgentFromPair(pair) {
|
|
415
|
+
// Try to get agent info from inference system first
|
|
416
|
+
const event = pair.pre_event || pair.post_event;
|
|
417
|
+
if (event && this.agentInference) {
|
|
418
|
+
const inference = this.agentInference.getInferredAgentForEvent(event);
|
|
419
|
+
if (inference) {
|
|
420
|
+
return {
|
|
421
|
+
name: inference.agentName || 'Unknown',
|
|
422
|
+
confidence: inference.confidence || 'unknown'
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Fallback to direct event properties
|
|
428
|
+
const agentName = event?.agent_type || event?.subagent_type ||
|
|
429
|
+
pair.pre_event?.agent_type || pair.post_event?.agent_type || 'PM';
|
|
430
|
+
|
|
431
|
+
return {
|
|
432
|
+
name: agentName,
|
|
433
|
+
confidence: 'direct'
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Get detailed operation information from event pair
|
|
439
|
+
* @param {Object} pair - Event pair object
|
|
440
|
+
* @returns {Object} - Operation details
|
|
441
|
+
*/
|
|
442
|
+
getFileOperationDetailsFromPair(pair) {
|
|
443
|
+
const details = {};
|
|
444
|
+
|
|
445
|
+
// Extract details from pre_event (parameters)
|
|
446
|
+
if (pair.pre_event) {
|
|
447
|
+
const params = pair.pre_event.tool_parameters || pair.pre_event.data?.tool_parameters || {};
|
|
448
|
+
details.parameters = params;
|
|
449
|
+
details.tool_input = pair.pre_event.tool_input;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Extract details from post_event (results)
|
|
453
|
+
if (pair.post_event) {
|
|
454
|
+
details.result = pair.post_event.result;
|
|
455
|
+
details.success = pair.post_event.success;
|
|
456
|
+
details.error = pair.post_event.error;
|
|
457
|
+
details.exit_code = pair.post_event.exit_code;
|
|
458
|
+
details.duration_ms = pair.post_event.duration_ms;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
return details;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Get file operations map
|
|
466
|
+
* @returns {Map} - File operations map
|
|
467
|
+
*/
|
|
468
|
+
getFileOperations() {
|
|
469
|
+
return this.fileOperations;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Get tool calls map
|
|
474
|
+
* @returns {Map} - Tool calls map
|
|
475
|
+
*/
|
|
476
|
+
getToolCalls() {
|
|
477
|
+
return this.toolCalls;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Get tool calls as array for unique instance view
|
|
482
|
+
* Each entry represents a unique tool call instance
|
|
483
|
+
* @returns {Array} - Array of [key, toolCall] pairs
|
|
484
|
+
*/
|
|
485
|
+
getToolCallsArray() {
|
|
486
|
+
return Array.from(this.toolCalls.entries());
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Get file operations for a specific file
|
|
491
|
+
* @param {string} filePath - File path
|
|
492
|
+
* @returns {Object|null} - File operations data or null
|
|
493
|
+
*/
|
|
494
|
+
getFileOperationsForFile(filePath) {
|
|
495
|
+
return this.fileOperations.get(filePath) || null;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* Get tool call by key
|
|
500
|
+
* @param {string} key - Tool call key
|
|
501
|
+
* @returns {Object|null} - Tool call data or null
|
|
502
|
+
*/
|
|
503
|
+
getToolCall(key) {
|
|
504
|
+
return this.toolCalls.get(key) || null;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* Clear all tracking data
|
|
509
|
+
*/
|
|
510
|
+
clear() {
|
|
511
|
+
this.fileOperations.clear();
|
|
512
|
+
this.toolCalls.clear();
|
|
513
|
+
console.log('File-tool tracker cleared');
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* Get statistics about tracked operations
|
|
518
|
+
* @returns {Object} - Statistics
|
|
519
|
+
*/
|
|
520
|
+
getStatistics() {
|
|
521
|
+
return {
|
|
522
|
+
fileOperations: this.fileOperations.size,
|
|
523
|
+
toolCalls: this.toolCalls.size,
|
|
524
|
+
uniqueFiles: this.fileOperations.size,
|
|
525
|
+
totalFileOperations: Array.from(this.fileOperations.values())
|
|
526
|
+
.reduce((sum, data) => sum + data.operations.length, 0)
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* Compare tool parameters between pre_tool and post_tool events
|
|
532
|
+
* to determine if they're likely from the same tool call
|
|
533
|
+
* @param {Object} preEvent - Pre-tool event
|
|
534
|
+
* @param {Object} postEvent - Post-tool event
|
|
535
|
+
* @returns {boolean} - True if parameters suggest same tool call
|
|
536
|
+
*/
|
|
537
|
+
compareToolParameters(preEvent, postEvent) {
|
|
538
|
+
// Extract parameters from both events
|
|
539
|
+
const preParams = preEvent.tool_parameters || preEvent.data?.tool_parameters || {};
|
|
540
|
+
const postParams = postEvent.tool_parameters || postEvent.data?.tool_parameters || {};
|
|
541
|
+
|
|
542
|
+
// If no parameters in either event, can't compare meaningfully
|
|
543
|
+
if (Object.keys(preParams).length === 0 && Object.keys(postParams).length === 0) {
|
|
544
|
+
return false; // No boost for empty parameters
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// Compare key parameters that are likely to be the same
|
|
548
|
+
const importantParams = ['file_path', 'path', 'pattern', 'command', 'notebook_path'];
|
|
549
|
+
let matchedParams = 0;
|
|
550
|
+
let totalComparableParams = 0;
|
|
551
|
+
|
|
552
|
+
importantParams.forEach(param => {
|
|
553
|
+
const preValue = preParams[param];
|
|
554
|
+
const postValue = postParams[param];
|
|
555
|
+
|
|
556
|
+
if (preValue !== undefined || postValue !== undefined) {
|
|
557
|
+
totalComparableParams++;
|
|
558
|
+
if (preValue === postValue) {
|
|
559
|
+
matchedParams++;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
// If we found comparable parameters, check if most match
|
|
565
|
+
if (totalComparableParams > 0) {
|
|
566
|
+
return (matchedParams / totalComparableParams) >= 0.8; // 80% parameter match threshold
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// If no important parameters to compare, check if the parameter structure is similar
|
|
570
|
+
const preKeys = Object.keys(preParams).sort();
|
|
571
|
+
const postKeys = Object.keys(postParams).sort();
|
|
572
|
+
|
|
573
|
+
if (preKeys.length === 0 && postKeys.length === 0) {
|
|
574
|
+
return false;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// Simple structural similarity check
|
|
578
|
+
if (preKeys.length === postKeys.length) {
|
|
579
|
+
const keyMatches = preKeys.filter(key => postKeys.includes(key)).length;
|
|
580
|
+
return keyMatches >= Math.max(1, preKeys.length * 0.5); // At least 50% key overlap
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
return false;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* Extract agent information from event using inference system
|
|
588
|
+
* @param {Object} event - Event to extract agent from
|
|
589
|
+
* @returns {Object} - Agent info with name and confidence
|
|
590
|
+
*/
|
|
591
|
+
extractAgentFromEvent(event) {
|
|
592
|
+
if (this.agentInference) {
|
|
593
|
+
const inference = this.agentInference.getInferredAgentForEvent(event);
|
|
594
|
+
if (inference) {
|
|
595
|
+
return {
|
|
596
|
+
name: inference.agentName || 'Unknown',
|
|
597
|
+
confidence: inference.confidence || 'unknown'
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// Fallback to direct event properties
|
|
603
|
+
const agentName = event.agent_type || event.subagent_type ||
|
|
604
|
+
event.data?.agent_type || event.data?.subagent_type || 'PM';
|
|
605
|
+
|
|
606
|
+
return {
|
|
607
|
+
name: agentName,
|
|
608
|
+
confidence: 'direct'
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
}
|