claude-mpm 5.1.9__py3-none-any.whl → 5.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.

Potentially problematic release.


This version of claude-mpm might be problematic. Click here for more details.

Files changed (162) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/__init__.py +4 -0
  3. claude_mpm/agents/PM_INSTRUCTIONS.md +85 -0
  4. claude_mpm/agents/agent_loader.py +13 -44
  5. claude_mpm/agents/templates/circuit-breakers.md +138 -1
  6. claude_mpm/cli/__main__.py +4 -0
  7. claude_mpm/cli/commands/agent_state_manager.py +8 -17
  8. claude_mpm/cli/commands/auto_configure.py +210 -25
  9. claude_mpm/cli/commands/config.py +88 -2
  10. claude_mpm/cli/commands/configure.py +1097 -158
  11. claude_mpm/cli/commands/configure_agent_display.py +15 -6
  12. claude_mpm/cli/commands/mpm_init/core.py +160 -46
  13. claude_mpm/cli/commands/mpm_init/knowledge_extractor.py +481 -0
  14. claude_mpm/cli/commands/mpm_init/prompts.py +280 -0
  15. claude_mpm/cli/commands/skills.py +21 -2
  16. claude_mpm/cli/commands/summarize.py +413 -0
  17. claude_mpm/cli/executor.py +11 -3
  18. claude_mpm/cli/parsers/base_parser.py +5 -0
  19. claude_mpm/cli/parsers/config_parser.py +153 -83
  20. claude_mpm/cli/parsers/skills_parser.py +3 -2
  21. claude_mpm/cli/startup.py +333 -89
  22. claude_mpm/commands/mpm-config.md +266 -0
  23. claude_mpm/commands/{mpm-ticket-organize.md → mpm-organize.md} +4 -5
  24. claude_mpm/config/agent_sources.py +27 -0
  25. claude_mpm/core/framework/formatters/content_formatter.py +3 -13
  26. claude_mpm/core/framework/loaders/agent_loader.py +8 -5
  27. claude_mpm/core/framework_loader.py +4 -2
  28. claude_mpm/core/logger.py +13 -0
  29. claude_mpm/core/socketio_pool.py +3 -3
  30. claude_mpm/core/unified_agent_registry.py +5 -15
  31. claude_mpm/hooks/claude_hooks/correlation_manager.py +60 -0
  32. claude_mpm/hooks/claude_hooks/event_handlers.py +206 -78
  33. claude_mpm/hooks/claude_hooks/hook_handler.py +6 -0
  34. claude_mpm/hooks/claude_hooks/installer.py +33 -10
  35. claude_mpm/hooks/claude_hooks/memory_integration.py +26 -9
  36. claude_mpm/hooks/claude_hooks/response_tracking.py +2 -3
  37. claude_mpm/hooks/claude_hooks/services/connection_manager.py +4 -0
  38. claude_mpm/hooks/memory_integration_hook.py +46 -1
  39. claude_mpm/init.py +0 -19
  40. claude_mpm/scripts/claude-hook-handler.sh +58 -18
  41. claude_mpm/scripts/launch_monitor.py +93 -13
  42. claude_mpm/services/agents/agent_recommendation_service.py +278 -0
  43. claude_mpm/services/agents/agent_review_service.py +280 -0
  44. claude_mpm/services/agents/deployment/agent_discovery_service.py +2 -3
  45. claude_mpm/services/agents/deployment/agent_template_builder.py +4 -2
  46. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +78 -9
  47. claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +335 -53
  48. claude_mpm/services/agents/git_source_manager.py +34 -0
  49. claude_mpm/services/agents/loading/base_agent_manager.py +1 -13
  50. claude_mpm/services/agents/sources/git_source_sync_service.py +8 -1
  51. claude_mpm/services/agents/toolchain_detector.py +10 -6
  52. claude_mpm/services/analysis/__init__.py +11 -1
  53. claude_mpm/services/analysis/clone_detector.py +1030 -0
  54. claude_mpm/services/command_deployment_service.py +71 -10
  55. claude_mpm/services/event_bus/config.py +3 -1
  56. claude_mpm/services/git/git_operations_service.py +93 -8
  57. claude_mpm/services/monitor/daemon.py +9 -2
  58. claude_mpm/services/monitor/daemon_manager.py +39 -3
  59. claude_mpm/services/monitor/server.py +225 -19
  60. claude_mpm/services/self_upgrade_service.py +120 -12
  61. claude_mpm/services/skills/__init__.py +3 -0
  62. claude_mpm/services/skills/git_skill_source_manager.py +32 -2
  63. claude_mpm/services/skills/selective_skill_deployer.py +230 -0
  64. claude_mpm/services/skills/skill_to_agent_mapper.py +406 -0
  65. claude_mpm/services/skills_deployer.py +64 -3
  66. claude_mpm/services/socketio/event_normalizer.py +15 -1
  67. claude_mpm/services/socketio/server/core.py +160 -21
  68. claude_mpm/services/version_control/git_operations.py +103 -0
  69. claude_mpm/utils/agent_filters.py +17 -44
  70. {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.14.dist-info}/METADATA +47 -84
  71. {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.14.dist-info}/RECORD +76 -150
  72. claude_mpm-5.4.14.dist-info/entry_points.txt +5 -0
  73. claude_mpm-5.4.14.dist-info/licenses/LICENSE +94 -0
  74. claude_mpm-5.4.14.dist-info/licenses/LICENSE-FAQ.md +153 -0
  75. claude_mpm/agents/BASE_AGENT_TEMPLATE.md +0 -292
  76. claude_mpm/agents/BASE_DOCUMENTATION.md +0 -53
  77. claude_mpm/agents/BASE_ENGINEER.md +0 -658
  78. claude_mpm/agents/BASE_OPS.md +0 -219
  79. claude_mpm/agents/BASE_PM.md +0 -480
  80. claude_mpm/agents/BASE_PROMPT_ENGINEER.md +0 -787
  81. claude_mpm/agents/BASE_QA.md +0 -167
  82. claude_mpm/agents/BASE_RESEARCH.md +0 -53
  83. claude_mpm/agents/base_agent.json +0 -31
  84. claude_mpm/agents/base_agent_loader.py +0 -601
  85. claude_mpm/cli/ticket_cli.py +0 -35
  86. claude_mpm/commands/mpm-config-view.md +0 -150
  87. claude_mpm/dashboard/analysis_runner.py +0 -455
  88. claude_mpm/dashboard/index.html +0 -13
  89. claude_mpm/dashboard/open_dashboard.py +0 -66
  90. claude_mpm/dashboard/static/css/activity.css +0 -1958
  91. claude_mpm/dashboard/static/css/connection-status.css +0 -370
  92. claude_mpm/dashboard/static/css/dashboard.css +0 -4701
  93. claude_mpm/dashboard/static/js/components/activity-tree.js +0 -1871
  94. claude_mpm/dashboard/static/js/components/agent-hierarchy.js +0 -777
  95. claude_mpm/dashboard/static/js/components/agent-inference.js +0 -956
  96. claude_mpm/dashboard/static/js/components/build-tracker.js +0 -333
  97. claude_mpm/dashboard/static/js/components/code-simple.js +0 -857
  98. claude_mpm/dashboard/static/js/components/connection-debug.js +0 -654
  99. claude_mpm/dashboard/static/js/components/diff-viewer.js +0 -891
  100. claude_mpm/dashboard/static/js/components/event-processor.js +0 -542
  101. claude_mpm/dashboard/static/js/components/event-viewer.js +0 -1155
  102. claude_mpm/dashboard/static/js/components/export-manager.js +0 -368
  103. claude_mpm/dashboard/static/js/components/file-change-tracker.js +0 -443
  104. claude_mpm/dashboard/static/js/components/file-change-viewer.js +0 -690
  105. claude_mpm/dashboard/static/js/components/file-tool-tracker.js +0 -724
  106. claude_mpm/dashboard/static/js/components/file-viewer.js +0 -580
  107. claude_mpm/dashboard/static/js/components/hud-library-loader.js +0 -211
  108. claude_mpm/dashboard/static/js/components/hud-manager.js +0 -671
  109. claude_mpm/dashboard/static/js/components/hud-visualizer.js +0 -1718
  110. claude_mpm/dashboard/static/js/components/module-viewer.js +0 -2764
  111. claude_mpm/dashboard/static/js/components/session-manager.js +0 -579
  112. claude_mpm/dashboard/static/js/components/socket-manager.js +0 -368
  113. claude_mpm/dashboard/static/js/components/ui-state-manager.js +0 -749
  114. claude_mpm/dashboard/static/js/components/unified-data-viewer.js +0 -1824
  115. claude_mpm/dashboard/static/js/components/working-directory.js +0 -920
  116. claude_mpm/dashboard/static/js/connection-manager.js +0 -536
  117. claude_mpm/dashboard/static/js/dashboard.js +0 -1914
  118. claude_mpm/dashboard/static/js/extension-error-handler.js +0 -164
  119. claude_mpm/dashboard/static/js/socket-client.js +0 -1474
  120. claude_mpm/dashboard/static/js/tab-isolation-fix.js +0 -185
  121. claude_mpm/dashboard/static/socket.io.min.js +0 -7
  122. claude_mpm/dashboard/static/socket.io.v4.8.1.backup.js +0 -7
  123. claude_mpm/dashboard/templates/code_simple.html +0 -153
  124. claude_mpm/dashboard/templates/index.html +0 -606
  125. claude_mpm/dashboard/test_dashboard.html +0 -372
  126. claude_mpm/scripts/mcp_server.py +0 -75
  127. claude_mpm/scripts/mcp_wrapper.py +0 -39
  128. claude_mpm/services/mcp_gateway/__init__.py +0 -159
  129. claude_mpm/services/mcp_gateway/auto_configure.py +0 -369
  130. claude_mpm/services/mcp_gateway/config/__init__.py +0 -17
  131. claude_mpm/services/mcp_gateway/config/config_loader.py +0 -296
  132. claude_mpm/services/mcp_gateway/config/config_schema.py +0 -243
  133. claude_mpm/services/mcp_gateway/config/configuration.py +0 -429
  134. claude_mpm/services/mcp_gateway/core/__init__.py +0 -43
  135. claude_mpm/services/mcp_gateway/core/base.py +0 -312
  136. claude_mpm/services/mcp_gateway/core/exceptions.py +0 -253
  137. claude_mpm/services/mcp_gateway/core/interfaces.py +0 -443
  138. claude_mpm/services/mcp_gateway/core/process_pool.py +0 -977
  139. claude_mpm/services/mcp_gateway/core/singleton_manager.py +0 -315
  140. claude_mpm/services/mcp_gateway/core/startup_verification.py +0 -316
  141. claude_mpm/services/mcp_gateway/main.py +0 -589
  142. claude_mpm/services/mcp_gateway/registry/__init__.py +0 -12
  143. claude_mpm/services/mcp_gateway/registry/service_registry.py +0 -412
  144. claude_mpm/services/mcp_gateway/registry/tool_registry.py +0 -489
  145. claude_mpm/services/mcp_gateway/server/__init__.py +0 -15
  146. claude_mpm/services/mcp_gateway/server/mcp_gateway.py +0 -414
  147. claude_mpm/services/mcp_gateway/server/stdio_handler.py +0 -372
  148. claude_mpm/services/mcp_gateway/server/stdio_server.py +0 -712
  149. claude_mpm/services/mcp_gateway/tools/__init__.py +0 -36
  150. claude_mpm/services/mcp_gateway/tools/base_adapter.py +0 -485
  151. claude_mpm/services/mcp_gateway/tools/document_summarizer.py +0 -789
  152. claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +0 -654
  153. claude_mpm/services/mcp_gateway/tools/health_check_tool.py +0 -456
  154. claude_mpm/services/mcp_gateway/tools/hello_world.py +0 -551
  155. claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +0 -555
  156. claude_mpm/services/mcp_gateway/utils/__init__.py +0 -14
  157. claude_mpm/services/mcp_gateway/utils/package_version_checker.py +0 -160
  158. claude_mpm/services/mcp_gateway/utils/update_preferences.py +0 -170
  159. claude_mpm-5.1.9.dist-info/entry_points.txt +0 -10
  160. claude_mpm-5.1.9.dist-info/licenses/LICENSE +0 -21
  161. {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.14.dist-info}/WHEEL +0 -0
  162. {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.14.dist-info}/top_level.txt +0 -0
