claude-mpm 4.1.11__py3-none-any.whl → 4.1.13__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/INSTRUCTIONS.md +8 -0
- claude_mpm/cli/__init__.py +1 -1
- claude_mpm/cli/commands/monitor.py +88 -627
- claude_mpm/cli/commands/mpm_init.py +127 -107
- claude_mpm/cli/commands/mpm_init_handler.py +24 -23
- claude_mpm/cli/parsers/mpm_init_parser.py +34 -28
- claude_mpm/core/config.py +18 -0
- claude_mpm/core/instruction_reinforcement_hook.py +266 -0
- claude_mpm/core/pm_hook_interceptor.py +105 -8
- claude_mpm/dashboard/static/built/components/activity-tree.js +1 -1
- claude_mpm/dashboard/static/built/components/code-tree.js +1 -1
- claude_mpm/dashboard/static/built/dashboard.js +1 -1
- claude_mpm/dashboard/static/built/socket-client.js +1 -1
- claude_mpm/dashboard/static/css/activity.css +1239 -267
- claude_mpm/dashboard/static/css/dashboard.css +511 -0
- claude_mpm/dashboard/static/dist/components/activity-tree.js +1 -1
- claude_mpm/dashboard/static/dist/components/code-tree.js +1 -1
- claude_mpm/dashboard/static/dist/components/module-viewer.js +1 -1
- claude_mpm/dashboard/static/dist/dashboard.js +1 -1
- claude_mpm/dashboard/static/dist/socket-client.js +1 -1
- claude_mpm/dashboard/static/js/components/activity-tree.js +1193 -892
- claude_mpm/dashboard/static/js/components/build-tracker.js +15 -13
- claude_mpm/dashboard/static/js/components/code-tree.js +534 -143
- claude_mpm/dashboard/static/js/components/module-viewer.js +21 -7
- claude_mpm/dashboard/static/js/components/unified-data-viewer.js +1066 -0
- claude_mpm/dashboard/static/js/connection-manager.js +1 -1
- claude_mpm/dashboard/static/js/dashboard.js +227 -84
- claude_mpm/dashboard/static/js/socket-client.js +2 -2
- claude_mpm/dashboard/templates/index.html +100 -23
- claude_mpm/services/agents/deployment/agent_template_builder.py +11 -7
- claude_mpm/services/cli/socketio_manager.py +39 -8
- claude_mpm/services/infrastructure/monitoring.py +1 -1
- claude_mpm/services/socketio/handlers/code_analysis.py +83 -136
- claude_mpm/tools/code_tree_analyzer.py +290 -202
- {claude_mpm-4.1.11.dist-info → claude_mpm-4.1.13.dist-info}/METADATA +1 -1
- {claude_mpm-4.1.11.dist-info → claude_mpm-4.1.13.dist-info}/RECORD +41 -39
- {claude_mpm-4.1.11.dist-info → claude_mpm-4.1.13.dist-info}/WHEEL +0 -0
- {claude_mpm-4.1.11.dist-info → claude_mpm-4.1.13.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.1.11.dist-info → claude_mpm-4.1.13.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.1.11.dist-info → claude_mpm-4.1.13.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,1066 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified Data Viewer Component
|
|
3
|
+
*
|
|
4
|
+
* Consolidates all data formatting and display logic from event-driven tabs
|
|
5
|
+
* (Activity, Events, Agents) into a single, reusable component.
|
|
6
|
+
*
|
|
7
|
+
* WHY: Eliminates code duplication across multiple components and provides
|
|
8
|
+
* consistent data display formatting throughout the dashboard.
|
|
9
|
+
*
|
|
10
|
+
* DESIGN DECISION: Auto-detects data type and applies appropriate formatting,
|
|
11
|
+
* while allowing manual type specification for edge cases.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
class UnifiedDataViewer {
|
|
15
|
+
constructor(containerId = 'module-data-content') {
|
|
16
|
+
this.container = document.getElementById(containerId);
|
|
17
|
+
this.currentData = null;
|
|
18
|
+
this.currentType = null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Main display method - auto-detects type and renders data
|
|
23
|
+
* @param {Object|Array} data - Data to display
|
|
24
|
+
* @param {string|null} type - Optional type override
|
|
25
|
+
*/
|
|
26
|
+
display(data, type = null) {
|
|
27
|
+
if (!this.container) {
|
|
28
|
+
console.warn('UnifiedDataViewer: Container not found');
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Store current data for reference
|
|
33
|
+
this.currentData = data;
|
|
34
|
+
this.currentType = type;
|
|
35
|
+
|
|
36
|
+
// Auto-detect type if not provided
|
|
37
|
+
if (!type) {
|
|
38
|
+
type = this.detectType(data);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Clear container
|
|
42
|
+
this.container.innerHTML = '';
|
|
43
|
+
|
|
44
|
+
// Display based on type
|
|
45
|
+
switch(type) {
|
|
46
|
+
case 'event':
|
|
47
|
+
this.displayEvent(data);
|
|
48
|
+
break;
|
|
49
|
+
case 'agent':
|
|
50
|
+
this.displayAgent(data);
|
|
51
|
+
break;
|
|
52
|
+
case 'tool':
|
|
53
|
+
this.displayTool(data);
|
|
54
|
+
break;
|
|
55
|
+
case 'todo':
|
|
56
|
+
this.displayTodo(data);
|
|
57
|
+
break;
|
|
58
|
+
case 'instruction':
|
|
59
|
+
this.displayInstruction(data);
|
|
60
|
+
break;
|
|
61
|
+
case 'session':
|
|
62
|
+
this.displaySession(data);
|
|
63
|
+
break;
|
|
64
|
+
case 'file_operation':
|
|
65
|
+
this.displayFileOperation(data);
|
|
66
|
+
break;
|
|
67
|
+
case 'hook':
|
|
68
|
+
this.displayHook(data);
|
|
69
|
+
break;
|
|
70
|
+
default:
|
|
71
|
+
this.displayGeneric(data);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Auto-detect data type based on object properties
|
|
77
|
+
* @param {Object} data - Data to analyze
|
|
78
|
+
* @returns {string} Detected type
|
|
79
|
+
*/
|
|
80
|
+
detectType(data) {
|
|
81
|
+
if (!data || typeof data !== 'object') return 'generic';
|
|
82
|
+
|
|
83
|
+
// Event detection
|
|
84
|
+
if (data.hook_event_name || data.event_type || (data.type && data.timestamp)) {
|
|
85
|
+
return 'event';
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Agent detection
|
|
89
|
+
if (data.agent_name || data.agentName ||
|
|
90
|
+
(data.name && (data.status === 'active' || data.status === 'completed'))) {
|
|
91
|
+
return 'agent';
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Tool detection - PRIORITY: Check if it's a tool first
|
|
95
|
+
// This includes TodoWrite tools which should always be displayed as tools, not todos
|
|
96
|
+
if (data.tool_name || data.name === 'TodoWrite' || data.name === 'Read' ||
|
|
97
|
+
data.tool_parameters || (data.params && data.icon) ||
|
|
98
|
+
(data.name && data.type === 'tool')) {
|
|
99
|
+
return 'tool';
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Todo detection - Only for standalone todo lists, not tool todos
|
|
103
|
+
if (data.todos && !data.name && !data.params) {
|
|
104
|
+
return 'todo';
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Single todo item detection
|
|
108
|
+
if (data.content && data.activeForm && data.status && !data.name && !data.params) {
|
|
109
|
+
return 'todo';
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Instruction detection
|
|
113
|
+
if (data.text && data.preview && data.type === 'user_instruction') {
|
|
114
|
+
return 'instruction';
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Session detection
|
|
118
|
+
if (data.session_id && (data.startTime || data.lastActivity)) {
|
|
119
|
+
return 'session';
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// File operation detection
|
|
123
|
+
if (data.file_path && (data.operations || data.operation)) {
|
|
124
|
+
return 'file_operation';
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Hook detection
|
|
128
|
+
if (data.event_type && (data.hook_name || data.subtype)) {
|
|
129
|
+
return 'hook';
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return 'generic';
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Display event data with comprehensive formatting
|
|
137
|
+
*/
|
|
138
|
+
displayEvent(data) {
|
|
139
|
+
const eventType = this.formatEventType(data);
|
|
140
|
+
const timestamp = this.formatTimestamp(data.timestamp);
|
|
141
|
+
|
|
142
|
+
let html = `
|
|
143
|
+
<div class="unified-viewer-header">
|
|
144
|
+
<h6>${eventType}</h6>
|
|
145
|
+
<span class="unified-viewer-timestamp">${timestamp}</span>
|
|
146
|
+
</div>
|
|
147
|
+
<div class="unified-viewer-content">
|
|
148
|
+
`;
|
|
149
|
+
|
|
150
|
+
// Event-specific details
|
|
151
|
+
html += this.formatEventDetails(data);
|
|
152
|
+
|
|
153
|
+
// Tool parameters if present
|
|
154
|
+
if (data.tool_parameters || (data.data && data.data.tool_parameters)) {
|
|
155
|
+
const params = data.tool_parameters || data.data.tool_parameters;
|
|
156
|
+
html += this.formatParameters(params, 'Tool Parameters');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Event data if present
|
|
160
|
+
if (data.data && Object.keys(data.data).length > 0) {
|
|
161
|
+
html += this.formatEventData(data);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
html += '</div>';
|
|
165
|
+
this.container.innerHTML = html;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Display agent data with full details
|
|
170
|
+
*/
|
|
171
|
+
displayAgent(data) {
|
|
172
|
+
const agentIcon = this.getAgentIcon(data.name || data.agentName);
|
|
173
|
+
const status = this.formatStatus(data.status);
|
|
174
|
+
|
|
175
|
+
let html = `
|
|
176
|
+
<div class="unified-viewer-header">
|
|
177
|
+
<h6>${agentIcon} ${data.name || data.agentName || 'Unknown Agent'}</h6>
|
|
178
|
+
<span class="unified-viewer-status">${status}</span>
|
|
179
|
+
</div>
|
|
180
|
+
<div class="unified-viewer-content">
|
|
181
|
+
<div class="detail-row">
|
|
182
|
+
<span class="detail-label">Status:</span>
|
|
183
|
+
<span class="detail-value">${status}</span>
|
|
184
|
+
</div>
|
|
185
|
+
<div class="detail-row">
|
|
186
|
+
<span class="detail-label">Session ID:</span>
|
|
187
|
+
<span class="detail-value">${data.sessionId || data.session_id || 'N/A'}</span>
|
|
188
|
+
</div>
|
|
189
|
+
<div class="detail-row">
|
|
190
|
+
<span class="detail-label">Timestamp:</span>
|
|
191
|
+
<span class="detail-value">${this.formatTimestamp(data.timestamp)}</span>
|
|
192
|
+
</div>
|
|
193
|
+
`;
|
|
194
|
+
|
|
195
|
+
// Tools used by agent
|
|
196
|
+
if (data.tools && data.tools.length > 0) {
|
|
197
|
+
html += `
|
|
198
|
+
<div class="detail-section">
|
|
199
|
+
<span class="detail-section-title">Tools Used (${data.tools.length}):</span>
|
|
200
|
+
<div class="tools-list">
|
|
201
|
+
${data.tools.map(tool => `
|
|
202
|
+
<div class="tool-summary">
|
|
203
|
+
<span class="tool-icon">${this.getToolIcon(tool.name)}</span>
|
|
204
|
+
<span class="tool-name">${tool.name}</span>
|
|
205
|
+
<span class="tool-status ${this.formatStatusClass(tool.status)}">${tool.status}</span>
|
|
206
|
+
</div>
|
|
207
|
+
`).join('')}
|
|
208
|
+
</div>
|
|
209
|
+
</div>
|
|
210
|
+
`;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
html += '</div>';
|
|
214
|
+
this.container.innerHTML = html;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Display tool data with parameters and results
|
|
219
|
+
*/
|
|
220
|
+
displayTool(data) {
|
|
221
|
+
const toolIcon = this.getToolIcon(data.name || data.tool_name);
|
|
222
|
+
const status = this.formatStatus(data.status);
|
|
223
|
+
|
|
224
|
+
let html = `
|
|
225
|
+
<div class="unified-viewer-header">
|
|
226
|
+
<h6>${toolIcon} ${data.name || data.tool_name || 'Unknown Tool'}</h6>
|
|
227
|
+
<span class="unified-viewer-status">${status}</span>
|
|
228
|
+
</div>
|
|
229
|
+
<div class="unified-viewer-content">
|
|
230
|
+
<div class="detail-row">
|
|
231
|
+
<span class="detail-label">Type:</span>
|
|
232
|
+
<span class="detail-value">Tool</span>
|
|
233
|
+
</div>
|
|
234
|
+
<div class="detail-row">
|
|
235
|
+
<span class="detail-label">Name:</span>
|
|
236
|
+
<span class="detail-value">${data.name || data.tool_name || 'Unknown Tool'}</span>
|
|
237
|
+
</div>
|
|
238
|
+
<div class="detail-row">
|
|
239
|
+
<span class="detail-label">Status:</span>
|
|
240
|
+
<span class="detail-value">${status}</span>
|
|
241
|
+
</div>
|
|
242
|
+
`;
|
|
243
|
+
|
|
244
|
+
// Tool parameters
|
|
245
|
+
if (data.params || data.tool_parameters) {
|
|
246
|
+
const params = data.params || data.tool_parameters;
|
|
247
|
+
html += this.formatParameters(params, 'Parameters');
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Timestamp
|
|
251
|
+
if (data.timestamp) {
|
|
252
|
+
html += `
|
|
253
|
+
<div class="detail-row">
|
|
254
|
+
<span class="detail-label">Timestamp:</span>
|
|
255
|
+
<span class="detail-value">${this.formatTimestamp(data.timestamp)}</span>
|
|
256
|
+
</div>
|
|
257
|
+
`;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Tool result
|
|
261
|
+
if (data.result) {
|
|
262
|
+
html += `
|
|
263
|
+
<div class="detail-section">
|
|
264
|
+
<span class="detail-section-title">Result:</span>
|
|
265
|
+
<pre class="tool-result">${this.escapeHtml(JSON.stringify(data.result, null, 2))}</pre>
|
|
266
|
+
</div>
|
|
267
|
+
`;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
html += '</div>';
|
|
271
|
+
this.container.innerHTML = html;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Display todo data with checklist formatting
|
|
276
|
+
*/
|
|
277
|
+
displayTodo(data) {
|
|
278
|
+
// Handle different data structures for TodoWrite
|
|
279
|
+
let todos;
|
|
280
|
+
let toolName = 'Todo List';
|
|
281
|
+
let timestamp = null;
|
|
282
|
+
let status = null;
|
|
283
|
+
|
|
284
|
+
if (data.todos && Array.isArray(data.todos)) {
|
|
285
|
+
// Direct todo list format
|
|
286
|
+
todos = data.todos;
|
|
287
|
+
} else if (data.tool_parameters && data.tool_parameters.todos) {
|
|
288
|
+
// TodoWrite tool format
|
|
289
|
+
todos = data.tool_parameters.todos;
|
|
290
|
+
toolName = 'TodoWrite';
|
|
291
|
+
timestamp = data.timestamp;
|
|
292
|
+
status = data.status;
|
|
293
|
+
} else if (Array.isArray(data)) {
|
|
294
|
+
// Array of todos
|
|
295
|
+
todos = data;
|
|
296
|
+
} else if (data.content && data.activeForm && data.status) {
|
|
297
|
+
// Single todo item
|
|
298
|
+
todos = [data];
|
|
299
|
+
} else {
|
|
300
|
+
// Fallback
|
|
301
|
+
todos = [];
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
let html = `
|
|
305
|
+
<div class="unified-viewer-header">
|
|
306
|
+
<h6>📝 ${toolName}</h6>
|
|
307
|
+
${status ? `<span class="unified-viewer-status">${this.formatStatus(status)}</span>` : ''}
|
|
308
|
+
</div>
|
|
309
|
+
<div class="unified-viewer-content">
|
|
310
|
+
`;
|
|
311
|
+
|
|
312
|
+
// Show timestamp if available
|
|
313
|
+
if (timestamp) {
|
|
314
|
+
html += `
|
|
315
|
+
<div class="detail-row">
|
|
316
|
+
<span class="detail-label">Timestamp:</span>
|
|
317
|
+
<span class="detail-value">${this.formatTimestamp(timestamp)}</span>
|
|
318
|
+
</div>
|
|
319
|
+
`;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (todos.length > 0) {
|
|
323
|
+
// Status summary with enhanced formatting
|
|
324
|
+
const statusCounts = this.getTodoStatusCounts(todos);
|
|
325
|
+
html += `
|
|
326
|
+
<div class="detail-section">
|
|
327
|
+
<span class="detail-section-title">Todo Summary</span>
|
|
328
|
+
<div class="todo-summary">
|
|
329
|
+
<div class="summary-item completed">
|
|
330
|
+
<span class="summary-icon">✅</span>
|
|
331
|
+
<span class="summary-count">${statusCounts.completed}</span>
|
|
332
|
+
<span class="summary-label">Completed</span>
|
|
333
|
+
</div>
|
|
334
|
+
<div class="summary-item in_progress">
|
|
335
|
+
<span class="summary-icon">🔄</span>
|
|
336
|
+
<span class="summary-count">${statusCounts.in_progress}</span>
|
|
337
|
+
<span class="summary-label">In Progress</span>
|
|
338
|
+
</div>
|
|
339
|
+
<div class="summary-item pending">
|
|
340
|
+
<span class="summary-icon">⏳</span>
|
|
341
|
+
<span class="summary-count">${statusCounts.pending}</span>
|
|
342
|
+
<span class="summary-label">Pending</span>
|
|
343
|
+
</div>
|
|
344
|
+
</div>
|
|
345
|
+
</div>
|
|
346
|
+
`;
|
|
347
|
+
|
|
348
|
+
// Enhanced todo items display
|
|
349
|
+
html += `
|
|
350
|
+
<div class="detail-section">
|
|
351
|
+
<span class="detail-section-title">Todo List (${todos.length} items)</span>
|
|
352
|
+
<div class="todo-checklist">
|
|
353
|
+
`;
|
|
354
|
+
|
|
355
|
+
todos.forEach((todo, index) => {
|
|
356
|
+
const statusIcon = this.getCheckboxIcon(todo.status);
|
|
357
|
+
const displayText = todo.status === 'in_progress' ?
|
|
358
|
+
(todo.activeForm || todo.content) : todo.content;
|
|
359
|
+
const statusClass = this.formatStatusClass(todo.status);
|
|
360
|
+
|
|
361
|
+
html += `
|
|
362
|
+
<div class="todo-checklist-item ${todo.status}">
|
|
363
|
+
<div class="todo-checkbox">
|
|
364
|
+
<span class="checkbox-icon ${statusClass}">${statusIcon}</span>
|
|
365
|
+
</div>
|
|
366
|
+
<div class="todo-text">
|
|
367
|
+
<span class="todo-content">${this.escapeHtml(displayText)}</span>
|
|
368
|
+
<span class="todo-status-badge ${statusClass}">${todo.status.replace('_', ' ')}</span>
|
|
369
|
+
</div>
|
|
370
|
+
</div>
|
|
371
|
+
`;
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
html += `
|
|
375
|
+
</div>
|
|
376
|
+
</div>
|
|
377
|
+
`;
|
|
378
|
+
} else {
|
|
379
|
+
html += `
|
|
380
|
+
<div class="detail-section">
|
|
381
|
+
<div class="no-todos">No todo items found</div>
|
|
382
|
+
</div>
|
|
383
|
+
`;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
html += '</div>';
|
|
387
|
+
this.container.innerHTML = html;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Display instruction data
|
|
392
|
+
*/
|
|
393
|
+
displayInstruction(data) {
|
|
394
|
+
let html = `
|
|
395
|
+
<div class="unified-viewer-header">
|
|
396
|
+
<h6>💬 User Instruction</h6>
|
|
397
|
+
<span class="unified-viewer-timestamp">${this.formatTimestamp(data.timestamp)}</span>
|
|
398
|
+
</div>
|
|
399
|
+
<div class="unified-viewer-content">
|
|
400
|
+
<div class="detail-row">
|
|
401
|
+
<span class="detail-label">Content:</span>
|
|
402
|
+
<div class="detail-value instruction-text">${this.escapeHtml(data.text)}</div>
|
|
403
|
+
</div>
|
|
404
|
+
<div class="detail-row">
|
|
405
|
+
<span class="detail-label">Length:</span>
|
|
406
|
+
<span class="detail-value">${data.text.length} characters</span>
|
|
407
|
+
</div>
|
|
408
|
+
</div>
|
|
409
|
+
`;
|
|
410
|
+
this.container.innerHTML = html;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Display session data
|
|
415
|
+
*/
|
|
416
|
+
displaySession(data) {
|
|
417
|
+
let html = `
|
|
418
|
+
<div class="unified-viewer-header">
|
|
419
|
+
<h6>🎯 Session: ${data.session_id || data.id}</h6>
|
|
420
|
+
<span class="unified-viewer-status">${this.formatStatus(data.status || 'active')}</span>
|
|
421
|
+
</div>
|
|
422
|
+
<div class="unified-viewer-content">
|
|
423
|
+
<div class="detail-row">
|
|
424
|
+
<span class="detail-label">Session ID:</span>
|
|
425
|
+
<span class="detail-value">${data.session_id || data.id}</span>
|
|
426
|
+
</div>
|
|
427
|
+
<div class="detail-row">
|
|
428
|
+
<span class="detail-label">Start Time:</span>
|
|
429
|
+
<span class="detail-value">${this.formatTimestamp(data.startTime || data.timestamp)}</span>
|
|
430
|
+
</div>
|
|
431
|
+
`;
|
|
432
|
+
|
|
433
|
+
if (data.working_directory) {
|
|
434
|
+
html += `
|
|
435
|
+
<div class="detail-row">
|
|
436
|
+
<span class="detail-label">Working Directory:</span>
|
|
437
|
+
<span class="detail-value">${data.working_directory}</span>
|
|
438
|
+
</div>
|
|
439
|
+
`;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (data.git_branch) {
|
|
443
|
+
html += `
|
|
444
|
+
<div class="detail-row">
|
|
445
|
+
<span class="detail-label">Git Branch:</span>
|
|
446
|
+
<span class="detail-value">${data.git_branch}</span>
|
|
447
|
+
</div>
|
|
448
|
+
`;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
if (data.eventCount !== undefined) {
|
|
452
|
+
html += `
|
|
453
|
+
<div class="detail-row">
|
|
454
|
+
<span class="detail-label">Events:</span>
|
|
455
|
+
<span class="detail-value">${data.eventCount}</span>
|
|
456
|
+
</div>
|
|
457
|
+
`;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
html += '</div>';
|
|
461
|
+
this.container.innerHTML = html;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Display file operation data
|
|
466
|
+
*/
|
|
467
|
+
displayFileOperation(data) {
|
|
468
|
+
let html = `
|
|
469
|
+
<div class="unified-viewer-header">
|
|
470
|
+
<h6>📄 File: ${data.file_path}</h6>
|
|
471
|
+
<span class="unified-viewer-count">${data.operations ? data.operations.length : 1} operation${data.operations && data.operations.length !== 1 ? 's' : ''}</span>
|
|
472
|
+
</div>
|
|
473
|
+
<div class="unified-viewer-content">
|
|
474
|
+
<div class="detail-row">
|
|
475
|
+
<span class="detail-label">File Path:</span>
|
|
476
|
+
<span class="detail-value">${data.file_path}</span>
|
|
477
|
+
</div>
|
|
478
|
+
`;
|
|
479
|
+
|
|
480
|
+
if (data.operations && Array.isArray(data.operations)) {
|
|
481
|
+
html += `
|
|
482
|
+
<div class="detail-section">
|
|
483
|
+
<span class="detail-section-title">Operations:</span>
|
|
484
|
+
<div class="operations-list">
|
|
485
|
+
${data.operations.map(op => `
|
|
486
|
+
<div class="operation-item">
|
|
487
|
+
<span class="operation-type">${op.operation}</span>
|
|
488
|
+
<span class="operation-timestamp">${this.formatTimestamp(op.timestamp)}</span>
|
|
489
|
+
</div>
|
|
490
|
+
`).join('')}
|
|
491
|
+
</div>
|
|
492
|
+
</div>
|
|
493
|
+
`;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
html += '</div>';
|
|
497
|
+
this.container.innerHTML = html;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Display hook event data
|
|
502
|
+
*/
|
|
503
|
+
displayHook(data) {
|
|
504
|
+
const hookType = data.event_type || data.subtype || 'unknown';
|
|
505
|
+
|
|
506
|
+
let html = `
|
|
507
|
+
<div class="unified-viewer-header">
|
|
508
|
+
<h6>🔗 Hook: ${hookType}</h6>
|
|
509
|
+
<span class="unified-viewer-timestamp">${this.formatTimestamp(data.timestamp)}</span>
|
|
510
|
+
</div>
|
|
511
|
+
<div class="unified-viewer-content">
|
|
512
|
+
`;
|
|
513
|
+
|
|
514
|
+
html += this.formatHookDetails(data);
|
|
515
|
+
html += '</div>';
|
|
516
|
+
this.container.innerHTML = html;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* Display generic data with fallback formatting
|
|
521
|
+
*/
|
|
522
|
+
displayGeneric(data) {
|
|
523
|
+
let html = `
|
|
524
|
+
<div class="unified-viewer-header">
|
|
525
|
+
<h6>📊 Data Details</h6>
|
|
526
|
+
${data.timestamp ? `<span class="unified-viewer-timestamp">${this.formatTimestamp(data.timestamp)}</span>` : ''}
|
|
527
|
+
</div>
|
|
528
|
+
<div class="unified-viewer-content">
|
|
529
|
+
`;
|
|
530
|
+
|
|
531
|
+
if (typeof data === 'object' && data !== null) {
|
|
532
|
+
// Display meaningful properties
|
|
533
|
+
const meaningfulProps = ['id', 'name', 'type', 'status', 'timestamp', 'text', 'content', 'message'];
|
|
534
|
+
|
|
535
|
+
for (let prop of meaningfulProps) {
|
|
536
|
+
if (data[prop] !== undefined) {
|
|
537
|
+
let value = data[prop];
|
|
538
|
+
if (typeof value === 'string' && value.length > 200) {
|
|
539
|
+
value = value.substring(0, 200) + '...';
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
html += `
|
|
543
|
+
<div class="detail-row">
|
|
544
|
+
<span class="detail-label">${prop}:</span>
|
|
545
|
+
<span class="detail-value">${this.escapeHtml(String(value))}</span>
|
|
546
|
+
</div>
|
|
547
|
+
`;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
} else {
|
|
551
|
+
html += `<div class="simple-value">${this.escapeHtml(String(data))}</div>`;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
html += '</div>';
|
|
555
|
+
this.container.innerHTML = html;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// ==================== FORMATTING UTILITIES ====================
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* Format event type for display
|
|
562
|
+
*/
|
|
563
|
+
formatEventType(event) {
|
|
564
|
+
if (event.type && event.subtype) {
|
|
565
|
+
if (event.type === event.subtype || event.subtype === 'generic') {
|
|
566
|
+
return event.type;
|
|
567
|
+
}
|
|
568
|
+
return `${event.type}.${event.subtype}`;
|
|
569
|
+
}
|
|
570
|
+
if (event.type) return event.type;
|
|
571
|
+
if (event.hook_event_name) return event.hook_event_name;
|
|
572
|
+
return 'unknown';
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* Format detailed event data based on type
|
|
577
|
+
*/
|
|
578
|
+
formatEventDetails(event) {
|
|
579
|
+
const data = event.data || {};
|
|
580
|
+
|
|
581
|
+
switch (event.type) {
|
|
582
|
+
case 'hook':
|
|
583
|
+
return this.formatHookDetails(event);
|
|
584
|
+
case 'agent':
|
|
585
|
+
return this.formatAgentEventDetails(event);
|
|
586
|
+
case 'todo':
|
|
587
|
+
return this.formatTodoEventDetails(event);
|
|
588
|
+
case 'session':
|
|
589
|
+
return this.formatSessionEventDetails(event);
|
|
590
|
+
default:
|
|
591
|
+
return this.formatGenericEventDetails(event);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* Format hook event details
|
|
597
|
+
*/
|
|
598
|
+
formatHookDetails(event) {
|
|
599
|
+
const data = event.data || {};
|
|
600
|
+
const hookType = event.subtype || event.event_type || 'unknown';
|
|
601
|
+
|
|
602
|
+
let html = `
|
|
603
|
+
<div class="detail-row">
|
|
604
|
+
<span class="detail-label">Hook Type:</span>
|
|
605
|
+
<span class="detail-value">${hookType}</span>
|
|
606
|
+
</div>
|
|
607
|
+
`;
|
|
608
|
+
|
|
609
|
+
switch (hookType) {
|
|
610
|
+
case 'user_prompt':
|
|
611
|
+
const prompt = data.prompt_text || data.prompt_preview || '';
|
|
612
|
+
html += `
|
|
613
|
+
<div class="detail-row">
|
|
614
|
+
<span class="detail-label">Prompt:</span>
|
|
615
|
+
<div class="detail-value prompt-text">${this.escapeHtml(prompt)}</div>
|
|
616
|
+
</div>
|
|
617
|
+
`;
|
|
618
|
+
break;
|
|
619
|
+
|
|
620
|
+
case 'pre_tool':
|
|
621
|
+
case 'post_tool':
|
|
622
|
+
const toolName = data.tool_name || 'Unknown tool';
|
|
623
|
+
html += `
|
|
624
|
+
<div class="detail-row">
|
|
625
|
+
<span class="detail-label">Tool:</span>
|
|
626
|
+
<span class="detail-value">${toolName}</span>
|
|
627
|
+
</div>
|
|
628
|
+
`;
|
|
629
|
+
if (data.operation_type) {
|
|
630
|
+
html += `
|
|
631
|
+
<div class="detail-row">
|
|
632
|
+
<span class="detail-label">Operation:</span>
|
|
633
|
+
<span class="detail-value">${data.operation_type}</span>
|
|
634
|
+
</div>
|
|
635
|
+
`;
|
|
636
|
+
}
|
|
637
|
+
if (hookType === 'post_tool' && data.duration_ms) {
|
|
638
|
+
html += `
|
|
639
|
+
<div class="detail-row">
|
|
640
|
+
<span class="detail-label">Duration:</span>
|
|
641
|
+
<span class="detail-value">${data.duration_ms}ms</span>
|
|
642
|
+
</div>
|
|
643
|
+
`;
|
|
644
|
+
}
|
|
645
|
+
break;
|
|
646
|
+
|
|
647
|
+
case 'subagent_start':
|
|
648
|
+
case 'subagent_stop':
|
|
649
|
+
const agentType = data.agent_type || data.agent || 'Unknown';
|
|
650
|
+
html += `
|
|
651
|
+
<div class="detail-row">
|
|
652
|
+
<span class="detail-label">Agent:</span>
|
|
653
|
+
<span class="detail-value">${agentType}</span>
|
|
654
|
+
</div>
|
|
655
|
+
`;
|
|
656
|
+
if (hookType === 'subagent_start' && data.prompt) {
|
|
657
|
+
html += `
|
|
658
|
+
<div class="detail-row">
|
|
659
|
+
<span class="detail-label">Task:</span>
|
|
660
|
+
<div class="detail-value">${this.escapeHtml(data.prompt)}</div>
|
|
661
|
+
</div>
|
|
662
|
+
`;
|
|
663
|
+
}
|
|
664
|
+
if (hookType === 'subagent_stop' && data.reason) {
|
|
665
|
+
html += `
|
|
666
|
+
<div class="detail-row">
|
|
667
|
+
<span class="detail-label">Reason:</span>
|
|
668
|
+
<span class="detail-value">${data.reason}</span>
|
|
669
|
+
</div>
|
|
670
|
+
`;
|
|
671
|
+
}
|
|
672
|
+
break;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
return html;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* Format agent event details
|
|
680
|
+
*/
|
|
681
|
+
formatAgentEventDetails(event) {
|
|
682
|
+
const data = event.data || {};
|
|
683
|
+
let html = '';
|
|
684
|
+
|
|
685
|
+
if (data.agent_type || data.name) {
|
|
686
|
+
html += `
|
|
687
|
+
<div class="detail-row">
|
|
688
|
+
<span class="detail-label">Agent Type:</span>
|
|
689
|
+
<span class="detail-value">${data.agent_type || data.name}</span>
|
|
690
|
+
</div>
|
|
691
|
+
`;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
if (event.subtype) {
|
|
695
|
+
html += `
|
|
696
|
+
<div class="detail-row">
|
|
697
|
+
<span class="detail-label">Action:</span>
|
|
698
|
+
<span class="detail-value">${event.subtype}</span>
|
|
699
|
+
</div>
|
|
700
|
+
`;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
return html;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
/**
|
|
707
|
+
* Format todo event details
|
|
708
|
+
*/
|
|
709
|
+
formatTodoEventDetails(event) {
|
|
710
|
+
const data = event.data || {};
|
|
711
|
+
let html = '';
|
|
712
|
+
|
|
713
|
+
if (data.todos && Array.isArray(data.todos)) {
|
|
714
|
+
const statusCounts = this.getTodoStatusCounts(data.todos);
|
|
715
|
+
html += `
|
|
716
|
+
<div class="detail-row">
|
|
717
|
+
<span class="detail-label">Todo Items:</span>
|
|
718
|
+
<span class="detail-value">${data.todos.length} total</span>
|
|
719
|
+
</div>
|
|
720
|
+
<div class="detail-row">
|
|
721
|
+
<span class="detail-label">Status:</span>
|
|
722
|
+
<span class="detail-value">${statusCounts.completed} completed, ${statusCounts.in_progress} in progress</span>
|
|
723
|
+
</div>
|
|
724
|
+
`;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
return html;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
/**
|
|
731
|
+
* Format session event details
|
|
732
|
+
*/
|
|
733
|
+
formatSessionEventDetails(event) {
|
|
734
|
+
const data = event.data || {};
|
|
735
|
+
let html = '';
|
|
736
|
+
|
|
737
|
+
if (data.session_id) {
|
|
738
|
+
html += `
|
|
739
|
+
<div class="detail-row">
|
|
740
|
+
<span class="detail-label">Session ID:</span>
|
|
741
|
+
<span class="detail-value">${data.session_id}</span>
|
|
742
|
+
</div>
|
|
743
|
+
`;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
if (event.subtype) {
|
|
747
|
+
html += `
|
|
748
|
+
<div class="detail-row">
|
|
749
|
+
<span class="detail-label">Action:</span>
|
|
750
|
+
<span class="detail-value">${event.subtype}</span>
|
|
751
|
+
</div>
|
|
752
|
+
`;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
return html;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
/**
|
|
759
|
+
* Format generic event details
|
|
760
|
+
*/
|
|
761
|
+
formatGenericEventDetails(event) {
|
|
762
|
+
const data = event.data || {};
|
|
763
|
+
let html = '';
|
|
764
|
+
|
|
765
|
+
// Show basic data properties
|
|
766
|
+
const basicProps = ['message', 'description', 'value', 'result'];
|
|
767
|
+
for (let prop of basicProps) {
|
|
768
|
+
if (data[prop] !== undefined) {
|
|
769
|
+
let value = data[prop];
|
|
770
|
+
if (typeof value === 'string' && value.length > 200) {
|
|
771
|
+
value = value.substring(0, 200) + '...';
|
|
772
|
+
}
|
|
773
|
+
html += `
|
|
774
|
+
<div class="detail-row">
|
|
775
|
+
<span class="detail-label">${prop}:</span>
|
|
776
|
+
<span class="detail-value">${this.escapeHtml(String(value))}</span>
|
|
777
|
+
</div>
|
|
778
|
+
`;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
return html;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
/**
|
|
786
|
+
* Format event data section
|
|
787
|
+
*/
|
|
788
|
+
formatEventData(event) {
|
|
789
|
+
const data = event.data;
|
|
790
|
+
if (!data || Object.keys(data).length === 0) return '';
|
|
791
|
+
|
|
792
|
+
return `
|
|
793
|
+
<div class="detail-section">
|
|
794
|
+
<span class="detail-section-title">Event Data:</span>
|
|
795
|
+
<pre class="event-data-json">${this.escapeHtml(JSON.stringify(data, null, 2))}</pre>
|
|
796
|
+
</div>
|
|
797
|
+
`;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
/**
|
|
801
|
+
* Format tool/event parameters
|
|
802
|
+
*/
|
|
803
|
+
formatParameters(params, title = 'Parameters') {
|
|
804
|
+
if (!params || Object.keys(params).length === 0) {
|
|
805
|
+
return `
|
|
806
|
+
<div class="detail-section">
|
|
807
|
+
<span class="detail-section-title">${title}:</span>
|
|
808
|
+
<div class="no-params">No parameters</div>
|
|
809
|
+
</div>
|
|
810
|
+
`;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
const paramKeys = Object.keys(params);
|
|
814
|
+
return `
|
|
815
|
+
<div class="detail-section">
|
|
816
|
+
<span class="detail-section-title">${title} (${paramKeys.length}):</span>
|
|
817
|
+
<div class="params-list">
|
|
818
|
+
${paramKeys.map(key => {
|
|
819
|
+
const value = params[key];
|
|
820
|
+
const displayValue = this.formatParameterValue(value);
|
|
821
|
+
return `
|
|
822
|
+
<div class="param-item">
|
|
823
|
+
<div class="param-key">${key}:</div>
|
|
824
|
+
<div class="param-value">${displayValue}</div>
|
|
825
|
+
</div>
|
|
826
|
+
`;
|
|
827
|
+
}).join('')}
|
|
828
|
+
</div>
|
|
829
|
+
</div>
|
|
830
|
+
`;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
/**
|
|
834
|
+
* Format parameter value with appropriate styling
|
|
835
|
+
*/
|
|
836
|
+
formatParameterValue(value) {
|
|
837
|
+
if (typeof value === 'string') {
|
|
838
|
+
if (value.length > 500) {
|
|
839
|
+
return `<pre class="param-text-long">${this.escapeHtml(value.substring(0, 500) + '...\n\n[Content truncated - ' + value.length + ' total characters]')}</pre>`;
|
|
840
|
+
} else if (value.length > 100) {
|
|
841
|
+
return `<pre class="param-text">${this.escapeHtml(value)}</pre>`;
|
|
842
|
+
} else {
|
|
843
|
+
return `<span class="param-text-short">${this.escapeHtml(value)}</span>`;
|
|
844
|
+
}
|
|
845
|
+
} else if (typeof value === 'object' && value !== null) {
|
|
846
|
+
// Special handling for todos array - display as formatted list instead of raw JSON
|
|
847
|
+
if (Array.isArray(value) && value.length > 0 &&
|
|
848
|
+
value[0].hasOwnProperty('content') && value[0].hasOwnProperty('status')) {
|
|
849
|
+
return this.formatTodosAsParameter(value);
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
try {
|
|
853
|
+
return `<pre class="param-json">${this.escapeHtml(JSON.stringify(value, null, 2))}</pre>`;
|
|
854
|
+
} catch (e) {
|
|
855
|
+
return `<span class="param-error">Error displaying object</span>`;
|
|
856
|
+
}
|
|
857
|
+
} else {
|
|
858
|
+
return `<span class="param-primitive">${this.escapeHtml(String(value))}</span>`;
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
/**
|
|
863
|
+
* Format todos array as a parameter value
|
|
864
|
+
*/
|
|
865
|
+
formatTodosAsParameter(todos) {
|
|
866
|
+
const statusCounts = this.getTodoStatusCounts(todos);
|
|
867
|
+
|
|
868
|
+
let html = `
|
|
869
|
+
<div class="param-todos">
|
|
870
|
+
<div class="param-todos-header">
|
|
871
|
+
Array of todo objects (${todos.length} items)
|
|
872
|
+
</div>
|
|
873
|
+
<div class="param-todos-summary">
|
|
874
|
+
${statusCounts.completed} completed • ${statusCounts.in_progress} in progress • ${statusCounts.pending} pending
|
|
875
|
+
</div>
|
|
876
|
+
<div class="param-todos-list">
|
|
877
|
+
`;
|
|
878
|
+
|
|
879
|
+
todos.forEach((todo, index) => {
|
|
880
|
+
const statusIcon = this.getCheckboxIcon(todo.status);
|
|
881
|
+
const displayText = todo.status === 'in_progress' ?
|
|
882
|
+
(todo.activeForm || todo.content) : todo.content;
|
|
883
|
+
const statusClass = this.formatStatusClass(todo.status);
|
|
884
|
+
|
|
885
|
+
html += `
|
|
886
|
+
<div class="param-todo-item ${todo.status}">
|
|
887
|
+
<div class="param-todo-checkbox">
|
|
888
|
+
<span class="param-checkbox-icon ${statusClass}">${statusIcon}</span>
|
|
889
|
+
</div>
|
|
890
|
+
<div class="param-todo-text">
|
|
891
|
+
<span class="param-todo-content">${this.escapeHtml(displayText)}</span>
|
|
892
|
+
<span class="param-todo-status-badge ${statusClass}">${todo.status.replace('_', ' ')}</span>
|
|
893
|
+
</div>
|
|
894
|
+
</div>
|
|
895
|
+
`;
|
|
896
|
+
});
|
|
897
|
+
|
|
898
|
+
html += `
|
|
899
|
+
</div>
|
|
900
|
+
</div>
|
|
901
|
+
`;
|
|
902
|
+
|
|
903
|
+
return html;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
// ==================== UTILITY METHODS ====================
|
|
907
|
+
|
|
908
|
+
/**
|
|
909
|
+
* Format timestamp for display
|
|
910
|
+
*/
|
|
911
|
+
formatTimestamp(timestamp) {
|
|
912
|
+
if (!timestamp) return 'Unknown time';
|
|
913
|
+
|
|
914
|
+
try {
|
|
915
|
+
const date = new Date(timestamp);
|
|
916
|
+
if (isNaN(date.getTime())) return 'Invalid date';
|
|
917
|
+
return date.toLocaleString();
|
|
918
|
+
} catch (error) {
|
|
919
|
+
return 'Invalid date';
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
/**
|
|
924
|
+
* Format status with appropriate styling
|
|
925
|
+
*/
|
|
926
|
+
formatStatus(status) {
|
|
927
|
+
if (!status) return 'unknown';
|
|
928
|
+
|
|
929
|
+
const statusMap = {
|
|
930
|
+
'active': '🟢 Active',
|
|
931
|
+
'completed': '✅ Completed',
|
|
932
|
+
'in_progress': '🔄 In Progress',
|
|
933
|
+
'pending': '⏳ Pending',
|
|
934
|
+
'error': '❌ Error',
|
|
935
|
+
'failed': '❌ Failed'
|
|
936
|
+
};
|
|
937
|
+
|
|
938
|
+
return statusMap[status] || status;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
/**
|
|
942
|
+
* Get CSS class for status styling
|
|
943
|
+
*/
|
|
944
|
+
formatStatusClass(status) {
|
|
945
|
+
return `status-${status}`;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
/**
|
|
949
|
+
* Get icon for agent type
|
|
950
|
+
*/
|
|
951
|
+
getAgentIcon(agentName) {
|
|
952
|
+
const icons = {
|
|
953
|
+
'PM': '🎯',
|
|
954
|
+
'Engineer': '🔧',
|
|
955
|
+
'Engineer Agent': '🔧',
|
|
956
|
+
'Research': '🔍',
|
|
957
|
+
'Research Agent': '🔍',
|
|
958
|
+
'QA': '✅',
|
|
959
|
+
'QA Agent': '✅',
|
|
960
|
+
'Architect': '🏗️',
|
|
961
|
+
'Architect Agent': '🏗️',
|
|
962
|
+
'Ops': '⚙️',
|
|
963
|
+
'Ops Agent': '⚙️'
|
|
964
|
+
};
|
|
965
|
+
return icons[agentName] || '🤖';
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
/**
|
|
969
|
+
* Get icon for tool type
|
|
970
|
+
*/
|
|
971
|
+
getToolIcon(toolName) {
|
|
972
|
+
const icons = {
|
|
973
|
+
'Read': '👁️',
|
|
974
|
+
'Write': '✍️',
|
|
975
|
+
'Edit': '✏️',
|
|
976
|
+
'MultiEdit': '📝',
|
|
977
|
+
'Bash': '💻',
|
|
978
|
+
'Grep': '🔍',
|
|
979
|
+
'Glob': '📂',
|
|
980
|
+
'LS': '📁',
|
|
981
|
+
'TodoWrite': '📝',
|
|
982
|
+
'Task': '📋',
|
|
983
|
+
'WebFetch': '🌐'
|
|
984
|
+
};
|
|
985
|
+
return icons[toolName] || '🔧';
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
/**
|
|
989
|
+
* Get checkbox icon for todo status
|
|
990
|
+
*/
|
|
991
|
+
getCheckboxIcon(status) {
|
|
992
|
+
const icons = {
|
|
993
|
+
'pending': '⏳',
|
|
994
|
+
'in_progress': '🔄',
|
|
995
|
+
'completed': '✅'
|
|
996
|
+
};
|
|
997
|
+
return icons[status] || '❓';
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
/**
|
|
1001
|
+
* Get todo status counts
|
|
1002
|
+
*/
|
|
1003
|
+
getTodoStatusCounts(todos) {
|
|
1004
|
+
const counts = { completed: 0, in_progress: 0, pending: 0 };
|
|
1005
|
+
|
|
1006
|
+
todos.forEach(todo => {
|
|
1007
|
+
if (counts.hasOwnProperty(todo.status)) {
|
|
1008
|
+
counts[todo.status]++;
|
|
1009
|
+
}
|
|
1010
|
+
});
|
|
1011
|
+
|
|
1012
|
+
return counts;
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
/**
|
|
1016
|
+
* Escape HTML for safe display
|
|
1017
|
+
*/
|
|
1018
|
+
escapeHtml(text) {
|
|
1019
|
+
if (typeof text !== 'string') return '';
|
|
1020
|
+
|
|
1021
|
+
const div = document.createElement('div');
|
|
1022
|
+
div.textContent = text;
|
|
1023
|
+
return div.innerHTML;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
// ==================== PUBLIC API METHODS ====================
|
|
1027
|
+
|
|
1028
|
+
/**
|
|
1029
|
+
* Clear the viewer
|
|
1030
|
+
*/
|
|
1031
|
+
clear() {
|
|
1032
|
+
if (this.container) {
|
|
1033
|
+
this.container.innerHTML = '';
|
|
1034
|
+
}
|
|
1035
|
+
this.currentData = null;
|
|
1036
|
+
this.currentType = null;
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
/**
|
|
1040
|
+
* Get current displayed data
|
|
1041
|
+
*/
|
|
1042
|
+
getCurrentData() {
|
|
1043
|
+
return this.currentData;
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
/**
|
|
1047
|
+
* Get current data type
|
|
1048
|
+
*/
|
|
1049
|
+
getCurrentType() {
|
|
1050
|
+
return this.currentType;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
/**
|
|
1054
|
+
* Check if viewer has data
|
|
1055
|
+
*/
|
|
1056
|
+
hasData() {
|
|
1057
|
+
return this.currentData !== null;
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
// Export for module use
|
|
1062
|
+
export { UnifiedDataViewer };
|
|
1063
|
+
export default UnifiedDataViewer;
|
|
1064
|
+
|
|
1065
|
+
// Make globally available for non-module usage
|
|
1066
|
+
window.UnifiedDataViewer = UnifiedDataViewer;
|