claude-mpm 5.1.8__py3-none-any.whl → 5.4.22__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 (191) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/__init__.py +4 -0
  3. claude_mpm/agents/{PM_INSTRUCTIONS_TEACH.md → CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md} +721 -41
  4. claude_mpm/agents/PM_INSTRUCTIONS.md +290 -34
  5. claude_mpm/agents/agent_loader.py +13 -44
  6. claude_mpm/agents/frontmatter_validator.py +68 -0
  7. claude_mpm/agents/templates/circuit-breakers.md +138 -1
  8. claude_mpm/cli/__main__.py +4 -0
  9. claude_mpm/cli/chrome_devtools_installer.py +175 -0
  10. claude_mpm/cli/commands/agent_state_manager.py +8 -17
  11. claude_mpm/cli/commands/agents.py +169 -31
  12. claude_mpm/cli/commands/auto_configure.py +210 -25
  13. claude_mpm/cli/commands/config.py +88 -2
  14. claude_mpm/cli/commands/configure.py +1111 -161
  15. claude_mpm/cli/commands/configure_agent_display.py +15 -6
  16. claude_mpm/cli/commands/mpm_init/core.py +160 -46
  17. claude_mpm/cli/commands/mpm_init/knowledge_extractor.py +481 -0
  18. claude_mpm/cli/commands/mpm_init/prompts.py +280 -0
  19. claude_mpm/cli/commands/skills.py +214 -189
  20. claude_mpm/cli/commands/summarize.py +413 -0
  21. claude_mpm/cli/executor.py +11 -3
  22. claude_mpm/cli/parsers/agents_parser.py +54 -9
  23. claude_mpm/cli/parsers/auto_configure_parser.py +0 -138
  24. claude_mpm/cli/parsers/base_parser.py +5 -0
  25. claude_mpm/cli/parsers/config_parser.py +153 -83
  26. claude_mpm/cli/parsers/skills_parser.py +3 -2
  27. claude_mpm/cli/startup.py +550 -94
  28. claude_mpm/commands/mpm-config.md +265 -0
  29. claude_mpm/commands/mpm-help.md +14 -95
  30. claude_mpm/commands/mpm-organize.md +500 -0
  31. claude_mpm/config/agent_sources.py +27 -0
  32. claude_mpm/core/framework/formatters/content_formatter.py +3 -13
  33. claude_mpm/core/framework/loaders/agent_loader.py +8 -5
  34. claude_mpm/core/framework_loader.py +4 -2
  35. claude_mpm/core/logger.py +13 -0
  36. claude_mpm/core/output_style_manager.py +173 -43
  37. claude_mpm/core/socketio_pool.py +3 -3
  38. claude_mpm/core/unified_agent_registry.py +134 -16
  39. claude_mpm/hooks/claude_hooks/correlation_manager.py +60 -0
  40. claude_mpm/hooks/claude_hooks/event_handlers.py +211 -78
  41. claude_mpm/hooks/claude_hooks/hook_handler.py +6 -0
  42. claude_mpm/hooks/claude_hooks/installer.py +33 -10
  43. claude_mpm/hooks/claude_hooks/memory_integration.py +26 -9
  44. claude_mpm/hooks/claude_hooks/response_tracking.py +2 -3
  45. claude_mpm/hooks/claude_hooks/services/connection_manager.py +4 -0
  46. claude_mpm/hooks/memory_integration_hook.py +46 -1
  47. claude_mpm/init.py +0 -19
  48. claude_mpm/models/agent_definition.py +7 -0
  49. claude_mpm/scripts/claude-hook-handler.sh +58 -18
  50. claude_mpm/scripts/launch_monitor.py +93 -13
  51. claude_mpm/scripts/start_activity_logging.py +0 -0
  52. claude_mpm/services/agents/agent_recommendation_service.py +278 -0
  53. claude_mpm/services/agents/agent_review_service.py +280 -0
  54. claude_mpm/services/agents/deployment/agent_discovery_service.py +2 -3
  55. claude_mpm/services/agents/deployment/agent_template_builder.py +4 -2
  56. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +188 -12
  57. claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +531 -55
  58. claude_mpm/services/agents/git_source_manager.py +34 -0
  59. claude_mpm/services/agents/loading/base_agent_manager.py +1 -13
  60. claude_mpm/services/agents/sources/git_source_sync_service.py +8 -1
  61. claude_mpm/services/agents/toolchain_detector.py +10 -6
  62. claude_mpm/services/analysis/__init__.py +11 -1
  63. claude_mpm/services/analysis/clone_detector.py +1030 -0
  64. claude_mpm/services/command_deployment_service.py +81 -10
  65. claude_mpm/services/event_bus/config.py +3 -1
  66. claude_mpm/services/git/git_operations_service.py +93 -8
  67. claude_mpm/services/monitor/daemon.py +9 -2
  68. claude_mpm/services/monitor/daemon_manager.py +39 -3
  69. claude_mpm/services/monitor/server.py +225 -19
  70. claude_mpm/services/self_upgrade_service.py +120 -12
  71. claude_mpm/services/skills/__init__.py +3 -0
  72. claude_mpm/services/skills/git_skill_source_manager.py +32 -2
  73. claude_mpm/services/skills/selective_skill_deployer.py +704 -0
  74. claude_mpm/services/skills/skill_to_agent_mapper.py +406 -0
  75. claude_mpm/services/skills_deployer.py +126 -9
  76. claude_mpm/services/socketio/event_normalizer.py +15 -1
  77. claude_mpm/services/socketio/server/core.py +160 -21
  78. claude_mpm/services/version_control/git_operations.py +103 -0
  79. claude_mpm/utils/agent_filters.py +17 -44
  80. {claude_mpm-5.1.8.dist-info → claude_mpm-5.4.22.dist-info}/METADATA +47 -84
  81. {claude_mpm-5.1.8.dist-info → claude_mpm-5.4.22.dist-info}/RECORD +86 -176
  82. claude_mpm-5.4.22.dist-info/entry_points.txt +5 -0
  83. claude_mpm-5.4.22.dist-info/licenses/LICENSE +94 -0
  84. claude_mpm-5.4.22.dist-info/licenses/LICENSE-FAQ.md +153 -0
  85. claude_mpm/agents/BASE_AGENT_TEMPLATE.md +0 -292
  86. claude_mpm/agents/BASE_DOCUMENTATION.md +0 -53
  87. claude_mpm/agents/BASE_ENGINEER.md +0 -658
  88. claude_mpm/agents/BASE_OPS.md +0 -219
  89. claude_mpm/agents/BASE_PM.md +0 -480
  90. claude_mpm/agents/BASE_PROMPT_ENGINEER.md +0 -787
  91. claude_mpm/agents/BASE_QA.md +0 -167
  92. claude_mpm/agents/BASE_RESEARCH.md +0 -53
  93. claude_mpm/agents/base_agent.json +0 -31
  94. claude_mpm/agents/base_agent_loader.py +0 -601
  95. claude_mpm/cli/commands/agents_detect.py +0 -380
  96. claude_mpm/cli/commands/agents_recommend.py +0 -309
  97. claude_mpm/cli/ticket_cli.py +0 -35
  98. claude_mpm/commands/mpm-agents-auto-configure.md +0 -278
  99. claude_mpm/commands/mpm-agents-detect.md +0 -177
  100. claude_mpm/commands/mpm-agents-list.md +0 -131
  101. claude_mpm/commands/mpm-agents-recommend.md +0 -223
  102. claude_mpm/commands/mpm-config-view.md +0 -150
  103. claude_mpm/commands/mpm-ticket-organize.md +0 -304
  104. claude_mpm/dashboard/analysis_runner.py +0 -455
  105. claude_mpm/dashboard/index.html +0 -13
  106. claude_mpm/dashboard/open_dashboard.py +0 -66
  107. claude_mpm/dashboard/static/css/activity.css +0 -1958
  108. claude_mpm/dashboard/static/css/connection-status.css +0 -370
  109. claude_mpm/dashboard/static/css/dashboard.css +0 -4701
  110. claude_mpm/dashboard/static/js/components/activity-tree.js +0 -1871
  111. claude_mpm/dashboard/static/js/components/agent-hierarchy.js +0 -777
  112. claude_mpm/dashboard/static/js/components/agent-inference.js +0 -956
  113. claude_mpm/dashboard/static/js/components/build-tracker.js +0 -333
  114. claude_mpm/dashboard/static/js/components/code-simple.js +0 -857
  115. claude_mpm/dashboard/static/js/components/connection-debug.js +0 -654
  116. claude_mpm/dashboard/static/js/components/diff-viewer.js +0 -891
  117. claude_mpm/dashboard/static/js/components/event-processor.js +0 -542
  118. claude_mpm/dashboard/static/js/components/event-viewer.js +0 -1155
  119. claude_mpm/dashboard/static/js/components/export-manager.js +0 -368
  120. claude_mpm/dashboard/static/js/components/file-change-tracker.js +0 -443
  121. claude_mpm/dashboard/static/js/components/file-change-viewer.js +0 -690
  122. claude_mpm/dashboard/static/js/components/file-tool-tracker.js +0 -724
  123. claude_mpm/dashboard/static/js/components/file-viewer.js +0 -580
  124. claude_mpm/dashboard/static/js/components/hud-library-loader.js +0 -211
  125. claude_mpm/dashboard/static/js/components/hud-manager.js +0 -671
  126. claude_mpm/dashboard/static/js/components/hud-visualizer.js +0 -1718
  127. claude_mpm/dashboard/static/js/components/module-viewer.js +0 -2764
  128. claude_mpm/dashboard/static/js/components/session-manager.js +0 -579
  129. claude_mpm/dashboard/static/js/components/socket-manager.js +0 -368
  130. claude_mpm/dashboard/static/js/components/ui-state-manager.js +0 -749
  131. claude_mpm/dashboard/static/js/components/unified-data-viewer.js +0 -1824
  132. claude_mpm/dashboard/static/js/components/working-directory.js +0 -920
  133. claude_mpm/dashboard/static/js/connection-manager.js +0 -536
  134. claude_mpm/dashboard/static/js/dashboard.js +0 -1914
  135. claude_mpm/dashboard/static/js/extension-error-handler.js +0 -164
  136. claude_mpm/dashboard/static/js/socket-client.js +0 -1474
  137. claude_mpm/dashboard/static/js/tab-isolation-fix.js +0 -185
  138. claude_mpm/dashboard/static/socket.io.min.js +0 -7
  139. claude_mpm/dashboard/static/socket.io.v4.8.1.backup.js +0 -7
  140. claude_mpm/dashboard/templates/code_simple.html +0 -153
  141. claude_mpm/dashboard/templates/index.html +0 -606
  142. claude_mpm/dashboard/test_dashboard.html +0 -372
  143. claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-313.pyc +0 -0
  144. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-313.pyc +0 -0
  145. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-313.pyc +0 -0
  146. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-313.pyc +0 -0
  147. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-313.pyc +0 -0
  148. claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-313.pyc +0 -0
  149. claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-313.pyc +0 -0
  150. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-313.pyc +0 -0
  151. claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-313.pyc +0 -0
  152. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-313.pyc +0 -0
  153. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-313.pyc +0 -0
  154. claude_mpm/scripts/mcp_server.py +0 -75
  155. claude_mpm/scripts/mcp_wrapper.py +0 -39
  156. claude_mpm/services/mcp_gateway/__init__.py +0 -159
  157. claude_mpm/services/mcp_gateway/auto_configure.py +0 -369
  158. claude_mpm/services/mcp_gateway/config/__init__.py +0 -17
  159. claude_mpm/services/mcp_gateway/config/config_loader.py +0 -296
  160. claude_mpm/services/mcp_gateway/config/config_schema.py +0 -243
  161. claude_mpm/services/mcp_gateway/config/configuration.py +0 -429
  162. claude_mpm/services/mcp_gateway/core/__init__.py +0 -43
  163. claude_mpm/services/mcp_gateway/core/base.py +0 -312
  164. claude_mpm/services/mcp_gateway/core/exceptions.py +0 -253
  165. claude_mpm/services/mcp_gateway/core/interfaces.py +0 -443
  166. claude_mpm/services/mcp_gateway/core/process_pool.py +0 -977
  167. claude_mpm/services/mcp_gateway/core/singleton_manager.py +0 -315
  168. claude_mpm/services/mcp_gateway/core/startup_verification.py +0 -316
  169. claude_mpm/services/mcp_gateway/main.py +0 -589
  170. claude_mpm/services/mcp_gateway/registry/__init__.py +0 -12
  171. claude_mpm/services/mcp_gateway/registry/service_registry.py +0 -412
  172. claude_mpm/services/mcp_gateway/registry/tool_registry.py +0 -489
  173. claude_mpm/services/mcp_gateway/server/__init__.py +0 -15
  174. claude_mpm/services/mcp_gateway/server/mcp_gateway.py +0 -414
  175. claude_mpm/services/mcp_gateway/server/stdio_handler.py +0 -372
  176. claude_mpm/services/mcp_gateway/server/stdio_server.py +0 -712
  177. claude_mpm/services/mcp_gateway/tools/__init__.py +0 -36
  178. claude_mpm/services/mcp_gateway/tools/base_adapter.py +0 -485
  179. claude_mpm/services/mcp_gateway/tools/document_summarizer.py +0 -789
  180. claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +0 -654
  181. claude_mpm/services/mcp_gateway/tools/health_check_tool.py +0 -456
  182. claude_mpm/services/mcp_gateway/tools/hello_world.py +0 -551
  183. claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +0 -555
  184. claude_mpm/services/mcp_gateway/utils/__init__.py +0 -14
  185. claude_mpm/services/mcp_gateway/utils/package_version_checker.py +0 -160
  186. claude_mpm/services/mcp_gateway/utils/update_preferences.py +0 -170
  187. claude_mpm-5.1.8.dist-info/entry_points.txt +0 -10
  188. claude_mpm-5.1.8.dist-info/licenses/LICENSE +0 -21
  189. /claude_mpm/agents/{OUTPUT_STYLE.md → CLAUDE_MPM_OUTPUT_STYLE.md} +0 -0
  190. {claude_mpm-5.1.8.dist-info → claude_mpm-5.4.22.dist-info}/WHEEL +0 -0
  191. {claude_mpm-5.1.8.dist-info → claude_mpm-5.4.22.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;