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.
Files changed (41) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/INSTRUCTIONS.md +8 -0
  3. claude_mpm/cli/__init__.py +1 -1
  4. claude_mpm/cli/commands/monitor.py +88 -627
  5. claude_mpm/cli/commands/mpm_init.py +127 -107
  6. claude_mpm/cli/commands/mpm_init_handler.py +24 -23
  7. claude_mpm/cli/parsers/mpm_init_parser.py +34 -28
  8. claude_mpm/core/config.py +18 -0
  9. claude_mpm/core/instruction_reinforcement_hook.py +266 -0
  10. claude_mpm/core/pm_hook_interceptor.py +105 -8
  11. claude_mpm/dashboard/static/built/components/activity-tree.js +1 -1
  12. claude_mpm/dashboard/static/built/components/code-tree.js +1 -1
  13. claude_mpm/dashboard/static/built/dashboard.js +1 -1
  14. claude_mpm/dashboard/static/built/socket-client.js +1 -1
  15. claude_mpm/dashboard/static/css/activity.css +1239 -267
  16. claude_mpm/dashboard/static/css/dashboard.css +511 -0
  17. claude_mpm/dashboard/static/dist/components/activity-tree.js +1 -1
  18. claude_mpm/dashboard/static/dist/components/code-tree.js +1 -1
  19. claude_mpm/dashboard/static/dist/components/module-viewer.js +1 -1
  20. claude_mpm/dashboard/static/dist/dashboard.js +1 -1
  21. claude_mpm/dashboard/static/dist/socket-client.js +1 -1
  22. claude_mpm/dashboard/static/js/components/activity-tree.js +1193 -892
  23. claude_mpm/dashboard/static/js/components/build-tracker.js +15 -13
  24. claude_mpm/dashboard/static/js/components/code-tree.js +534 -143
  25. claude_mpm/dashboard/static/js/components/module-viewer.js +21 -7
  26. claude_mpm/dashboard/static/js/components/unified-data-viewer.js +1066 -0
  27. claude_mpm/dashboard/static/js/connection-manager.js +1 -1
  28. claude_mpm/dashboard/static/js/dashboard.js +227 -84
  29. claude_mpm/dashboard/static/js/socket-client.js +2 -2
  30. claude_mpm/dashboard/templates/index.html +100 -23
  31. claude_mpm/services/agents/deployment/agent_template_builder.py +11 -7
  32. claude_mpm/services/cli/socketio_manager.py +39 -8
  33. claude_mpm/services/infrastructure/monitoring.py +1 -1
  34. claude_mpm/services/socketio/handlers/code_analysis.py +83 -136
  35. claude_mpm/tools/code_tree_analyzer.py +290 -202
  36. {claude_mpm-4.1.11.dist-info → claude_mpm-4.1.13.dist-info}/METADATA +1 -1
  37. {claude_mpm-4.1.11.dist-info → claude_mpm-4.1.13.dist-info}/RECORD +41 -39
  38. {claude_mpm-4.1.11.dist-info → claude_mpm-4.1.13.dist-info}/WHEEL +0 -0
  39. {claude_mpm-4.1.11.dist-info → claude_mpm-4.1.13.dist-info}/entry_points.txt +0 -0
  40. {claude_mpm-4.1.11.dist-info → claude_mpm-4.1.13.dist-info}/licenses/LICENSE +0 -0
  41. {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;