claude-mpm 4.1.12__py3-none-any.whl → 4.1.13__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- claude_mpm/VERSION +1 -1
- claude_mpm/cli/commands/monitor.py +88 -627
- claude_mpm/cli/commands/mpm_init.py +7 -2
- claude_mpm/dashboard/static/built/components/activity-tree.js +1 -1
- claude_mpm/dashboard/static/built/components/code-tree.js +1 -1
- claude_mpm/dashboard/static/built/dashboard.js +1 -1
- claude_mpm/dashboard/static/built/socket-client.js +1 -1
- claude_mpm/dashboard/static/css/activity.css +1239 -267
- claude_mpm/dashboard/static/css/dashboard.css +511 -0
- claude_mpm/dashboard/static/dist/components/activity-tree.js +1 -1
- claude_mpm/dashboard/static/dist/components/code-tree.js +2 -2593
- claude_mpm/dashboard/static/dist/components/module-viewer.js +1 -1
- claude_mpm/dashboard/static/dist/dashboard.js +1 -1
- claude_mpm/dashboard/static/dist/socket-client.js +1 -1
- claude_mpm/dashboard/static/js/components/activity-tree.js +1193 -892
- claude_mpm/dashboard/static/js/components/code-tree.js +0 -17
- claude_mpm/dashboard/static/js/components/module-viewer.js +21 -7
- claude_mpm/dashboard/static/js/components/unified-data-viewer.js +1066 -0
- claude_mpm/dashboard/static/js/connection-manager.js +1 -1
- claude_mpm/dashboard/static/js/dashboard.js +196 -43
- claude_mpm/dashboard/static/js/socket-client.js +2 -2
- claude_mpm/dashboard/templates/index.html +95 -25
- claude_mpm/services/cli/socketio_manager.py +1 -1
- claude_mpm/services/infrastructure/monitoring.py +1 -1
- {claude_mpm-4.1.12.dist-info → claude_mpm-4.1.13.dist-info}/METADATA +1 -1
- {claude_mpm-4.1.12.dist-info → claude_mpm-4.1.13.dist-info}/RECORD +30 -29
- {claude_mpm-4.1.12.dist-info → claude_mpm-4.1.13.dist-info}/WHEEL +0 -0
- {claude_mpm-4.1.12.dist-info → claude_mpm-4.1.13.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.1.12.dist-info → claude_mpm-4.1.13.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.1.12.dist-info → claude_mpm-4.1.13.dist-info}/top_level.txt +0 -0
|
@@ -1,49 +1,40 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Activity Tree Component
|
|
2
|
+
* Activity Tree Component - Linear Tree View
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* HTML/CSS-based linear tree visualization for showing PM activity hierarchy.
|
|
5
|
+
* Replaces D3.js with simpler, cleaner linear tree structure.
|
|
6
|
+
* Uses simple display methods for data visualization.
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
9
|
class ActivityTree {
|
|
9
10
|
constructor() {
|
|
10
11
|
this.container = null;
|
|
11
|
-
this.svg = null;
|
|
12
|
-
this.treeData = null;
|
|
13
|
-
this.root = null;
|
|
14
|
-
this.treeLayout = null;
|
|
15
|
-
this.treeGroup = null;
|
|
16
12
|
this.events = [];
|
|
17
|
-
this.
|
|
18
|
-
this.
|
|
19
|
-
this.
|
|
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;
|
|
13
|
+
this.sessions = new Map();
|
|
14
|
+
this.currentSession = null;
|
|
15
|
+
this.selectedSessionFilter = 'all';
|
|
25
16
|
this.timeRange = '30min';
|
|
26
17
|
this.searchTerm = '';
|
|
27
|
-
this.tooltip = null;
|
|
28
18
|
this.initialized = false;
|
|
19
|
+
this.expandedSessions = new Set();
|
|
20
|
+
this.expandedAgents = new Set();
|
|
21
|
+
this.expandedTools = new Set();
|
|
22
|
+
this.selectedItem = null;
|
|
29
23
|
}
|
|
30
24
|
|
|
31
25
|
/**
|
|
32
|
-
* Initialize the activity tree
|
|
26
|
+
* Initialize the activity tree
|
|
33
27
|
*/
|
|
34
28
|
initialize() {
|
|
35
29
|
console.log('ActivityTree.initialize() called, initialized:', this.initialized);
|
|
36
30
|
|
|
37
|
-
// Check if already initialized
|
|
38
31
|
if (this.initialized) {
|
|
39
32
|
console.log('Activity tree already initialized, skipping');
|
|
40
33
|
return;
|
|
41
34
|
}
|
|
42
35
|
|
|
43
|
-
// First try to find the container
|
|
44
36
|
this.container = document.getElementById('activity-tree-container');
|
|
45
37
|
if (!this.container) {
|
|
46
|
-
// Fall back to the inner div if container not found
|
|
47
38
|
this.container = document.getElementById('activity-tree');
|
|
48
39
|
if (!this.container) {
|
|
49
40
|
console.error('Activity tree container not found in DOM');
|
|
@@ -51,14 +42,6 @@ class ActivityTree {
|
|
|
51
42
|
}
|
|
52
43
|
}
|
|
53
44
|
|
|
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
45
|
// Check if the container is visible before initializing
|
|
63
46
|
const tabPanel = document.getElementById('activity-tab');
|
|
64
47
|
if (!tabPanel) {
|
|
@@ -66,48 +49,17 @@ class ActivityTree {
|
|
|
66
49
|
return;
|
|
67
50
|
}
|
|
68
51
|
|
|
69
|
-
// Initialize even if tab is not active
|
|
52
|
+
// Initialize even if tab is not active
|
|
70
53
|
if (!tabPanel.classList.contains('active')) {
|
|
71
54
|
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
55
|
this.setupControls();
|
|
78
|
-
this.initializeTreeData();
|
|
79
56
|
this.subscribeToEvents();
|
|
80
57
|
this.initialized = true;
|
|
81
58
|
return;
|
|
82
59
|
}
|
|
83
60
|
|
|
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
61
|
this.setupControls();
|
|
91
|
-
this.
|
|
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
|
-
|
|
62
|
+
this.createLinearTreeView();
|
|
111
63
|
this.subscribeToEvents();
|
|
112
64
|
|
|
113
65
|
this.initialized = true;
|
|
@@ -120,7 +72,6 @@ class ActivityTree {
|
|
|
120
72
|
forceShow() {
|
|
121
73
|
console.log('ActivityTree.forceShow() called');
|
|
122
74
|
|
|
123
|
-
// Ensure container is available
|
|
124
75
|
if (!this.container) {
|
|
125
76
|
this.container = document.getElementById('activity-tree-container') || document.getElementById('activity-tree');
|
|
126
77
|
if (!this.container) {
|
|
@@ -129,217 +80,112 @@ class ActivityTree {
|
|
|
129
80
|
}
|
|
130
81
|
}
|
|
131
82
|
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
}
|
|
83
|
+
this.createLinearTreeView();
|
|
84
|
+
this.renderTree();
|
|
161
85
|
}
|
|
162
86
|
|
|
163
87
|
/**
|
|
164
|
-
* Render the visualization when tab becomes visible
|
|
88
|
+
* Render the visualization when tab becomes visible
|
|
165
89
|
*/
|
|
166
90
|
renderWhenVisible() {
|
|
167
91
|
console.log('ActivityTree.renderWhenVisible() called');
|
|
168
92
|
|
|
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
93
|
if (!this.initialized) {
|
|
176
94
|
console.log('Not initialized yet, calling initialize...');
|
|
177
95
|
this.initialize();
|
|
178
96
|
return;
|
|
179
97
|
}
|
|
180
98
|
|
|
181
|
-
|
|
182
|
-
|
|
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
|
-
}
|
|
99
|
+
this.createLinearTreeView();
|
|
100
|
+
this.renderTree();
|
|
207
101
|
}
|
|
208
102
|
|
|
209
103
|
/**
|
|
210
104
|
* Setup control handlers
|
|
211
105
|
*/
|
|
212
106
|
setupControls() {
|
|
213
|
-
//
|
|
107
|
+
// Time range filter dropdown
|
|
108
|
+
const timeRangeSelect = document.getElementById('time-range');
|
|
109
|
+
if (timeRangeSelect) {
|
|
110
|
+
timeRangeSelect.addEventListener('change', (e) => {
|
|
111
|
+
this.timeRange = e.target.value;
|
|
112
|
+
console.log(`ActivityTree: Time range changed to: ${this.timeRange}`);
|
|
113
|
+
this.renderTree();
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Listen for session filter changes from SessionManager
|
|
118
|
+
document.addEventListener('sessionFilterChanged', (e) => {
|
|
119
|
+
this.selectedSessionFilter = e.detail.sessionId || 'all';
|
|
120
|
+
console.log(`ActivityTree: Session filter changed to: ${this.selectedSessionFilter} (from SessionManager)`);
|
|
121
|
+
this.renderTree();
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// Also listen for sessionChanged for backward compatibility
|
|
125
|
+
document.addEventListener('sessionChanged', (e) => {
|
|
126
|
+
this.selectedSessionFilter = e.detail.sessionId || 'all';
|
|
127
|
+
console.log(`ActivityTree: Session changed to: ${this.selectedSessionFilter} (from SessionManager - backward compat)`);
|
|
128
|
+
this.renderTree();
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Initialize with current session filter from SessionManager
|
|
132
|
+
setTimeout(() => {
|
|
133
|
+
if (window.sessionManager) {
|
|
134
|
+
const currentFilter = window.sessionManager.getCurrentFilter();
|
|
135
|
+
if (currentFilter !== this.selectedSessionFilter) {
|
|
136
|
+
this.selectedSessionFilter = currentFilter || 'all';
|
|
137
|
+
console.log(`ActivityTree: Initialized with current session filter: ${this.selectedSessionFilter}`);
|
|
138
|
+
this.renderTree();
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}, 100); // Small delay to ensure SessionManager is initialized
|
|
142
|
+
|
|
143
|
+
// Expand all button - expand all sessions
|
|
214
144
|
const expandAllBtn = document.getElementById('expand-all');
|
|
215
145
|
if (expandAllBtn) {
|
|
216
|
-
expandAllBtn.addEventListener('click', () => this.
|
|
146
|
+
expandAllBtn.addEventListener('click', () => this.expandAllSessions());
|
|
217
147
|
}
|
|
218
148
|
|
|
219
|
-
// Collapse all button
|
|
149
|
+
// Collapse all button - collapse all sessions
|
|
220
150
|
const collapseAllBtn = document.getElementById('collapse-all');
|
|
221
151
|
if (collapseAllBtn) {
|
|
222
|
-
collapseAllBtn.addEventListener('click', () => this.
|
|
152
|
+
collapseAllBtn.addEventListener('click', () => this.collapseAllSessions());
|
|
223
153
|
}
|
|
224
154
|
|
|
225
|
-
// Reset zoom button
|
|
155
|
+
// Reset zoom button functionality
|
|
226
156
|
const resetZoomBtn = document.getElementById('reset-zoom');
|
|
227
157
|
if (resetZoomBtn) {
|
|
158
|
+
resetZoomBtn.style.display = 'inline-block';
|
|
228
159
|
resetZoomBtn.addEventListener('click', () => this.resetZoom());
|
|
229
160
|
}
|
|
230
161
|
|
|
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
162
|
// Search input
|
|
241
163
|
const searchInput = document.getElementById('activity-search');
|
|
242
164
|
if (searchInput) {
|
|
243
165
|
searchInput.addEventListener('input', (e) => {
|
|
244
166
|
this.searchTerm = e.target.value.toLowerCase();
|
|
245
|
-
this.
|
|
167
|
+
this.renderTree();
|
|
246
168
|
});
|
|
247
169
|
}
|
|
248
170
|
}
|
|
249
171
|
|
|
250
172
|
/**
|
|
251
|
-
* Create the
|
|
173
|
+
* Create the linear tree view container
|
|
252
174
|
*/
|
|
253
|
-
|
|
254
|
-
|
|
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]);
|
|
175
|
+
createLinearTreeView() {
|
|
176
|
+
console.log('Creating linear tree view');
|
|
300
177
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
// Create tooltip
|
|
304
|
-
this.tooltip = d3.select('body').append('div')
|
|
305
|
-
.attr('class', 'activity-tooltip')
|
|
306
|
-
.style('opacity', 0);
|
|
178
|
+
// Clear container
|
|
179
|
+
this.container.innerHTML = '';
|
|
307
180
|
|
|
308
|
-
|
|
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;
|
|
181
|
+
// Create main tree container
|
|
182
|
+
const treeContainer = document.createElement('div');
|
|
183
|
+
treeContainer.id = 'linear-tree';
|
|
184
|
+
treeContainer.className = 'linear-tree';
|
|
338
185
|
|
|
339
|
-
|
|
186
|
+
this.container.appendChild(treeContainer);
|
|
340
187
|
|
|
341
|
-
|
|
342
|
-
this.updateStats();
|
|
188
|
+
console.log('Linear tree view created');
|
|
343
189
|
}
|
|
344
190
|
|
|
345
191
|
/**
|
|
@@ -355,45 +201,98 @@ class ActivityTree {
|
|
|
355
201
|
console.log('ActivityTree: Setting up event subscription');
|
|
356
202
|
|
|
357
203
|
// Subscribe to event updates from the socket client
|
|
358
|
-
//
|
|
359
|
-
window.socketClient.onEventUpdate((events) => {
|
|
360
|
-
console.log(`ActivityTree: onEventUpdate called with ${events.length} total events`);
|
|
204
|
+
// FIXED: Now correctly receives both events AND sessions from socket client
|
|
205
|
+
window.socketClient.onEventUpdate((events, sessions) => {
|
|
206
|
+
console.log(`ActivityTree: onEventUpdate called with ${events.length} total events and ${sessions.size} sessions`);
|
|
207
|
+
|
|
208
|
+
// Use the authoritative sessions from socket client instead of building our own
|
|
209
|
+
this.sessions.clear();
|
|
210
|
+
|
|
211
|
+
// Convert authoritative sessions Map to our format
|
|
212
|
+
for (const [sessionId, sessionData] of sessions.entries()) {
|
|
213
|
+
const activitySession = {
|
|
214
|
+
id: sessionId,
|
|
215
|
+
timestamp: new Date(sessionData.lastActivity || sessionData.startTime || new Date()),
|
|
216
|
+
expanded: this.expandedSessions.has(sessionId) || true, // Preserve expansion state
|
|
217
|
+
agents: new Map(),
|
|
218
|
+
todos: [],
|
|
219
|
+
userInstructions: [],
|
|
220
|
+
tools: [],
|
|
221
|
+
status: 'active',
|
|
222
|
+
currentTodoTool: null,
|
|
223
|
+
// Preserve additional session metadata
|
|
224
|
+
working_directory: sessionData.working_directory,
|
|
225
|
+
git_branch: sessionData.git_branch,
|
|
226
|
+
eventCount: sessionData.eventCount
|
|
227
|
+
};
|
|
228
|
+
this.sessions.set(sessionId, activitySession);
|
|
229
|
+
}
|
|
361
230
|
|
|
362
231
|
// Process only the new events since last update
|
|
363
232
|
const newEventCount = events.length - this.events.length;
|
|
364
233
|
if (newEventCount > 0) {
|
|
365
|
-
// Process only the new events
|
|
366
234
|
const newEvents = events.slice(this.events.length);
|
|
367
|
-
|
|
368
235
|
console.log(`ActivityTree: Processing ${newEventCount} new events`, newEvents);
|
|
369
236
|
|
|
370
|
-
// Process all events, regardless of format
|
|
371
237
|
newEvents.forEach(event => {
|
|
372
238
|
this.processEvent(event);
|
|
373
239
|
});
|
|
374
|
-
|
|
375
|
-
// Update our event count
|
|
376
|
-
this.events = [...events];
|
|
377
240
|
}
|
|
241
|
+
|
|
242
|
+
this.events = [...events];
|
|
243
|
+
this.renderTree();
|
|
244
|
+
|
|
245
|
+
// Debug: Log session state after processing
|
|
246
|
+
console.log(`ActivityTree: Sessions after sync with socket client:`, Array.from(this.sessions.entries()));
|
|
378
247
|
});
|
|
379
248
|
|
|
380
|
-
// Load existing
|
|
381
|
-
const
|
|
249
|
+
// Load existing data from socket client
|
|
250
|
+
const socketState = window.socketClient?.getState();
|
|
382
251
|
|
|
383
|
-
if (
|
|
384
|
-
console.log(`ActivityTree:
|
|
385
|
-
|
|
252
|
+
if (socketState && socketState.events.length > 0) {
|
|
253
|
+
console.log(`ActivityTree: Loading existing data - ${socketState.events.length} events, ${socketState.sessions.size} sessions`);
|
|
254
|
+
|
|
255
|
+
// Initialize from existing socket client data
|
|
256
|
+
this.sessions.clear();
|
|
257
|
+
|
|
258
|
+
// Convert authoritative sessions Map to our format
|
|
259
|
+
for (const [sessionId, sessionData] of socketState.sessions.entries()) {
|
|
260
|
+
const activitySession = {
|
|
261
|
+
id: sessionId,
|
|
262
|
+
timestamp: new Date(sessionData.lastActivity || sessionData.startTime || new Date()),
|
|
263
|
+
expanded: this.expandedSessions.has(sessionId) || true,
|
|
264
|
+
agents: new Map(),
|
|
265
|
+
todos: [],
|
|
266
|
+
userInstructions: [],
|
|
267
|
+
tools: [],
|
|
268
|
+
status: 'active',
|
|
269
|
+
currentTodoTool: null,
|
|
270
|
+
working_directory: sessionData.working_directory,
|
|
271
|
+
git_branch: sessionData.git_branch,
|
|
272
|
+
eventCount: sessionData.eventCount
|
|
273
|
+
};
|
|
274
|
+
this.sessions.set(sessionId, activitySession);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Process existing events to populate activity data
|
|
278
|
+
socketState.events.forEach(event => {
|
|
386
279
|
this.processEvent(event);
|
|
387
280
|
});
|
|
388
|
-
this.events = [...
|
|
281
|
+
this.events = [...socketState.events];
|
|
282
|
+
this.renderTree();
|
|
283
|
+
|
|
284
|
+
// Debug: Log initial session state
|
|
285
|
+
console.log(`ActivityTree: Initial sessions state:`, Array.from(this.sessions.entries()));
|
|
389
286
|
} else {
|
|
390
287
|
console.log('ActivityTree: No existing events found');
|
|
391
288
|
this.events = [];
|
|
289
|
+
this.sessions.clear();
|
|
290
|
+
this.renderTree();
|
|
392
291
|
}
|
|
393
292
|
}
|
|
394
293
|
|
|
395
294
|
/**
|
|
396
|
-
* Process an event and update the
|
|
295
|
+
* Process an event and update the session structure
|
|
397
296
|
*/
|
|
398
297
|
processEvent(event) {
|
|
399
298
|
if (!event) {
|
|
@@ -401,75 +300,68 @@ class ActivityTree {
|
|
|
401
300
|
return;
|
|
402
301
|
}
|
|
403
302
|
|
|
404
|
-
//
|
|
405
|
-
let eventType =
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
if (event.hook_event_name) {
|
|
409
|
-
eventType = event.hook_event_name;
|
|
303
|
+
// Determine event type
|
|
304
|
+
let eventType = this.getEventType(event);
|
|
305
|
+
if (!eventType) {
|
|
306
|
+
return;
|
|
410
307
|
}
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
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';
|
|
308
|
+
|
|
309
|
+
console.log(`ActivityTree: Processing event: ${eventType}`, event);
|
|
310
|
+
|
|
311
|
+
// Fix timestamp processing - ensure we get a valid date
|
|
312
|
+
let timestamp;
|
|
313
|
+
if (event.timestamp) {
|
|
314
|
+
// Handle both ISO strings and already parsed dates
|
|
315
|
+
timestamp = new Date(event.timestamp);
|
|
316
|
+
// Check if date is valid
|
|
317
|
+
if (isNaN(timestamp.getTime())) {
|
|
318
|
+
console.warn('ActivityTree: Invalid timestamp, using current time:', event.timestamp);
|
|
319
|
+
timestamp = new Date();
|
|
432
320
|
}
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
eventType = 'Start';
|
|
321
|
+
} else {
|
|
322
|
+
console.warn('ActivityTree: No timestamp found, using current time');
|
|
323
|
+
timestamp = new Date();
|
|
437
324
|
}
|
|
438
325
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
326
|
+
// Get session ID from event - this should match the authoritative sessions
|
|
327
|
+
const sessionId = event.session_id || event.data?.session_id;
|
|
328
|
+
|
|
329
|
+
// Skip events without session ID - they can't be properly categorized
|
|
330
|
+
if (!sessionId) {
|
|
331
|
+
console.log(`ActivityTree: Skipping event without session_id: ${eventType}`);
|
|
444
332
|
return;
|
|
445
333
|
}
|
|
446
334
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
if (!this.isEventInTimeRange(timestamp)) {
|
|
335
|
+
// Find the session - it should already exist from authoritative sessions
|
|
336
|
+
if (!this.sessions.has(sessionId)) {
|
|
337
|
+
console.warn(`ActivityTree: Session ${sessionId} not found in authoritative sessions - skipping event`);
|
|
451
338
|
return;
|
|
452
339
|
}
|
|
453
340
|
|
|
341
|
+
const session = this.sessions.get(sessionId);
|
|
342
|
+
|
|
454
343
|
switch (eventType) {
|
|
344
|
+
case 'Start':
|
|
345
|
+
// New PM session started
|
|
346
|
+
this.currentSession = session;
|
|
347
|
+
break;
|
|
348
|
+
case 'user_prompt':
|
|
349
|
+
this.processUserInstruction(event, session);
|
|
350
|
+
break;
|
|
455
351
|
case 'TodoWrite':
|
|
456
|
-
this.processTodoWrite(event);
|
|
352
|
+
this.processTodoWrite(event, session);
|
|
457
353
|
break;
|
|
458
354
|
case 'SubagentStart':
|
|
459
|
-
this.processSubagentStart(event);
|
|
355
|
+
this.processSubagentStart(event, session);
|
|
460
356
|
break;
|
|
461
357
|
case 'SubagentStop':
|
|
462
|
-
this.processSubagentStop(event);
|
|
358
|
+
this.processSubagentStop(event, session);
|
|
463
359
|
break;
|
|
464
360
|
case 'PreToolUse':
|
|
465
|
-
this.processToolUse(event);
|
|
361
|
+
this.processToolUse(event, session);
|
|
466
362
|
break;
|
|
467
363
|
case 'PostToolUse':
|
|
468
|
-
this.updateToolStatus(event, 'completed');
|
|
469
|
-
break;
|
|
470
|
-
case 'Start':
|
|
471
|
-
this.initializeTreeData();
|
|
472
|
-
this.update(this.root);
|
|
364
|
+
this.updateToolStatus(event, session, 'completed');
|
|
473
365
|
break;
|
|
474
366
|
}
|
|
475
367
|
|
|
@@ -477,256 +369,576 @@ class ActivityTree {
|
|
|
477
369
|
}
|
|
478
370
|
|
|
479
371
|
/**
|
|
480
|
-
*
|
|
372
|
+
* Get event type from event data
|
|
481
373
|
*/
|
|
482
|
-
|
|
483
|
-
|
|
374
|
+
getEventType(event) {
|
|
375
|
+
if (event.hook_event_name) {
|
|
376
|
+
return event.hook_event_name;
|
|
377
|
+
}
|
|
484
378
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
379
|
+
if (event.type === 'hook' && event.subtype) {
|
|
380
|
+
const mapping = {
|
|
381
|
+
'pre_tool': 'PreToolUse',
|
|
382
|
+
'post_tool': 'PostToolUse',
|
|
383
|
+
'subagent_start': 'SubagentStart',
|
|
384
|
+
'subagent_stop': 'SubagentStop',
|
|
385
|
+
'todo_write': 'TodoWrite'
|
|
386
|
+
};
|
|
387
|
+
return mapping[event.subtype];
|
|
388
|
+
}
|
|
490
389
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
todos = todos.todos;
|
|
390
|
+
if (event.type === 'todo' && event.subtype === 'updated') {
|
|
391
|
+
return 'TodoWrite';
|
|
494
392
|
}
|
|
495
393
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
return;
|
|
394
|
+
if (event.type === 'subagent') {
|
|
395
|
+
if (event.subtype === 'started') return 'SubagentStart';
|
|
396
|
+
if (event.subtype === 'stopped') return 'SubagentStop';
|
|
500
397
|
}
|
|
501
398
|
|
|
502
|
-
if (
|
|
503
|
-
|
|
504
|
-
return;
|
|
399
|
+
if (event.type === 'start') {
|
|
400
|
+
return 'Start';
|
|
505
401
|
}
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
if (!activeTodo) {
|
|
510
|
-
console.log('ActivityTree: No in-progress todo found');
|
|
511
|
-
return;
|
|
402
|
+
|
|
403
|
+
if (event.type === 'user_prompt' || event.subtype === 'user_prompt') {
|
|
404
|
+
return 'user_prompt';
|
|
512
405
|
}
|
|
406
|
+
|
|
407
|
+
return null;
|
|
408
|
+
}
|
|
513
409
|
|
|
514
|
-
|
|
410
|
+
// getSessionId method removed - now using authoritative session IDs directly from socket client
|
|
515
411
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
412
|
+
/**
|
|
413
|
+
* Process user instruction/prompt event
|
|
414
|
+
*/
|
|
415
|
+
processUserInstruction(event, session) {
|
|
416
|
+
const promptText = event.prompt_text || event.data?.prompt_text || event.prompt || '';
|
|
417
|
+
if (!promptText) return;
|
|
418
|
+
|
|
419
|
+
const instruction = {
|
|
420
|
+
id: `instruction-${session.id}-${Date.now()}`,
|
|
421
|
+
text: promptText,
|
|
422
|
+
preview: promptText.length > 100 ? promptText.substring(0, 100) + '...' : promptText,
|
|
423
|
+
timestamp: event.timestamp || new Date().toISOString(),
|
|
424
|
+
type: 'user_instruction'
|
|
527
425
|
};
|
|
528
|
-
|
|
529
|
-
// Add to PM root
|
|
530
|
-
if (!this.root) {
|
|
531
|
-
console.error('ActivityTree: No root node!');
|
|
532
|
-
return;
|
|
533
|
-
}
|
|
534
426
|
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
return;
|
|
538
|
-
}
|
|
427
|
+
// Add to session's user instructions
|
|
428
|
+
session.userInstructions.push(instruction);
|
|
539
429
|
|
|
540
|
-
|
|
541
|
-
|
|
430
|
+
// Keep only last 5 instructions to prevent memory bloat
|
|
431
|
+
if (session.userInstructions.length > 5) {
|
|
432
|
+
session.userInstructions = session.userInstructions.slice(-5);
|
|
542
433
|
}
|
|
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
434
|
}
|
|
557
435
|
|
|
558
436
|
/**
|
|
559
|
-
* Process
|
|
437
|
+
* Process TodoWrite event - attach TODOs to session and active agent
|
|
560
438
|
*/
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
439
|
+
processTodoWrite(event, session) {
|
|
440
|
+
let todos = event.todos || event.data?.todos || event.data || [];
|
|
441
|
+
|
|
442
|
+
if (todos && typeof todos === 'object' && todos.todos) {
|
|
443
|
+
todos = todos.todos;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
if (!Array.isArray(todos) || todos.length === 0) {
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Update session's todos directly for overall checklist view
|
|
451
|
+
session.todos = todos.map(todo => ({
|
|
452
|
+
content: todo.content,
|
|
453
|
+
activeForm: todo.activeForm,
|
|
454
|
+
status: todo.status,
|
|
455
|
+
timestamp: event.timestamp
|
|
456
|
+
}));
|
|
457
|
+
|
|
458
|
+
// Create TodoWrite tool for session-level display
|
|
459
|
+
const sessionTodoTool = {
|
|
460
|
+
id: `todo-session-${session.id}-${Date.now()}`,
|
|
461
|
+
name: 'TodoWrite',
|
|
462
|
+
type: 'tool',
|
|
463
|
+
icon: '📝',
|
|
576
464
|
timestamp: event.timestamp,
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
465
|
+
status: 'active',
|
|
466
|
+
params: {
|
|
467
|
+
todos: todos
|
|
468
|
+
},
|
|
469
|
+
isPrioritizedTool: true
|
|
581
470
|
};
|
|
471
|
+
|
|
472
|
+
// Update session-level TodoWrite tool
|
|
473
|
+
session.tools = session.tools.filter(t => t.name !== 'TodoWrite');
|
|
474
|
+
session.tools.unshift(sessionTodoTool);
|
|
475
|
+
session.currentTodoTool = sessionTodoTool;
|
|
582
476
|
|
|
583
|
-
//
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
477
|
+
// ALSO attach TodoWrite to the active agent that triggered it
|
|
478
|
+
const agentSessionId = event.session_id || event.data?.session_id;
|
|
479
|
+
let targetAgent = null;
|
|
480
|
+
|
|
481
|
+
// Find the appropriate agent to attach this TodoWrite to
|
|
482
|
+
// First try to find by session ID
|
|
483
|
+
if (agentSessionId && session.agents.has(agentSessionId)) {
|
|
484
|
+
targetAgent = session.agents.get(agentSessionId);
|
|
485
|
+
} else {
|
|
486
|
+
// Fall back to most recent active agent
|
|
487
|
+
const activeAgents = Array.from(session.agents.values())
|
|
488
|
+
.filter(agent => agent.status === 'active' || agent.status === 'in_progress')
|
|
489
|
+
.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
|
|
490
|
+
|
|
491
|
+
if (activeAgents.length > 0) {
|
|
492
|
+
targetAgent = activeAgents[0];
|
|
493
|
+
} else {
|
|
494
|
+
// If no active agents, use the most recently used agent
|
|
495
|
+
const allAgents = Array.from(session.agents.values())
|
|
496
|
+
.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
|
|
497
|
+
if (allAgents.length > 0) {
|
|
498
|
+
targetAgent = allAgents[0];
|
|
499
|
+
}
|
|
590
500
|
}
|
|
591
501
|
}
|
|
592
502
|
|
|
593
|
-
|
|
594
|
-
|
|
503
|
+
// Create agent-specific TodoWrite tool
|
|
504
|
+
if (targetAgent) {
|
|
505
|
+
const agentTodoTool = {
|
|
506
|
+
id: `todo-agent-${targetAgent.id}-${Date.now()}`,
|
|
507
|
+
name: 'TodoWrite',
|
|
508
|
+
type: 'tool',
|
|
509
|
+
icon: '📝',
|
|
510
|
+
timestamp: event.timestamp,
|
|
511
|
+
status: 'active',
|
|
512
|
+
params: {
|
|
513
|
+
todos: todos
|
|
514
|
+
},
|
|
515
|
+
isPrioritizedTool: true
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
// Remove existing TodoWrite tool from agent and add the updated one
|
|
519
|
+
targetAgent.tools = targetAgent.tools.filter(t => t.name !== 'TodoWrite');
|
|
520
|
+
targetAgent.tools.unshift(agentTodoTool);
|
|
595
521
|
}
|
|
522
|
+
}
|
|
596
523
|
|
|
597
|
-
|
|
598
|
-
|
|
524
|
+
/**
|
|
525
|
+
* Process SubagentStart event
|
|
526
|
+
*/
|
|
527
|
+
processSubagentStart(event, session) {
|
|
528
|
+
const agentName = event.agent_name || event.data?.agent_name || event.data?.agent_type || event.agent_type || event.agent || 'unknown';
|
|
529
|
+
const agentSessionId = event.session_id || event.data?.session_id;
|
|
530
|
+
|
|
531
|
+
// Use session ID as unique agent identifier, or create unique ID
|
|
532
|
+
const agentId = agentSessionId || `agent-${Date.now()}-${Math.random()}`;
|
|
533
|
+
|
|
534
|
+
// Check if agent already exists in this session
|
|
535
|
+
if (!session.agents.has(agentId)) {
|
|
536
|
+
const agent = {
|
|
537
|
+
id: agentId,
|
|
538
|
+
name: agentName,
|
|
539
|
+
type: 'agent',
|
|
540
|
+
icon: this.getAgentIcon(agentName),
|
|
541
|
+
timestamp: event.timestamp,
|
|
542
|
+
status: 'active',
|
|
543
|
+
tools: [],
|
|
544
|
+
sessionId: agentSessionId,
|
|
545
|
+
isPM: false
|
|
546
|
+
};
|
|
547
|
+
|
|
548
|
+
session.agents.set(agentId, agent);
|
|
549
|
+
} else {
|
|
550
|
+
// Update existing agent status to active
|
|
551
|
+
const existingAgent = session.agents.get(agentId);
|
|
552
|
+
existingAgent.status = 'active';
|
|
553
|
+
existingAgent.timestamp = event.timestamp; // Update timestamp
|
|
599
554
|
}
|
|
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
555
|
}
|
|
608
556
|
|
|
609
557
|
/**
|
|
610
558
|
* Process SubagentStop event
|
|
611
559
|
*/
|
|
612
|
-
processSubagentStop(event) {
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
this.activeAgentStack[this.activeAgentStack.length - 1] : null;
|
|
560
|
+
processSubagentStop(event, session) {
|
|
561
|
+
const agentSessionId = event.session_id || event.data?.session_id;
|
|
562
|
+
|
|
563
|
+
// Find and mark agent as completed
|
|
564
|
+
if (agentSessionId && session.agents.has(agentSessionId)) {
|
|
565
|
+
const agent = session.agents.get(agentSessionId);
|
|
566
|
+
agent.status = 'completed';
|
|
620
567
|
}
|
|
621
|
-
|
|
622
|
-
this.update(this.root);
|
|
623
568
|
}
|
|
624
569
|
|
|
625
570
|
/**
|
|
626
571
|
* Process tool use event
|
|
627
572
|
*/
|
|
628
|
-
processToolUse(event) {
|
|
629
|
-
|
|
630
|
-
const
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
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 = {
|
|
573
|
+
processToolUse(event, session) {
|
|
574
|
+
const toolName = event.tool_name || event.data?.tool_name || event.tool || event.data?.tool || 'unknown';
|
|
575
|
+
const params = event.tool_parameters || event.data?.tool_parameters || event.parameters || event.data?.parameters || {};
|
|
576
|
+
const agentSessionId = event.session_id || event.data?.session_id;
|
|
577
|
+
|
|
578
|
+
const tool = {
|
|
579
|
+
id: `tool-${Date.now()}-${Math.random()}`,
|
|
647
580
|
name: toolName,
|
|
648
581
|
type: 'tool',
|
|
649
|
-
icon:
|
|
582
|
+
icon: this.getToolIcon(toolName),
|
|
650
583
|
timestamp: event.timestamp,
|
|
651
584
|
status: 'in_progress',
|
|
652
|
-
|
|
653
|
-
_children: null,
|
|
585
|
+
params: params,
|
|
654
586
|
eventId: event.id
|
|
655
587
|
};
|
|
656
588
|
|
|
657
|
-
//
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
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
|
-
});
|
|
589
|
+
// Find the appropriate agent to attach this tool to
|
|
590
|
+
let targetAgent = null;
|
|
591
|
+
|
|
592
|
+
// First try to find by session ID
|
|
593
|
+
if (agentSessionId && session.agents.has(agentSessionId)) {
|
|
594
|
+
targetAgent = session.agents.get(agentSessionId);
|
|
595
|
+
} else {
|
|
596
|
+
// Fall back to most recent active agent
|
|
597
|
+
const activeAgents = Array.from(session.agents.values())
|
|
598
|
+
.filter(agent => agent.status === 'active')
|
|
599
|
+
.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
|
|
600
|
+
|
|
601
|
+
if (activeAgents.length > 0) {
|
|
602
|
+
targetAgent = activeAgents[0];
|
|
603
|
+
} else {
|
|
604
|
+
// If no active agents, attach to session (PM level)
|
|
605
|
+
session.tools.push(tool);
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
693
608
|
}
|
|
694
609
|
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
if (!parent.children) {
|
|
698
|
-
parent.children = [];
|
|
610
|
+
if (targetAgent) {
|
|
611
|
+
targetAgent.tools.push(tool);
|
|
699
612
|
}
|
|
700
|
-
parent.children.push(toolNode);
|
|
701
|
-
|
|
702
|
-
this.update(this.root);
|
|
703
613
|
}
|
|
704
614
|
|
|
705
615
|
/**
|
|
706
616
|
* Update tool status after completion
|
|
707
617
|
*/
|
|
708
|
-
updateToolStatus(event, status) {
|
|
709
|
-
// Find
|
|
710
|
-
const
|
|
711
|
-
if (
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
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;
|
|
618
|
+
updateToolStatus(event, session, status) {
|
|
619
|
+
// Find and update tool status across all agents
|
|
620
|
+
const findAndUpdateTool = (agent) => {
|
|
621
|
+
if (agent.tools) {
|
|
622
|
+
const tool = agent.tools.find(t => t.eventId === event.id);
|
|
623
|
+
if (tool) {
|
|
624
|
+
tool.status = status;
|
|
625
|
+
return true;
|
|
723
626
|
}
|
|
724
627
|
}
|
|
725
628
|
return false;
|
|
726
629
|
};
|
|
727
630
|
|
|
728
|
-
|
|
729
|
-
|
|
631
|
+
// Check all agents in session
|
|
632
|
+
for (let agent of session.agents.values()) {
|
|
633
|
+
if (findAndUpdateTool(agent)) return;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// Check session-level tools (PM level)
|
|
637
|
+
if (session.tools && findAndUpdateTool(session)) return;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
/**
|
|
641
|
+
* Render the linear tree view
|
|
642
|
+
*/
|
|
643
|
+
renderTree() {
|
|
644
|
+
const treeContainer = document.getElementById('linear-tree');
|
|
645
|
+
if (!treeContainer) return;
|
|
646
|
+
|
|
647
|
+
// Clear tree
|
|
648
|
+
treeContainer.innerHTML = '';
|
|
649
|
+
|
|
650
|
+
// Add sessions directly (no project root)
|
|
651
|
+
const sortedSessions = Array.from(this.sessions.values())
|
|
652
|
+
.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
|
|
653
|
+
|
|
654
|
+
for (let session of sortedSessions) {
|
|
655
|
+
if (this.selectedSessionFilter !== 'all' && this.selectedSessionFilter !== session.id) {
|
|
656
|
+
continue;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
const sessionElement = this.createSessionElement(session);
|
|
660
|
+
treeContainer.appendChild(sessionElement);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// Session filtering is now handled by the main session selector via event listeners
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
|
|
667
|
+
/**
|
|
668
|
+
* Create session element
|
|
669
|
+
*/
|
|
670
|
+
createSessionElement(session) {
|
|
671
|
+
const isExpanded = this.expandedSessions.has(session.id) || session.expanded;
|
|
672
|
+
|
|
673
|
+
// Ensure timestamp is valid and format it consistently
|
|
674
|
+
let sessionTime;
|
|
675
|
+
try {
|
|
676
|
+
const sessionDate = session.timestamp instanceof Date ? session.timestamp : new Date(session.timestamp);
|
|
677
|
+
if (isNaN(sessionDate.getTime())) {
|
|
678
|
+
sessionTime = 'Invalid Date';
|
|
679
|
+
console.warn('ActivityTree: Invalid session timestamp:', session.timestamp);
|
|
680
|
+
} else {
|
|
681
|
+
sessionTime = sessionDate.toLocaleString();
|
|
682
|
+
}
|
|
683
|
+
} catch (error) {
|
|
684
|
+
sessionTime = 'Invalid Date';
|
|
685
|
+
console.error('ActivityTree: Error formatting session timestamp:', error, session.timestamp);
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
const element = document.createElement('div');
|
|
689
|
+
element.className = 'tree-node session';
|
|
690
|
+
element.dataset.sessionId = session.id;
|
|
691
|
+
|
|
692
|
+
const expandIcon = isExpanded ? '▼' : '▶';
|
|
693
|
+
const agentCount = session.agents ? session.agents.size : 0;
|
|
694
|
+
const todoCount = session.todos ? session.todos.length : 0;
|
|
695
|
+
const instructionCount = session.userInstructions ? session.userInstructions.length : 0;
|
|
696
|
+
|
|
697
|
+
console.log(`ActivityTree: Rendering session ${session.id}: ${agentCount} agents, ${instructionCount} instructions, ${todoCount} todos at ${sessionTime}`);
|
|
698
|
+
|
|
699
|
+
element.innerHTML = `
|
|
700
|
+
<div class="tree-node-content" onclick="window.activityTreeInstance.toggleSession('${session.id}')">
|
|
701
|
+
<span class="tree-expand-icon">${expandIcon}</span>
|
|
702
|
+
<span class="tree-icon">🎯</span>
|
|
703
|
+
<span class="tree-label">PM Session</span>
|
|
704
|
+
<span class="tree-meta">${sessionTime} • ${agentCount} agent(s) • ${instructionCount} instruction(s) • ${todoCount} todo(s)</span>
|
|
705
|
+
</div>
|
|
706
|
+
<div class="tree-children" style="display: ${isExpanded ? 'block' : 'none'}">
|
|
707
|
+
${this.renderSessionContent(session)}
|
|
708
|
+
</div>
|
|
709
|
+
`;
|
|
710
|
+
|
|
711
|
+
return element;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
/**
|
|
715
|
+
* Render session content (user instructions, todos, agents, tools)
|
|
716
|
+
*/
|
|
717
|
+
renderSessionContent(session) {
|
|
718
|
+
let html = '';
|
|
719
|
+
|
|
720
|
+
// Render user instructions first
|
|
721
|
+
if (session.userInstructions && session.userInstructions.length > 0) {
|
|
722
|
+
for (let instruction of session.userInstructions.slice(-3)) { // Show last 3 instructions
|
|
723
|
+
html += this.renderUserInstructionElement(instruction, 1);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// Render TODOs as checklist directly under session
|
|
728
|
+
if (session.todos && session.todos.length > 0) {
|
|
729
|
+
html += this.renderTodoChecklistElement(session.todos, 1);
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
// Render session-level tools (PM tools)
|
|
733
|
+
if (session.tools && session.tools.length > 0) {
|
|
734
|
+
for (let tool of session.tools) {
|
|
735
|
+
// Show all tools including TodoWrite - both checklist and tool views are useful
|
|
736
|
+
html += this.renderToolElement(tool, 1);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
// Render agents
|
|
741
|
+
const agents = Array.from(session.agents.values())
|
|
742
|
+
.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
|
|
743
|
+
|
|
744
|
+
for (let agent of agents) {
|
|
745
|
+
html += this.renderAgentElement(agent, 1);
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
return html;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
/**
|
|
752
|
+
* Render user instruction element
|
|
753
|
+
*/
|
|
754
|
+
renderUserInstructionElement(instruction, level) {
|
|
755
|
+
const isSelected = this.selectedItem && this.selectedItem.type === 'instruction' && this.selectedItem.data.id === instruction.id;
|
|
756
|
+
const selectedClass = isSelected ? 'selected' : '';
|
|
757
|
+
|
|
758
|
+
return `
|
|
759
|
+
<div class="tree-node user-instruction ${selectedClass}" data-level="${level}">
|
|
760
|
+
<div class="tree-node-content">
|
|
761
|
+
<span class="tree-expand-icon"></span>
|
|
762
|
+
<span class="tree-icon">💬</span>
|
|
763
|
+
<span class="tree-label clickable" onclick="window.activityTreeInstance.selectItem(${this.escapeJson(instruction)}, 'instruction', event)">User: "${this.escapeHtml(instruction.preview)}"</span>
|
|
764
|
+
<span class="tree-status status-active">instruction</span>
|
|
765
|
+
</div>
|
|
766
|
+
</div>
|
|
767
|
+
`;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
/**
|
|
771
|
+
* Render TODO checklist element
|
|
772
|
+
*/
|
|
773
|
+
renderTodoChecklistElement(todos, level) {
|
|
774
|
+
const checklistId = `checklist-${Date.now()}`;
|
|
775
|
+
const isExpanded = this.expandedTools.has(checklistId) !== false; // Default to expanded
|
|
776
|
+
const expandIcon = isExpanded ? '▼' : '▶';
|
|
777
|
+
|
|
778
|
+
// Calculate status summary
|
|
779
|
+
let completedCount = 0;
|
|
780
|
+
let inProgressCount = 0;
|
|
781
|
+
let pendingCount = 0;
|
|
782
|
+
|
|
783
|
+
todos.forEach(todo => {
|
|
784
|
+
if (todo.status === 'completed') completedCount++;
|
|
785
|
+
else if (todo.status === 'in_progress') inProgressCount++;
|
|
786
|
+
else pendingCount++;
|
|
787
|
+
});
|
|
788
|
+
|
|
789
|
+
let statusSummary = '';
|
|
790
|
+
if (inProgressCount > 0) {
|
|
791
|
+
statusSummary = `${inProgressCount} in progress, ${completedCount} completed`;
|
|
792
|
+
} else if (completedCount === todos.length && todos.length > 0) {
|
|
793
|
+
statusSummary = `All ${todos.length} completed`;
|
|
794
|
+
} else {
|
|
795
|
+
statusSummary = `${todos.length} todo(s)`;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
let html = `
|
|
799
|
+
<div class="tree-node todo-checklist" data-level="${level}">
|
|
800
|
+
<div class="tree-node-content">
|
|
801
|
+
<span class="tree-expand-icon" onclick="window.activityTreeInstance.toggleTodoChecklist('${checklistId}'); event.stopPropagation();">${expandIcon}</span>
|
|
802
|
+
<span class="tree-icon">☑️</span>
|
|
803
|
+
<span class="tree-label">TODOs</span>
|
|
804
|
+
<span class="tree-params">${statusSummary}</span>
|
|
805
|
+
<span class="tree-status status-active">checklist</span>
|
|
806
|
+
</div>
|
|
807
|
+
`;
|
|
808
|
+
|
|
809
|
+
// Show expanded todo items if expanded
|
|
810
|
+
if (isExpanded) {
|
|
811
|
+
html += '<div class="tree-children">';
|
|
812
|
+
for (let todo of todos) {
|
|
813
|
+
const statusIcon = this.getCheckboxIcon(todo.status);
|
|
814
|
+
const statusClass = `status-${todo.status}`;
|
|
815
|
+
const displayText = todo.status === 'in_progress' ? todo.activeForm : todo.content;
|
|
816
|
+
|
|
817
|
+
html += `
|
|
818
|
+
<div class="tree-node todo-item ${statusClass}" data-level="${level + 1}">
|
|
819
|
+
<div class="tree-node-content">
|
|
820
|
+
<span class="tree-expand-icon"></span>
|
|
821
|
+
<span class="tree-icon">${statusIcon}</span>
|
|
822
|
+
<span class="tree-label">${this.escapeHtml(displayText)}</span>
|
|
823
|
+
<span class="tree-status ${statusClass}">${todo.status.replace('_', ' ')}</span>
|
|
824
|
+
</div>
|
|
825
|
+
</div>
|
|
826
|
+
`;
|
|
827
|
+
}
|
|
828
|
+
html += '</div>';
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
html += '</div>';
|
|
832
|
+
return html;
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
/**
|
|
836
|
+
* Render agent element
|
|
837
|
+
*/
|
|
838
|
+
renderAgentElement(agent, level) {
|
|
839
|
+
const statusClass = agent.status === 'active' ? 'status-active' : 'status-completed';
|
|
840
|
+
const isExpanded = this.expandedAgents.has(agent.id);
|
|
841
|
+
const hasTools = agent.tools && agent.tools.length > 0;
|
|
842
|
+
const isSelected = this.selectedItem && this.selectedItem.type === 'agent' && this.selectedItem.data.id === agent.id;
|
|
843
|
+
|
|
844
|
+
const expandIcon = hasTools ? (isExpanded ? '▼' : '▶') : '';
|
|
845
|
+
const selectedClass = isSelected ? 'selected' : '';
|
|
846
|
+
|
|
847
|
+
let html = `
|
|
848
|
+
<div class="tree-node agent ${statusClass} ${selectedClass}" data-level="${level}">
|
|
849
|
+
<div class="tree-node-content">
|
|
850
|
+
${expandIcon ? `<span class="tree-expand-icon" onclick="window.activityTreeInstance.toggleAgent('${agent.id}'); event.stopPropagation();">${expandIcon}</span>` : '<span class="tree-expand-icon"></span>'}
|
|
851
|
+
<span class="tree-icon">${agent.icon}</span>
|
|
852
|
+
<span class="tree-label clickable" onclick="window.activityTreeInstance.selectItem(${this.escapeJson(agent)}, 'agent', event)">${agent.name}</span>
|
|
853
|
+
<span class="tree-status ${statusClass}">${agent.status}</span>
|
|
854
|
+
</div>
|
|
855
|
+
`;
|
|
856
|
+
|
|
857
|
+
// Render tools under this agent
|
|
858
|
+
if (hasTools && isExpanded) {
|
|
859
|
+
html += '<div class="tree-children">';
|
|
860
|
+
for (let tool of agent.tools) {
|
|
861
|
+
html += this.renderToolElement(tool, level + 1);
|
|
862
|
+
}
|
|
863
|
+
html += '</div>';
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
html += '</div>';
|
|
867
|
+
return html;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
/**
|
|
871
|
+
* Render tool element (non-expandable, clickable to show data)
|
|
872
|
+
*/
|
|
873
|
+
renderToolElement(tool, level) {
|
|
874
|
+
const statusClass = `status-${tool.status}`;
|
|
875
|
+
const params = this.getToolParams(tool);
|
|
876
|
+
const isSelected = this.selectedItem && this.selectedItem.type === 'tool' && this.selectedItem.data.id === tool.id;
|
|
877
|
+
const selectedClass = isSelected ? 'selected' : '';
|
|
878
|
+
|
|
879
|
+
let html = `
|
|
880
|
+
<div class="tree-node tool ${statusClass} ${selectedClass}" data-level="${level}">
|
|
881
|
+
<div class="tree-node-content">
|
|
882
|
+
<span class="tree-expand-icon"></span>
|
|
883
|
+
<span class="tree-icon">${tool.icon}</span>
|
|
884
|
+
<span class="tree-label clickable" onclick="window.activityTreeInstance.selectItem(${this.escapeJson(tool)}, 'tool', event)">${tool.name} (click to view details)</span>
|
|
885
|
+
<span class="tree-params">${params}</span>
|
|
886
|
+
<span class="tree-status ${statusClass}">${tool.status}</span>
|
|
887
|
+
</div>
|
|
888
|
+
</div>
|
|
889
|
+
`;
|
|
890
|
+
|
|
891
|
+
return html;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
/**
|
|
895
|
+
* Get formatted tool parameters
|
|
896
|
+
*/
|
|
897
|
+
getToolParams(tool) {
|
|
898
|
+
if (!tool.params) return '';
|
|
899
|
+
|
|
900
|
+
if (tool.name === 'Read' && tool.params.file_path) {
|
|
901
|
+
return tool.params.file_path;
|
|
902
|
+
}
|
|
903
|
+
if (tool.name === 'Edit' && tool.params.file_path) {
|
|
904
|
+
return tool.params.file_path;
|
|
905
|
+
}
|
|
906
|
+
if (tool.name === 'Write' && tool.params.file_path) {
|
|
907
|
+
return tool.params.file_path;
|
|
908
|
+
}
|
|
909
|
+
if (tool.name === 'Bash' && tool.params.command) {
|
|
910
|
+
const cmd = tool.params.command;
|
|
911
|
+
return cmd.length > 50 ? cmd.substring(0, 50) + '...' : cmd;
|
|
912
|
+
}
|
|
913
|
+
if (tool.name === 'WebFetch' && tool.params.url) {
|
|
914
|
+
return tool.params.url;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
return '';
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
/**
|
|
921
|
+
* Get status icon for todo status
|
|
922
|
+
*/
|
|
923
|
+
getStatusIcon(status) {
|
|
924
|
+
const icons = {
|
|
925
|
+
'pending': '⏸️',
|
|
926
|
+
'in_progress': '🔄',
|
|
927
|
+
'completed': '✅'
|
|
928
|
+
};
|
|
929
|
+
return icons[status] || '❓';
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
/**
|
|
933
|
+
* Get checkbox icon for todo checklist items
|
|
934
|
+
*/
|
|
935
|
+
getCheckboxIcon(status) {
|
|
936
|
+
const icons = {
|
|
937
|
+
'pending': '⏳',
|
|
938
|
+
'in_progress': '🔄',
|
|
939
|
+
'completed': '✅'
|
|
940
|
+
};
|
|
941
|
+
return icons[status] || '❓';
|
|
730
942
|
}
|
|
731
943
|
|
|
732
944
|
/**
|
|
@@ -762,508 +974,606 @@ class ActivityTree {
|
|
|
762
974
|
}
|
|
763
975
|
|
|
764
976
|
/**
|
|
765
|
-
*
|
|
977
|
+
* Toggle session expansion
|
|
766
978
|
*/
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
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;
|
|
979
|
+
toggleSession(sessionId) {
|
|
980
|
+
if (this.expandedSessions.has(sessionId)) {
|
|
981
|
+
this.expandedSessions.delete(sessionId);
|
|
982
|
+
} else {
|
|
983
|
+
this.expandedSessions.add(sessionId);
|
|
814
984
|
}
|
|
815
985
|
|
|
816
|
-
//
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
} catch (error) {
|
|
821
|
-
console.error('ActivityTree: Error computing tree layout:', error);
|
|
822
|
-
return;
|
|
986
|
+
// Update the session in the data structure
|
|
987
|
+
const session = this.sessions.get(sessionId);
|
|
988
|
+
if (session) {
|
|
989
|
+
session.expanded = this.expandedSessions.has(sessionId);
|
|
823
990
|
}
|
|
824
991
|
|
|
825
|
-
|
|
826
|
-
|
|
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);
|
|
992
|
+
this.renderTree();
|
|
993
|
+
}
|
|
916
994
|
|
|
917
|
-
|
|
918
|
-
|
|
995
|
+
/**
|
|
996
|
+
* Expand all sessions
|
|
997
|
+
*/
|
|
998
|
+
expandAllSessions() {
|
|
999
|
+
for (let sessionId of this.sessions.keys()) {
|
|
1000
|
+
this.expandedSessions.add(sessionId);
|
|
1001
|
+
const session = this.sessions.get(sessionId);
|
|
1002
|
+
if (session) session.expanded = true;
|
|
1003
|
+
}
|
|
1004
|
+
this.renderTree();
|
|
1005
|
+
}
|
|
919
1006
|
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
1007
|
+
/**
|
|
1008
|
+
* Collapse all sessions
|
|
1009
|
+
*/
|
|
1010
|
+
collapseAllSessions() {
|
|
1011
|
+
this.expandedSessions.clear();
|
|
1012
|
+
for (let session of this.sessions.values()) {
|
|
1013
|
+
session.expanded = false;
|
|
1014
|
+
}
|
|
1015
|
+
this.renderTree();
|
|
1016
|
+
}
|
|
923
1017
|
|
|
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
1018
|
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
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
|
-
});
|
|
1019
|
+
/**
|
|
1020
|
+
* Update statistics
|
|
1021
|
+
*/
|
|
1022
|
+
updateStats() {
|
|
1023
|
+
const totalNodes = this.countTotalNodes();
|
|
1024
|
+
const activeNodes = this.countActiveNodes();
|
|
1025
|
+
const maxDepth = this.calculateMaxDepth();
|
|
953
1026
|
|
|
954
|
-
|
|
955
|
-
|
|
1027
|
+
const nodeCountEl = document.getElementById('node-count');
|
|
1028
|
+
const activeCountEl = document.getElementById('active-count');
|
|
1029
|
+
const depthEl = document.getElementById('tree-depth');
|
|
1030
|
+
|
|
1031
|
+
if (nodeCountEl) nodeCountEl.textContent = totalNodes;
|
|
1032
|
+
if (activeCountEl) activeCountEl.textContent = activeNodes;
|
|
1033
|
+
if (depthEl) depthEl.textContent = maxDepth;
|
|
1034
|
+
|
|
1035
|
+
console.log(`ActivityTree: Stats updated - Nodes: ${totalNodes}, Active: ${activeNodes}, Depth: ${maxDepth}`);
|
|
956
1036
|
}
|
|
957
1037
|
|
|
958
1038
|
/**
|
|
959
|
-
*
|
|
1039
|
+
* Count total nodes across all sessions
|
|
960
1040
|
*/
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
1041
|
+
countTotalNodes() {
|
|
1042
|
+
let count = 0; // No project root anymore
|
|
1043
|
+
for (let session of this.sessions.values()) {
|
|
1044
|
+
count += 1; // Session
|
|
1045
|
+
count += session.agents.size; // Agents
|
|
1046
|
+
|
|
1047
|
+
// Count user instructions
|
|
1048
|
+
if (session.userInstructions) {
|
|
1049
|
+
count += session.userInstructions.length;
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
// Count todos
|
|
1053
|
+
if (session.todos) {
|
|
1054
|
+
count += session.todos.length;
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
// Count session-level tools
|
|
1058
|
+
if (session.tools) {
|
|
1059
|
+
count += session.tools.length;
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
// Count tools in agents
|
|
1063
|
+
for (let agent of session.agents.values()) {
|
|
1064
|
+
if (agent.tools) {
|
|
1065
|
+
count += agent.tools.length;
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
return count;
|
|
966
1070
|
}
|
|
967
1071
|
|
|
968
1072
|
/**
|
|
969
|
-
*
|
|
1073
|
+
* Count active nodes (in progress)
|
|
970
1074
|
*/
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
1075
|
+
countActiveNodes() {
|
|
1076
|
+
let count = 0;
|
|
1077
|
+
for (let session of this.sessions.values()) {
|
|
1078
|
+
// Count active session
|
|
1079
|
+
if (session.status === 'active') count++;
|
|
1080
|
+
|
|
1081
|
+
// Count active todos
|
|
1082
|
+
if (session.todos) {
|
|
1083
|
+
for (let todo of session.todos) {
|
|
1084
|
+
if (todo.status === 'in_progress') count++;
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
// Count session-level tools
|
|
1089
|
+
if (session.tools) {
|
|
1090
|
+
for (let tool of session.tools) {
|
|
1091
|
+
if (tool.status === 'in_progress') count++;
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
// Count agents and their tools
|
|
1096
|
+
for (let agent of session.agents.values()) {
|
|
1097
|
+
if (agent.status === 'active') count++;
|
|
1098
|
+
if (agent.tools) {
|
|
1099
|
+
for (let tool of agent.tools) {
|
|
1100
|
+
if (tool.status === 'in_progress') count++;
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
978
1104
|
}
|
|
979
|
-
|
|
980
|
-
this.updateBreadcrumb(d);
|
|
1105
|
+
return count;
|
|
981
1106
|
}
|
|
982
1107
|
|
|
983
1108
|
/**
|
|
984
|
-
*
|
|
1109
|
+
* Calculate maximum depth
|
|
985
1110
|
*/
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
1111
|
+
calculateMaxDepth() {
|
|
1112
|
+
let maxDepth = 0; // No project root anymore
|
|
1113
|
+
for (let session of this.sessions.values()) {
|
|
1114
|
+
let sessionDepth = 1; // Session level (now root level)
|
|
1115
|
+
|
|
1116
|
+
// Check session content (instructions, todos, tools)
|
|
1117
|
+
if (session.userInstructions && session.userInstructions.length > 0) {
|
|
1118
|
+
sessionDepth = Math.max(sessionDepth, 2); // Instruction level
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
if (session.todos && session.todos.length > 0) {
|
|
1122
|
+
sessionDepth = Math.max(sessionDepth, 3); // Todo checklist -> todo items
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
if (session.tools && session.tools.length > 0) {
|
|
1126
|
+
sessionDepth = Math.max(sessionDepth, 2); // Tool level
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
// Check agents
|
|
1130
|
+
for (let agent of session.agents.values()) {
|
|
1131
|
+
if (agent.tools && agent.tools.length > 0) {
|
|
1132
|
+
sessionDepth = Math.max(sessionDepth, 3); // Tool level under agents
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
maxDepth = Math.max(maxDepth, sessionDepth);
|
|
1137
|
+
}
|
|
1138
|
+
return maxDepth;
|
|
997
1139
|
}
|
|
998
1140
|
|
|
999
1141
|
/**
|
|
1000
|
-
*
|
|
1142
|
+
* Toggle agent expansion
|
|
1001
1143
|
*/
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
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');
|
|
1144
|
+
toggleAgent(agentId) {
|
|
1145
|
+
if (this.expandedAgents.has(agentId)) {
|
|
1146
|
+
this.expandedAgents.delete(agentId);
|
|
1147
|
+
} else {
|
|
1148
|
+
this.expandedAgents.add(agentId);
|
|
1149
|
+
}
|
|
1150
|
+
this.renderTree();
|
|
1017
1151
|
}
|
|
1018
|
-
|
|
1152
|
+
|
|
1019
1153
|
/**
|
|
1020
|
-
*
|
|
1154
|
+
* Toggle tool expansion (deprecated - tools are no longer expandable)
|
|
1021
1155
|
*/
|
|
1022
|
-
|
|
1023
|
-
this
|
|
1024
|
-
|
|
1025
|
-
.style('opacity', 0);
|
|
1156
|
+
toggleTool(toolId) {
|
|
1157
|
+
// Tools are no longer expandable - this method is kept for compatibility
|
|
1158
|
+
console.log('Tool expansion is disabled. Tools now show data in the left pane when clicked.');
|
|
1026
1159
|
}
|
|
1027
1160
|
|
|
1028
1161
|
/**
|
|
1029
|
-
*
|
|
1162
|
+
* Toggle TODO checklist expansion
|
|
1030
1163
|
*/
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
d.children.forEach(expand);
|
|
1039
|
-
}
|
|
1040
|
-
};
|
|
1041
|
-
|
|
1042
|
-
expand(this.root);
|
|
1043
|
-
this.update(this.root);
|
|
1164
|
+
toggleTodoChecklist(checklistId) {
|
|
1165
|
+
if (this.expandedTools.has(checklistId)) {
|
|
1166
|
+
this.expandedTools.delete(checklistId);
|
|
1167
|
+
} else {
|
|
1168
|
+
this.expandedTools.add(checklistId);
|
|
1169
|
+
}
|
|
1170
|
+
this.renderTree();
|
|
1044
1171
|
}
|
|
1045
1172
|
|
|
1046
1173
|
/**
|
|
1047
|
-
*
|
|
1174
|
+
* Handle item click to show data in left pane
|
|
1048
1175
|
*/
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
d.children = null;
|
|
1055
|
-
}
|
|
1056
|
-
};
|
|
1176
|
+
selectItem(item, itemType, event) {
|
|
1177
|
+
// Stop event propagation to prevent expand/collapse when clicking on label
|
|
1178
|
+
if (event) {
|
|
1179
|
+
event.stopPropagation();
|
|
1180
|
+
}
|
|
1057
1181
|
|
|
1058
|
-
this.
|
|
1059
|
-
this.
|
|
1182
|
+
this.selectedItem = { data: item, type: itemType };
|
|
1183
|
+
this.displayItemData(item, itemType);
|
|
1184
|
+
this.renderTree(); // Re-render to show selection highlight
|
|
1060
1185
|
}
|
|
1061
1186
|
|
|
1062
1187
|
/**
|
|
1063
|
-
*
|
|
1188
|
+
* Display item data in left pane using simple display methods
|
|
1064
1189
|
*/
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1190
|
+
displayItemData(item, itemType) {
|
|
1191
|
+
// Special handling for TodoWrite tools to match Tools view display
|
|
1192
|
+
if (itemType === 'tool' && item.name === 'TodoWrite' && item.params && item.params.todos) {
|
|
1193
|
+
this.displayTodoWriteData(item);
|
|
1068
1194
|
return;
|
|
1069
1195
|
}
|
|
1070
1196
|
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
this.
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1197
|
+
// Use simple display methods based on item type
|
|
1198
|
+
switch(itemType) {
|
|
1199
|
+
case 'agent':
|
|
1200
|
+
this.displayAgentData(item);
|
|
1201
|
+
break;
|
|
1202
|
+
case 'tool':
|
|
1203
|
+
this.displayToolData(item);
|
|
1204
|
+
break;
|
|
1205
|
+
case 'instruction':
|
|
1206
|
+
this.displayInstructionData(item);
|
|
1207
|
+
break;
|
|
1208
|
+
default:
|
|
1209
|
+
this.displayGenericData(item, itemType);
|
|
1210
|
+
break;
|
|
1211
|
+
}
|
|
1082
1212
|
|
|
1083
|
-
//
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1213
|
+
// Update module header for consistency
|
|
1214
|
+
const moduleHeader = document.querySelector('.module-data-header h5');
|
|
1215
|
+
if (moduleHeader) {
|
|
1216
|
+
const icons = {
|
|
1217
|
+
'agent': '🤖',
|
|
1218
|
+
'tool': '🔧',
|
|
1219
|
+
'instruction': '💬',
|
|
1220
|
+
'session': '🎯'
|
|
1221
|
+
};
|
|
1222
|
+
const icon = icons[itemType] || '📊';
|
|
1223
|
+
const name = item.name || item.agentName || item.tool_name || 'Item';
|
|
1224
|
+
moduleHeader.textContent = `${icon} ${itemType}: ${name}`;
|
|
1225
|
+
}
|
|
1087
1226
|
}
|
|
1088
1227
|
|
|
1089
1228
|
/**
|
|
1090
|
-
*
|
|
1229
|
+
* Display TodoWrite data in the same clean format as Tools view
|
|
1091
1230
|
*/
|
|
1092
|
-
|
|
1093
|
-
|
|
1231
|
+
displayTodoWriteData(item) {
|
|
1232
|
+
const todos = item.params.todos || [];
|
|
1233
|
+
const timestamp = this.formatTimestamp(item.timestamp);
|
|
1234
|
+
|
|
1235
|
+
// Calculate status summary
|
|
1236
|
+
let completedCount = 0;
|
|
1237
|
+
let inProgressCount = 0;
|
|
1238
|
+
let pendingCount = 0;
|
|
1239
|
+
|
|
1240
|
+
todos.forEach(todo => {
|
|
1241
|
+
if (todo.status === 'completed') completedCount++;
|
|
1242
|
+
else if (todo.status === 'in_progress') inProgressCount++;
|
|
1243
|
+
else pendingCount++;
|
|
1244
|
+
});
|
|
1245
|
+
|
|
1246
|
+
let html = `
|
|
1247
|
+
<div class="unified-viewer-header">
|
|
1248
|
+
<h6>📝 TodoWrite: PM ${timestamp}</h6>
|
|
1249
|
+
<span class="unified-viewer-status">${this.formatStatus(item.status)}</span>
|
|
1250
|
+
</div>
|
|
1251
|
+
<div class="unified-viewer-content">
|
|
1252
|
+
`;
|
|
1253
|
+
|
|
1254
|
+
if (todos.length > 0) {
|
|
1255
|
+
// Status summary
|
|
1256
|
+
html += `
|
|
1257
|
+
<div class="detail-section">
|
|
1258
|
+
<span class="detail-section-title">Todo Summary</span>
|
|
1259
|
+
<div class="todo-summary">
|
|
1260
|
+
<div class="summary-item completed">
|
|
1261
|
+
<span class="summary-icon">✅</span>
|
|
1262
|
+
<span class="summary-count">${completedCount}</span>
|
|
1263
|
+
<span class="summary-label">Completed</span>
|
|
1264
|
+
</div>
|
|
1265
|
+
<div class="summary-item in_progress">
|
|
1266
|
+
<span class="summary-icon">🔄</span>
|
|
1267
|
+
<span class="summary-count">${inProgressCount}</span>
|
|
1268
|
+
<span class="summary-label">In Progress</span>
|
|
1269
|
+
</div>
|
|
1270
|
+
<div class="summary-item pending">
|
|
1271
|
+
<span class="summary-icon">⏳</span>
|
|
1272
|
+
<span class="summary-count">${pendingCount}</span>
|
|
1273
|
+
<span class="summary-label">Pending</span>
|
|
1274
|
+
</div>
|
|
1275
|
+
</div>
|
|
1276
|
+
</div>
|
|
1277
|
+
`;
|
|
1278
|
+
|
|
1279
|
+
// Todo list display (same as Tools view)
|
|
1280
|
+
html += `
|
|
1281
|
+
<div class="detail-section">
|
|
1282
|
+
<span class="detail-section-title">Todo List (${todos.length} items)</span>
|
|
1283
|
+
<div class="todo-checklist">
|
|
1284
|
+
`;
|
|
1285
|
+
|
|
1286
|
+
todos.forEach((todo, index) => {
|
|
1287
|
+
const statusIcon = this.getCheckboxIcon(todo.status);
|
|
1288
|
+
const displayText = todo.status === 'in_progress' ?
|
|
1289
|
+
(todo.activeForm || todo.content) : todo.content;
|
|
1290
|
+
const statusClass = this.formatStatusClass(todo.status);
|
|
1291
|
+
|
|
1292
|
+
html += `
|
|
1293
|
+
<div class="todo-checklist-item ${todo.status}">
|
|
1294
|
+
<div class="todo-checkbox">
|
|
1295
|
+
<span class="checkbox-icon ${statusClass}">${statusIcon}</span>
|
|
1296
|
+
</div>
|
|
1297
|
+
<div class="todo-text">
|
|
1298
|
+
<span class="todo-content">${this.escapeHtml(displayText)}</span>
|
|
1299
|
+
<span class="todo-status-badge ${statusClass}">${todo.status.replace('_', ' ')}</span>
|
|
1300
|
+
</div>
|
|
1301
|
+
</div>
|
|
1302
|
+
`;
|
|
1303
|
+
});
|
|
1304
|
+
|
|
1305
|
+
html += `
|
|
1306
|
+
</div>
|
|
1307
|
+
</div>
|
|
1308
|
+
`;
|
|
1309
|
+
} else {
|
|
1310
|
+
html += `
|
|
1311
|
+
<div class="detail-section">
|
|
1312
|
+
<div class="no-todos">No todo items found</div>
|
|
1313
|
+
</div>
|
|
1314
|
+
`;
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
// Add raw JSON section at the bottom
|
|
1318
|
+
html += `
|
|
1319
|
+
<div class="detail-section">
|
|
1320
|
+
<span class="detail-section-title">Parameters (${Object.keys(item.params).length})</span>
|
|
1321
|
+
<div class="params-list">
|
|
1322
|
+
<div class="param-item">
|
|
1323
|
+
<div class="param-key">todos:</div>
|
|
1324
|
+
<div class="param-value">
|
|
1325
|
+
<pre class="param-json">${this.escapeHtml(JSON.stringify(todos, null, 2))}</pre>
|
|
1326
|
+
</div>
|
|
1327
|
+
</div>
|
|
1328
|
+
</div>
|
|
1329
|
+
</div>
|
|
1330
|
+
`;
|
|
1094
1331
|
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1332
|
+
html += '</div>';
|
|
1333
|
+
|
|
1334
|
+
// Set the content directly
|
|
1335
|
+
const container = document.getElementById('module-data-content');
|
|
1336
|
+
if (container) {
|
|
1337
|
+
container.innerHTML = html;
|
|
1338
|
+
}
|
|
1098
1339
|
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
default: return true;
|
|
1340
|
+
// Update module header
|
|
1341
|
+
const moduleHeader = document.querySelector('.module-data-header h5');
|
|
1342
|
+
if (moduleHeader) {
|
|
1343
|
+
moduleHeader.textContent = `📝 tool: TodoWrite`;
|
|
1104
1344
|
}
|
|
1105
1345
|
}
|
|
1106
1346
|
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1347
|
+
// Utility methods for TodoWrite display
|
|
1348
|
+
formatStatus(status) {
|
|
1349
|
+
if (!status) return 'unknown';
|
|
1350
|
+
|
|
1351
|
+
const statusMap = {
|
|
1352
|
+
'active': '🟢 Active',
|
|
1353
|
+
'completed': '✅ Completed',
|
|
1354
|
+
'in_progress': '🔄 In Progress',
|
|
1355
|
+
'pending': '⏳ Pending',
|
|
1356
|
+
'error': '❌ Error',
|
|
1357
|
+
'failed': '❌ Failed'
|
|
1358
|
+
};
|
|
1112
1359
|
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1360
|
+
return statusMap[status] || status;
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
formatStatusClass(status) {
|
|
1364
|
+
return `status-${status}`;
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
formatTimestamp(timestamp) {
|
|
1368
|
+
if (!timestamp) return '';
|
|
1369
|
+
|
|
1370
|
+
try {
|
|
1371
|
+
const date = new Date(timestamp);
|
|
1372
|
+
if (isNaN(date.getTime())) return '';
|
|
1373
|
+
return date.toLocaleTimeString();
|
|
1374
|
+
} catch (error) {
|
|
1375
|
+
return '';
|
|
1118
1376
|
}
|
|
1119
1377
|
}
|
|
1120
1378
|
|
|
1121
1379
|
/**
|
|
1122
|
-
*
|
|
1380
|
+
* Display agent data in a simple format
|
|
1123
1381
|
*/
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1382
|
+
displayAgentData(agent) {
|
|
1383
|
+
const timestamp = this.formatTimestamp(agent.timestamp);
|
|
1384
|
+
const container = document.getElementById('module-data-content');
|
|
1385
|
+
if (!container) return;
|
|
1386
|
+
|
|
1387
|
+
let html = `
|
|
1388
|
+
<div class="detail-section">
|
|
1389
|
+
<span class="detail-section-title">Agent Information</span>
|
|
1390
|
+
<div class="agent-info">
|
|
1391
|
+
<div class="info-item">
|
|
1392
|
+
<span class="info-label">Name:</span>
|
|
1393
|
+
<span class="info-value">${this.escapeHtml(agent.name)}</span>
|
|
1394
|
+
</div>
|
|
1395
|
+
<div class="info-item">
|
|
1396
|
+
<span class="info-label">Status:</span>
|
|
1397
|
+
<span class="info-value status-${agent.status}">${agent.status}</span>
|
|
1398
|
+
</div>
|
|
1399
|
+
<div class="info-item">
|
|
1400
|
+
<span class="info-label">Timestamp:</span>
|
|
1401
|
+
<span class="info-value">${timestamp}</span>
|
|
1402
|
+
</div>
|
|
1403
|
+
<div class="info-item">
|
|
1404
|
+
<span class="info-label">Session ID:</span>
|
|
1405
|
+
<span class="info-value">${agent.sessionId || 'N/A'}</span>
|
|
1406
|
+
</div>
|
|
1407
|
+
</div>
|
|
1408
|
+
</div>
|
|
1409
|
+
`;
|
|
1410
|
+
|
|
1411
|
+
if (agent.tools && agent.tools.length > 0) {
|
|
1412
|
+
html += `
|
|
1413
|
+
<div class="detail-section">
|
|
1414
|
+
<span class="detail-section-title">Tools (${agent.tools.length})</span>
|
|
1415
|
+
<div class="tool-list">
|
|
1416
|
+
`;
|
|
1132
1417
|
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1418
|
+
agent.tools.forEach(tool => {
|
|
1419
|
+
html += `
|
|
1420
|
+
<div class="tool-item">
|
|
1421
|
+
<span class="tool-name">${this.escapeHtml(tool.name)}</span>
|
|
1422
|
+
<span class="tool-status status-${tool.status}">${tool.status}</span>
|
|
1423
|
+
</div>
|
|
1424
|
+
`;
|
|
1425
|
+
});
|
|
1426
|
+
|
|
1427
|
+
html += `
|
|
1428
|
+
</div>
|
|
1429
|
+
</div>
|
|
1430
|
+
`;
|
|
1137
1431
|
}
|
|
1138
|
-
|
|
1139
|
-
const nodeCount = this.countNodes(this.root);
|
|
1140
|
-
const activeCount = this.countActiveNodes(this.root.data);
|
|
1141
|
-
const depth = this.getTreeDepth(this.root);
|
|
1142
1432
|
|
|
1143
|
-
|
|
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}`);
|
|
1433
|
+
container.innerHTML = html;
|
|
1152
1434
|
}
|
|
1153
1435
|
|
|
1154
1436
|
/**
|
|
1155
|
-
*
|
|
1437
|
+
* Display tool data in a simple format
|
|
1156
1438
|
*/
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1439
|
+
displayToolData(tool) {
|
|
1440
|
+
const timestamp = this.formatTimestamp(tool.timestamp);
|
|
1441
|
+
const container = document.getElementById('module-data-content');
|
|
1442
|
+
if (!container) return;
|
|
1443
|
+
|
|
1444
|
+
let html = `
|
|
1445
|
+
<div class="detail-section">
|
|
1446
|
+
<span class="detail-section-title">Tool Information</span>
|
|
1447
|
+
<div class="tool-info">
|
|
1448
|
+
<div class="info-item">
|
|
1449
|
+
<span class="info-label">Name:</span>
|
|
1450
|
+
<span class="info-value">${this.escapeHtml(tool.name)}</span>
|
|
1451
|
+
</div>
|
|
1452
|
+
<div class="info-item">
|
|
1453
|
+
<span class="info-label">Status:</span>
|
|
1454
|
+
<span class="info-value status-${tool.status}">${tool.status}</span>
|
|
1455
|
+
</div>
|
|
1456
|
+
<div class="info-item">
|
|
1457
|
+
<span class="info-label">Timestamp:</span>
|
|
1458
|
+
<span class="info-value">${timestamp}</span>
|
|
1459
|
+
</div>
|
|
1460
|
+
</div>
|
|
1461
|
+
</div>
|
|
1462
|
+
`;
|
|
1463
|
+
|
|
1464
|
+
if (tool.params && Object.keys(tool.params).length > 0) {
|
|
1465
|
+
html += `
|
|
1466
|
+
<div class="detail-section">
|
|
1467
|
+
<span class="detail-section-title">Parameters</span>
|
|
1468
|
+
<div class="params-list">
|
|
1469
|
+
`;
|
|
1470
|
+
|
|
1471
|
+
Object.entries(tool.params).forEach(([key, value]) => {
|
|
1472
|
+
html += `
|
|
1473
|
+
<div class="param-item">
|
|
1474
|
+
<div class="param-key">${this.escapeHtml(key)}:</div>
|
|
1475
|
+
<div class="param-value">${this.escapeHtml(String(value))}</div>
|
|
1476
|
+
</div>
|
|
1477
|
+
`;
|
|
1167
1478
|
});
|
|
1479
|
+
|
|
1480
|
+
html += `
|
|
1481
|
+
</div>
|
|
1482
|
+
</div>
|
|
1483
|
+
`;
|
|
1168
1484
|
}
|
|
1169
|
-
|
|
1485
|
+
|
|
1486
|
+
container.innerHTML = html;
|
|
1170
1487
|
}
|
|
1171
1488
|
|
|
1172
1489
|
/**
|
|
1173
|
-
*
|
|
1490
|
+
* Display instruction data in a simple format
|
|
1174
1491
|
*/
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1492
|
+
displayInstructionData(instruction) {
|
|
1493
|
+
const timestamp = this.formatTimestamp(instruction.timestamp);
|
|
1494
|
+
const container = document.getElementById('module-data-content');
|
|
1495
|
+
if (!container) return;
|
|
1496
|
+
|
|
1497
|
+
const html = `
|
|
1498
|
+
<div class="detail-section">
|
|
1499
|
+
<span class="detail-section-title">User Instruction</span>
|
|
1500
|
+
<div class="instruction-info">
|
|
1501
|
+
<div class="info-item">
|
|
1502
|
+
<span class="info-label">Timestamp:</span>
|
|
1503
|
+
<span class="info-value">${timestamp}</span>
|
|
1504
|
+
</div>
|
|
1505
|
+
<div class="instruction-content">
|
|
1506
|
+
<div class="instruction-text">${this.escapeHtml(instruction.text)}</div>
|
|
1507
|
+
</div>
|
|
1508
|
+
</div>
|
|
1509
|
+
</div>
|
|
1510
|
+
`;
|
|
1511
|
+
|
|
1512
|
+
container.innerHTML = html;
|
|
1188
1513
|
}
|
|
1189
1514
|
|
|
1190
1515
|
/**
|
|
1191
|
-
*
|
|
1516
|
+
* Display generic data for unknown types
|
|
1192
1517
|
*/
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1518
|
+
displayGenericData(item, itemType) {
|
|
1519
|
+
const container = document.getElementById('module-data-content');
|
|
1520
|
+
if (!container) return;
|
|
1521
|
+
|
|
1522
|
+
let html = `
|
|
1523
|
+
<div class="detail-section">
|
|
1524
|
+
<span class="detail-section-title">${itemType || 'Item'} Data</span>
|
|
1525
|
+
<div class="generic-data">
|
|
1526
|
+
<pre>${this.escapeHtml(JSON.stringify(item, null, 2))}</pre>
|
|
1527
|
+
</div>
|
|
1528
|
+
</div>
|
|
1529
|
+
`;
|
|
1530
|
+
|
|
1531
|
+
container.innerHTML = html;
|
|
1199
1532
|
}
|
|
1200
1533
|
|
|
1201
1534
|
/**
|
|
1202
|
-
*
|
|
1535
|
+
* Escape HTML for safe display
|
|
1203
1536
|
*/
|
|
1204
|
-
|
|
1205
|
-
const
|
|
1206
|
-
|
|
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
|
-
}
|
|
1537
|
+
escapeHtml(text) {
|
|
1538
|
+
const div = document.createElement('div');
|
|
1539
|
+
div.textContent = text;
|
|
1540
|
+
return div.innerHTML;
|
|
1217
1541
|
}
|
|
1218
1542
|
|
|
1219
1543
|
/**
|
|
1220
|
-
*
|
|
1544
|
+
* Reset zoom and pan to initial state
|
|
1221
1545
|
*/
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
.style('fill', d => {
|
|
1236
|
-
return d.data.name.toLowerCase().includes(this.searchTerm) ? '#e53e3e' : '#2d3748';
|
|
1237
|
-
});
|
|
1546
|
+
resetZoom() {
|
|
1547
|
+
if (this.svg && this.zoom) {
|
|
1548
|
+
this.svg.transition()
|
|
1549
|
+
.duration(this.duration)
|
|
1550
|
+
.call(this.zoom.transform, d3.zoomIdentity);
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
/**
|
|
1555
|
+
* Escape JSON for safe inclusion in HTML attributes
|
|
1556
|
+
*/
|
|
1557
|
+
escapeJson(obj) {
|
|
1558
|
+
return JSON.stringify(obj).replace(/'/g, ''').replace(/"/g, '"');
|
|
1238
1559
|
}
|
|
1239
1560
|
}
|
|
1240
1561
|
|
|
1241
|
-
// Make ActivityTree globally available
|
|
1562
|
+
// Make ActivityTree globally available
|
|
1242
1563
|
window.ActivityTree = ActivityTree;
|
|
1243
1564
|
|
|
1244
1565
|
// Initialize when the Activity tab is selected
|
|
1245
|
-
// Only set up event listeners when DOM is ready, but expose class immediately
|
|
1246
1566
|
const setupActivityTreeListeners = () => {
|
|
1247
1567
|
let activityTree = null;
|
|
1248
1568
|
|
|
1249
|
-
// Function to initialize the tree
|
|
1250
1569
|
const initializeActivityTree = () => {
|
|
1251
1570
|
if (!activityTree) {
|
|
1252
1571
|
console.log('Creating new Activity Tree instance...');
|
|
1253
1572
|
activityTree = new ActivityTree();
|
|
1254
|
-
// Store instance globally for dashboard access
|
|
1255
1573
|
window.activityTreeInstance = activityTree;
|
|
1574
|
+
window.activityTree = () => activityTree; // For debugging
|
|
1256
1575
|
}
|
|
1257
1576
|
|
|
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
1577
|
setTimeout(() => {
|
|
1268
1578
|
console.log('Attempting to initialize Activity Tree visualization...');
|
|
1269
1579
|
activityTree.initialize();
|
|
@@ -1278,11 +1588,9 @@ const setupActivityTreeListeners = () => {
|
|
|
1278
1588
|
if (tabName === 'activity') {
|
|
1279
1589
|
console.log('Activity tab button clicked, initializing tree...');
|
|
1280
1590
|
initializeActivityTree();
|
|
1281
|
-
// Also call renderWhenVisible and forceShow to ensure proper rendering
|
|
1282
1591
|
if (activityTree) {
|
|
1283
1592
|
setTimeout(() => {
|
|
1284
1593
|
activityTree.renderWhenVisible();
|
|
1285
|
-
// Force show to ensure SVG is visible
|
|
1286
1594
|
activityTree.forceShow();
|
|
1287
1595
|
}, 150);
|
|
1288
1596
|
}
|
|
@@ -1290,16 +1598,14 @@ const setupActivityTreeListeners = () => {
|
|
|
1290
1598
|
});
|
|
1291
1599
|
});
|
|
1292
1600
|
|
|
1293
|
-
//
|
|
1601
|
+
// Listen for custom tab change events
|
|
1294
1602
|
document.addEventListener('tabChanged', (e) => {
|
|
1295
1603
|
if (e.detail && e.detail.newTab === 'activity') {
|
|
1296
1604
|
console.log('Tab changed to activity, initializing tree...');
|
|
1297
1605
|
initializeActivityTree();
|
|
1298
|
-
// Also call renderWhenVisible and forceShow to ensure proper rendering
|
|
1299
1606
|
if (activityTree) {
|
|
1300
1607
|
setTimeout(() => {
|
|
1301
1608
|
activityTree.renderWhenVisible();
|
|
1302
|
-
// Force show to ensure SVG is visible
|
|
1303
1609
|
activityTree.forceShow();
|
|
1304
1610
|
}, 150);
|
|
1305
1611
|
}
|
|
@@ -1313,7 +1619,6 @@ const setupActivityTreeListeners = () => {
|
|
|
1313
1619
|
initializeActivityTree();
|
|
1314
1620
|
}
|
|
1315
1621
|
|
|
1316
|
-
// Also check the tab panel directly
|
|
1317
1622
|
const activityPanel = document.getElementById('activity-tab');
|
|
1318
1623
|
if (activityPanel && activityPanel.classList.contains('active')) {
|
|
1319
1624
|
console.log('Activity panel is active on load, initializing tree...');
|
|
@@ -1321,16 +1626,12 @@ const setupActivityTreeListeners = () => {
|
|
|
1321
1626
|
initializeActivityTree();
|
|
1322
1627
|
}
|
|
1323
1628
|
}
|
|
1324
|
-
|
|
1325
|
-
// Export for debugging
|
|
1326
|
-
window.activityTree = () => activityTree; // Expose instance getter for debugging
|
|
1327
1629
|
};
|
|
1328
1630
|
|
|
1329
1631
|
// Set up listeners when DOM is ready
|
|
1330
1632
|
if (document.readyState === 'loading') {
|
|
1331
1633
|
document.addEventListener('DOMContentLoaded', setupActivityTreeListeners);
|
|
1332
1634
|
} else {
|
|
1333
|
-
// DOM already loaded
|
|
1334
1635
|
setupActivityTreeListeners();
|
|
1335
1636
|
}
|
|
1336
1637
|
|