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