@@ -1,724 +0,0 @@
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 || (event.data && event.data.tool_name);
64
- const sessionId = event.session_id || (event.data && event.data.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 && !event.subtype.includes('post'))) {
78
- pair.pre_event = event;
79
- } else if (event.subtype === 'post_tool' || (event.subtype && 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 && !event.subtype.includes('post'))) {
166
- preToolEvents.push(event);
167
- } else if (event.subtype === 'post_tool' || (event.subtype && 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 || (preEvent.data && preEvent.data.tool_name);
185
- const sessionId = preEvent.session_id || (preEvent.data && preEvent.data.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
- const postToolName = postEvent.tool_name || (postEvent.data && postEvent.data.tool_name);
221
- const postSessionId = postEvent.session_id || (postEvent.data && postEvent.data.session_id) || 'unknown';
222
- if (postToolName !== toolName || postSessionId !== sessionId) return;
223
-
224
- const postTimestamp = new Date(postEvent.timestamp).getTime();
225
- const timeDiff = Math.abs(postTimestamp - preTimestamp);
226
-
227
- // Post event should generally come after pre event (or very close)
228
- const isTemporallyValid = postTimestamp >= preTimestamp - 1000; // Allow 1s clock skew
229
-
230
- // Calculate correlation score (higher is better)
231
- let score = 0;
232
- if (isTemporallyValid && timeDiff <= maxTimeDiffMs) {
233
- score = 1000 - (timeDiff / 1000); // Prefer closer timestamps
234
-
235
- // Boost score for parameter similarity (if available)
236
- if (this.compareToolParameters(preEvent, postEvent)) {
237
- score += 500;
238
- }
239
-
240
- // Boost score for same working directory
241
- if (preEvent.working_directory && postEvent.working_directory &&
242
- preEvent.working_directory === postEvent.working_directory) {
243
- score += 100;
244
- }
245
- }
246
-
247
- if (score > bestMatchScore) {
248
- bestMatchScore = score;
249
- bestMatchIndex = postIndex;
250
- }
251
- });
252
-
253
- // If we found a good match, pair them
254
- if (bestMatchIndex >= 0 && bestMatchScore > 0) {
255
- const postEvent = postToolEvents[bestMatchIndex];
256
- pair.post_event = postEvent;
257
- pair.duration_ms = postEvent.duration_ms;
258
- pair.success = postEvent.success;
259
- pair.exit_code = postEvent.exit_code;
260
- pair.result_summary = postEvent.result_summary;
261
-
262
- usedPostEvents.add(bestMatchIndex);
263
- console.log(`Paired pre_tool ${toolName} at ${preEvent.timestamp} with post_tool at ${postEvent.timestamp} (score: ${bestMatchScore})`);
264
- } else {
265
- console.log(`No matching post_tool found for ${toolName} at ${preEvent.timestamp} (still running or orphaned)`);
266
- }
267
-
268
- toolCallPairs.set(pairKey, pair);
269
- });
270
-
271
- // Third pass: handle any orphaned post_tool events (shouldn't happen but be safe)
272
- postToolEvents.forEach((postEvent, postIndex) => {
273
- if (usedPostEvents.has(postIndex)) return;
274
-
275
- const toolName = postEvent.tool_name || (postEvent.data && postEvent.data.tool_name);
276
- console.log('Orphaned post_tool event found:', toolName, 'at', postEvent.timestamp);
277
-
278
- const sessionId = postEvent.session_id || (postEvent.data && postEvent.data.session_id) || 'unknown';
279
- const postTimestamp = new Date(postEvent.timestamp).getTime();
280
-
281
- const pairKey = `orphaned_${sessionId}_${toolName}_${postIndex}_${postTimestamp}`;
282
- const pair = {
283
- pre_event: null,
284
- post_event: postEvent,
285
- tool_name: toolName,
286
- session_id: sessionId,
287
- operation_type: 'tool_execution',
288
- timestamp: postEvent.timestamp,
289
- duration_ms: postEvent.duration_ms,
290
- success: postEvent.success,
291
- exit_code: postEvent.exit_code,
292
- result_summary: postEvent.result_summary,
293
- agent_type: null,
294
- agent_confidence: null
295
- };
296
-
297
- const agentInfo = this.extractAgentFromEvent(postEvent);
298
- pair.agent_type = agentInfo.name;
299
- pair.agent_confidence = agentInfo.confidence;
300
-
301
- toolCallPairs.set(pairKey, pair);
302
- });
303
-
304
- // Store the correlated tool calls
305
- this.toolCalls = toolCallPairs;
306
-
307
- console.log('updateToolCalls - final result:', this.toolCalls.size, 'tool calls');
308
- if (this.toolCalls.size > 0) {
309
- console.log('Tool calls map keys:', Array.from(this.toolCalls.keys()));
310
- }
311
- }
312
-
313
- /**
314
- * Check if event is a tool operation
315
- * @param {Object} event - Event to check
316
- * @returns {boolean} - True if tool operation
317
- */
318
- isToolOperation(event) {
319
- // Tool operations have tool_name - be more inclusive about event types
320
- // Check both top-level and data.tool_name for compatibility
321
- const hasToolName = event.tool_name || (event.data && event.data.tool_name);
322
-
323
- // Accept multiple event types that might contain tool operations
324
- const validEventTypes = ['hook', 'tool_use', 'tool', 'agent', 'response'];
325
- const isValidEventType = validEventTypes.includes(event.type) ||
326
- (event.type && event.type.includes('tool'));
327
-
328
- // Check for tool-related subtypes or any indication this is a tool operation
329
- const isToolSubtype = event.subtype === 'pre_tool' ||
330
- event.subtype === 'post_tool' ||
331
- (event.subtype && typeof event.subtype === 'string' && event.subtype.includes('tool')) ||
332
- event.type === 'tool_use' ||
333
- event.type === 'tool';
334
-
335
- // If it has a tool_name and either a valid event type or tool subtype, it's a tool operation
336
- return hasToolName && (isValidEventType || isToolSubtype);
337
- }
338
-
339
- /**
340
- * Check if event is a file operation
341
- * @param {Object} event - Event to check
342
- * @returns {boolean} - True if file operation
343
- */
344
- isFileOperation(event) {
345
- // File operations are events with file-related tools - be more inclusive
346
- // Check both top-level and data for tool_name
347
- let toolName = event.tool_name || (event.data && event.data.tool_name) || '';
348
-
349
- // If no tool name, not a file operation
350
- if (!toolName) {
351
- return false;
352
- }
353
-
354
- toolName = toolName.toLowerCase();
355
-
356
- // Check case-insensitively since tool names can come in different cases
357
- const fileTools = ['read', 'write', 'edit', 'grep', 'multiedit', 'glob', 'ls', 'bash', 'notebookedit'];
358
-
359
- // Get tool parameters from either location
360
- const toolParams = event.tool_parameters || (event.data && event.data.tool_parameters);
361
-
362
- // Also check if Bash commands involve file operations
363
- if (toolName === 'bash' && toolParams) {
364
- const command = toolParams.command || '';
365
- // Check for common file operations in bash commands
366
- if (command.match(/\b(cat|less|more|head|tail|touch|mv|cp|rm|mkdir|ls|find)\b/)) {
367
- return true;
368
- }
369
- }
370
-
371
- // If it's a file tool, it's a file operation regardless of event type
372
- return fileTools.includes(toolName);
373
- }
374
-
375
- /**
376
- * Extract file path from event
377
- * @param {Object} event - Event to extract from
378
- * @returns {string|null} - File path or null
379
- */
380
- extractFilePath(event) {
381
- // Debug logging for file path extraction
382
- const fileTools = ['Read', 'Write', 'Edit', 'MultiEdit', 'NotebookEdit'];
383
- const toolName = event.tool_name || (event.data && event.data.tool_name);
384
-
385
- if (fileTools.includes(toolName)) {
386
- console.log('Extracting file path from event:', {
387
- tool_name: toolName,
388
- has_tool_parameters_top: !!event.tool_parameters,
389
- has_tool_parameters_data: !!(event.data && event.data.tool_parameters),
390
- tool_parameters: event.tool_parameters,
391
- data_tool_parameters: event.data?.tool_parameters
392
- });
393
- }
394
-
395
- // Try various locations where file path might be stored
396
- // Check top-level tool_parameters first (after transformation)
397
- if (event.tool_parameters?.file_path) return event.tool_parameters.file_path;
398
- if (event.tool_parameters?.path) return event.tool_parameters.path;
399
- if (event.tool_parameters?.notebook_path) return event.tool_parameters.notebook_path;
400
-
401
- // Check in data object as fallback
402
- if (event.data?.tool_parameters?.file_path) return event.data.tool_parameters.file_path;
403
- if (event.data?.tool_parameters?.path) return event.data.tool_parameters.path;
404
- if (event.data?.tool_parameters?.notebook_path) return event.data.tool_parameters.notebook_path;
405
- if (event.file_path) return event.file_path;
406
- if (event.path) return event.path;
407
-
408
- // For Glob tool, use the pattern as a pseudo-path
409
- if (event.tool_name?.toLowerCase() === 'glob' && event.tool_parameters?.pattern) {
410
- return `[glob] ${event.tool_parameters.pattern}`;
411
- }
412
-
413
- // For Bash commands, try to extract file paths from the command
414
- if (event.tool_name?.toLowerCase() === 'bash' && event.tool_parameters?.command) {
415
- const command = event.tool_parameters.command;
416
-
417
- // Enhanced regex to handle commands with flags
418
- // Match command followed by optional flags (starting with -) and then the file path
419
- // Patterns to handle:
420
- // 1. tail -50 /path/to/file
421
- // 2. head -n 100 /path/to/file
422
- // 3. cat /path/to/file
423
- // 4. grep -r "pattern" /path/to/file
424
- const fileMatch = command.match(/(?:cat|less|more|head|tail|touch|mv|cp|rm|mkdir|ls|find|echo.*>|sed|awk|grep)(?:\s+-[a-zA-Z0-9]+)*(?:\s+[0-9]+)*\s+([^\s;|&]+)/);
425
-
426
- // If first match might be a flag, try a more specific pattern
427
- if (fileMatch && fileMatch[1]) {
428
- const possiblePath = fileMatch[1];
429
- // Check if it's actually a flag (starts with -)
430
- if (possiblePath.startsWith('-')) {
431
- // Try alternative pattern that skips all flags
432
- const altMatch = command.match(/(?:cat|less|more|head|tail|touch|mv|cp|rm|mkdir|ls|find|echo.*>|sed|awk|grep)(?:\s+-[^\s]+)*\s+([^-][^\s;|&]*)/);
433
- if (altMatch && altMatch[1]) {
434
- return altMatch[1];
435
- }
436
- }
437
- return possiblePath;
438
- }
439
- }
440
-
441
- return null;
442
- }
443
-
444
- /**
445
- * Extract file path from event pair
446
- * @param {Object} pair - Event pair object
447
- * @returns {string|null} - File path or null
448
- */
449
- extractFilePathFromPair(pair) {
450
- // Try pre_event first, then post_event
451
- let filePath = null;
452
-
453
- if (pair.pre_event) {
454
- filePath = this.extractFilePath(pair.pre_event);
455
- }
456
-
457
- if (!filePath && pair.post_event) {
458
- filePath = this.extractFilePath(pair.post_event);
459
- }
460
-
461
- return filePath;
462
- }
463
-
464
- /**
465
- * Get file operation type from event
466
- * @param {Object} event - Event to analyze
467
- * @returns {string} - Operation type
468
- */
469
- getFileOperation(event) {
470
- if (!event.tool_name) return 'unknown';
471
-
472
- const toolName = event.tool_name.toLowerCase();
473
- switch (toolName) {
474
- case 'read': return 'read';
475
- case 'write': return 'write';
476
- case 'edit': return 'edit';
477
- case 'multiedit': return 'edit';
478
- case 'notebookedit': return 'edit';
479
- case 'grep': return 'search';
480
- case 'glob': return 'search';
481
- case 'ls': return 'list';
482
- case 'bash':
483
- // Check bash command for file operation type
484
- const command = event.tool_parameters?.command || '';
485
- if (command.match(/\b(cat|less|more|head|tail)\b/)) return 'read';
486
- if (command.match(/\b(touch|echo.*>|tee)\b/)) return 'write';
487
- if (command.match(/\b(sed|awk)\b/)) return 'edit';
488
- if (command.match(/\b(grep|find)\b/)) return 'search';
489
- if (command.match(/\b(ls|dir)\b/)) return 'list';
490
- if (command.match(/\b(mv|cp)\b/)) return 'copy/move';
491
- if (command.match(/\b(rm|rmdir)\b/)) return 'delete';
492
- if (command.match(/\b(mkdir)\b/)) return 'create';
493
- return 'bash';
494
- default: return toolName;
495
- }
496
- }
497
-
498
- /**
499
- * Get file operation from event pair
500
- * @param {Object} pair - Event pair object
501
- * @returns {string} - Operation type
502
- */
503
- getFileOperationFromPair(pair) {
504
- // Try pre_event first, then post_event
505
- if (pair.pre_event) {
506
- return this.getFileOperation(pair.pre_event);
507
- }
508
-
509
- if (pair.post_event) {
510
- return this.getFileOperation(pair.post_event);
511
- }
512
-
513
- return 'unknown';
514
- }
515
-
516
- /**
517
- * Extract agent information from event pair
518
- * @param {Object} pair - Event pair object
519
- * @returns {Object} - Agent info with name and confidence
520
- */
521
- extractAgentFromPair(pair) {
522
- // Try to get agent info from inference system first
523
- const event = pair.pre_event || pair.post_event;
524
- if (event && this.agentInference) {
525
- const inference = this.agentInference.getInferredAgentForEvent(event);
526
- if (inference) {
527
- return {
528
- name: inference.agentName || 'Unknown',
529
- confidence: inference.confidence || 'unknown'
530
- };
531
- }
532
- }
533
-
534
- // Fallback to direct event properties
535
- const agentName = event?.agent_type || event?.subagent_type ||
536
- pair.pre_event?.agent_type || pair.post_event?.agent_type || 'PM';
537
-
538
- return {
539
- name: agentName,
540
- confidence: 'direct'
541
- };
542
- }
543
-
544
- /**
545
- * Get detailed operation information from event pair
546
- * @param {Object} pair - Event pair object
547
- * @returns {Object} - Operation details
548
- */
549
- getFileOperationDetailsFromPair(pair) {
550
- const details = {};
551
-
552
- // Extract details from pre_event (parameters)
553
- if (pair.pre_event) {
554
- const params = pair.pre_event.tool_parameters || pair.pre_event.data?.tool_parameters || {};
555
- details.parameters = params;
556
- details.tool_input = pair.pre_event.tool_input;
557
- }
558
-
559
- // Extract details from post_event (results)
560
- if (pair.post_event) {
561
- details.result = pair.post_event.result;
562
- details.success = pair.post_event.success;
563
- details.error = pair.post_event.error;
564
- details.exit_code = pair.post_event.exit_code;
565
- details.duration_ms = pair.post_event.duration_ms;
566
- }
567
-
568
- return details;
569
- }
570
-
571
- /**
572
- * Get file operations map
573
- * @returns {Map} - File operations map
574
- */
575
- getFileOperations() {
576
- return this.fileOperations;
577
- }
578
-
579
- /**
580
- * Get tool calls map
581
- * @returns {Map} - Tool calls map
582
- */
583
- getToolCalls() {
584
- return this.toolCalls;
585
- }
586
-
587
- /**
588
- * Get tool calls as array for unique instance view
589
- * Each entry represents a unique tool call instance
590
- * @returns {Array} - Array of [key, toolCall] pairs
591
- */
592
- getToolCallsArray() {
593
- return Array.from(this.toolCalls.entries());
594
- }
595
-
596
- /**
597
- * Get file operations for a specific file
598
- * @param {string} filePath - File path
599
- * @returns {Object|null} - File operations data or null
600
- */
601
- getFileOperationsForFile(filePath) {
602
- return this.fileOperations.get(filePath) || null;
603
- }
604
-
605
- /**
606
- * Get tool call by key
607
- * @param {string} key - Tool call key
608
- * @returns {Object|null} - Tool call data or null
609
- */
610
- getToolCall(key) {
611
- return this.toolCalls.get(key) || null;
612
- }
613
-
614
- /**
615
- * Clear all tracking data
616
- */
617
- clear() {
618
- this.fileOperations.clear();
619
- this.toolCalls.clear();
620
- console.log('File-tool tracker cleared');
621
- }
622
-
623
- /**
624
- * Get statistics about tracked operations
625
- * @returns {Object} - Statistics
626
- */
627
- getStatistics() {
628
- return {
629
- fileOperations: this.fileOperations.size,
630
- toolCalls: this.toolCalls.size,
631
- uniqueFiles: this.fileOperations.size,
632
- totalFileOperations: Array.from(this.fileOperations.values())
633
- .reduce((sum, data) => sum + data.operations.length, 0)
634
- };
635
- }
636
-
637
- /**
638
- * Compare tool parameters between pre_tool and post_tool events
639
- * to determine if they're likely from the same tool call
640
- * @param {Object} preEvent - Pre-tool event
641
- * @param {Object} postEvent - Post-tool event
642
- * @returns {boolean} - True if parameters suggest same tool call
643
- */
644
- compareToolParameters(preEvent, postEvent) {
645
- // Extract parameters from both events
646
- const preParams = preEvent.tool_parameters || preEvent.data?.tool_parameters || {};
647
- const postParams = postEvent.tool_parameters || postEvent.data?.tool_parameters || {};
648
-
649
- // If no parameters in either event, can't compare meaningfully
650
- if (Object.keys(preParams).length === 0 && Object.keys(postParams).length === 0) {
651
- return false; // No boost for empty parameters
652
- }
653
-
654
- // Compare key parameters that are likely to be the same
655
- const importantParams = ['file_path', 'path', 'pattern', 'command', 'notebook_path'];
656
- let matchedParams = 0;
657
- let totalComparableParams = 0;
658
-
659
- importantParams.forEach(param => {
660
- const preValue = preParams[param];
661
- const postValue = postParams[param];
662
-
663
- if (preValue !== undefined || postValue !== undefined) {
664
- totalComparableParams++;
665
- if (preValue === postValue) {
666
- matchedParams++;
667
- }
668
- }
669
- });
670
-
671
- // If we found comparable parameters, check if most match
672
- if (totalComparableParams > 0) {
673
- return (matchedParams / totalComparableParams) >= 0.8; // 80% parameter match threshold
674
- }
675
-
676
- // If no important parameters to compare, check if the parameter structure is similar
677
- const preKeys = Object.keys(preParams).sort();
678
- const postKeys = Object.keys(postParams).sort();
679
-
680
- if (preKeys.length === 0 && postKeys.length === 0) {
681
- return false;
682
- }
683
-
684
- // Simple structural similarity check
685
- if (preKeys.length === postKeys.length) {
686
- const keyMatches = preKeys.filter(key => postKeys.includes(key)).length;
687
- return keyMatches >= Math.max(1, preKeys.length * 0.5); // At least 50% key overlap
688
- }
689
-
690
- return false;
691
- }
692
-
693
- /**
694
- * Extract agent information from event using inference system
695
- * @param {Object} event - Event to extract agent from
696
- * @returns {Object} - Agent info with name and confidence
697
- */
698
- extractAgentFromEvent(event) {
699
- if (this.agentInference) {
700
- const inference = this.agentInference.getInferredAgentForEvent(event);
701
- if (inference) {
702
- return {
703
- name: inference.agentName || 'Unknown',
704
- confidence: inference.confidence || 'unknown'
705
- };
706
- }
707
- }
708
-
709
- // Fallback to direct event properties
710
- const agentName = event.agent_type || event.subagent_type ||
711
- event.data?.agent_type || event.data?.subagent_type || 'PM';
712
-
713
- return {
714
- name: agentName,
715
- confidence: 'direct'
716
- };
717
- }
718
- }
719
- // ES6 Module export
720
- export { FileToolTracker };
721
- export default FileToolTracker;
722
-
723
- // Make FileToolTracker globally available for dist/dashboard.js
724
- window.FileToolTracker = FileToolTracker;