claude-mpm 4.1.7__py3-none-any.whl → 4.1.10__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 (109) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/INSTRUCTIONS.md +26 -1
  3. claude_mpm/agents/OUTPUT_STYLE.md +73 -0
  4. claude_mpm/agents/agents_metadata.py +57 -0
  5. claude_mpm/agents/templates/.claude-mpm/memories/README.md +17 -0
  6. claude_mpm/agents/templates/.claude-mpm/memories/engineer_memories.md +3 -0
  7. claude_mpm/agents/templates/agent-manager.json +263 -17
  8. claude_mpm/agents/templates/agent-manager.md +248 -10
  9. claude_mpm/agents/templates/agentic_coder_optimizer.json +222 -0
  10. claude_mpm/agents/templates/code_analyzer.json +18 -8
  11. claude_mpm/agents/templates/engineer.json +1 -1
  12. claude_mpm/agents/templates/logs/prompts/agent_engineer_20250826_014258_728.md +39 -0
  13. claude_mpm/agents/templates/qa.json +1 -1
  14. claude_mpm/agents/templates/research.json +1 -1
  15. claude_mpm/cli/__init__.py +4 -0
  16. claude_mpm/cli/commands/__init__.py +6 -0
  17. claude_mpm/cli/commands/analyze.py +547 -0
  18. claude_mpm/cli/commands/analyze_code.py +524 -0
  19. claude_mpm/cli/commands/configure.py +223 -25
  20. claude_mpm/cli/commands/configure_tui.py +65 -61
  21. claude_mpm/cli/commands/debug.py +1387 -0
  22. claude_mpm/cli/parsers/analyze_code_parser.py +170 -0
  23. claude_mpm/cli/parsers/analyze_parser.py +135 -0
  24. claude_mpm/cli/parsers/base_parser.py +29 -0
  25. claude_mpm/cli/parsers/configure_parser.py +23 -0
  26. claude_mpm/cli/parsers/debug_parser.py +319 -0
  27. claude_mpm/config/socketio_config.py +21 -21
  28. claude_mpm/constants.py +3 -1
  29. claude_mpm/core/framework_loader.py +148 -6
  30. claude_mpm/core/log_manager.py +16 -13
  31. claude_mpm/core/logger.py +1 -1
  32. claude_mpm/core/unified_agent_registry.py +1 -1
  33. claude_mpm/dashboard/.claude-mpm/socketio-instances.json +1 -0
  34. claude_mpm/dashboard/analysis_runner.py +428 -0
  35. claude_mpm/dashboard/static/built/components/activity-tree.js +2 -0
  36. claude_mpm/dashboard/static/built/components/agent-inference.js +1 -1
  37. claude_mpm/dashboard/static/built/components/event-viewer.js +1 -1
  38. claude_mpm/dashboard/static/built/components/file-tool-tracker.js +1 -1
  39. claude_mpm/dashboard/static/built/components/module-viewer.js +1 -1
  40. claude_mpm/dashboard/static/built/components/session-manager.js +1 -1
  41. claude_mpm/dashboard/static/built/components/working-directory.js +1 -1
  42. claude_mpm/dashboard/static/built/dashboard.js +1 -1
  43. claude_mpm/dashboard/static/built/socket-client.js +1 -1
  44. claude_mpm/dashboard/static/css/activity.css +549 -0
  45. claude_mpm/dashboard/static/css/code-tree.css +846 -0
  46. claude_mpm/dashboard/static/css/dashboard.css +245 -0
  47. claude_mpm/dashboard/static/dist/components/activity-tree.js +2 -0
  48. claude_mpm/dashboard/static/dist/components/code-tree.js +2 -0
  49. claude_mpm/dashboard/static/dist/components/code-viewer.js +2 -0
  50. claude_mpm/dashboard/static/dist/components/event-viewer.js +1 -1
  51. claude_mpm/dashboard/static/dist/components/session-manager.js +1 -1
  52. claude_mpm/dashboard/static/dist/components/working-directory.js +1 -1
  53. claude_mpm/dashboard/static/dist/dashboard.js +1 -1
  54. claude_mpm/dashboard/static/dist/socket-client.js +1 -1
  55. claude_mpm/dashboard/static/js/components/activity-tree.js +1139 -0
  56. claude_mpm/dashboard/static/js/components/code-tree.js +1357 -0
  57. claude_mpm/dashboard/static/js/components/code-viewer.js +480 -0
  58. claude_mpm/dashboard/static/js/components/event-viewer.js +11 -0
  59. claude_mpm/dashboard/static/js/components/session-manager.js +40 -4
  60. claude_mpm/dashboard/static/js/components/socket-manager.js +12 -0
  61. claude_mpm/dashboard/static/js/components/ui-state-manager.js +4 -0
  62. claude_mpm/dashboard/static/js/components/working-directory.js +17 -1
  63. claude_mpm/dashboard/static/js/dashboard.js +39 -0
  64. claude_mpm/dashboard/static/js/socket-client.js +414 -20
  65. claude_mpm/dashboard/templates/index.html +184 -4
  66. claude_mpm/hooks/claude_hooks/hook_handler.py +182 -5
  67. claude_mpm/hooks/claude_hooks/installer.py +728 -0
  68. claude_mpm/scripts/claude-hook-handler.sh +161 -0
  69. claude_mpm/scripts/socketio_daemon.py +121 -8
  70. claude_mpm/services/agents/deployment/agent_config_provider.py +127 -27
  71. claude_mpm/services/agents/deployment/agent_lifecycle_manager_refactored.py +2 -2
  72. claude_mpm/services/agents/deployment/agent_record_service.py +1 -2
  73. claude_mpm/services/agents/memory/memory_format_service.py +1 -5
  74. claude_mpm/services/cli/agent_cleanup_service.py +1 -2
  75. claude_mpm/services/cli/agent_dependency_service.py +1 -1
  76. claude_mpm/services/cli/agent_validation_service.py +3 -4
  77. claude_mpm/services/cli/dashboard_launcher.py +2 -3
  78. claude_mpm/services/cli/startup_checker.py +0 -10
  79. claude_mpm/services/core/cache_manager.py +1 -2
  80. claude_mpm/services/core/path_resolver.py +1 -4
  81. claude_mpm/services/core/service_container.py +2 -2
  82. claude_mpm/services/diagnostics/checks/instructions_check.py +2 -5
  83. claude_mpm/services/event_bus/direct_relay.py +98 -20
  84. claude_mpm/services/infrastructure/monitoring/__init__.py +11 -11
  85. claude_mpm/services/infrastructure/monitoring.py +11 -11
  86. claude_mpm/services/project/architecture_analyzer.py +1 -1
  87. claude_mpm/services/project/dependency_analyzer.py +4 -4
  88. claude_mpm/services/project/language_analyzer.py +3 -3
  89. claude_mpm/services/project/metrics_collector.py +3 -6
  90. claude_mpm/services/socketio/handlers/__init__.py +2 -0
  91. claude_mpm/services/socketio/handlers/code_analysis.py +170 -0
  92. claude_mpm/services/socketio/handlers/registry.py +2 -0
  93. claude_mpm/services/socketio/server/connection_manager.py +95 -65
  94. claude_mpm/services/socketio/server/core.py +125 -17
  95. claude_mpm/services/socketio/server/main.py +44 -5
  96. claude_mpm/services/visualization/__init__.py +19 -0
  97. claude_mpm/services/visualization/mermaid_generator.py +938 -0
  98. claude_mpm/tools/__main__.py +208 -0
  99. claude_mpm/tools/code_tree_analyzer.py +778 -0
  100. claude_mpm/tools/code_tree_builder.py +632 -0
  101. claude_mpm/tools/code_tree_events.py +318 -0
  102. claude_mpm/tools/socketio_debug.py +671 -0
  103. {claude_mpm-4.1.7.dist-info → claude_mpm-4.1.10.dist-info}/METADATA +1 -1
  104. {claude_mpm-4.1.7.dist-info → claude_mpm-4.1.10.dist-info}/RECORD +108 -77
  105. claude_mpm/agents/schema/agent_schema.json +0 -314
  106. {claude_mpm-4.1.7.dist-info → claude_mpm-4.1.10.dist-info}/WHEEL +0 -0
  107. {claude_mpm-4.1.7.dist-info → claude_mpm-4.1.10.dist-info}/entry_points.txt +0 -0
  108. {claude_mpm-4.1.7.dist-info → claude_mpm-4.1.10.dist-info}/licenses/LICENSE +0 -0
  109. {claude_mpm-4.1.7.dist-info → claude_mpm-4.1.10.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,1139 @@
1
+ /**
2
+ * Activity Tree Component
3
+ *
4
+ * D3.js-based collapsible tree visualization for showing PM activity hierarchy.
5
+ * Displays PM actions, TodoWrite delegations, agent assignments, and tool usage.
6
+ */
7
+
8
+ class ActivityTree {
9
+ constructor() {
10
+ this.container = null;
11
+ this.svg = null;
12
+ this.treeData = null;
13
+ this.root = null;
14
+ this.treeLayout = null;
15
+ this.treeGroup = null;
16
+ this.events = [];
17
+ this.todoWriteStack = [];
18
+ this.activeAgent = null;
19
+ this.activeAgentStack = [];
20
+ this.margin = {top: 20, right: 120, bottom: 20, left: 120};
21
+ this.width = 960 - this.margin.left - this.margin.right;
22
+ this.height = 500 - this.margin.top - this.margin.bottom;
23
+ this.nodeId = 0;
24
+ this.duration = 750;
25
+ this.timeRange = '30min';
26
+ this.searchTerm = '';
27
+ this.tooltip = null;
28
+ this.initialized = false;
29
+ }
30
+
31
+ /**
32
+ * Initialize the activity tree visualization
33
+ */
34
+ initialize() {
35
+ console.log('ActivityTree.initialize() called, initialized:', this.initialized);
36
+
37
+ // Check if already initialized
38
+ if (this.initialized) {
39
+ console.log('Activity tree already initialized, skipping');
40
+ return;
41
+ }
42
+
43
+ // First try to find the container
44
+ this.container = document.getElementById('activity-tree-container');
45
+ if (!this.container) {
46
+ // Fall back to the inner div if container not found
47
+ this.container = document.getElementById('activity-tree');
48
+ if (!this.container) {
49
+ console.error('Activity tree container not found in DOM');
50
+ return;
51
+ }
52
+ }
53
+
54
+ console.log('Activity tree container found:', this.container);
55
+
56
+ // Check if the container is visible before initializing
57
+ const tabPanel = document.getElementById('activity-tab');
58
+ if (!tabPanel) {
59
+ console.error('Activity tab panel (#activity-tab) not found in DOM');
60
+ return;
61
+ }
62
+
63
+ // Initialize even if tab is not active, but don't render until visible
64
+ if (!tabPanel.classList.contains('active')) {
65
+ console.log('Activity tab not active, initializing but deferring render');
66
+ // Set up basic structure but defer visualization
67
+ this.setupControls();
68
+ this.initializeTreeData();
69
+ this.subscribeToEvents();
70
+ this.initialized = true;
71
+ return;
72
+ }
73
+
74
+ this.setupControls();
75
+ this.createVisualization();
76
+
77
+ if (!this.svg || !this.treeGroup) {
78
+ console.error('Failed to create D3 visualization elements');
79
+ return;
80
+ }
81
+
82
+ this.initializeTreeData();
83
+ this.update(this.root);
84
+ this.subscribeToEvents();
85
+
86
+ this.initialized = true;
87
+ console.log('Activity tree initialization complete');
88
+ }
89
+
90
+ /**
91
+ * Render the visualization when tab becomes visible (called when switching to Activity tab)
92
+ */
93
+ renderWhenVisible() {
94
+ console.log('ActivityTree.renderWhenVisible() called');
95
+
96
+ if (!this.initialized) {
97
+ console.log('Not initialized yet, calling initialize...');
98
+ this.initialize();
99
+ return;
100
+ }
101
+
102
+ // If already initialized but no visualization, create it
103
+ if (!this.svg) {
104
+ console.log('Creating deferred visualization...');
105
+ this.createVisualization();
106
+ if (this.svg && this.treeGroup) {
107
+ this.update(this.root);
108
+ }
109
+ }
110
+
111
+ // Force update to ensure tree is rendered with current data
112
+ if (this.root && this.svg) {
113
+ console.log('Updating tree with current data...');
114
+ this.update(this.root);
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Setup control handlers
120
+ */
121
+ setupControls() {
122
+ // Expand all button
123
+ const expandAllBtn = document.getElementById('expand-all');
124
+ if (expandAllBtn) {
125
+ expandAllBtn.addEventListener('click', () => this.expandAll());
126
+ }
127
+
128
+ // Collapse all button
129
+ const collapseAllBtn = document.getElementById('collapse-all');
130
+ if (collapseAllBtn) {
131
+ collapseAllBtn.addEventListener('click', () => this.collapseAll());
132
+ }
133
+
134
+ // Reset zoom button
135
+ const resetZoomBtn = document.getElementById('reset-zoom');
136
+ if (resetZoomBtn) {
137
+ resetZoomBtn.addEventListener('click', () => this.resetZoom());
138
+ }
139
+
140
+ // Time range selector
141
+ const timeRangeSelect = document.getElementById('time-range');
142
+ if (timeRangeSelect) {
143
+ timeRangeSelect.addEventListener('change', (e) => {
144
+ this.timeRange = e.target.value;
145
+ this.filterEventsByTime();
146
+ });
147
+ }
148
+
149
+ // Search input
150
+ const searchInput = document.getElementById('activity-search');
151
+ if (searchInput) {
152
+ searchInput.addEventListener('input', (e) => {
153
+ this.searchTerm = e.target.value.toLowerCase();
154
+ this.highlightSearchResults();
155
+ });
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Create the D3 visualization
161
+ */
162
+ createVisualization() {
163
+ // Check if D3 is available
164
+ if (typeof d3 === 'undefined') {
165
+ console.error('D3.js is not loaded! Cannot create activity tree visualization.');
166
+ return;
167
+ }
168
+
169
+ // Calculate dimensions based on container
170
+ const containerRect = this.container.getBoundingClientRect();
171
+ this.width = containerRect.width - this.margin.left - this.margin.right;
172
+ this.height = Math.max(500, containerRect.height - this.margin.top - this.margin.bottom);
173
+
174
+ console.log('Creating D3 visualization with dimensions:', { width: this.width, height: this.height });
175
+
176
+ // Clear any existing SVG
177
+ d3.select(this.container).select('svg').remove();
178
+
179
+ // Create SVG
180
+ this.svg = d3.select(this.container)
181
+ .append('svg')
182
+ .attr('width', '100%')
183
+ .attr('height', '100%')
184
+ .attr('viewBox', `0 0 ${this.width + this.margin.left + this.margin.right} ${this.height + this.margin.top + this.margin.bottom}`);
185
+
186
+ // Create main group for tree positioning
187
+ this.treeGroup = this.svg.append('g')
188
+ .attr('class', 'tree-group')
189
+ .attr('transform', `translate(${this.margin.left},${this.margin.top})`);
190
+
191
+ // Add zoom behavior
192
+ const zoom = d3.zoom()
193
+ .scaleExtent([0.1, 3])
194
+ .on('zoom', (event) => {
195
+ this.treeGroup.attr('transform',
196
+ `translate(${this.margin.left + event.transform.x},${this.margin.top + event.transform.y}) scale(${event.transform.k})`
197
+ );
198
+ });
199
+
200
+ this.svg.call(zoom);
201
+
202
+ // Create tree layout
203
+ this.treeLayout = d3.tree()
204
+ .size([this.height, this.width]);
205
+
206
+ console.log('ActivityTree: Tree layout created:', this.treeLayout);
207
+
208
+ // Create tooltip
209
+ this.tooltip = d3.select('body').append('div')
210
+ .attr('class', 'activity-tooltip')
211
+ .style('opacity', 0);
212
+
213
+ console.log('ActivityTree: Visualization complete, svg:', this.svg, 'treeGroup:', this.treeGroup);
214
+ }
215
+
216
+ /**
217
+ * Initialize tree data structure
218
+ */
219
+ initializeTreeData() {
220
+ console.log('ActivityTree: Initializing tree data');
221
+
222
+ this.treeData = {
223
+ name: 'PM',
224
+ type: 'pm',
225
+ icon: '🎯',
226
+ children: [],
227
+ _children: null
228
+ };
229
+
230
+ // Check if D3 is available
231
+ if (typeof d3 === 'undefined') {
232
+ console.error('ActivityTree: D3 is not available - cannot create hierarchy!');
233
+ return;
234
+ }
235
+
236
+ this.root = d3.hierarchy(this.treeData);
237
+ this.root.x0 = this.height / 2;
238
+ this.root.y0 = 0;
239
+
240
+ console.log('ActivityTree: Root node created:', this.root);
241
+ }
242
+
243
+ /**
244
+ * Subscribe to socket events
245
+ */
246
+ subscribeToEvents() {
247
+ if (!window.socketClient) {
248
+ console.warn('Socket client not available for activity tree');
249
+ setTimeout(() => this.subscribeToEvents(), 1000);
250
+ return;
251
+ }
252
+
253
+ console.log('ActivityTree: Setting up event subscription');
254
+
255
+ // Subscribe to event updates from the socket client
256
+ // Process ALL events and determine their type internally
257
+ window.socketClient.onEventUpdate((events) => {
258
+ console.log(`ActivityTree: onEventUpdate called with ${events.length} total events`);
259
+
260
+ // Process only the new events since last update
261
+ const newEventCount = events.length - this.events.length;
262
+ if (newEventCount > 0) {
263
+ // Process only the new events
264
+ const newEvents = events.slice(this.events.length);
265
+
266
+ console.log(`ActivityTree: Processing ${newEventCount} new events`, newEvents);
267
+
268
+ // Process all events, regardless of format
269
+ newEvents.forEach(event => {
270
+ this.processEvent(event);
271
+ });
272
+
273
+ // Update our event count
274
+ this.events = [...events];
275
+ }
276
+ });
277
+
278
+ // Load existing events if available
279
+ const existingEvents = window.socketClient?.events || window.eventViewer?.events || [];
280
+
281
+ if (existingEvents.length > 0) {
282
+ console.log(`ActivityTree: Processing ${existingEvents.length} existing events`, existingEvents);
283
+ existingEvents.forEach(event => {
284
+ this.processEvent(event);
285
+ });
286
+ this.events = [...existingEvents];
287
+ } else {
288
+ console.log('ActivityTree: No existing events found');
289
+ this.events = [];
290
+ }
291
+ }
292
+
293
+ /**
294
+ * Process an event and update the tree
295
+ */
296
+ processEvent(event) {
297
+ if (!event) {
298
+ console.log('ActivityTree: Ignoring null event');
299
+ return;
300
+ }
301
+
302
+ // Handle events with the actual format from the server
303
+ let eventType = null;
304
+
305
+ // First check if hook_event_name exists (from transformation)
306
+ if (event.hook_event_name) {
307
+ eventType = event.hook_event_name;
308
+ }
309
+ // Map from type/subtype for hook events
310
+ else if (event.type === 'hook' && event.subtype) {
311
+ const mapping = {
312
+ 'pre_tool': 'PreToolUse',
313
+ 'post_tool': 'PostToolUse',
314
+ 'subagent_start': 'SubagentStart',
315
+ 'subagent_stop': 'SubagentStop',
316
+ 'todo_write': 'TodoWrite'
317
+ };
318
+ eventType = mapping[event.subtype];
319
+ }
320
+ // Handle todo events
321
+ else if (event.type === 'todo' && event.subtype === 'updated') {
322
+ eventType = 'TodoWrite';
323
+ }
324
+ // Handle subagent events
325
+ else if (event.type === 'subagent') {
326
+ if (event.subtype === 'started') {
327
+ eventType = 'SubagentStart';
328
+ } else if (event.subtype === 'stopped') {
329
+ eventType = 'SubagentStop';
330
+ }
331
+ }
332
+ // Handle start event
333
+ else if (event.type === 'start') {
334
+ eventType = 'Start';
335
+ }
336
+
337
+ if (!eventType) {
338
+ // Only log if it's a potentially relevant event
339
+ if (event.type === 'hook' || event.type === 'todo' || event.type === 'subagent') {
340
+ console.log('ActivityTree: Cannot determine event type for:', event);
341
+ }
342
+ return;
343
+ }
344
+
345
+ console.log(`ActivityTree: Processing event: ${eventType}`, event);
346
+
347
+ const timestamp = new Date(event.timestamp);
348
+ if (!this.isEventInTimeRange(timestamp)) {
349
+ return;
350
+ }
351
+
352
+ switch (eventType) {
353
+ case 'TodoWrite':
354
+ this.processTodoWrite(event);
355
+ break;
356
+ case 'SubagentStart':
357
+ this.processSubagentStart(event);
358
+ break;
359
+ case 'SubagentStop':
360
+ this.processSubagentStop(event);
361
+ break;
362
+ case 'PreToolUse':
363
+ this.processToolUse(event);
364
+ break;
365
+ case 'PostToolUse':
366
+ this.updateToolStatus(event, 'completed');
367
+ break;
368
+ case 'Start':
369
+ this.initializeTreeData();
370
+ this.update(this.root);
371
+ break;
372
+ }
373
+
374
+ this.updateStats();
375
+ }
376
+
377
+ /**
378
+ * Process TodoWrite event
379
+ */
380
+ processTodoWrite(event) {
381
+ console.log('ActivityTree: Processing TodoWrite event:', event);
382
+
383
+ // Look for todos in multiple places for compatibility
384
+ let todos = event.todos ||
385
+ event.data?.todos ||
386
+ event.data || // Sometimes todos are directly in data
387
+ [];
388
+
389
+ // Handle case where todos might be an object with todos property
390
+ if (todos && typeof todos === 'object' && todos.todos) {
391
+ todos = todos.todos;
392
+ }
393
+
394
+ // Ensure todos is an array
395
+ if (!Array.isArray(todos)) {
396
+ console.log('ActivityTree: Invalid todos format in event:', event);
397
+ return;
398
+ }
399
+
400
+ if (todos.length === 0) {
401
+ console.log('ActivityTree: No todos in event');
402
+ return;
403
+ }
404
+
405
+ // Find in-progress todo
406
+ const activeTodo = todos.find(t => t.status === 'in_progress');
407
+ if (!activeTodo) {
408
+ console.log('ActivityTree: No in-progress todo found');
409
+ return;
410
+ }
411
+
412
+ console.log('ActivityTree: Found active todo:', activeTodo);
413
+
414
+ // Create TodoWrite node
415
+ const todoNode = {
416
+ name: activeTodo.activeForm || activeTodo.content,
417
+ type: 'todowrite',
418
+ icon: '📝',
419
+ content: activeTodo.content,
420
+ status: activeTodo.status,
421
+ timestamp: event.timestamp,
422
+ children: [],
423
+ _children: null,
424
+ eventId: event.id
425
+ };
426
+
427
+ // Add to PM root
428
+ if (!this.root) {
429
+ console.error('ActivityTree: No root node!');
430
+ return;
431
+ }
432
+
433
+ if (!this.root.data) {
434
+ console.error('ActivityTree: Root has no data!');
435
+ return;
436
+ }
437
+
438
+ if (!this.root.data.children) {
439
+ this.root.data.children = [];
440
+ }
441
+
442
+ console.log('ActivityTree: Adding TodoWrite node to root');
443
+ this.root.data.children.push(todoNode);
444
+
445
+ // Track this TodoWrite
446
+ this.todoWriteStack.push({
447
+ node: todoNode,
448
+ content: activeTodo.content
449
+ });
450
+
451
+ console.log('ActivityTree: Calling update with root:', this.root);
452
+ this.update(this.root);
453
+ console.log('ActivityTree: Update complete');
454
+ }
455
+
456
+ /**
457
+ * Process SubagentStart event
458
+ */
459
+ processSubagentStart(event) {
460
+ // Look for agent_name in multiple places for compatibility
461
+ const agentName = event.agent_name ||
462
+ event.data?.agent_name ||
463
+ event.data?.agent_type ||
464
+ event.agent_type || // Check direct agent_type field
465
+ event.agent || // Check agent field
466
+ 'unknown';
467
+ const agentIcon = this.getAgentIcon(agentName);
468
+
469
+ // Create agent node
470
+ const agentNode = {
471
+ name: agentName,
472
+ type: 'agent',
473
+ icon: agentIcon,
474
+ timestamp: event.timestamp,
475
+ children: [],
476
+ _children: null,
477
+ eventId: event.id,
478
+ sessionId: event.session_id || event.data?.session_id
479
+ };
480
+
481
+ // Find parent - either last TodoWrite or PM root
482
+ let parent = null;
483
+ if (this.todoWriteStack.length > 0) {
484
+ // Check if TodoWrite mentions this agent
485
+ const todoWrite = this.todoWriteStack[this.todoWriteStack.length - 1];
486
+ if (todoWrite.content && todoWrite.content.toLowerCase().includes(agentName.toLowerCase())) {
487
+ parent = todoWrite.node;
488
+ }
489
+ }
490
+
491
+ if (!parent) {
492
+ parent = this.root.data;
493
+ }
494
+
495
+ if (!parent.children) {
496
+ parent.children = [];
497
+ }
498
+ parent.children.push(agentNode);
499
+
500
+ // Track active agent
501
+ this.activeAgent = agentNode;
502
+ this.activeAgentStack.push(agentNode);
503
+
504
+ this.update(this.root);
505
+ }
506
+
507
+ /**
508
+ * Process SubagentStop event
509
+ */
510
+ processSubagentStop(event) {
511
+ // Mark agent as completed (look for session_id in multiple places)
512
+ const sessionId = event.session_id || event.data?.session_id;
513
+ if (this.activeAgent && this.activeAgent.sessionId === sessionId) {
514
+ this.activeAgent.status = 'completed';
515
+ this.activeAgentStack.pop();
516
+ this.activeAgent = this.activeAgentStack.length > 0 ?
517
+ this.activeAgentStack[this.activeAgentStack.length - 1] : null;
518
+ }
519
+
520
+ this.update(this.root);
521
+ }
522
+
523
+ /**
524
+ * Process tool use event
525
+ */
526
+ processToolUse(event) {
527
+ // Get tool name from various possible locations
528
+ const toolName = event.tool_name ||
529
+ event.data?.tool_name ||
530
+ event.tool || // Check event.tool field
531
+ event.data?.tool ||
532
+ 'unknown';
533
+
534
+ const toolIcon = this.getToolIcon(toolName);
535
+
536
+ // Get parameters from various possible locations
537
+ const params = event.tool_parameters ||
538
+ event.data?.tool_parameters ||
539
+ event.parameters || // Check event.parameters field
540
+ event.data?.parameters ||
541
+ {};
542
+
543
+ // Create tool node
544
+ const toolNode = {
545
+ name: toolName,
546
+ type: 'tool',
547
+ icon: toolIcon,
548
+ timestamp: event.timestamp,
549
+ status: 'in_progress',
550
+ children: [],
551
+ _children: null,
552
+ eventId: event.id
553
+ };
554
+
555
+ // Add file/command as child if applicable
556
+ if (toolName === 'Read' && params.file_path) {
557
+ toolNode.children.push({
558
+ name: params.file_path,
559
+ type: 'file',
560
+ icon: '📄',
561
+ timestamp: event.timestamp
562
+ });
563
+ } else if (toolName === 'Edit' && params.file_path) {
564
+ toolNode.children.push({
565
+ name: params.file_path,
566
+ type: 'file',
567
+ icon: '✏️',
568
+ timestamp: event.timestamp
569
+ });
570
+ } else if (toolName === 'Write' && params.file_path) {
571
+ toolNode.children.push({
572
+ name: params.file_path,
573
+ type: 'file',
574
+ icon: '💾',
575
+ timestamp: event.timestamp
576
+ });
577
+ } else if (toolName === 'Bash' && params.command) {
578
+ toolNode.children.push({
579
+ name: params.command.substring(0, 50) + (params.command.length > 50 ? '...' : ''),
580
+ type: 'command',
581
+ icon: '⚡',
582
+ timestamp: event.timestamp
583
+ });
584
+ } else if (toolName === 'WebFetch' && params.url) {
585
+ toolNode.children.push({
586
+ name: params.url,
587
+ type: 'url',
588
+ icon: '🌐',
589
+ timestamp: event.timestamp
590
+ });
591
+ }
592
+
593
+ // Find parent - active agent or PM root
594
+ let parent = this.activeAgent || this.root.data;
595
+ if (!parent.children) {
596
+ parent.children = [];
597
+ }
598
+ parent.children.push(toolNode);
599
+
600
+ this.update(this.root);
601
+ }
602
+
603
+ /**
604
+ * Update tool status after completion
605
+ */
606
+ updateToolStatus(event, status) {
607
+ // Find tool node by event ID and update status
608
+ const findAndUpdate = (node) => {
609
+ if (node.eventId === event.id) {
610
+ node.status = status;
611
+ return true;
612
+ }
613
+ if (node.children) {
614
+ for (let child of node.children) {
615
+ if (findAndUpdate(child)) return true;
616
+ }
617
+ }
618
+ if (node._children) {
619
+ for (let child of node._children) {
620
+ if (findAndUpdate(child)) return true;
621
+ }
622
+ }
623
+ return false;
624
+ };
625
+
626
+ findAndUpdate(this.root.data);
627
+ this.update(this.root);
628
+ }
629
+
630
+ /**
631
+ * Get agent icon based on name
632
+ */
633
+ getAgentIcon(agentName) {
634
+ const icons = {
635
+ 'engineer': '👷',
636
+ 'research': '🔬',
637
+ 'qa': '🧪',
638
+ 'ops': '⚙️',
639
+ 'pm': '📊',
640
+ 'architect': '🏗️'
641
+ };
642
+ return icons[agentName.toLowerCase()] || '🤖';
643
+ }
644
+
645
+ /**
646
+ * Get tool icon based on name
647
+ */
648
+ getToolIcon(toolName) {
649
+ const icons = {
650
+ 'read': '👁️',
651
+ 'write': '✍️',
652
+ 'edit': '✏️',
653
+ 'bash': '💻',
654
+ 'webfetch': '🌐',
655
+ 'grep': '🔍',
656
+ 'glob': '📂',
657
+ 'todowrite': '📝'
658
+ };
659
+ return icons[toolName.toLowerCase()] || '🔧';
660
+ }
661
+
662
+ /**
663
+ * Update the tree visualization
664
+ */
665
+ update(source) {
666
+ console.log('ActivityTree: update() called with source:', source);
667
+
668
+ // Check if visualization is ready
669
+ if (!this.svg || !this.treeGroup) {
670
+ console.warn('ActivityTree: Cannot update - SVG not initialized');
671
+ return;
672
+ }
673
+
674
+ if (!this.treeLayout) {
675
+ console.warn('ActivityTree: Cannot update - tree layout not initialized');
676
+ return;
677
+ }
678
+
679
+ // Compute the new tree layout
680
+ const treeData = this.treeLayout(this.root);
681
+ const nodes = treeData.descendants();
682
+ const links = treeData.links();
683
+
684
+ console.log(`ActivityTree: Updating tree with ${nodes.length} nodes`);
685
+
686
+ // Normalize for fixed-depth
687
+ nodes.forEach((d) => {
688
+ d.y = d.depth * 180;
689
+ });
690
+
691
+ // Update nodes
692
+ const node = this.treeGroup.selectAll('g.node')
693
+ .data(nodes, (d) => d.id || (d.id = ++this.nodeId));
694
+
695
+ // Enter new nodes
696
+ const nodeEnter = node.enter().append('g')
697
+ .attr('class', 'node')
698
+ .attr('transform', (d) => `translate(${source.y0},${source.x0})`)
699
+ .on('click', (event, d) => this.click(d));
700
+
701
+ // Add circles for nodes
702
+ nodeEnter.append('circle')
703
+ .attr('class', (d) => `node-circle ${d.data.type}`)
704
+ .attr('r', 1e-6)
705
+ .style('fill', (d) => d._children ? this.getNodeColor(d.data.type) : '#fff')
706
+ .style('stroke', (d) => this.getNodeColor(d.data.type));
707
+
708
+ // Add icons
709
+ nodeEnter.append('text')
710
+ .attr('class', 'node-icon')
711
+ .attr('dy', '.35em')
712
+ .attr('text-anchor', 'middle')
713
+ .style('font-size', '14px')
714
+ .text((d) => d.data.icon || '');
715
+
716
+ // Add labels
717
+ nodeEnter.append('text')
718
+ .attr('class', 'node-label')
719
+ .attr('dy', '.35em')
720
+ .attr('x', (d) => d.children || d._children ? -25 : 25)
721
+ .attr('text-anchor', (d) => d.children || d._children ? 'end' : 'start')
722
+ .text((d) => d.data.name)
723
+ .style('fill-opacity', 1e-6);
724
+
725
+ // Add tooltips
726
+ nodeEnter.on('mouseover', (event, d) => this.showTooltip(event, d))
727
+ .on('mouseout', () => this.hideTooltip());
728
+
729
+ // Update existing nodes
730
+ const nodeUpdate = nodeEnter.merge(node);
731
+
732
+ // Transition nodes to new position
733
+ nodeUpdate.transition()
734
+ .duration(this.duration)
735
+ .attr('transform', (d) => `translate(${d.y},${d.x})`);
736
+
737
+ nodeUpdate.select('circle.node-circle')
738
+ .attr('r', 10)
739
+ .style('fill', (d) => {
740
+ if (d.data.status === 'in_progress') {
741
+ return this.getNodeColor(d.data.type);
742
+ }
743
+ return d._children ? this.getNodeColor(d.data.type) : '#fff';
744
+ })
745
+ .attr('class', (d) => {
746
+ let classes = `node-circle ${d.data.type}`;
747
+ if (d.data.status === 'in_progress') classes += ' pulsing';
748
+ if (d.data.status === 'failed') classes += ' failed';
749
+ return classes;
750
+ });
751
+
752
+ nodeUpdate.select('text.node-label')
753
+ .style('fill-opacity', 1);
754
+
755
+ // Remove exiting nodes
756
+ const nodeExit = node.exit().transition()
757
+ .duration(this.duration)
758
+ .attr('transform', (d) => `translate(${source.y},${source.x})`)
759
+ .remove();
760
+
761
+ nodeExit.select('circle')
762
+ .attr('r', 1e-6);
763
+
764
+ nodeExit.select('text')
765
+ .style('fill-opacity', 1e-6);
766
+
767
+ // Update links
768
+ const link = this.treeGroup.selectAll('path.link')
769
+ .data(links, (d) => d.target.id);
770
+
771
+ // Enter new links
772
+ const linkEnter = link.enter().insert('path', 'g')
773
+ .attr('class', 'link')
774
+ .attr('d', (d) => {
775
+ const o = {x: source.x0, y: source.y0};
776
+ return this.diagonal({source: o, target: o});
777
+ });
778
+
779
+ // Update existing links
780
+ const linkUpdate = linkEnter.merge(link);
781
+
782
+ linkUpdate.transition()
783
+ .duration(this.duration)
784
+ .attr('d', this.diagonal);
785
+
786
+ // Remove exiting links
787
+ link.exit().transition()
788
+ .duration(this.duration)
789
+ .attr('d', (d) => {
790
+ const o = {x: source.x, y: source.y};
791
+ return this.diagonal({source: o, target: o});
792
+ })
793
+ .remove();
794
+
795
+ // Store old positions for transition
796
+ nodes.forEach((d) => {
797
+ d.x0 = d.x;
798
+ d.y0 = d.y;
799
+ });
800
+
801
+ // Update breadcrumb on node click
802
+ this.updateBreadcrumb(source);
803
+ }
804
+
805
+ /**
806
+ * Create diagonal path for links
807
+ */
808
+ diagonal(d) {
809
+ return `M ${d.source.y} ${d.source.x}
810
+ C ${(d.source.y + d.target.y) / 2} ${d.source.x},
811
+ ${(d.source.y + d.target.y) / 2} ${d.target.x},
812
+ ${d.target.y} ${d.target.x}`;
813
+ }
814
+
815
+ /**
816
+ * Handle node click for expand/collapse
817
+ */
818
+ click(d) {
819
+ if (d.children) {
820
+ d._children = d.children;
821
+ d.children = null;
822
+ } else {
823
+ d.children = d._children;
824
+ d._children = null;
825
+ }
826
+ this.update(d);
827
+ this.updateBreadcrumb(d);
828
+ }
829
+
830
+ /**
831
+ * Get node color based on type
832
+ */
833
+ getNodeColor(type) {
834
+ const colors = {
835
+ 'pm': '#4299e1',
836
+ 'todowrite': '#48bb78',
837
+ 'agent': '#ed8936',
838
+ 'tool': '#9f7aea',
839
+ 'file': '#38b2ac',
840
+ 'command': '#f56565',
841
+ 'url': '#4299e1'
842
+ };
843
+ return colors[type] || '#718096';
844
+ }
845
+
846
+ /**
847
+ * Show tooltip
848
+ */
849
+ showTooltip(event, d) {
850
+ const content = `
851
+ <strong>${d.data.name}</strong><br>
852
+ Type: ${d.data.type}<br>
853
+ ${d.data.timestamp ? `Time: ${new Date(d.data.timestamp).toLocaleTimeString()}` : ''}
854
+ ${d.data.status ? `<br>Status: ${d.data.status}` : ''}
855
+ `;
856
+
857
+ this.tooltip.transition()
858
+ .duration(200)
859
+ .style('opacity', .9);
860
+
861
+ this.tooltip.html(content)
862
+ .style('left', (event.pageX + 10) + 'px')
863
+ .style('top', (event.pageY - 28) + 'px');
864
+ }
865
+
866
+ /**
867
+ * Hide tooltip
868
+ */
869
+ hideTooltip() {
870
+ this.tooltip.transition()
871
+ .duration(500)
872
+ .style('opacity', 0);
873
+ }
874
+
875
+ /**
876
+ * Expand all nodes
877
+ */
878
+ expandAll() {
879
+ const expand = (d) => {
880
+ if (d._children) {
881
+ d.children = d._children;
882
+ d._children = null;
883
+ }
884
+ if (d.children) {
885
+ d.children.forEach(expand);
886
+ }
887
+ };
888
+
889
+ expand(this.root);
890
+ this.update(this.root);
891
+ }
892
+
893
+ /**
894
+ * Collapse all nodes
895
+ */
896
+ collapseAll() {
897
+ const collapse = (d) => {
898
+ if (d.children) {
899
+ d._children = d.children;
900
+ d._children.forEach(collapse);
901
+ d.children = null;
902
+ }
903
+ };
904
+
905
+ this.root.children?.forEach(collapse);
906
+ this.update(this.root);
907
+ }
908
+
909
+ /**
910
+ * Reset zoom
911
+ */
912
+ resetZoom() {
913
+ if (!this.svg) {
914
+ console.warn('Cannot reset zoom: SVG not initialized');
915
+ return;
916
+ }
917
+
918
+ const zoom = d3.zoom()
919
+ .scaleExtent([0.1, 3])
920
+ .on('zoom', (event) => {
921
+ this.treeGroup.attr('transform',
922
+ `translate(${this.margin.left + event.transform.x},${this.margin.top + event.transform.y}) scale(${event.transform.k})`
923
+ );
924
+ });
925
+
926
+ this.svg.transition()
927
+ .duration(750)
928
+ .call(zoom.transform, d3.zoomIdentity);
929
+
930
+ // Reset the tree group transform
931
+ this.treeGroup.transition()
932
+ .duration(750)
933
+ .attr('transform', `translate(${this.margin.left},${this.margin.top})`);
934
+ }
935
+
936
+ /**
937
+ * Check if event is in time range
938
+ */
939
+ isEventInTimeRange(timestamp) {
940
+ if (this.timeRange === 'all') return true;
941
+
942
+ const now = new Date();
943
+ const diff = now - timestamp;
944
+ const minutes = diff / (1000 * 60);
945
+
946
+ switch (this.timeRange) {
947
+ case '10min': return minutes <= 10;
948
+ case '30min': return minutes <= 30;
949
+ case 'hour': return minutes <= 60;
950
+ default: return true;
951
+ }
952
+ }
953
+
954
+ /**
955
+ * Filter events by time
956
+ */
957
+ filterEventsByTime() {
958
+ this.initializeTreeData();
959
+
960
+ // Reprocess all events with new time filter
961
+ if (window.eventViewer && window.eventViewer.events) {
962
+ window.eventViewer.events.forEach(event => {
963
+ this.processEvent(event);
964
+ });
965
+ }
966
+ }
967
+
968
+ /**
969
+ * Update statistics
970
+ */
971
+ updateStats() {
972
+ const nodeCount = this.countNodes(this.root);
973
+ const activeCount = this.countActiveNodes(this.root.data);
974
+ const depth = this.getTreeDepth(this.root);
975
+
976
+ document.getElementById('node-count').textContent = nodeCount;
977
+ document.getElementById('active-count').textContent = activeCount;
978
+ document.getElementById('tree-depth').textContent = depth;
979
+ }
980
+
981
+ /**
982
+ * Count total nodes
983
+ */
984
+ countNodes(node) {
985
+ let count = 1;
986
+ if (node.children) {
987
+ node.children.forEach(child => {
988
+ count += this.countNodes(child);
989
+ });
990
+ }
991
+ if (node._children) {
992
+ node._children.forEach(child => {
993
+ count += this.countNodes(child);
994
+ });
995
+ }
996
+ return count;
997
+ }
998
+
999
+ /**
1000
+ * Count active nodes
1001
+ */
1002
+ countActiveNodes(node) {
1003
+ let count = node.status === 'in_progress' ? 1 : 0;
1004
+ if (node.children) {
1005
+ node.children.forEach(child => {
1006
+ count += this.countActiveNodes(child);
1007
+ });
1008
+ }
1009
+ if (node._children) {
1010
+ node._children.forEach(child => {
1011
+ count += this.countActiveNodes(child);
1012
+ });
1013
+ }
1014
+ return count;
1015
+ }
1016
+
1017
+ /**
1018
+ * Get tree depth
1019
+ */
1020
+ getTreeDepth(node) {
1021
+ if (!node.children && !node._children) return 0;
1022
+
1023
+ const children = node.children || node._children;
1024
+ const depths = children.map(child => this.getTreeDepth(child));
1025
+ return Math.max(...depths) + 1;
1026
+ }
1027
+
1028
+ /**
1029
+ * Update breadcrumb
1030
+ */
1031
+ updateBreadcrumb(node) {
1032
+ const path = [];
1033
+ let current = node;
1034
+
1035
+ while (current) {
1036
+ path.unshift(current.data.name);
1037
+ current = current.parent;
1038
+ }
1039
+
1040
+ const breadcrumb = document.getElementById('activity-breadcrumb');
1041
+ if (breadcrumb) {
1042
+ breadcrumb.textContent = path.join(' > ');
1043
+ }
1044
+ }
1045
+
1046
+ /**
1047
+ * Highlight search results
1048
+ */
1049
+ highlightSearchResults() {
1050
+ // Clear previous highlights
1051
+ this.treeGroup.selectAll('.node-label')
1052
+ .style('font-weight', 'normal')
1053
+ .style('fill', '#2d3748');
1054
+
1055
+ if (!this.searchTerm) return;
1056
+
1057
+ // Highlight matching nodes
1058
+ this.treeGroup.selectAll('.node-label')
1059
+ .style('font-weight', d => {
1060
+ return d.data.name.toLowerCase().includes(this.searchTerm) ? 'bold' : 'normal';
1061
+ })
1062
+ .style('fill', d => {
1063
+ return d.data.name.toLowerCase().includes(this.searchTerm) ? '#e53e3e' : '#2d3748';
1064
+ });
1065
+ }
1066
+ }
1067
+
1068
+ // Make ActivityTree globally available immediately when module loads
1069
+ window.ActivityTree = ActivityTree;
1070
+
1071
+ // Initialize when the Activity tab is selected
1072
+ // Only set up event listeners when DOM is ready, but expose class immediately
1073
+ const setupActivityTreeListeners = () => {
1074
+ let activityTree = null;
1075
+
1076
+ // Function to initialize the tree
1077
+ const initializeActivityTree = () => {
1078
+ if (!activityTree) {
1079
+ console.log('Creating new Activity Tree instance...');
1080
+ activityTree = new ActivityTree();
1081
+ // Store instance globally for dashboard access
1082
+ window.activityTreeInstance = activityTree;
1083
+ }
1084
+ // Always try to initialize when tab becomes active, even if instance exists
1085
+ // Small delay to ensure DOM is ready and tab is visible
1086
+ setTimeout(() => {
1087
+ console.log('Attempting to initialize Activity Tree visualization...');
1088
+ activityTree.initialize();
1089
+ }, 100);
1090
+ };
1091
+
1092
+ // Tab switching logic
1093
+ document.querySelectorAll('.tab-button').forEach(button => {
1094
+ button.addEventListener('click', (e) => {
1095
+ const tabName = e.target.getAttribute('data-tab');
1096
+
1097
+ if (tabName === 'activity') {
1098
+ console.log('Activity tab button clicked, initializing tree...');
1099
+ initializeActivityTree();
1100
+ // Also call renderWhenVisible to ensure proper rendering
1101
+ if (activityTree) {
1102
+ setTimeout(() => activityTree.renderWhenVisible(), 150);
1103
+ }
1104
+ }
1105
+ });
1106
+ });
1107
+
1108
+ // Also listen for custom tab change events
1109
+ document.addEventListener('tabChanged', (e) => {
1110
+ if (e.detail && e.detail.newTab === 'activity') {
1111
+ console.log('Tab changed to activity, initializing tree...');
1112
+ initializeActivityTree();
1113
+ // Also call renderWhenVisible to ensure proper rendering
1114
+ if (activityTree) {
1115
+ setTimeout(() => activityTree.renderWhenVisible(), 150);
1116
+ }
1117
+ }
1118
+ });
1119
+
1120
+ // Check if activity tab is already active on load
1121
+ const activeTab = document.querySelector('.tab-button.active');
1122
+ if (activeTab && activeTab.getAttribute('data-tab') === 'activity') {
1123
+ initializeActivityTree();
1124
+ }
1125
+
1126
+ // Export for debugging
1127
+ window.activityTree = () => activityTree; // Expose instance getter for debugging
1128
+ };
1129
+
1130
+ // Set up listeners when DOM is ready
1131
+ if (document.readyState === 'loading') {
1132
+ document.addEventListener('DOMContentLoaded', setupActivityTreeListeners);
1133
+ } else {
1134
+ // DOM already loaded
1135
+ setupActivityTreeListeners();
1136
+ }
1137
+
1138
+ export { ActivityTree };
1139
+ export default ActivityTree;