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,2764 +0,0 @@
1
- /**
2
- * Module Viewer Component
3
- * Displays detailed information about selected events organized by class/type
4
- * Now uses UnifiedDataViewer for consistent data formatting
5
- */
6
-
7
- import { UnifiedDataViewer } from './unified-data-viewer.js';
8
-
9
- class ModuleViewer {
10
- constructor(containerId) {
11
- this.container = document.getElementById(containerId);
12
- this.dataContainer = null;
13
- this.jsonContainer = null;
14
- this.currentEvent = null;
15
- this.eventsByClass = new Map();
16
-
17
- // Global JSON visibility state - persisted across all events
18
- // When true, all events show JSON expanded; when false, all collapsed
19
- this.globalJsonExpanded = localStorage.getItem('dashboard-json-expanded') === 'true';
20
-
21
- // Separate state for Full Event Data sections
22
- this.fullEventDataExpanded = localStorage.getItem('dashboard-full-event-expanded') === 'true';
23
-
24
- // Track if keyboard listener has been added to avoid duplicates
25
- this.keyboardListenerAdded = false;
26
-
27
- // Initialize unified data viewer
28
- this.unifiedViewer = new UnifiedDataViewer('module-data-content');
29
-
30
- // Sync unified viewer's JSON state with module viewer's state
31
- // This ensures both viewers maintain consistent JSON visibility
32
- this.unifiedViewer.globalJsonExpanded = this.globalJsonExpanded;
33
- this.unifiedViewer.fullEventDataExpanded = this.fullEventDataExpanded;
34
-
35
- // Listen for JSON state changes from unified viewer
36
- document.addEventListener('jsonToggleChanged', (e) => {
37
- this.globalJsonExpanded = e.detail.expanded;
38
- // Also update the unified viewer's state if it's different
39
- if (this.unifiedViewer.globalJsonExpanded !== e.detail.expanded) {
40
- this.unifiedViewer.globalJsonExpanded = e.detail.expanded;
41
- }
42
- });
43
-
44
- // Listen for Full Event Data state changes
45
- document.addEventListener('fullEventToggleChanged', (e) => {
46
- this.fullEventDataExpanded = e.detail.expanded;
47
- // Also update the unified viewer's state if it's different
48
- if (this.unifiedViewer.fullEventDataExpanded !== e.detail.expanded) {
49
- this.unifiedViewer.fullEventDataExpanded = e.detail.expanded;
50
- }
51
- });
52
-
53
- this.init();
54
- }
55
-
56
- /**
57
- * Initialize the module viewer
58
- */
59
- init() {
60
- this.setupContainers();
61
- this.setupEventHandlers();
62
- this.showEmptyState();
63
- }
64
-
65
- /**
66
- * Setup container references for the two-pane layout
67
- */
68
- setupContainers() {
69
- this.dataContainer = document.getElementById('module-data-content');
70
- this.jsonContainer = null; // No longer used - JSON is handled via collapsible sections
71
-
72
- if (!this.dataContainer) {
73
- console.error('Module viewer data container not found');
74
- }
75
- }
76
-
77
- /**
78
- * Setup event handlers
79
- */
80
- setupEventHandlers() {
81
- // Listen for event selection
82
- document.addEventListener('eventSelected', (e) => {
83
- this.showEventDetails(e.detail.event);
84
- });
85
-
86
- // Listen for selection cleared
87
- document.addEventListener('eventSelectionCleared', () => {
88
- this.showEmptyState();
89
- });
90
-
91
- // Listen for socket event updates to maintain event classification
92
- document.addEventListener('socketEventUpdate', (e) => {
93
- this.updateEventsByClass(e.detail.events);
94
- });
95
- }
96
-
97
- /**
98
- * Show empty state when no event is selected
99
- */
100
- showEmptyState() {
101
- if (this.dataContainer) {
102
- this.dataContainer.innerHTML = `
103
- <div class="module-empty">
104
- <p>Click on an event to view structured data</p>
105
- <p class="module-hint">Data is organized by event type</p>
106
- </div>
107
- `;
108
- }
109
-
110
- // JSON container no longer exists - handled via collapsible sections
111
-
112
- this.currentEvent = null;
113
- }
114
-
115
- /**
116
- * Show details for a selected event using UnifiedDataViewer
117
- * @param {Object} event - The selected event
118
- */
119
- showEventDetails(event) {
120
- this.currentEvent = event;
121
-
122
- if (!this.unifiedViewer) {
123
- console.warn('ModuleViewer: UnifiedDataViewer not available');
124
- // Fallback to legacy rendering
125
- this.renderStructuredData(event);
126
- this.renderJsonData(event);
127
- return;
128
- }
129
-
130
- // Use unified viewer to display event data
131
- this.unifiedViewer.display(event, 'event');
132
- }
133
-
134
- /**
135
- * Render structured data in the data pane with collapsible JSON section
136
- * @param {Object} event - The event to render
137
- */
138
- renderStructuredData(event) {
139
- if (!this.dataContainer) return;
140
-
141
- // Create contextual header
142
- const contextualHeader = this.createContextualHeader(event);
143
-
144
- // Create structured view based on event type
145
- const structuredView = this.createEventStructuredView(event);
146
-
147
- // Create collapsible JSON section
148
- const collapsibleJsonSection = this.createCollapsibleJsonSection(event);
149
-
150
- // Combine all sections in data container
151
- this.dataContainer.innerHTML = contextualHeader + structuredView + collapsibleJsonSection;
152
-
153
- // Initialize JSON toggle functionality
154
- this.initializeJsonToggle();
155
- }
156
-
157
- /**
158
- * Render JSON data in the JSON pane (legacy support - now using collapsible section)
159
- * @param {Object} event - The event to render
160
- */
161
- renderJsonData(event) {
162
- // JSON is now integrated into data container as collapsible section
163
- // Hide the JSON pane completely by clearing it
164
- // JSON container no longer exists - handled via collapsible sections
165
- }
166
-
167
- /**
168
- * Ingest method that determines how to render event(s)
169
- * @param {Object|Array} eventData - Single event or array of events
170
- */
171
- ingest(eventData) {
172
- if (Array.isArray(eventData)) {
173
- // Handle multiple events - for now, show the first one
174
- if (eventData.length > 0) {
175
- this.showEventDetails(eventData[0]);
176
- } else {
177
- this.showEmptyState();
178
- }
179
- } else if (eventData && typeof eventData === 'object') {
180
- // Handle single event
181
- this.showEventDetails(eventData);
182
- } else {
183
- // Invalid data
184
- this.showEmptyState();
185
- }
186
- }
187
-
188
- /**
189
- * Update events grouped by class for analysis
190
- * @param {Array} events - All events
191
- */
192
- updateEventsByClass(events) {
193
- this.eventsByClass.clear();
194
-
195
- events.forEach(event => {
196
- const eventClass = this.getEventClass(event);
197
- if (!this.eventsByClass.has(eventClass)) {
198
- this.eventsByClass.set(eventClass, []);
199
- }
200
- this.eventsByClass.get(eventClass).push(event);
201
- });
202
- }
203
-
204
- /**
205
- * Get event class/category for grouping
206
- * @param {Object} event - Event object
207
- * @returns {string} Event class
208
- */
209
- getEventClass(event) {
210
- if (!event.type) return 'unknown';
211
-
212
- // Group similar event types
213
- switch (event.type) {
214
- case 'session':
215
- return 'Session Management';
216
- case 'claude':
217
- return 'Claude Interactions';
218
- case 'agent':
219
- return 'Agent Operations';
220
- case 'hook':
221
- return 'Hook System';
222
- case 'todo':
223
- return 'Task Management';
224
- case 'memory':
225
- return 'Memory Operations';
226
- case 'log':
227
- return 'System Logs';
228
- case 'connection':
229
- return 'Connection Events';
230
- default:
231
- return 'Other Events';
232
- }
233
- }
234
-
235
- /**
236
- * Create contextual header for the structured data
237
- * @param {Object} event - Event to display
238
- * @returns {string} HTML content
239
- */
240
- createContextualHeader(event) {
241
- const timestamp = this.formatTimestamp(event.timestamp);
242
- const data = event.data || {};
243
- let headerText = '';
244
-
245
- // Determine header text based on event type
246
- switch (event.type) {
247
- case 'hook':
248
- // For Tools: "ToolName: [Agent] [time]"
249
- const toolName = this.extractToolName(data);
250
- const agent = this.extractAgent(event) || 'Unknown';
251
- if (toolName) {
252
- headerText = `${toolName}: ${agent} ${timestamp}`;
253
- } else {
254
- const hookName = this.getHookDisplayName(event, data);
255
- headerText = `${hookName}: ${agent} ${timestamp}`;
256
- }
257
- break;
258
-
259
- case 'agent':
260
- // For Agents: "Agent: [AgentType] [time]"
261
- const agentType = data.agent_type || data.name || 'Unknown';
262
- headerText = `Agent: ${agentType} ${timestamp}`;
263
- break;
264
-
265
- case 'todo':
266
- // For TodoWrite: "TodoWrite: [Agent] [time]"
267
- const todoAgent = this.extractAgent(event) || 'PM';
268
- headerText = `TodoWrite: ${todoAgent} ${timestamp}`;
269
- break;
270
-
271
- case 'memory':
272
- // For Memory: "Memory: [Operation] [time]"
273
- const operation = data.operation || 'Unknown';
274
- headerText = `Memory: ${operation} ${timestamp}`;
275
- break;
276
-
277
- case 'session':
278
- case 'claude':
279
- case 'log':
280
- case 'connection':
281
- // For Events: "Event: [Type.Subtype] [time]"
282
- const eventType = event.type;
283
- const subtype = event.subtype || 'default';
284
- headerText = `Event: ${eventType}.${subtype} ${timestamp}`;
285
- break;
286
-
287
- default:
288
- // For Files and other events: "File: [filename] [time]" or generic
289
- const fileName = this.extractFileName(data);
290
- if (fileName) {
291
- headerText = `File: ${fileName} ${timestamp}`;
292
- } else {
293
- const eventType = event.type || 'Unknown';
294
- const subtype = event.subtype || 'default';
295
- headerText = `Event: ${eventType}.${subtype} ${timestamp}`;
296
- }
297
- break;
298
- }
299
-
300
- return `
301
- <div class="contextual-header">
302
- <h3 class="contextual-header-text">${headerText}</h3>
303
- </div>
304
- `;
305
- }
306
-
307
- /**
308
- * Create structured view for an event
309
- * @param {Object} event - Event to display
310
- * @returns {string} HTML content
311
- */
312
- createEventStructuredView(event) {
313
- const eventClass = this.getEventClass(event);
314
- const relatedEvents = this.eventsByClass.get(eventClass) || [];
315
- const eventCount = relatedEvents.length;
316
-
317
- let content = `
318
- <div class="structured-view-section">
319
- ${this.createEventDetailCard(event.type, event, eventCount)}
320
- </div>
321
- `;
322
-
323
- // Add type-specific content
324
- switch (event.type) {
325
- case 'agent':
326
- content += this.createAgentStructuredView(event);
327
- break;
328
- case 'hook':
329
- // Check if this is actually a Task delegation (agent-related hook)
330
- if (event.data?.tool_name === 'Task' && event.data?.tool_parameters?.subagent_type) {
331
- content += this.createAgentStructuredView(event);
332
- } else {
333
- content += this.createHookStructuredView(event);
334
- }
335
- break;
336
- case 'todo':
337
- content += this.createTodoStructuredView(event);
338
- break;
339
- case 'memory':
340
- content += this.createMemoryStructuredView(event);
341
- break;
342
- case 'claude':
343
- content += this.createClaudeStructuredView(event);
344
- break;
345
- case 'session':
346
- content += this.createSessionStructuredView(event);
347
- break;
348
- default:
349
- content += this.createGenericStructuredView(event);
350
- break;
351
- }
352
-
353
- // Note: JSON section is now rendered separately in the JSON pane
354
- return content;
355
- }
356
-
357
- /**
358
- * Create event detail card
359
- */
360
- createEventDetailCard(eventType, event, count) {
361
- const timestamp = new Date(event.timestamp).toLocaleString();
362
- const eventIcon = this.getEventIcon(eventType);
363
-
364
- return `
365
- <div class="event-detail-card">
366
- <div class="event-detail-header">
367
- <div class="event-detail-title">
368
- ${eventIcon} ${eventType || 'Unknown'}.${event.subtype || 'default'}
369
- </div>
370
- <div class="event-detail-time">${timestamp}</div>
371
- </div>
372
- <div class="event-detail-content">
373
- ${this.createProperty('Event ID', event.id || 'N/A')}
374
- ${this.createProperty('Type', `${eventType}.${event.subtype || 'default'}`)}
375
- ${this.createProperty('Class Events', count)}
376
- ${event.data && event.data.session_id ?
377
- this.createProperty('Session', event.data.session_id) : ''}
378
- </div>
379
- </div>
380
- `;
381
- }
382
-
383
- /**
384
- * Create agent-specific structured view
385
- */
386
- createAgentStructuredView(event) {
387
- const data = event.data || {};
388
-
389
- // Handle Task delegation events (which appear as hook events but contain agent info)
390
- if (event.type === 'hook' && data.tool_name === 'Task' && data.tool_parameters?.subagent_type) {
391
- const taskData = data.tool_parameters;
392
- return `
393
- <div class="structured-view-section">
394
- <div class="structured-data">
395
- ${this.createProperty('Agent Type', taskData.subagent_type)}
396
- ${this.createProperty('Task Type', 'Subagent Delegation')}
397
- ${this.createProperty('Phase', event.subtype || 'pre_tool')}
398
- ${taskData.description ? this.createProperty('Description', taskData.description) : ''}
399
- ${taskData.prompt ? this.createProperty('Prompt Preview', this.truncateText(taskData.prompt, 200)) : ''}
400
- ${data.session_id ? this.createProperty('Session ID', data.session_id) : ''}
401
- ${data.working_directory ? this.createProperty('Working Directory', data.working_directory) : ''}
402
- </div>
403
- ${taskData.prompt ? `
404
- <div class="prompt-section">
405
- <div class="contextual-header">
406
- <h3 class="contextual-header-text">📝 Task Prompt</h3>
407
- </div>
408
- <div class="structured-data">
409
- <div class="task-prompt" style="white-space: pre-wrap; max-height: 300px; overflow-y: auto; padding: 10px; background: #f8fafc; border-radius: 6px; font-family: monospace; font-size: 12px; line-height: 1.4;">
410
- ${taskData.prompt}
411
- </div>
412
- </div>
413
- </div>
414
- ` : ''}
415
- </div>
416
- `;
417
- }
418
-
419
- // Handle regular agent events
420
- return `
421
- <div class="structured-view-section">
422
- <div class="structured-data">
423
- ${this.createProperty('Agent Type', data.agent_type || data.subagent_type || 'Unknown')}
424
- ${this.createProperty('Name', data.name || 'N/A')}
425
- ${this.createProperty('Phase', event.subtype || 'N/A')}
426
- ${data.config ? this.createProperty('Config', typeof data.config === 'object' ? Object.keys(data.config).join(', ') : String(data.config)) : ''}
427
- ${data.capabilities ? this.createProperty('Capabilities', data.capabilities.join(', ')) : ''}
428
- ${data.result ? this.createProperty('Result', typeof data.result === 'object' ? '[Object]' : String(data.result)) : ''}
429
- </div>
430
- </div>
431
- `;
432
- }
433
-
434
- /**
435
- * Create hook-specific structured view
436
- */
437
- createHookStructuredView(event) {
438
- const data = event.data || {};
439
-
440
- // Extract file path information from tool parameters
441
- const filePath = this.extractFilePathFromHook(data);
442
- const toolInfo = this.extractToolInfoFromHook(data);
443
-
444
- // Note: Git diff functionality moved to Files tab only
445
- // Events tab no longer shows git diff buttons
446
-
447
- // Create inline tool result content if available (without separate section header)
448
- const toolResultContent = this.createInlineToolResultContent(data, event);
449
-
450
- return `
451
- <div class="structured-view-section">
452
- <div class="structured-data">
453
- ${this.createProperty('Hook Name', this.getHookDisplayName(event, data))}
454
- ${this.createProperty('Event Type', data.event_type || event.subtype || 'N/A')}
455
- ${filePath ? this.createProperty('File Path', filePath) : ''}
456
- ${toolInfo.tool_name ? this.createProperty('Tool', toolInfo.tool_name) : ''}
457
- ${toolInfo.operation_type ? this.createProperty('Operation', toolInfo.operation_type) : ''}
458
- ${data.session_id ? this.createProperty('Session ID', data.session_id) : ''}
459
- ${data.working_directory ? this.createProperty('Working Directory', data.working_directory) : ''}
460
- ${data.duration_ms ? this.createProperty('Duration', `${data.duration_ms}ms`) : ''}
461
- ${toolResultContent}
462
- </div>
463
- </div>
464
- `;
465
- }
466
-
467
- /**
468
- * Create inline tool result content (no separate section header)
469
- * @param {Object} data - Event data
470
- * @param {Object} event - Full event object (optional, for phase checking)
471
- * @returns {string} HTML content for inline tool result display
472
- */
473
- createInlineToolResultContent(data, event = null) {
474
- const resultSummary = data.result_summary;
475
-
476
- // Determine if this is a post-tool event
477
- // Check multiple possible locations for the event phase
478
- const eventPhase = event?.subtype || data.event_type || data.phase;
479
- const isPostTool = eventPhase === 'post_tool' || eventPhase?.includes('post');
480
-
481
- // Debug logging to help troubleshoot tool result display issues
482
- if (window.DEBUG_TOOL_RESULTS) {
483
- console.log('🔧 createInlineToolResultContent debug:', {
484
- hasResultSummary: !!resultSummary,
485
- eventPhase,
486
- isPostTool,
487
- eventSubtype: event?.subtype,
488
- dataEventType: data.event_type,
489
- dataPhase: data.phase,
490
- toolName: data.tool_name,
491
- resultSummaryKeys: resultSummary ? Object.keys(resultSummary) : []
492
- });
493
- }
494
-
495
- // Only show results if we have result data and this is a post-tool event
496
- // OR if we have result_summary regardless of phase (some events may not have proper phase info)
497
- if (!resultSummary) {
498
- return '';
499
- }
500
-
501
- // If we know this is a pre-tool event, don't show results
502
- if (eventPhase === 'pre_tool' || (eventPhase?.includes('pre') && !eventPhase?.includes('post'))) {
503
- return '';
504
- }
505
-
506
- let resultContent = '';
507
-
508
- // Add output preview if available
509
- if (resultSummary.has_output && resultSummary.output_preview) {
510
- resultContent += `
511
- ${this.createProperty('Output', this.truncateText(resultSummary.output_preview, 200))}
512
- ${resultSummary.output_lines ? this.createProperty('Output Lines', resultSummary.output_lines) : ''}
513
- `;
514
- }
515
-
516
- // Add error preview if available
517
- if (resultSummary.has_error && resultSummary.error_preview) {
518
- resultContent += `
519
- ${this.createProperty('Error', this.truncateText(resultSummary.error_preview, 200))}
520
- `;
521
- }
522
-
523
- // If no specific output or error, but we have other result info
524
- if (!resultSummary.has_output && !resultSummary.has_error && Object.keys(resultSummary).length > 3) {
525
- // Show other result fields
526
- const otherFields = Object.entries(resultSummary)
527
- .filter(([key, value]) => !['has_output', 'has_error', 'exit_code'].includes(key) && value !== undefined)
528
- .map(([key, value]) => this.createProperty(this.formatFieldName(key), String(value)))
529
- .join('');
530
-
531
- resultContent += otherFields;
532
- }
533
-
534
- return resultContent;
535
- }
536
-
537
- /**
538
- * Create tool result section if result data is available
539
- * @param {Object} data - Event data
540
- * @param {Object} event - Full event object (optional, for phase checking)
541
- * @returns {string} HTML content for tool result section
542
- */
543
- createToolResultSection(data, event = null) {
544
- const resultSummary = data.result_summary;
545
-
546
- // Determine if this is a post-tool event
547
- // Check multiple possible locations for the event phase
548
- const eventPhase = event?.subtype || data.event_type || data.phase;
549
- const isPostTool = eventPhase === 'post_tool' || eventPhase?.includes('post');
550
-
551
- // Debug logging to help troubleshoot tool result display issues
552
- if (window.DEBUG_TOOL_RESULTS) {
553
- console.log('🔧 createToolResultSection debug:', {
554
- hasResultSummary: !!resultSummary,
555
- eventPhase,
556
- isPostTool,
557
- eventSubtype: event?.subtype,
558
- dataEventType: data.event_type,
559
- dataPhase: data.phase,
560
- toolName: data.tool_name,
561
- resultSummaryKeys: resultSummary ? Object.keys(resultSummary) : []
562
- });
563
- }
564
-
565
- // Only show results if we have result data and this is a post-tool event
566
- // OR if we have result_summary regardless of phase (some events may not have proper phase info)
567
- if (!resultSummary) {
568
- return '';
569
- }
570
-
571
- // If we know this is a pre-tool event, don't show results
572
- if (eventPhase === 'pre_tool' || (eventPhase?.includes('pre') && !eventPhase?.includes('post'))) {
573
- return '';
574
- }
575
-
576
- // Determine result status and icon
577
- let statusIcon = '⏳';
578
- let statusClass = 'tool-running';
579
- let statusText = 'Unknown';
580
-
581
- if (data.success === true) {
582
- statusIcon = '✅';
583
- statusClass = 'tool-success';
584
- statusText = 'Success';
585
- } else if (data.success === false) {
586
- statusIcon = '❌';
587
- statusClass = 'tool-failure';
588
- statusText = 'Failed';
589
- } else if (data.exit_code === 0) {
590
- statusIcon = '✅';
591
- statusClass = 'tool-success';
592
- statusText = 'Completed';
593
- } else if (data.exit_code === 2) {
594
- statusIcon = '⚠️';
595
- statusClass = 'tool-blocked';
596
- statusText = 'Blocked';
597
- } else if (data.exit_code !== undefined && data.exit_code !== 0) {
598
- statusIcon = '❌';
599
- statusClass = 'tool-failure';
600
- statusText = 'Error';
601
- }
602
-
603
- let resultContent = '';
604
-
605
- // Add basic result info
606
- resultContent += `
607
- <div class="tool-result-status ${statusClass}">
608
- <span class="tool-result-icon">${statusIcon}</span>
609
- <span class="tool-result-text">${statusText}</span>
610
- ${data.exit_code !== undefined ? `<span class="tool-exit-code">Exit Code: ${data.exit_code}</span>` : ''}
611
- </div>
612
- `;
613
-
614
- // Add output preview if available
615
- if (resultSummary.has_output && resultSummary.output_preview) {
616
- resultContent += `
617
- <div class="tool-result-output">
618
- <div class="tool-result-label">📄 Output:</div>
619
- <div class="tool-result-preview">
620
- <pre>${this.escapeHtml(resultSummary.output_preview)}</pre>
621
- </div>
622
- ${resultSummary.output_lines ? `<div class="tool-result-meta">Lines: ${resultSummary.output_lines}</div>` : ''}
623
- </div>
624
- `;
625
- }
626
-
627
- // Add error preview if available
628
- if (resultSummary.has_error && resultSummary.error_preview) {
629
- resultContent += `
630
- <div class="tool-result-error">
631
- <div class="tool-result-label">⚠️ Error:</div>
632
- <div class="tool-result-preview error-preview">
633
- <pre>${this.escapeHtml(resultSummary.error_preview)}</pre>
634
- </div>
635
- </div>
636
- `;
637
- }
638
-
639
- // If no specific output or error, but we have other result info
640
- if (!resultSummary.has_output && !resultSummary.has_error && Object.keys(resultSummary).length > 3) {
641
- // Show other result fields
642
- const otherFields = Object.entries(resultSummary)
643
- .filter(([key, value]) => !['has_output', 'has_error', 'exit_code'].includes(key) && value !== undefined)
644
- .map(([key, value]) => this.createProperty(this.formatFieldName(key), String(value)))
645
- .join('');
646
-
647
- if (otherFields) {
648
- resultContent += `
649
- <div class="tool-result-other">
650
- <div class="tool-result-label">📊 Result Details:</div>
651
- <div class="structured-data">
652
- ${otherFields}
653
- </div>
654
- </div>
655
- `;
656
- }
657
- }
658
-
659
- // Only return content if we have something to show
660
- if (!resultContent.trim()) {
661
- return '';
662
- }
663
-
664
- return `
665
- <div class="tool-result-section">
666
- <div class="contextual-header">
667
- <h3 class="contextual-header-text">🔧 Tool Result</h3>
668
- </div>
669
- <div class="tool-result-content">
670
- ${resultContent}
671
- </div>
672
- </div>
673
- `;
674
- }
675
-
676
- /**
677
- * Check if this is a write operation that modifies files
678
- * @param {string} toolName - Name of the tool used
679
- * @param {Object} data - Event data
680
- * @returns {boolean} True if this is a write operation
681
- */
682
- isWriteOperation(toolName, data) {
683
- // Common write operation tool names
684
- const writeTools = [
685
- 'Write',
686
- 'Edit',
687
- 'MultiEdit',
688
- 'NotebookEdit'
689
- ];
690
-
691
- if (writeTools.includes(toolName)) {
692
- return true;
693
- }
694
-
695
- // Check for write-related parameters in the data
696
- if (data.tool_parameters) {
697
- const params = data.tool_parameters;
698
-
699
- // Check for content or editing parameters
700
- if (params.content || params.new_string || params.edits) {
701
- return true;
702
- }
703
-
704
- // Check for file modification indicators
705
- if (params.edit_mode && params.edit_mode !== 'read') {
706
- return true;
707
- }
708
- }
709
-
710
- // Check event subtype for write operations
711
- if (data.event_type === 'post_tool' || data.event_type === 'pre_tool') {
712
- // Additional heuristics based on tool usage patterns
713
- if (toolName && (
714
- toolName.toLowerCase().includes('write') ||
715
- toolName.toLowerCase().includes('edit') ||
716
- toolName.toLowerCase().includes('modify')
717
- )) {
718
- return true;
719
- }
720
- }
721
-
722
- return false;
723
- }
724
-
725
- /**
726
- * Determines if a file operation is read-only or modifies the file
727
- * @param {string} operation - The operation type
728
- * @returns {boolean} True if operation is read-only, false if it modifies the file
729
- */
730
- isReadOnlyOperation(operation) {
731
- if (!operation) return true; // Default to read-only for safety
732
-
733
- const readOnlyOperations = ['read'];
734
- const editOperations = ['write', 'edit', 'multiedit', 'create', 'delete', 'move', 'copy'];
735
-
736
- const opLower = operation.toLowerCase();
737
-
738
- // Explicitly read-only operations
739
- if (readOnlyOperations.includes(opLower)) {
740
- return true;
741
- }
742
-
743
- // Explicitly edit operations
744
- if (editOperations.includes(opLower)) {
745
- return false;
746
- }
747
-
748
- // Default to read-only for unknown operations
749
- return true;
750
- }
751
-
752
- /**
753
- * Create todo-specific structured view
754
- */
755
- createTodoStructuredView(event) {
756
- const data = event.data || {};
757
-
758
- let content = '';
759
-
760
- // Add todo checklist if available - start directly with checklist
761
- if (data.todos && Array.isArray(data.todos)) {
762
- content += `
763
- <div class="todo-checklist">
764
- ${data.todos.map(todo => `
765
- <div class="todo-item todo-${todo.status || 'pending'}">
766
- <span class="todo-status">${this.getTodoStatusIcon(todo.status)}</span>
767
- <span class="todo-content">${todo.content || 'No content'}</span>
768
- <span class="todo-priority priority-${todo.priority || 'medium'}">${this.getTodoPriorityIcon(todo.priority)}</span>
769
- </div>
770
- `).join('')}
771
- </div>
772
- `;
773
- }
774
-
775
- return content;
776
- }
777
-
778
- /**
779
- * Create memory-specific structured view
780
- */
781
- createMemoryStructuredView(event) {
782
- const data = event.data || {};
783
-
784
- return `
785
- <div class="structured-view-section">
786
- <div class="structured-data">
787
- ${this.createProperty('Operation', data.operation || 'Unknown')}
788
- ${this.createProperty('Key', data.key || 'N/A')}
789
- ${data.value ? this.createProperty('Value', typeof data.value === 'object' ? '[Object]' : String(data.value)) : ''}
790
- ${data.namespace ? this.createProperty('Namespace', data.namespace) : ''}
791
- ${data.metadata ? this.createProperty('Metadata', typeof data.metadata === 'object' ? '[Object]' : String(data.metadata)) : ''}
792
- </div>
793
- </div>
794
- `;
795
- }
796
-
797
- /**
798
- * Create Claude-specific structured view
799
- */
800
- createClaudeStructuredView(event) {
801
- const data = event.data || {};
802
-
803
- return `
804
- <div class="structured-view-section">
805
- <div class="structured-data">
806
- ${this.createProperty('Type', event.subtype || 'N/A')}
807
- ${data.prompt ? this.createProperty('Prompt', this.truncateText(data.prompt, 200)) : ''}
808
- ${data.message ? this.createProperty('Message', this.truncateText(data.message, 200)) : ''}
809
- ${data.response ? this.createProperty('Response', this.truncateText(data.response, 200)) : ''}
810
- ${data.content ? this.createProperty('Content', this.truncateText(data.content, 200)) : ''}
811
- ${data.tokens ? this.createProperty('Tokens', data.tokens) : ''}
812
- ${data.model ? this.createProperty('Model', data.model) : ''}
813
- </div>
814
- </div>
815
- `;
816
- }
817
-
818
- /**
819
- * Create session-specific structured view
820
- */
821
- createSessionStructuredView(event) {
822
- const data = event.data || {};
823
-
824
- return `
825
- <div class="structured-view-section">
826
- <div class="structured-data">
827
- ${this.createProperty('Action', event.subtype || 'N/A')}
828
- ${this.createProperty('Session ID', data.session_id || 'N/A')}
829
- ${data.working_directory ? this.createProperty('Working Dir', data.working_directory) : ''}
830
- ${data.git_branch ? this.createProperty('Git Branch', data.git_branch) : ''}
831
- ${data.agent_type ? this.createProperty('Agent Type', data.agent_type) : ''}
832
- </div>
833
- </div>
834
- `;
835
- }
836
-
837
- /**
838
- * Create generic structured view
839
- */
840
- createGenericStructuredView(event) {
841
- const data = event.data || {};
842
- const keys = Object.keys(data);
843
-
844
- if (keys.length === 0) {
845
- return '';
846
- }
847
-
848
- return `
849
- <div class="structured-view-section">
850
- <div class="structured-data">
851
- ${keys.map(key =>
852
- this.createProperty(key, typeof data[key] === 'object' ?
853
- '[Object]' : String(data[key]))
854
- ).join('')}
855
- </div>
856
- </div>
857
- `;
858
- }
859
-
860
- /**
861
- * Create collapsible JSON section that appears below main content
862
- * WHY: Uses global state to maintain consistent JSON visibility across all events
863
- * DESIGN DECISION: Sticky toggle improves debugging workflow by maintaining JSON
864
- * visibility preference as user navigates through different events
865
- * @param {Object} event - The event to render
866
- * @returns {string} HTML content
867
- */
868
- createCollapsibleJsonSection(event) {
869
- const uniqueId = 'json-section-' + Math.random().toString(36).substr(2, 9);
870
- const jsonString = this.formatJSON(event);
871
-
872
- // Use global state to determine initial visibility
873
- const isExpanded = this.globalJsonExpanded;
874
- const display = isExpanded ? 'block' : 'none';
875
- const arrow = isExpanded ? '▲' : '▼';
876
- const ariaExpanded = isExpanded ? 'true' : 'false';
877
-
878
- return `
879
- <div class="collapsible-json-section" id="${uniqueId}">
880
- <div class="json-toggle-header"
881
- onclick="window.moduleViewer.toggleJsonSection()"
882
- role="button"
883
- tabindex="0"
884
- aria-expanded="${ariaExpanded}"
885
- onkeydown="if(event.key==='Enter'||event.key===' '){window.moduleViewer.toggleJsonSection();event.preventDefault();}">
886
- <span class="json-toggle-text">Raw JSON</span>
887
- <span class="json-toggle-arrow">${arrow}</span>
888
- </div>
889
- <div class="json-content-collapsible" style="display: ${display};" aria-hidden="${!isExpanded}">
890
- <div class="json-display" onclick="window.moduleViewer.copyJsonToClipboard(event)">
891
- <pre>${jsonString}</pre>
892
- </div>
893
- </div>
894
- </div>
895
- `;
896
- }
897
-
898
- /**
899
- * Copy JSON content to clipboard
900
- * @param {Event} event - Click event
901
- */
902
- async copyJsonToClipboard(event) {
903
- // Only trigger on the copy icon area (top-right corner)
904
- const rect = event.currentTarget.getBoundingClientRect();
905
- const clickX = event.clientX - rect.left;
906
- const clickY = event.clientY - rect.top;
907
-
908
- // Check if click is in the top-right corner (copy icon area)
909
- if (clickX > rect.width - 50 && clickY < 30) {
910
- const preElement = event.currentTarget.querySelector('pre');
911
- if (preElement) {
912
- try {
913
- await navigator.clipboard.writeText(preElement.textContent);
914
- this.showNotification('JSON copied to clipboard', 'success');
915
- } catch (err) {
916
- console.error('Failed to copy JSON:', err);
917
- this.showNotification('Failed to copy JSON', 'error');
918
- }
919
- }
920
- event.stopPropagation();
921
- }
922
- }
923
-
924
- /**
925
- * Initialize JSON toggle functionality
926
- * WHY: Ensures newly rendered events respect the current global JSON visibility state
927
- */
928
- initializeJsonToggle() {
929
- // Make sure the moduleViewer is available globally for onclick handlers
930
- window.moduleViewer = this;
931
-
932
- // Apply global state to newly rendered JSON sections
933
- // This ensures new events respect the current global state
934
- if (this.globalJsonExpanded) {
935
- // Small delay to ensure DOM is ready
936
- setTimeout(() => {
937
- this.updateAllJsonSections();
938
- }, 0);
939
- }
940
-
941
- // Add keyboard navigation support (only add once to avoid duplicates)
942
- if (!this.keyboardListenerAdded) {
943
- this.keyboardListenerAdded = true;
944
- document.addEventListener('keydown', (e) => {
945
- if (e.target.classList.contains('json-toggle-header')) {
946
- if (e.key === 'Enter' || e.key === ' ') {
947
- this.toggleJsonSection();
948
- e.preventDefault();
949
- }
950
- }
951
- });
952
- }
953
- }
954
-
955
- /**
956
- * Toggle JSON section visibility globally - affects ALL events
957
- * WHY: Sticky toggle maintains user preference across all events for better debugging
958
- * DESIGN DECISION: Uses localStorage to persist preference across page refreshes
959
- */
960
- toggleJsonSection() {
961
- // Toggle the global state
962
- this.globalJsonExpanded = !this.globalJsonExpanded;
963
-
964
- // Persist the preference to localStorage
965
- localStorage.setItem('dashboard-json-expanded', this.globalJsonExpanded.toString());
966
-
967
- // Update ALL JSON sections on the page
968
- this.updateAllJsonSections();
969
-
970
- // Dispatch event to notify other components of the change
971
- document.dispatchEvent(new CustomEvent('jsonToggleChanged', {
972
- detail: { expanded: this.globalJsonExpanded }
973
- }));
974
- }
975
-
976
- /**
977
- * Update all JSON sections on the page to match global state
978
- * WHY: Ensures consistent JSON visibility across all displayed events
979
- */
980
- updateAllJsonSections() {
981
- // Find all JSON content sections and toggle headers
982
- const allJsonContents = document.querySelectorAll('.json-content-collapsible');
983
- const allArrows = document.querySelectorAll('.json-toggle-arrow');
984
- const allHeaders = document.querySelectorAll('.json-toggle-header');
985
-
986
- // Update each JSON section
987
- allJsonContents.forEach((jsonContent, index) => {
988
- if (this.globalJsonExpanded) {
989
- // Show JSON content
990
- jsonContent.style.display = 'block';
991
- jsonContent.setAttribute('aria-hidden', 'false');
992
- if (allArrows[index]) {
993
- allArrows[index].textContent = '▲';
994
- }
995
- if (allHeaders[index]) {
996
- allHeaders[index].setAttribute('aria-expanded', 'true');
997
- }
998
- } else {
999
- // Hide JSON content
1000
- jsonContent.style.display = 'none';
1001
- jsonContent.setAttribute('aria-hidden', 'true');
1002
- if (allArrows[index]) {
1003
- allArrows[index].textContent = '▼';
1004
- }
1005
- if (allHeaders[index]) {
1006
- allHeaders[index].setAttribute('aria-expanded', 'false');
1007
- }
1008
- }
1009
- });
1010
-
1011
- // If expanded and there's content, scroll the first visible one into view
1012
- if (this.globalJsonExpanded && allJsonContents.length > 0) {
1013
- setTimeout(() => {
1014
- const firstVisible = allJsonContents[0];
1015
- if (firstVisible) {
1016
- firstVisible.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
1017
- }
1018
- }, 100);
1019
- }
1020
- }
1021
-
1022
- /**
1023
- * Create a property display element with optional file path detection
1024
- */
1025
- createProperty(key, value) {
1026
- const displayValue = this.truncateText(String(value), 300);
1027
-
1028
- // Check if this is a file path property that should be clickable
1029
- if (this.isFilePathProperty(key, value)) {
1030
- return `
1031
- <div class="event-property">
1032
- <span class="event-property-key">${key}:</span>
1033
- <span class="event-property-value">
1034
- ${this.createClickableFilePath(value)}
1035
- </span>
1036
- </div>
1037
- `;
1038
- }
1039
-
1040
- return `
1041
- <div class="event-property">
1042
- <span class="event-property-key">${key}:</span>
1043
- <span class="event-property-value">${displayValue}</span>
1044
- </div>
1045
- `;
1046
- }
1047
-
1048
- /**
1049
- * Check if a property represents a file path that should be clickable
1050
- * @param {string} key - Property key
1051
- * @param {string} value - Property value
1052
- * @returns {boolean} True if this should be a clickable file path
1053
- */
1054
- isFilePathProperty(key, value) {
1055
- const filePathKeys = [
1056
- 'File Path',
1057
- 'file_path',
1058
- 'notebook_path',
1059
- 'Full Path',
1060
- 'Working Directory',
1061
- 'working_directory'
1062
- ];
1063
-
1064
- // Check if key indicates a file path
1065
- if (filePathKeys.some(pathKey => key.toLowerCase().includes(pathKey.toLowerCase()))) {
1066
- // Ensure value looks like a file path (contains / or \\ and has reasonable length)
1067
- const strValue = String(value);
1068
- return strValue.length > 0 &&
1069
- (strValue.includes('/') || strValue.includes('\\')) &&
1070
- strValue.length < 500; // Reasonable path length limit
1071
- }
1072
-
1073
- return false;
1074
- }
1075
-
1076
- /**
1077
- * Create a clickable file path element
1078
- * @param {string} filePath - The file path to make clickable
1079
- * @returns {string} HTML for clickable file path
1080
- */
1081
- createClickableFilePath(filePath) {
1082
- const displayPath = this.truncateText(String(filePath), 300);
1083
- const escapedPath = filePath.replace(/'/g, "\\'");
1084
-
1085
- return `
1086
- <span class="clickable-file-path"
1087
- onclick="showFileViewerModal('${escapedPath}')"
1088
- title="Click to view file contents with syntax highlighting&#10;Path: ${filePath}">
1089
- ${displayPath}
1090
- </span>
1091
- `;
1092
- }
1093
-
1094
- /**
1095
- * Get icon for event type
1096
- */
1097
- getEventIcon(eventType) {
1098
- const icons = {
1099
- session: '📱',
1100
- claude: '🤖',
1101
- agent: '🎯',
1102
- hook: '🔗',
1103
- todo: '✅',
1104
- memory: '🧠',
1105
- log: '📝',
1106
- connection: '🔌',
1107
- unknown: '❓'
1108
- };
1109
- return icons[eventType] || icons.unknown;
1110
- }
1111
-
1112
- /**
1113
- * Get todo status icon
1114
- */
1115
- getTodoStatusIcon(status) {
1116
- const icons = {
1117
- completed: '✅',
1118
- 'in_progress': '🔄',
1119
- pending: '⏳',
1120
- cancelled: '❌'
1121
- };
1122
- return icons[status] || icons.pending;
1123
- }
1124
-
1125
- /**
1126
- * Get todo priority icon
1127
- */
1128
- getTodoPriorityIcon(priority) {
1129
- const icons = {
1130
- high: '🔴',
1131
- medium: '🟡',
1132
- low: '🟢'
1133
- };
1134
- return icons[priority] || icons.medium;
1135
- }
1136
-
1137
- /**
1138
- * Get meaningful hook display name from event data
1139
- */
1140
- getHookDisplayName(event, data) {
1141
- // First check if there's a specific hook name in the data
1142
- if (data.hook_name) return data.hook_name;
1143
- if (data.name) return data.name;
1144
-
1145
- // Use event.subtype or data.event_type to determine hook name
1146
- const eventType = event.subtype || data.event_type;
1147
-
1148
- // Map hook event types to meaningful display names
1149
- const hookNames = {
1150
- 'user_prompt': 'User Prompt',
1151
- 'pre_tool': 'Tool Execution (Pre)',
1152
- 'post_tool': 'Tool Execution (Post)',
1153
- 'notification': 'Notification',
1154
- 'stop': 'Session Stop',
1155
- 'subagent_stop': 'Subagent Stop'
1156
- };
1157
-
1158
- if (hookNames[eventType]) {
1159
- return hookNames[eventType];
1160
- }
1161
-
1162
- // If it's a compound event type like "hook.user_prompt", extract the part after "hook."
1163
- if (typeof event.type === 'string' && event.type.startsWith('hook.')) {
1164
- const hookType = event.type.replace('hook.', '');
1165
- if (hookNames[hookType]) {
1166
- return hookNames[hookType];
1167
- }
1168
- }
1169
-
1170
- // Fallback to formatting the event type nicely
1171
- if (eventType) {
1172
- return eventType.split('_')
1173
- .map(word => word.charAt(0).toUpperCase() + word.slice(1))
1174
- .join(' ');
1175
- }
1176
-
1177
- return 'Unknown Hook';
1178
- }
1179
-
1180
- /**
1181
- * Extract file path from hook event data
1182
- */
1183
- extractFilePathFromHook(data) {
1184
- // Check tool parameters for file path
1185
- if (data.tool_parameters && data.tool_parameters.file_path) {
1186
- return data.tool_parameters.file_path;
1187
- }
1188
-
1189
- // Check direct file_path field
1190
- if (data.file_path) {
1191
- return data.file_path;
1192
- }
1193
-
1194
- // Check nested in other common locations
1195
- if (data.tool_input && data.tool_input.file_path) {
1196
- return data.tool_input.file_path;
1197
- }
1198
-
1199
- // Check for notebook path (alternative field name)
1200
- if (data.tool_parameters && data.tool_parameters.notebook_path) {
1201
- return data.tool_parameters.notebook_path;
1202
- }
1203
-
1204
- return null;
1205
- }
1206
-
1207
- /**
1208
- * Extract tool information from hook event data
1209
- */
1210
- extractToolInfoFromHook(data) {
1211
- return {
1212
- tool_name: data.tool_name || (data.tool_parameters && data.tool_parameters.tool_name),
1213
- operation_type: data.operation_type || (data.tool_parameters && data.tool_parameters.operation_type)
1214
- };
1215
- }
1216
-
1217
- /**
1218
- * Truncate text to specified length
1219
- */
1220
- truncateText(text, maxLength) {
1221
- if (!text || text.length <= maxLength) return text;
1222
- return text.substring(0, maxLength) + '...';
1223
- }
1224
-
1225
- /**
1226
- * Format operation details object for display
1227
- * @param {Object} details - Details object containing operation information
1228
- * @returns {string} Formatted HTML string
1229
- */
1230
- formatOperationDetails(details) {
1231
- if (!details || typeof details !== 'object') {
1232
- return '';
1233
- }
1234
-
1235
- let formattedDetails = '';
1236
-
1237
- // Display the bash command if available
1238
- if (details.parameters && details.parameters.command) {
1239
- formattedDetails += `<br><strong>Command:</strong> <code>${this.escapeHtml(details.parameters.command)}</code>`;
1240
- }
1241
-
1242
- // Display success/error status
1243
- if (details.success !== undefined) {
1244
- formattedDetails += `<br><strong>Status:</strong> ${details.success ? '✅ Success' : '❌ Failed'}`;
1245
- }
1246
-
1247
- // Display exit code if available
1248
- if (details.exit_code !== undefined && details.exit_code !== null) {
1249
- formattedDetails += `<br><strong>Exit Code:</strong> ${details.exit_code}`;
1250
- }
1251
-
1252
- // Display duration if available
1253
- if (details.duration_ms !== undefined && details.duration_ms !== null) {
1254
- const duration = details.duration_ms > 1000
1255
- ? `${(details.duration_ms / 1000).toFixed(2)}s`
1256
- : `${details.duration_ms}ms`;
1257
- formattedDetails += `<br><strong>Duration:</strong> ${duration}`;
1258
- }
1259
-
1260
- // Display error message if available
1261
- if (details.error) {
1262
- formattedDetails += `<br><strong>Error:</strong> ${this.escapeHtml(this.truncateText(details.error, 200))}`;
1263
- }
1264
-
1265
- return formattedDetails;
1266
- }
1267
-
1268
- /**
1269
- * Escape HTML to prevent XSS
1270
- */
1271
- escapeHtml(text) {
1272
- if (!text) return '';
1273
- const div = document.createElement('div');
1274
- div.textContent = text;
1275
- return div.innerHTML;
1276
- }
1277
-
1278
- /**
1279
- * Format JSON for display
1280
- */
1281
- formatJSON(obj) {
1282
- try {
1283
- return JSON.stringify(obj, null, 2);
1284
- } catch (e) {
1285
- return String(obj);
1286
- }
1287
- }
1288
-
1289
- /**
1290
- * Format timestamp for display
1291
- * @param {string|number} timestamp - Timestamp to format
1292
- * @returns {string} Formatted time
1293
- */
1294
- formatTimestamp(timestamp) {
1295
- if (!timestamp) return 'Unknown time';
1296
-
1297
- try {
1298
- const date = new Date(timestamp);
1299
- return date.toLocaleTimeString('en-US', {
1300
- hour: 'numeric',
1301
- minute: '2-digit',
1302
- second: '2-digit',
1303
- hour12: true
1304
- });
1305
- } catch (e) {
1306
- return 'Invalid time';
1307
- }
1308
- }
1309
-
1310
- /**
1311
- * Escape HTML characters to prevent XSS
1312
- * @param {string} text - Text to escape
1313
- * @returns {string} Escaped text
1314
- */
1315
- escapeHtml(text) {
1316
- if (!text) return '';
1317
- const div = document.createElement('div');
1318
- div.textContent = text;
1319
- return div.innerHTML;
1320
- }
1321
-
1322
- /**
1323
- * Format field name for display (convert snake_case to Title Case)
1324
- * @param {string} fieldName - Field name to format
1325
- * @returns {string} Formatted field name
1326
- */
1327
- formatFieldName(fieldName) {
1328
- return fieldName
1329
- .split('_')
1330
- .map(word => word.charAt(0).toUpperCase() + word.slice(1))
1331
- .join(' ');
1332
- }
1333
-
1334
- /**
1335
- * Extract tool name from event data
1336
- * @param {Object} data - Event data
1337
- * @returns {string|null} Tool name
1338
- */
1339
- extractToolName(data) {
1340
- // Check various locations where tool name might be stored
1341
- if (data.tool_name) return data.tool_name;
1342
- if (data.tool_parameters && data.tool_parameters.tool_name) return data.tool_parameters.tool_name;
1343
- if (data.tool_input && data.tool_input.tool_name) return data.tool_input.tool_name;
1344
-
1345
- // Try to infer from other fields
1346
- if (data.tool_parameters) {
1347
- // Common tool patterns
1348
- if (data.tool_parameters.file_path || data.tool_parameters.notebook_path) {
1349
- return 'FileOperation';
1350
- }
1351
- if (data.tool_parameters.pattern) {
1352
- return 'Search';
1353
- }
1354
- if (data.tool_parameters.command) {
1355
- return 'Bash';
1356
- }
1357
- if (data.tool_parameters.todos) {
1358
- return 'TodoWrite';
1359
- }
1360
- }
1361
-
1362
- return null;
1363
- }
1364
-
1365
- /**
1366
- * Extract agent information from event data
1367
- * @param {Object} data - Event data
1368
- * @returns {string|null} Agent identifier
1369
- */
1370
- extractAgent(data) {
1371
- // First check if we have enhanced inference data from dashboard
1372
- if (data._agentName && data._agentName !== 'Unknown Agent') {
1373
- return data._agentName;
1374
- }
1375
-
1376
- // Check inference data if available
1377
- if (data._inference && data._inference.agentName && data._inference.agentName !== 'Unknown') {
1378
- return data._inference.agentName;
1379
- }
1380
-
1381
- // Check various locations where agent info might be stored
1382
- if (data.agent) return data.agent;
1383
- if (data.agent_type) return data.agent_type;
1384
- if (data.agent_name) return data.agent_name;
1385
-
1386
- // Check session data
1387
- if (data.session_id && typeof data.session_id === 'string') {
1388
- // Extract agent from session ID if it contains agent info
1389
- const sessionParts = data.session_id.split('_');
1390
- if (sessionParts.length > 1) {
1391
- return sessionParts[0].toUpperCase();
1392
- }
1393
- }
1394
-
1395
- // Infer from context
1396
- if (data.todos) return 'PM'; // TodoWrite typically from PM agent
1397
- if (data.tool_name === 'TodoWrite') return 'PM';
1398
-
1399
- return null;
1400
- }
1401
-
1402
- /**
1403
- * Extract file name from event data
1404
- * @param {Object} data - Event data
1405
- * @returns {string|null} File name
1406
- */
1407
- extractFileName(data) {
1408
- const filePath = this.extractFilePathFromHook(data);
1409
- if (filePath) {
1410
- // Extract just the filename from the full path
1411
- const pathParts = filePath.split('/');
1412
- return pathParts[pathParts.length - 1];
1413
- }
1414
-
1415
- // Check other common file fields
1416
- if (data.filename) return data.filename;
1417
- if (data.file) return data.file;
1418
-
1419
- return null;
1420
- }
1421
-
1422
- /**
1423
- * Clear the module viewer
1424
- */
1425
- clear() {
1426
- if (this.unifiedViewer) {
1427
- this.unifiedViewer.clear();
1428
- }
1429
- this.showEmptyState();
1430
- }
1431
-
1432
- /**
1433
- * Show tool call details (backward compatibility method)
1434
- * @param {Object} toolCall - The tool call data
1435
- * @param {string} toolCallKey - The tool call key
1436
- */
1437
- showToolCall(toolCall, toolCallKey) {
1438
- if (!toolCall) {
1439
- this.showEmptyState();
1440
- return;
1441
- }
1442
-
1443
- const toolName = toolCall.tool_name || 'Unknown Tool';
1444
- const agentName = toolCall.agent_type || 'PM';
1445
- const timestamp = this.formatTimestamp(toolCall.timestamp);
1446
-
1447
- // Extract information from pre and post events
1448
- const preEvent = toolCall.pre_event;
1449
- const postEvent = toolCall.post_event;
1450
-
1451
- // Get parameters from pre-event
1452
- const parameters = preEvent?.tool_parameters || {};
1453
- const target = preEvent ? this.extractToolTarget(toolName, parameters) : 'Unknown target';
1454
-
1455
- // Get execution results from post-event
1456
- const duration = toolCall.duration_ms ? `${toolCall.duration_ms}ms` : '-';
1457
- const success = toolCall.success !== undefined ? toolCall.success : null;
1458
- const exitCode = toolCall.exit_code !== undefined ? toolCall.exit_code : null;
1459
-
1460
- // Format result summary
1461
- let resultSummary = toolCall.result_summary || 'No summary available';
1462
- let formattedResultSummary = '';
1463
-
1464
- if (typeof resultSummary === 'object' && resultSummary !== null) {
1465
- const parts = [];
1466
- if (resultSummary.exit_code !== undefined) {
1467
- parts.push(`Exit Code: ${resultSummary.exit_code}`);
1468
- }
1469
- if (resultSummary.has_output !== undefined) {
1470
- parts.push(`Has Output: ${resultSummary.has_output ? 'Yes' : 'No'}`);
1471
- }
1472
- if (resultSummary.has_error !== undefined) {
1473
- parts.push(`Has Error: ${resultSummary.has_error ? 'Yes' : 'No'}`);
1474
- }
1475
- if (resultSummary.output_lines !== undefined) {
1476
- parts.push(`Output Lines: ${resultSummary.output_lines}`);
1477
- }
1478
- if (resultSummary.output_preview) {
1479
- parts.push(`Output Preview: ${resultSummary.output_preview}`);
1480
- }
1481
- if (resultSummary.error_preview) {
1482
- parts.push(`Error Preview: ${resultSummary.error_preview}`);
1483
- }
1484
- formattedResultSummary = parts.join('\n');
1485
- } else {
1486
- formattedResultSummary = String(resultSummary);
1487
- }
1488
-
1489
- // Status information
1490
- let statusIcon = '⏳';
1491
- let statusText = 'Running...';
1492
- let statusClass = 'tool-running';
1493
-
1494
- if (postEvent) {
1495
- if (success === true) {
1496
- statusIcon = '✅';
1497
- statusText = 'Success';
1498
- statusClass = 'tool-success';
1499
- } else if (success === false) {
1500
- statusIcon = '❌';
1501
- statusText = 'Failed';
1502
- statusClass = 'tool-failure';
1503
- } else {
1504
- statusIcon = '⏳';
1505
- statusText = 'Completed';
1506
- statusClass = 'tool-completed';
1507
- }
1508
- }
1509
-
1510
- // Create contextual header
1511
- const contextualHeader = `
1512
- <div class="contextual-header">
1513
- <h3 class="contextual-header-text">${toolName}: ${agentName} ${timestamp}</h3>
1514
- </div>
1515
- `;
1516
-
1517
- // Special handling for TodoWrite
1518
- if (toolName === 'TodoWrite' && parameters.todos) {
1519
- const todoContent = `
1520
- <div class="todo-checklist">
1521
- ${parameters.todos.map(todo => {
1522
- const statusIcon = this.getTodoStatusIcon(todo.status);
1523
- const priorityIcon = this.getTodoPriorityIcon(todo.priority);
1524
-
1525
- return `
1526
- <div class="todo-item todo-${todo.status || 'pending'}">
1527
- <span class="todo-status">${statusIcon}</span>
1528
- <span class="todo-content">${todo.content || 'No content'}</span>
1529
- <span class="todo-priority priority-${todo.priority || 'medium'}">${priorityIcon}</span>
1530
- </div>
1531
- `;
1532
- }).join('')}
1533
- </div>
1534
- `;
1535
-
1536
- // Create collapsible JSON section
1537
- const toolCallData = {
1538
- toolCall: toolCall,
1539
- preEvent: preEvent,
1540
- postEvent: postEvent
1541
- };
1542
- const collapsibleJsonSection = this.createCollapsibleJsonSection(toolCallData);
1543
-
1544
- if (this.dataContainer) {
1545
- this.dataContainer.innerHTML = contextualHeader + todoContent + collapsibleJsonSection;
1546
- }
1547
-
1548
- // Initialize JSON toggle functionality
1549
- this.initializeJsonToggle();
1550
- } else if (toolName === 'Grep' || toolName === 'Search' || (parameters && parameters.pattern && !parameters.file_path)) {
1551
- // Special handling for search operations (Grep tool or any tool with pattern parameter)
1552
- const searchPattern = parameters.pattern || 'No pattern specified';
1553
- const searchPath = parameters.path || parameters.directory || '.';
1554
- const searchType = parameters.type || parameters.glob || 'all files';
1555
-
1556
- // Extract search results from result_summary
1557
- let searchResultsContent = '';
1558
- if (toolCall.result_summary) {
1559
- if (typeof toolCall.result_summary === 'string') {
1560
- searchResultsContent = toolCall.result_summary;
1561
- } else if (toolCall.result_summary.output_preview) {
1562
- searchResultsContent = toolCall.result_summary.output_preview;
1563
- } else {
1564
- searchResultsContent = JSON.stringify(toolCall.result_summary, null, 2);
1565
- }
1566
- }
1567
-
1568
- const content = `
1569
- <div class="structured-view-section">
1570
- <div class="tool-call-details">
1571
- <div class="tool-call-info ${statusClass}">
1572
- <div class="structured-field">
1573
- <strong>Tool Name:</strong> ${toolName}
1574
- </div>
1575
- <div class="structured-field">
1576
- <strong>Agent:</strong> ${agentName}
1577
- </div>
1578
- <div class="structured-field">
1579
- <strong>Status:</strong> ${statusIcon} ${statusText}
1580
- </div>
1581
- <div class="structured-field">
1582
- <strong>Search Pattern:</strong> <code>${searchPattern}</code>
1583
- </div>
1584
- <div class="structured-field">
1585
- <strong>Search Path:</strong> ${searchPath}
1586
- </div>
1587
- <div class="structured-field">
1588
- <strong>File Type:</strong> ${searchType}
1589
- </div>
1590
- <div class="structured-field">
1591
- <strong>Started:</strong> ${new Date(toolCall.timestamp).toLocaleString()}
1592
- </div>
1593
- ${duration && duration !== '-' ? `
1594
- <div class="structured-field">
1595
- <strong>Duration:</strong> ${duration}
1596
- </div>
1597
- ` : ''}
1598
- </div>
1599
-
1600
- <div class="search-view-action" style="margin-top: 20px;">
1601
- <button class="btn-view-search" data-search-params='${JSON.stringify(parameters)}' data-search-results='${JSON.stringify(searchResultsContent).replace(/'/g, "&#39;")}' onclick="window.showSearchViewerModal(JSON.parse(this.getAttribute('data-search-params')), JSON.parse(this.getAttribute('data-search-results')))">
1602
- 🔍 View Search Details
1603
- </button>
1604
- </div>
1605
-
1606
- ${this.createToolResultFromToolCall(toolCall)}
1607
- </div>
1608
- </div>
1609
- `;
1610
-
1611
- // Create collapsible JSON section
1612
- const toolCallData = {
1613
- toolCall: toolCall,
1614
- preEvent: preEvent,
1615
- postEvent: postEvent
1616
- };
1617
- const collapsibleJsonSection = this.createCollapsibleJsonSection(toolCallData);
1618
-
1619
- if (this.dataContainer) {
1620
- this.dataContainer.innerHTML = contextualHeader + content + collapsibleJsonSection;
1621
- }
1622
-
1623
- // Initialize JSON toggle functionality
1624
- this.initializeJsonToggle();
1625
- } else {
1626
- // For other tools, show detailed information
1627
- const content = `
1628
- <div class="structured-view-section">
1629
- <div class="tool-call-details">
1630
- <div class="tool-call-info ${statusClass}">
1631
- <div class="structured-field">
1632
- <strong>Tool Name:</strong> ${toolName}
1633
- </div>
1634
- <div class="structured-field">
1635
- <strong>Agent:</strong> ${agentName}
1636
- </div>
1637
- <div class="structured-field">
1638
- <strong>Status:</strong> ${statusIcon} ${statusText}
1639
- </div>
1640
- <div class="structured-field">
1641
- <strong>Target:</strong> ${target}
1642
- </div>
1643
- <div class="structured-field">
1644
- <strong>Started:</strong> ${new Date(toolCall.timestamp).toLocaleString()}
1645
- </div>
1646
- ${duration && duration !== '-' ? `
1647
- <div class="structured-field">
1648
- <strong>Duration:</strong> ${duration}
1649
- </div>
1650
- ` : ''}
1651
- ${toolCall.session_id ? `
1652
- <div class="structured-field">
1653
- <strong>Session ID:</strong> ${toolCall.session_id}
1654
- </div>
1655
- ` : ''}
1656
- </div>
1657
-
1658
- ${this.createToolResultFromToolCall(toolCall)}
1659
- </div>
1660
- </div>
1661
- `;
1662
-
1663
- // Create collapsible JSON section
1664
- const toolCallData = {
1665
- toolCall: toolCall,
1666
- preEvent: preEvent,
1667
- postEvent: postEvent
1668
- };
1669
- const collapsibleJsonSection = this.createCollapsibleJsonSection(toolCallData);
1670
-
1671
- if (this.dataContainer) {
1672
- this.dataContainer.innerHTML = contextualHeader + content + collapsibleJsonSection;
1673
- }
1674
-
1675
- // Initialize JSON toggle functionality
1676
- this.initializeJsonToggle();
1677
- }
1678
-
1679
- // Hide JSON pane since data is integrated above
1680
- // JSON container no longer exists - handled via collapsible sections
1681
- }
1682
-
1683
- /**
1684
- * Show file operations using UnifiedDataViewer for consistency
1685
- * @param {Object} fileData - The file operations data
1686
- * @param {string} filePath - The file path
1687
- */
1688
- showFileOperations(fileData, filePath) {
1689
- if (!fileData || !filePath) {
1690
- this.showEmptyState();
1691
- return;
1692
- }
1693
-
1694
- // Initialize UnifiedDataViewer if not already available
1695
- if (!this.unifiedViewer) {
1696
- this.unifiedViewer = new UnifiedDataViewer('module-data-content');
1697
- }
1698
-
1699
- // Convert file data to standardized format
1700
- const standardizedFileData = {
1701
- file_path: filePath,
1702
- operations: fileData.operations || [],
1703
- lastOperation: fileData.lastOperation,
1704
- ...fileData // Preserve any additional data
1705
- };
1706
-
1707
- // Use UnifiedDataViewer for consistent display
1708
- this.unifiedViewer.display(standardizedFileData, 'file_operation');
1709
-
1710
- // Update module header for consistency
1711
- const moduleHeader = document.querySelector('.module-data-header h5');
1712
- if (moduleHeader) {
1713
- const fileName = filePath.split('/').pop() || filePath;
1714
- moduleHeader.textContent = `📄 File: ${fileName}`;
1715
- }
1716
-
1717
- // Check git tracking status and show track control if needed
1718
- this.checkAndShowTrackControl(filePath);
1719
- }
1720
-
1721
- /**
1722
- * Show error message (backward compatibility method)
1723
- * @param {string} title - Error title
1724
- * @param {string} message - Error message
1725
- */
1726
- showErrorMessage(title, message) {
1727
- const content = `
1728
- <div class="module-error">
1729
- <div class="error-header">
1730
- <h3>❌ ${title}</h3>
1731
- </div>
1732
- <div class="error-message">
1733
- <p>${message}</p>
1734
- </div>
1735
- </div>
1736
- `;
1737
-
1738
- // Create collapsible JSON section for error data
1739
- const errorData = { title, message };
1740
- const collapsibleJsonSection = this.createCollapsibleJsonSection(errorData);
1741
-
1742
- if (this.dataContainer) {
1743
- this.dataContainer.innerHTML = content + collapsibleJsonSection;
1744
- }
1745
-
1746
- // Initialize JSON toggle functionality
1747
- this.initializeJsonToggle();
1748
-
1749
- // JSON container no longer exists - handled via collapsible sections
1750
- }
1751
-
1752
- /**
1753
- * Show agent event details (backward compatibility method)
1754
- * @param {Object} event - The agent event
1755
- * @param {number} index - Event index
1756
- */
1757
- showAgentEvent(event, index) {
1758
- // Show comprehensive agent-specific data instead of just single event
1759
- this.showAgentSpecificDetails(event, index);
1760
- }
1761
-
1762
- /**
1763
- * Show comprehensive agent-specific details including prompt, todos, and tools
1764
- * @param {Object} event - The selected agent event
1765
- * @param {number} index - Event index
1766
- */
1767
- showAgentSpecificDetails(event, index) {
1768
- if (!event) {
1769
- this.showEmptyState();
1770
- return;
1771
- }
1772
-
1773
- // Get agent inference to determine which agent this is
1774
- const agentInference = window.dashboard?.agentInference;
1775
- const eventViewer = window.dashboard?.eventViewer;
1776
-
1777
- if (!agentInference || !eventViewer) {
1778
- console.warn('AgentInference or EventViewer not available, falling back to single event view');
1779
- this.showEventDetails(event);
1780
- return;
1781
- }
1782
-
1783
- const inference = agentInference.getInferredAgentForEvent(event);
1784
- const agentName = inference?.agentName || this.extractAgent(event) || 'Unknown';
1785
-
1786
- // Get all events from this agent
1787
- const allEvents = eventViewer.events || [];
1788
- const agentEvents = this.getAgentSpecificEvents(allEvents, agentName, agentInference);
1789
-
1790
- console.log(`Showing details for agent: ${agentName}, found ${agentEvents.length} related events`);
1791
-
1792
- // Extract agent-specific data
1793
- const agentData = this.extractAgentSpecificData(agentName, agentEvents);
1794
-
1795
- // Render agent-specific view
1796
- this.renderAgentSpecificView(agentName, agentData, event);
1797
- }
1798
-
1799
- /**
1800
- * Get all events related to a specific agent
1801
- * @param {Array} allEvents - All events
1802
- * @param {string} agentName - Name of the agent to filter for
1803
- * @param {Object} agentInference - Agent inference system
1804
- * @returns {Array} - Events related to this agent
1805
- */
1806
- getAgentSpecificEvents(allEvents, agentName, agentInference) {
1807
- return allEvents.filter(event => {
1808
- // Use agent inference to determine if this event belongs to the agent
1809
- const inference = agentInference.getInferredAgentForEvent(event);
1810
- const eventAgentName = inference?.agentName || this.extractAgent(event) || 'Unknown';
1811
-
1812
- // Match agent names (case insensitive)
1813
- return eventAgentName.toLowerCase() === agentName.toLowerCase();
1814
- });
1815
- }
1816
-
1817
- /**
1818
- * Extract agent-specific data from events
1819
- * @param {string} agentName - Name of the agent
1820
- * @param {Array} agentEvents - Events from this agent
1821
- * @returns {Object} - Extracted agent data
1822
- */
1823
- extractAgentSpecificData(agentName, agentEvents) {
1824
- const data = {
1825
- agentName: agentName,
1826
- totalEvents: agentEvents.length,
1827
- prompt: null,
1828
- todos: [],
1829
- toolsCalled: [],
1830
- sessions: new Set(),
1831
- firstSeen: null,
1832
- lastSeen: null,
1833
- eventTypes: new Set()
1834
- };
1835
-
1836
- agentEvents.forEach(event => {
1837
- const eventData = event.data || {};
1838
- const timestamp = new Date(event.timestamp);
1839
-
1840
- // Track timing
1841
- if (!data.firstSeen || timestamp < data.firstSeen) {
1842
- data.firstSeen = timestamp;
1843
- }
1844
- if (!data.lastSeen || timestamp > data.lastSeen) {
1845
- data.lastSeen = timestamp;
1846
- }
1847
-
1848
- // Track sessions
1849
- if (event.session_id || eventData.session_id) {
1850
- data.sessions.add(event.session_id || eventData.session_id);
1851
- }
1852
-
1853
- // Track event types
1854
- const eventType = event.hook_event_name || event.type || 'unknown';
1855
- data.eventTypes.add(eventType);
1856
-
1857
- // Extract prompt from Task delegation events
1858
- if (event.type === 'hook' && eventData.tool_name === 'Task' && eventData.tool_parameters) {
1859
- const taskParams = eventData.tool_parameters;
1860
- if (taskParams.prompt && !data.prompt) {
1861
- data.prompt = taskParams.prompt;
1862
- }
1863
- if (taskParams.description && !data.description) {
1864
- data.description = taskParams.description;
1865
- }
1866
- if (taskParams.subagent_type === agentName && taskParams.prompt) {
1867
- // Prefer prompts that match the specific agent
1868
- data.prompt = taskParams.prompt;
1869
- }
1870
- }
1871
-
1872
- // Also check for agent-specific prompts in other event types
1873
- if (eventData.prompt && (eventData.agent_type === agentName || eventData.subagent_type === agentName)) {
1874
- data.prompt = eventData.prompt;
1875
- }
1876
-
1877
- // Extract todos from TodoWrite events
1878
- if (event.type === 'todo' || (event.type === 'hook' && eventData.tool_name === 'TodoWrite')) {
1879
- const todos = eventData.todos || eventData.tool_parameters?.todos;
1880
- if (todos && Array.isArray(todos)) {
1881
- // Merge todos, keeping the most recent status for each
1882
- todos.forEach(todo => {
1883
- const existingIndex = data.todos.findIndex(t => t.id === todo.id || t.content === todo.content);
1884
- if (existingIndex >= 0) {
1885
- // Update existing todo with newer data
1886
- data.todos[existingIndex] = { ...data.todos[existingIndex], ...todo, timestamp };
1887
- } else {
1888
- // Add new todo
1889
- data.todos.push({ ...todo, timestamp });
1890
- }
1891
- });
1892
- }
1893
- }
1894
-
1895
- // Extract tool calls - collect pre and post events separately first
1896
- if (event.type === 'hook' && eventData.tool_name) {
1897
- const phase = event.subtype || eventData.event_type;
1898
- const toolCallId = this.generateToolCallId(eventData.tool_name, eventData.tool_parameters, timestamp);
1899
-
1900
- if (phase === 'pre_tool') {
1901
- // Store pre-tool event data
1902
- if (!data._preToolEvents) data._preToolEvents = new Map();
1903
- data._preToolEvents.set(toolCallId, {
1904
- toolName: eventData.tool_name,
1905
- timestamp: timestamp,
1906
- target: this.extractToolTarget(eventData.tool_name, eventData.tool_parameters, null),
1907
- parameters: eventData.tool_parameters
1908
- });
1909
- } else if (phase === 'post_tool') {
1910
- // Store post-tool event data
1911
- if (!data._postToolEvents) data._postToolEvents = new Map();
1912
- data._postToolEvents.set(toolCallId, {
1913
- toolName: eventData.tool_name,
1914
- timestamp: timestamp,
1915
- success: eventData.success,
1916
- duration: eventData.duration_ms,
1917
- resultSummary: eventData.result_summary,
1918
- exitCode: eventData.exit_code
1919
- });
1920
- }
1921
- }
1922
- });
1923
-
1924
- // Sort todos by timestamp (most recent first)
1925
- data.todos.sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0));
1926
-
1927
- // Consolidate pre and post tool events into single tool calls
1928
- data.toolsCalled = this.consolidateToolCalls(data._preToolEvents, data._postToolEvents);
1929
-
1930
- // Clean up temporary data
1931
- delete data._preToolEvents;
1932
- delete data._postToolEvents;
1933
-
1934
- // Sort tools by timestamp (most recent first)
1935
- data.toolsCalled.sort((a, b) => b.timestamp - a.timestamp);
1936
-
1937
- return data;
1938
- }
1939
-
1940
- /**
1941
- * Generate a unique ID for a tool call to match pre and post events
1942
- * @param {string} toolName - Name of the tool
1943
- * @param {Object} parameters - Tool parameters
1944
- * @param {Date} timestamp - Timestamp of the event
1945
- * @returns {string} - Unique tool call ID
1946
- */
1947
- generateToolCallId(toolName, parameters, timestamp) {
1948
- // Create a unique identifier based on tool name, key parameters, and approximate timestamp
1949
- // Use a wider time window to account for timing differences between pre/post events
1950
- const timeWindow = Math.floor(timestamp.getTime() / 5000); // Group by 5-second windows
1951
-
1952
- // Include key parameters that uniquely identify a tool call
1953
- let paramKey = '';
1954
- if (parameters) {
1955
- // Include important parameters that distinguish tool calls
1956
- const keyParams = [];
1957
- if (parameters.file_path) keyParams.push(parameters.file_path);
1958
- if (parameters.command) keyParams.push(parameters.command.substring(0, 50));
1959
- if (parameters.pattern) keyParams.push(parameters.pattern);
1960
- if (parameters.subagent_type) keyParams.push(parameters.subagent_type);
1961
- if (parameters.notebook_path) keyParams.push(parameters.notebook_path);
1962
- if (parameters.url) keyParams.push(parameters.url);
1963
- if (parameters.prompt) keyParams.push(parameters.prompt.substring(0, 30));
1964
-
1965
- paramKey = keyParams.join('|');
1966
- }
1967
-
1968
- // If no specific parameters, use just tool name and time window
1969
- if (!paramKey) {
1970
- paramKey = 'default';
1971
- }
1972
-
1973
- return `${toolName}:${timeWindow}:${paramKey}`;
1974
- }
1975
-
1976
- /**
1977
- * Consolidate pre and post tool events into single consolidated tool calls
1978
- * @param {Map} preToolEvents - Map of pre-tool events by tool call ID
1979
- * @param {Map} postToolEvents - Map of post-tool events by tool call ID
1980
- * @returns {Array} - Array of consolidated tool calls
1981
- */
1982
- consolidateToolCalls(preToolEvents, postToolEvents) {
1983
- const consolidatedCalls = [];
1984
- const processedIds = new Set();
1985
-
1986
- if (!preToolEvents) preToolEvents = new Map();
1987
- if (!postToolEvents) postToolEvents = new Map();
1988
-
1989
- // Process all pre-tool events first
1990
- for (const [toolCallId, preEvent] of preToolEvents) {
1991
- if (processedIds.has(toolCallId)) continue;
1992
-
1993
- const postEvent = postToolEvents.get(toolCallId);
1994
-
1995
- // Create consolidated tool call
1996
- const consolidatedCall = {
1997
- toolName: preEvent.toolName,
1998
- timestamp: preEvent.timestamp, // Use pre-tool timestamp as the start time
1999
- target: preEvent.target,
2000
- parameters: preEvent.parameters,
2001
- status: this.determineToolCallStatus(preEvent, postEvent),
2002
- statusIcon: this.getToolCallStatusIcon(preEvent, postEvent),
2003
- phase: postEvent ? 'completed' : 'running'
2004
- };
2005
-
2006
- // Add post-event data if available
2007
- if (postEvent) {
2008
- consolidatedCall.success = postEvent.success;
2009
- consolidatedCall.duration = postEvent.duration;
2010
- consolidatedCall.resultSummary = postEvent.resultSummary;
2011
- consolidatedCall.exitCode = postEvent.exitCode;
2012
- consolidatedCall.completedAt = postEvent.timestamp;
2013
- }
2014
-
2015
- consolidatedCalls.push(consolidatedCall);
2016
- processedIds.add(toolCallId);
2017
- }
2018
-
2019
- // Process any post-tool events that don't have matching pre-tool events (edge case)
2020
- for (const [toolCallId, postEvent] of postToolEvents) {
2021
- if (processedIds.has(toolCallId)) continue;
2022
-
2023
- // This is a post-tool event without a corresponding pre-tool event
2024
- const consolidatedCall = {
2025
- toolName: postEvent.toolName,
2026
- timestamp: postEvent.timestamp,
2027
- target: 'Unknown target', // We don't have pre-event data
2028
- parameters: null,
2029
- status: this.determineToolCallStatus(null, postEvent),
2030
- statusIcon: this.getToolCallStatusIcon(null, postEvent),
2031
- phase: 'completed',
2032
- success: postEvent.success,
2033
- duration: postEvent.duration,
2034
- resultSummary: postEvent.resultSummary,
2035
- exitCode: postEvent.exitCode,
2036
- completedAt: postEvent.timestamp
2037
- };
2038
-
2039
- consolidatedCalls.push(consolidatedCall);
2040
- processedIds.add(toolCallId);
2041
- }
2042
-
2043
- return consolidatedCalls;
2044
- }
2045
-
2046
- /**
2047
- * Determine the status of a tool call based on pre and post events
2048
- * @param {Object} preEvent - Pre-tool event data
2049
- * @param {Object} postEvent - Post-tool event data
2050
- * @returns {string} - Status text
2051
- */
2052
- determineToolCallStatus(preEvent, postEvent) {
2053
- if (!postEvent) {
2054
- return 'Running...';
2055
- }
2056
-
2057
- if (postEvent.success === true) {
2058
- return 'Success';
2059
- } else if (postEvent.success === false) {
2060
- return 'Failed';
2061
- } else if (postEvent.exitCode === 0) {
2062
- return 'Completed';
2063
- } else if (postEvent.exitCode === 2) {
2064
- return 'Blocked';
2065
- } else if (postEvent.exitCode !== undefined && postEvent.exitCode !== 0) {
2066
- return 'Error';
2067
- }
2068
-
2069
- return 'Completed';
2070
- }
2071
-
2072
- /**
2073
- * Get the status icon for a tool call
2074
- * @param {Object} preEvent - Pre-tool event data
2075
- * @param {Object} postEvent - Post-tool event data
2076
- * @returns {string} - Status icon
2077
- */
2078
- getToolCallStatusIcon(preEvent, postEvent) {
2079
- if (!postEvent) {
2080
- return '⏳'; // Still running
2081
- }
2082
-
2083
- if (postEvent.success === true) {
2084
- return '✅'; // Success
2085
- } else if (postEvent.success === false) {
2086
- return '❌'; // Failed
2087
- } else if (postEvent.exitCode === 0) {
2088
- return '✅'; // Completed successfully
2089
- } else if (postEvent.exitCode === 2) {
2090
- return '⚠️'; // Blocked
2091
- } else if (postEvent.exitCode !== undefined && postEvent.exitCode !== 0) {
2092
- return '❌'; // Error
2093
- }
2094
-
2095
- return '✅'; // Default to success for completed calls
2096
- }
2097
-
2098
- /**
2099
- * Estimate token count for text using a simple approximation
2100
- * @param {string} text - Text to estimate tokens for
2101
- * @returns {number} - Estimated token count
2102
- */
2103
- estimateTokenCount(text) {
2104
- if (!text || typeof text !== 'string') return 0;
2105
-
2106
- // Simple token estimation: words * 1.3 (accounts for subwords)
2107
- // Alternative: characters / 4 (common rule of thumb)
2108
- const wordCount = text.trim().split(/\s+/).length;
2109
- const charBasedEstimate = Math.ceil(text.length / 4);
2110
-
2111
- // Use the higher of the two estimates for safety
2112
- return Math.max(wordCount * 1.3, charBasedEstimate);
2113
- }
2114
-
2115
- /**
2116
- * Trim excessive whitespace from text while preserving structure
2117
- * @param {string} text - Text to trim
2118
- * @returns {string} - Trimmed text
2119
- */
2120
- trimPromptWhitespace(text) {
2121
- if (!text || typeof text !== 'string') return '';
2122
-
2123
- // Remove leading/trailing whitespace from the entire text
2124
- text = text.trim();
2125
-
2126
- // Reduce multiple consecutive newlines to maximum of 2
2127
- text = text.replace(/\n\s*\n\s*\n+/g, '\n\n');
2128
-
2129
- // Trim whitespace from each line while preserving intentional indentation
2130
- text = text.split('\n').map(line => {
2131
- // Only trim trailing whitespace, preserve leading whitespace for structure
2132
- return line.replace(/\s+$/, '');
2133
- }).join('\n');
2134
-
2135
- return text;
2136
- }
2137
-
2138
- /**
2139
- * Render agent-specific view with comprehensive data
2140
- * @param {string} agentName - Name of the agent
2141
- * @param {Object} agentData - Extracted agent data
2142
- * @param {Object} originalEvent - The original clicked event
2143
- */
2144
- renderAgentSpecificView(agentName, agentData, originalEvent) {
2145
- // Create contextual header
2146
- const timestamp = this.formatTimestamp(originalEvent.timestamp);
2147
- const contextualHeader = `
2148
- <div class="contextual-header">
2149
- <h3 class="contextual-header-text">🤖 ${agentName} Agent Details ${timestamp}</h3>
2150
- </div>
2151
- `;
2152
-
2153
- // Build comprehensive agent view
2154
- let content = `
2155
- <div class="agent-overview-section">
2156
- <div class="structured-data">
2157
- ${this.createProperty('Agent Name', agentName)}
2158
- ${this.createProperty('Total Events', agentData.totalEvents)}
2159
- ${this.createProperty('Active Sessions', agentData.sessions.size)}
2160
- ${this.createProperty('Event Types', Array.from(agentData.eventTypes).join(', '))}
2161
- ${agentData.firstSeen ? this.createProperty('First Seen', agentData.firstSeen.toLocaleString()) : ''}
2162
- ${agentData.lastSeen ? this.createProperty('Last Seen', agentData.lastSeen.toLocaleString()) : ''}
2163
- </div>
2164
- </div>
2165
- `;
2166
-
2167
- // Add prompt section if available
2168
- if (agentData.prompt) {
2169
- const trimmedPrompt = this.trimPromptWhitespace(agentData.prompt);
2170
- const tokenCount = Math.round(this.estimateTokenCount(trimmedPrompt));
2171
- const wordCount = trimmedPrompt.trim().split(/\s+/).length;
2172
-
2173
- content += `
2174
- <div class="agent-prompt-section">
2175
- <div class="contextual-header">
2176
- <h3 class="contextual-header-text">📝 Agent Task Prompt</h3>
2177
- <div class="prompt-stats" style="font-size: 11px; color: #64748b; margin-top: 4px;">
2178
- ~${tokenCount} tokens • ${wordCount} words • ${trimmedPrompt.length} characters
2179
- </div>
2180
- </div>
2181
- <div class="structured-data">
2182
- <div class="agent-prompt" style="white-space: pre-wrap; max-height: 300px; overflow-y: auto; padding: 10px; background: #f8fafc; border-radius: 6px; font-family: monospace; font-size: 12px; line-height: 1.4; border: 1px solid #e2e8f0;">
2183
- ${this.escapeHtml(trimmedPrompt)}
2184
- </div>
2185
- </div>
2186
- </div>
2187
- `;
2188
- }
2189
-
2190
- // Add todos section if available
2191
- if (agentData.todos.length > 0) {
2192
- content += `
2193
- <div class="agent-todos-section">
2194
- <div class="contextual-header">
2195
- <h3 class="contextual-header-text">✅ Agent Todo List (${agentData.todos.length} items)</h3>
2196
- </div>
2197
- <div class="todo-checklist">
2198
- ${agentData.todos.map(todo => `
2199
- <div class="todo-item todo-${todo.status || 'pending'}">
2200
- <span class="todo-status">${this.getTodoStatusIcon(todo.status)}</span>
2201
- <span class="todo-content">${todo.content || 'No content'}</span>
2202
- <span class="todo-priority priority-${todo.priority || 'medium'}">${this.getTodoPriorityIcon(todo.priority)}</span>
2203
- ${todo.timestamp ? `<span class="todo-timestamp">${new Date(todo.timestamp).toLocaleTimeString()}</span>` : ''}
2204
- </div>
2205
- `).join('')}
2206
- </div>
2207
- </div>
2208
- `;
2209
- }
2210
-
2211
- // Add tools section if available
2212
- if (agentData.toolsCalled.length > 0) {
2213
- content += `
2214
- <div class="agent-tools-section">
2215
- <div class="contextual-header">
2216
- <h3 class="contextual-header-text">🔧 Tools Called by Agent (${agentData.toolsCalled.length} calls)</h3>
2217
- </div>
2218
- <div class="tools-list">
2219
- ${agentData.toolsCalled.map(tool => {
2220
- // Determine CSS class for status
2221
- let statusClass = '';
2222
- if (tool.statusIcon === '✅') statusClass = 'status-success';
2223
- else if (tool.statusIcon === '❌') statusClass = 'status-failed';
2224
- else if (tool.statusIcon === '⚠️') statusClass = 'status-blocked';
2225
- else if (tool.statusIcon === '⏳') statusClass = 'status-running';
2226
-
2227
- return `
2228
- <div class="tool-call-item">
2229
- <div class="tool-call-header">
2230
- <div style="display: flex; align-items: center; gap: 12px; flex: 1;">
2231
- <span class="tool-name">🔧 ${tool.toolName}</span>
2232
- <span class="tool-agent">${agentName}</span>
2233
- <span class="tool-status-indicator ${statusClass}">${tool.statusIcon} ${tool.status}</span>
2234
- </div>
2235
- <span class="tool-timestamp" style="margin-left: auto;">${tool.timestamp.toLocaleTimeString()}</span>
2236
- </div>
2237
- <div class="tool-call-details">
2238
- ${tool.target ? `<span class="tool-target">Target: ${tool.target}</span>` : ''}
2239
- ${tool.duration ? `<span class="tool-duration">Duration: ${tool.duration}ms</span>` : ''}
2240
- ${tool.completedAt && tool.completedAt !== tool.timestamp ? `<span class="tool-completed">Completed: ${tool.completedAt.toLocaleTimeString()}</span>` : ''}
2241
- </div>
2242
- </div>
2243
- `;
2244
- }).join('')}
2245
- </div>
2246
- </div>
2247
- `;
2248
- }
2249
-
2250
- // Create collapsible JSON section for agent data
2251
- const agentJsonData = {
2252
- agentName: agentName,
2253
- agentData: agentData,
2254
- originalEvent: originalEvent
2255
- };
2256
- const collapsibleJsonSection = this.createCollapsibleJsonSection(agentJsonData);
2257
-
2258
- // Show structured data with JSON section in data pane
2259
- if (this.dataContainer) {
2260
- this.dataContainer.innerHTML = contextualHeader + content + collapsibleJsonSection;
2261
- }
2262
-
2263
- // Initialize JSON toggle functionality
2264
- this.initializeJsonToggle();
2265
-
2266
- // Hide JSON pane since data is integrated above
2267
- // JSON container no longer exists - handled via collapsible sections
2268
- }
2269
-
2270
- /**
2271
- * Create tool result section for backward compatibility with showToolCall method
2272
- * @param {Object} toolCall - Tool call data
2273
- * @returns {string} HTML content for tool result section
2274
- */
2275
- createToolResultFromToolCall(toolCall) {
2276
- // Check if we have result data
2277
- if (!toolCall.result_summary) {
2278
- return '';
2279
- }
2280
-
2281
- // Convert toolCall data to match the format expected by createInlineToolResultContent
2282
- const mockData = {
2283
- event_type: 'post_tool',
2284
- result_summary: toolCall.result_summary,
2285
- success: toolCall.success,
2286
- exit_code: toolCall.exit_code
2287
- };
2288
-
2289
- // Create a mock event object with proper subtype
2290
- const mockEvent = {
2291
- subtype: 'post_tool'
2292
- };
2293
-
2294
- // Get inline result content
2295
- const inlineContent = this.createInlineToolResultContent(mockData, mockEvent);
2296
-
2297
- // If we have content, wrap it in a simple section
2298
- if (inlineContent.trim()) {
2299
- return `
2300
- <div class="tool-result-inline">
2301
- <div class="structured-data">
2302
- ${inlineContent}
2303
- </div>
2304
- </div>
2305
- `;
2306
- }
2307
-
2308
- return '';
2309
- }
2310
-
2311
- /**
2312
- * Extract tool target from tool name and parameters
2313
- * @param {string} toolName - Name of the tool
2314
- * @param {Object} parameters - Tool parameters
2315
- * @param {Object} altParameters - Alternative parameters
2316
- * @returns {string} - Tool target description
2317
- */
2318
- extractToolTarget(toolName, parameters, altParameters) {
2319
- const params = parameters || altParameters || {};
2320
-
2321
- switch (toolName?.toLowerCase()) {
2322
- case 'write':
2323
- case 'read':
2324
- case 'edit':
2325
- case 'multiedit':
2326
- return params.file_path || 'Unknown file';
2327
- case 'bash':
2328
- return params.command ? `${params.command.substring(0, 50)}${params.command.length > 50 ? '...' : ''}` : 'Unknown command';
2329
- case 'grep':
2330
- return params.pattern ? `Pattern: ${params.pattern}` : 'Unknown pattern';
2331
- case 'glob':
2332
- return params.pattern ? `Pattern: ${params.pattern}` : 'Unknown glob';
2333
- case 'todowrite':
2334
- return `${params.todos?.length || 0} todos`;
2335
- case 'task':
2336
- return params.subagent_type || params.agent_type || 'Subagent delegation';
2337
- default:
2338
- // Try to find a meaningful parameter
2339
- if (params.file_path) return params.file_path;
2340
- if (params.pattern) return `Pattern: ${params.pattern}`;
2341
- if (params.command) return `Command: ${params.command.substring(0, 30)}...`;
2342
- if (params.path) return params.path;
2343
- return 'Unknown target';
2344
- }
2345
- }
2346
-
2347
-
2348
- /**
2349
- * Get operation icon for file operations
2350
- * @param {string} operation - Operation type
2351
- * @returns {string} - Icon for the operation
2352
- */
2353
- getOperationIcon(operation) {
2354
- const icons = {
2355
- 'read': '👁️',
2356
- 'write': '✏️',
2357
- 'edit': '📝',
2358
- 'multiedit': '📝',
2359
- 'create': '🆕',
2360
- 'delete': '🗑️',
2361
- 'move': '📦',
2362
- 'copy': '📋'
2363
- };
2364
- return icons[operation?.toLowerCase()] || '📄';
2365
- }
2366
-
2367
- /**
2368
- * Get current event
2369
- */
2370
- getCurrentEvent() {
2371
- return this.currentEvent;
2372
- }
2373
-
2374
- /**
2375
- * Check git tracking status and show track control if needed
2376
- * @param {string} filePath - Path to the file to check
2377
- */
2378
- async checkAndShowTrackControl(filePath) {
2379
- if (!filePath) return;
2380
-
2381
- try {
2382
- // Get the Socket.IO client
2383
- const socket = window.socket || window.dashboard?.socketClient?.socket;
2384
- if (!socket) {
2385
- console.warn('No socket connection available for git tracking check');
2386
- return;
2387
- }
2388
-
2389
- // Get working directory from dashboard with proper fallback
2390
- let workingDir = window.dashboard?.currentWorkingDir;
2391
-
2392
- // Don't use 'Unknown' as a working directory
2393
- if (!workingDir || workingDir === 'Unknown' || workingDir.trim() === '') {
2394
- // Try to get from footer element
2395
- const footerDir = document.getElementById('footer-working-dir');
2396
- if (footerDir?.textContent?.trim() && footerDir.textContent.trim() !== 'Unknown') {
2397
- workingDir = footerDir.textContent.trim();
2398
- } else {
2399
- // Final fallback to current directory
2400
- workingDir = '.';
2401
- }
2402
- console.log('[MODULE-VIEWER-DEBUG] Working directory fallback used:', workingDir);
2403
- }
2404
-
2405
- // Set up one-time listener for tracking status response
2406
- const responsePromise = new Promise((resolve, reject) => {
2407
- const responseHandler = (data) => {
2408
- if (data.file_path === filePath) {
2409
- socket.off('file_tracked_response', responseHandler);
2410
- resolve(data);
2411
- }
2412
- };
2413
-
2414
- socket.on('file_tracked_response', responseHandler);
2415
-
2416
- // Timeout after 5 seconds
2417
- setTimeout(() => {
2418
- socket.off('file_tracked_response', responseHandler);
2419
- reject(new Error('Request timeout'));
2420
- }, 5000);
2421
- });
2422
-
2423
- // Send tracking status request
2424
- socket.emit('check_file_tracked', {
2425
- file_path: filePath,
2426
- working_dir: workingDir
2427
- });
2428
-
2429
- // Wait for response
2430
- const result = await responsePromise;
2431
- this.displayTrackingStatus(filePath, result);
2432
-
2433
- } catch (error) {
2434
- console.error('Error checking file tracking status:', error);
2435
- this.displayTrackingStatus(filePath, {
2436
- success: false,
2437
- error: error.message,
2438
- file_path: filePath
2439
- });
2440
- }
2441
- }
2442
-
2443
- /**
2444
- * Display tracking status and show track control if needed
2445
- * @param {string} filePath - Path to the file
2446
- * @param {Object} result - Result from tracking status check
2447
- */
2448
- displayTrackingStatus(filePath, result) {
2449
- const statusElementId = `git-track-status-${filePath.replace(/[^a-zA-Z0-9]/g, '-')}`;
2450
- const statusElement = document.getElementById(statusElementId);
2451
-
2452
- if (!statusElement) return;
2453
-
2454
- if (result.success && result.is_tracked === false) {
2455
- // File is not tracked - show track button
2456
- statusElement.innerHTML = `
2457
- <div class="untracked-file-notice">
2458
- <span class="untracked-icon">⚠️</span>
2459
- <span class="untracked-text">This file is not tracked by git</span>
2460
- <button class="track-file-button"
2461
- onclick="window.moduleViewer.trackFile('${filePath}')"
2462
- title="Add this file to git tracking">
2463
- <span class="git-icon">📁</span> Track File
2464
- </button>
2465
- </div>
2466
- `;
2467
- } else if (result.success && result.is_tracked === true) {
2468
- // File is tracked - show status
2469
- statusElement.innerHTML = `
2470
- <div class="tracked-file-notice">
2471
- <span class="tracked-icon">✅</span>
2472
- <span class="tracked-text">This file is tracked by git</span>
2473
- </div>
2474
- `;
2475
- } else if (!result.success) {
2476
- // Error checking status
2477
- statusElement.innerHTML = `
2478
- <div class="tracking-error-notice">
2479
- <span class="error-icon">❌</span>
2480
- <span class="error-text">Could not check git status: ${result.error || 'Unknown error'}</span>
2481
- </div>
2482
- `;
2483
- }
2484
- }
2485
-
2486
- /**
2487
- * Track a file using git add
2488
- * @param {string} filePath - Path to the file to track
2489
- */
2490
- async trackFile(filePath) {
2491
- if (!filePath) return;
2492
-
2493
- try {
2494
- // Get the Socket.IO client
2495
- const socket = window.socket || window.dashboard?.socketClient?.socket;
2496
- if (!socket) {
2497
- console.warn('No socket connection available for git add');
2498
- return;
2499
- }
2500
-
2501
- // Get working directory from dashboard with proper fallback
2502
- let workingDir = window.dashboard?.currentWorkingDir;
2503
-
2504
- // Don't use 'Unknown' as a working directory
2505
- if (!workingDir || workingDir === 'Unknown' || workingDir.trim() === '') {
2506
- // Try to get from footer element
2507
- const footerDir = document.getElementById('footer-working-dir');
2508
- if (footerDir?.textContent?.trim() && footerDir.textContent.trim() !== 'Unknown') {
2509
- workingDir = footerDir.textContent.trim();
2510
- } else {
2511
- // Final fallback to current directory
2512
- workingDir = '.';
2513
- }
2514
- console.log('[MODULE-VIEWER-DEBUG] Working directory fallback used:', workingDir);
2515
- }
2516
-
2517
- // Update button to show loading state
2518
- const statusElementId = `git-track-status-${filePath.replace(/[^a-zA-Z0-9]/g, '-')}`;
2519
- const statusElement = document.getElementById(statusElementId);
2520
-
2521
- if (statusElement) {
2522
- statusElement.innerHTML = `
2523
- <div class="tracking-file-notice">
2524
- <span class="loading-icon">⏳</span>
2525
- <span class="loading-text">Adding file to git tracking...</span>
2526
- </div>
2527
- `;
2528
- }
2529
-
2530
- // Set up one-time listener for git add response
2531
- const responsePromise = new Promise((resolve, reject) => {
2532
- const responseHandler = (data) => {
2533
- if (data.file_path === filePath) {
2534
- socket.off('git_add_response', responseHandler);
2535
- resolve(data);
2536
- }
2537
- };
2538
-
2539
- socket.on('git_add_response', responseHandler);
2540
-
2541
- // Timeout after 10 seconds
2542
- setTimeout(() => {
2543
- socket.off('git_add_response', responseHandler);
2544
- reject(new Error('Request timeout'));
2545
- }, 10000);
2546
- });
2547
-
2548
- // Send git add request
2549
- socket.emit('git_add_file', {
2550
- file_path: filePath,
2551
- working_dir: workingDir
2552
- });
2553
-
2554
- console.log('📁 Git add request sent:', {
2555
- filePath,
2556
- workingDir
2557
- });
2558
-
2559
- // Wait for response
2560
- const result = await responsePromise;
2561
- console.log('📦 Git add result:', result);
2562
-
2563
- // Update UI based on result
2564
- if (result.success) {
2565
- if (statusElement) {
2566
- statusElement.innerHTML = `
2567
- <div class="tracked-file-notice">
2568
- <span class="tracked-icon">✅</span>
2569
- <span class="tracked-text">File successfully added to git tracking</span>
2570
- </div>
2571
- `;
2572
- }
2573
-
2574
- // Show success notification
2575
- this.showNotification('File tracked successfully', 'success');
2576
- } else {
2577
- if (statusElement) {
2578
- statusElement.innerHTML = `
2579
- <div class="tracking-error-notice">
2580
- <span class="error-icon">❌</span>
2581
- <span class="error-text">Failed to track file: ${result.error || 'Unknown error'}</span>
2582
- <button class="track-file-button"
2583
- onclick="window.moduleViewer.trackFile('${filePath}')"
2584
- title="Try again">
2585
- <span class="git-icon">📁</span> Retry
2586
- </button>
2587
- </div>
2588
- `;
2589
- }
2590
-
2591
- // Show error notification
2592
- this.showNotification(`Failed to track file: ${result.error}`, 'error');
2593
- }
2594
-
2595
- } catch (error) {
2596
- console.error('❌ Failed to track file:', error);
2597
-
2598
- // Update UI to show error
2599
- const statusElementId = `git-track-status-${filePath.replace(/[^a-zA-Z0-9]/g, '-')}`;
2600
- const statusElement = document.getElementById(statusElementId);
2601
-
2602
- if (statusElement) {
2603
- statusElement.innerHTML = `
2604
- <div class="tracking-error-notice">
2605
- <span class="error-icon">❌</span>
2606
- <span class="error-text">Error: ${error.message}</span>
2607
- <button class="track-file-button"
2608
- onclick="window.moduleViewer.trackFile('${filePath}')"
2609
- title="Try again">
2610
- <span class="git-icon">📁</span> Retry
2611
- </button>
2612
- </div>
2613
- `;
2614
- }
2615
-
2616
- // Show error notification
2617
- this.showNotification(`Error tracking file: ${error.message}`, 'error');
2618
- }
2619
- }
2620
-
2621
-
2622
- /**
2623
- * Show notification to user
2624
- * @param {string} message - Message to show
2625
- * @param {string} type - Type of notification (success, error, info)
2626
- */
2627
- showNotification(message, type = 'info') {
2628
- // Create notification element
2629
- const notification = document.createElement('div');
2630
- notification.className = `notification notification-${type}`;
2631
- notification.innerHTML = `
2632
- <span class="notification-icon">${type === 'success' ? '✅' : type === 'error' ? '❌' : 'ℹ️'}</span>
2633
- <span class="notification-message">${message}</span>
2634
- `;
2635
-
2636
- // Style the notification
2637
- notification.style.cssText = `
2638
- position: fixed;
2639
- top: 20px;
2640
- right: 20px;
2641
- background: ${type === 'success' ? '#d4edda' : type === 'error' ? '#f8d7da' : '#d1ecf1'};
2642
- color: ${type === 'success' ? '#155724' : type === 'error' ? '#721c24' : '#0c5460'};
2643
- border: 1px solid ${type === 'success' ? '#c3e6cb' : type === 'error' ? '#f5c6cb' : '#bee5eb'};
2644
- border-radius: 6px;
2645
- padding: 12px 16px;
2646
- font-size: 14px;
2647
- font-weight: 500;
2648
- z-index: 2000;
2649
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
2650
- display: flex;
2651
- align-items: center;
2652
- gap: 8px;
2653
- max-width: 400px;
2654
- animation: slideIn 0.3s ease-out;
2655
- `;
2656
-
2657
- // Add animation styles
2658
- const style = document.createElement('style');
2659
- style.textContent = `
2660
- @keyframes slideIn {
2661
- from { transform: translateX(100%); opacity: 0; }
2662
- to { transform: translateX(0); opacity: 1; }
2663
- }
2664
- @keyframes slideOut {
2665
- from { transform: translateX(0); opacity: 1; }
2666
- to { transform: translateX(100%); opacity: 0; }
2667
- }
2668
- `;
2669
- document.head.appendChild(style);
2670
-
2671
- // Add to page
2672
- document.body.appendChild(notification);
2673
-
2674
- // Remove after 5 seconds
2675
- setTimeout(() => {
2676
- notification.style.animation = 'slideOut 0.3s ease-in';
2677
- setTimeout(() => {
2678
- if (notification.parentNode) {
2679
- notification.parentNode.removeChild(notification);
2680
- }
2681
- if (style.parentNode) {
2682
- style.parentNode.removeChild(style);
2683
- }
2684
- }, 300);
2685
- }, 5000);
2686
- }
2687
-
2688
- /**
2689
- * Show agent instance details for PM delegations
2690
- * @param {Object} instance - Agent instance from PM delegation
2691
- */
2692
- showAgentInstance(instance) {
2693
- if (!instance) {
2694
- this.showEmptyState();
2695
- return;
2696
- }
2697
-
2698
- // Create a synthetic event object to work with existing showAgentSpecificDetails method
2699
- const syntheticEvent = {
2700
- type: 'pm_delegation',
2701
- subtype: instance.agentName,
2702
- agent_type: instance.agentName,
2703
- timestamp: instance.timestamp,
2704
- session_id: instance.sessionId,
2705
- metadata: {
2706
- delegation_type: 'explicit',
2707
- event_count: instance.agentEvents.length,
2708
- pm_call: instance.pmCall || null,
2709
- agent_events: instance.agentEvents
2710
- }
2711
- };
2712
-
2713
- console.log('Showing PM delegation details:', instance);
2714
- this.showAgentSpecificDetails(syntheticEvent, 0);
2715
- }
2716
-
2717
- /**
2718
- * Show implied agent details for agents without explicit PM delegation
2719
- * @param {Object} impliedInstance - Implied agent instance
2720
- */
2721
- showImpliedAgent(impliedInstance) {
2722
- if (!impliedInstance) {
2723
- this.showEmptyState();
2724
- return;
2725
- }
2726
-
2727
- // Create a synthetic event object to work with existing showAgentSpecificDetails method
2728
- const syntheticEvent = {
2729
- type: 'implied_delegation',
2730
- subtype: impliedInstance.agentName,
2731
- agent_type: impliedInstance.agentName,
2732
- timestamp: impliedInstance.timestamp,
2733
- session_id: impliedInstance.sessionId,
2734
- metadata: {
2735
- delegation_type: 'implied',
2736
- event_count: impliedInstance.eventCount,
2737
- pm_call: null,
2738
- note: 'No explicit PM call found - inferred from agent activity'
2739
- }
2740
- };
2741
-
2742
- console.log('Showing implied agent details:', impliedInstance);
2743
- this.showAgentSpecificDetails(syntheticEvent, 0);
2744
- }
2745
- }
2746
-
2747
- // Export for global use
2748
- // ES6 Module export
2749
- export { ModuleViewer };
2750
- export default ModuleViewer;
2751
-
2752
- // Backward compatibility - keep window export for non-module usage
2753
- window.ModuleViewer = ModuleViewer;
2754
-
2755
- // Debug helper function for troubleshooting tool result display
2756
- window.enableToolResultDebugging = function() {
2757
- window.DEBUG_TOOL_RESULTS = true;
2758
- console.log('🔧 Tool result debugging enabled. Click on tool events to see debug info.');
2759
- };
2760
-
2761
- window.disableToolResultDebugging = function() {
2762
- window.DEBUG_TOOL_RESULTS = false;
2763
- console.log('🔧 Tool result debugging disabled.');
2764
- };