claude-mpm 5.1.8__py3-none-any.whl → 5.4.22__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.
Potentially problematic release.
This version of claude-mpm might be problematic. Click here for more details.
- claude_mpm/VERSION +1 -1
- claude_mpm/__init__.py +4 -0
- claude_mpm/agents/{PM_INSTRUCTIONS_TEACH.md → CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md} +721 -41
- claude_mpm/agents/PM_INSTRUCTIONS.md +290 -34
- claude_mpm/agents/agent_loader.py +13 -44
- claude_mpm/agents/frontmatter_validator.py +68 -0
- claude_mpm/agents/templates/circuit-breakers.md +138 -1
- claude_mpm/cli/__main__.py +4 -0
- claude_mpm/cli/chrome_devtools_installer.py +175 -0
- claude_mpm/cli/commands/agent_state_manager.py +8 -17
- claude_mpm/cli/commands/agents.py +169 -31
- claude_mpm/cli/commands/auto_configure.py +210 -25
- claude_mpm/cli/commands/config.py +88 -2
- claude_mpm/cli/commands/configure.py +1111 -161
- claude_mpm/cli/commands/configure_agent_display.py +15 -6
- claude_mpm/cli/commands/mpm_init/core.py +160 -46
- claude_mpm/cli/commands/mpm_init/knowledge_extractor.py +481 -0
- claude_mpm/cli/commands/mpm_init/prompts.py +280 -0
- claude_mpm/cli/commands/skills.py +214 -189
- claude_mpm/cli/commands/summarize.py +413 -0
- claude_mpm/cli/executor.py +11 -3
- claude_mpm/cli/parsers/agents_parser.py +54 -9
- claude_mpm/cli/parsers/auto_configure_parser.py +0 -138
- claude_mpm/cli/parsers/base_parser.py +5 -0
- claude_mpm/cli/parsers/config_parser.py +153 -83
- claude_mpm/cli/parsers/skills_parser.py +3 -2
- claude_mpm/cli/startup.py +550 -94
- claude_mpm/commands/mpm-config.md +265 -0
- claude_mpm/commands/mpm-help.md +14 -95
- claude_mpm/commands/mpm-organize.md +500 -0
- claude_mpm/config/agent_sources.py +27 -0
- claude_mpm/core/framework/formatters/content_formatter.py +3 -13
- claude_mpm/core/framework/loaders/agent_loader.py +8 -5
- claude_mpm/core/framework_loader.py +4 -2
- claude_mpm/core/logger.py +13 -0
- claude_mpm/core/output_style_manager.py +173 -43
- claude_mpm/core/socketio_pool.py +3 -3
- claude_mpm/core/unified_agent_registry.py +134 -16
- claude_mpm/hooks/claude_hooks/correlation_manager.py +60 -0
- claude_mpm/hooks/claude_hooks/event_handlers.py +211 -78
- claude_mpm/hooks/claude_hooks/hook_handler.py +6 -0
- claude_mpm/hooks/claude_hooks/installer.py +33 -10
- claude_mpm/hooks/claude_hooks/memory_integration.py +26 -9
- claude_mpm/hooks/claude_hooks/response_tracking.py +2 -3
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +4 -0
- claude_mpm/hooks/memory_integration_hook.py +46 -1
- claude_mpm/init.py +0 -19
- claude_mpm/models/agent_definition.py +7 -0
- claude_mpm/scripts/claude-hook-handler.sh +58 -18
- claude_mpm/scripts/launch_monitor.py +93 -13
- claude_mpm/scripts/start_activity_logging.py +0 -0
- claude_mpm/services/agents/agent_recommendation_service.py +278 -0
- claude_mpm/services/agents/agent_review_service.py +280 -0
- claude_mpm/services/agents/deployment/agent_discovery_service.py +2 -3
- claude_mpm/services/agents/deployment/agent_template_builder.py +4 -2
- claude_mpm/services/agents/deployment/multi_source_deployment_service.py +188 -12
- claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +531 -55
- claude_mpm/services/agents/git_source_manager.py +34 -0
- claude_mpm/services/agents/loading/base_agent_manager.py +1 -13
- claude_mpm/services/agents/sources/git_source_sync_service.py +8 -1
- claude_mpm/services/agents/toolchain_detector.py +10 -6
- claude_mpm/services/analysis/__init__.py +11 -1
- claude_mpm/services/analysis/clone_detector.py +1030 -0
- claude_mpm/services/command_deployment_service.py +81 -10
- claude_mpm/services/event_bus/config.py +3 -1
- claude_mpm/services/git/git_operations_service.py +93 -8
- claude_mpm/services/monitor/daemon.py +9 -2
- claude_mpm/services/monitor/daemon_manager.py +39 -3
- claude_mpm/services/monitor/server.py +225 -19
- claude_mpm/services/self_upgrade_service.py +120 -12
- claude_mpm/services/skills/__init__.py +3 -0
- claude_mpm/services/skills/git_skill_source_manager.py +32 -2
- claude_mpm/services/skills/selective_skill_deployer.py +704 -0
- claude_mpm/services/skills/skill_to_agent_mapper.py +406 -0
- claude_mpm/services/skills_deployer.py +126 -9
- claude_mpm/services/socketio/event_normalizer.py +15 -1
- claude_mpm/services/socketio/server/core.py +160 -21
- claude_mpm/services/version_control/git_operations.py +103 -0
- claude_mpm/utils/agent_filters.py +17 -44
- {claude_mpm-5.1.8.dist-info → claude_mpm-5.4.22.dist-info}/METADATA +47 -84
- {claude_mpm-5.1.8.dist-info → claude_mpm-5.4.22.dist-info}/RECORD +86 -176
- claude_mpm-5.4.22.dist-info/entry_points.txt +5 -0
- claude_mpm-5.4.22.dist-info/licenses/LICENSE +94 -0
- claude_mpm-5.4.22.dist-info/licenses/LICENSE-FAQ.md +153 -0
- claude_mpm/agents/BASE_AGENT_TEMPLATE.md +0 -292
- claude_mpm/agents/BASE_DOCUMENTATION.md +0 -53
- claude_mpm/agents/BASE_ENGINEER.md +0 -658
- claude_mpm/agents/BASE_OPS.md +0 -219
- claude_mpm/agents/BASE_PM.md +0 -480
- claude_mpm/agents/BASE_PROMPT_ENGINEER.md +0 -787
- claude_mpm/agents/BASE_QA.md +0 -167
- claude_mpm/agents/BASE_RESEARCH.md +0 -53
- claude_mpm/agents/base_agent.json +0 -31
- claude_mpm/agents/base_agent_loader.py +0 -601
- claude_mpm/cli/commands/agents_detect.py +0 -380
- claude_mpm/cli/commands/agents_recommend.py +0 -309
- claude_mpm/cli/ticket_cli.py +0 -35
- claude_mpm/commands/mpm-agents-auto-configure.md +0 -278
- claude_mpm/commands/mpm-agents-detect.md +0 -177
- claude_mpm/commands/mpm-agents-list.md +0 -131
- claude_mpm/commands/mpm-agents-recommend.md +0 -223
- claude_mpm/commands/mpm-config-view.md +0 -150
- claude_mpm/commands/mpm-ticket-organize.md +0 -304
- claude_mpm/dashboard/analysis_runner.py +0 -455
- claude_mpm/dashboard/index.html +0 -13
- claude_mpm/dashboard/open_dashboard.py +0 -66
- claude_mpm/dashboard/static/css/activity.css +0 -1958
- claude_mpm/dashboard/static/css/connection-status.css +0 -370
- claude_mpm/dashboard/static/css/dashboard.css +0 -4701
- claude_mpm/dashboard/static/js/components/activity-tree.js +0 -1871
- claude_mpm/dashboard/static/js/components/agent-hierarchy.js +0 -777
- claude_mpm/dashboard/static/js/components/agent-inference.js +0 -956
- claude_mpm/dashboard/static/js/components/build-tracker.js +0 -333
- claude_mpm/dashboard/static/js/components/code-simple.js +0 -857
- claude_mpm/dashboard/static/js/components/connection-debug.js +0 -654
- claude_mpm/dashboard/static/js/components/diff-viewer.js +0 -891
- claude_mpm/dashboard/static/js/components/event-processor.js +0 -542
- claude_mpm/dashboard/static/js/components/event-viewer.js +0 -1155
- claude_mpm/dashboard/static/js/components/export-manager.js +0 -368
- claude_mpm/dashboard/static/js/components/file-change-tracker.js +0 -443
- claude_mpm/dashboard/static/js/components/file-change-viewer.js +0 -690
- claude_mpm/dashboard/static/js/components/file-tool-tracker.js +0 -724
- claude_mpm/dashboard/static/js/components/file-viewer.js +0 -580
- claude_mpm/dashboard/static/js/components/hud-library-loader.js +0 -211
- claude_mpm/dashboard/static/js/components/hud-manager.js +0 -671
- claude_mpm/dashboard/static/js/components/hud-visualizer.js +0 -1718
- claude_mpm/dashboard/static/js/components/module-viewer.js +0 -2764
- claude_mpm/dashboard/static/js/components/session-manager.js +0 -579
- claude_mpm/dashboard/static/js/components/socket-manager.js +0 -368
- claude_mpm/dashboard/static/js/components/ui-state-manager.js +0 -749
- claude_mpm/dashboard/static/js/components/unified-data-viewer.js +0 -1824
- claude_mpm/dashboard/static/js/components/working-directory.js +0 -920
- claude_mpm/dashboard/static/js/connection-manager.js +0 -536
- claude_mpm/dashboard/static/js/dashboard.js +0 -1914
- claude_mpm/dashboard/static/js/extension-error-handler.js +0 -164
- claude_mpm/dashboard/static/js/socket-client.js +0 -1474
- claude_mpm/dashboard/static/js/tab-isolation-fix.js +0 -185
- claude_mpm/dashboard/static/socket.io.min.js +0 -7
- claude_mpm/dashboard/static/socket.io.v4.8.1.backup.js +0 -7
- claude_mpm/dashboard/templates/code_simple.html +0 -153
- claude_mpm/dashboard/templates/index.html +0 -606
- claude_mpm/dashboard/test_dashboard.html +0 -372
- claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-313.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-313.pyc +0 -0
- claude_mpm/scripts/mcp_server.py +0 -75
- claude_mpm/scripts/mcp_wrapper.py +0 -39
- claude_mpm/services/mcp_gateway/__init__.py +0 -159
- claude_mpm/services/mcp_gateway/auto_configure.py +0 -369
- claude_mpm/services/mcp_gateway/config/__init__.py +0 -17
- claude_mpm/services/mcp_gateway/config/config_loader.py +0 -296
- claude_mpm/services/mcp_gateway/config/config_schema.py +0 -243
- claude_mpm/services/mcp_gateway/config/configuration.py +0 -429
- claude_mpm/services/mcp_gateway/core/__init__.py +0 -43
- claude_mpm/services/mcp_gateway/core/base.py +0 -312
- claude_mpm/services/mcp_gateway/core/exceptions.py +0 -253
- claude_mpm/services/mcp_gateway/core/interfaces.py +0 -443
- claude_mpm/services/mcp_gateway/core/process_pool.py +0 -977
- claude_mpm/services/mcp_gateway/core/singleton_manager.py +0 -315
- claude_mpm/services/mcp_gateway/core/startup_verification.py +0 -316
- claude_mpm/services/mcp_gateway/main.py +0 -589
- claude_mpm/services/mcp_gateway/registry/__init__.py +0 -12
- claude_mpm/services/mcp_gateway/registry/service_registry.py +0 -412
- claude_mpm/services/mcp_gateway/registry/tool_registry.py +0 -489
- claude_mpm/services/mcp_gateway/server/__init__.py +0 -15
- claude_mpm/services/mcp_gateway/server/mcp_gateway.py +0 -414
- claude_mpm/services/mcp_gateway/server/stdio_handler.py +0 -372
- claude_mpm/services/mcp_gateway/server/stdio_server.py +0 -712
- claude_mpm/services/mcp_gateway/tools/__init__.py +0 -36
- claude_mpm/services/mcp_gateway/tools/base_adapter.py +0 -485
- claude_mpm/services/mcp_gateway/tools/document_summarizer.py +0 -789
- claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +0 -654
- claude_mpm/services/mcp_gateway/tools/health_check_tool.py +0 -456
- claude_mpm/services/mcp_gateway/tools/hello_world.py +0 -551
- claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +0 -555
- claude_mpm/services/mcp_gateway/utils/__init__.py +0 -14
- claude_mpm/services/mcp_gateway/utils/package_version_checker.py +0 -160
- claude_mpm/services/mcp_gateway/utils/update_preferences.py +0 -170
- claude_mpm-5.1.8.dist-info/entry_points.txt +0 -10
- claude_mpm-5.1.8.dist-info/licenses/LICENSE +0 -21
- /claude_mpm/agents/{OUTPUT_STYLE.md → CLAUDE_MPM_OUTPUT_STYLE.md} +0 -0
- {claude_mpm-5.1.8.dist-info → claude_mpm-5.4.22.dist-info}/WHEEL +0 -0
- {claude_mpm-5.1.8.dist-info → claude_mpm-5.4.22.dist-info}/top_level.txt +0 -0
|
@@ -1,1871 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Activity Tree Component - Linear Tree View
|
|
3
|
-
*
|
|
4
|
-
* HTML/CSS-based linear tree visualization for showing PM activity hierarchy.
|
|
5
|
-
* Replaces D3.js with simpler, cleaner linear tree structure.
|
|
6
|
-
* Uses UnifiedDataViewer for consistent data display with Tools viewer.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
// Import UnifiedDataViewer for consistent data display
|
|
10
|
-
import { UnifiedDataViewer } from './unified-data-viewer.js';
|
|
11
|
-
|
|
12
|
-
class ActivityTree {
|
|
13
|
-
constructor() {
|
|
14
|
-
this.container = null;
|
|
15
|
-
this.events = [];
|
|
16
|
-
this.processedEventIds = new Set(); // Track which events we've already processed
|
|
17
|
-
this.sessions = new Map();
|
|
18
|
-
this.currentSession = null;
|
|
19
|
-
this.selectedSessionFilter = 'all';
|
|
20
|
-
this.timeRange = '30min';
|
|
21
|
-
this.searchTerm = '';
|
|
22
|
-
this.initialized = false;
|
|
23
|
-
this.expandedSessions = new Set();
|
|
24
|
-
this.expandedAgents = new Set();
|
|
25
|
-
this.expandedTools = new Set();
|
|
26
|
-
this.selectedItem = null;
|
|
27
|
-
this.sessionFilterInitialized = false; // Flag to prevent initialization loop
|
|
28
|
-
|
|
29
|
-
// Add debounce for renderTree to prevent excessive DOM rebuilds
|
|
30
|
-
this.renderTreeDebounced = this.debounce(() => this.renderTree(), 100);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Debounce helper to prevent excessive DOM updates
|
|
35
|
-
*/
|
|
36
|
-
debounce(func, wait) {
|
|
37
|
-
let timeout;
|
|
38
|
-
return function executedFunction(...args) {
|
|
39
|
-
const later = () => {
|
|
40
|
-
clearTimeout(timeout);
|
|
41
|
-
func(...args);
|
|
42
|
-
};
|
|
43
|
-
clearTimeout(timeout);
|
|
44
|
-
timeout = setTimeout(later, wait);
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Initialize the activity tree
|
|
50
|
-
*/
|
|
51
|
-
initialize() {
|
|
52
|
-
console.log('ActivityTree.initialize() called, initialized:', this.initialized);
|
|
53
|
-
|
|
54
|
-
if (this.initialized) {
|
|
55
|
-
console.log('Activity tree already initialized, skipping');
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
this.container = document.getElementById('activity-tree-container');
|
|
60
|
-
if (!this.container) {
|
|
61
|
-
this.container = document.getElementById('activity-tree');
|
|
62
|
-
if (!this.container) {
|
|
63
|
-
console.error('Activity tree container not found in DOM');
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Check if the container is visible before initializing
|
|
69
|
-
const tabPanel = document.getElementById('activity-tab');
|
|
70
|
-
if (!tabPanel) {
|
|
71
|
-
console.error('Activity tab panel (#activity-tab) not found in DOM');
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Initialize even if tab is not active
|
|
76
|
-
if (!tabPanel.classList.contains('active')) {
|
|
77
|
-
console.log('Activity tab not active, initializing but deferring render');
|
|
78
|
-
this.setupControls();
|
|
79
|
-
this.subscribeToEvents();
|
|
80
|
-
this.initialized = true;
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
this.setupControls();
|
|
85
|
-
this.createLinearTreeView();
|
|
86
|
-
this.subscribeToEvents();
|
|
87
|
-
|
|
88
|
-
this.initialized = true;
|
|
89
|
-
console.log('Activity tree initialization complete');
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Force show the tree visualization
|
|
94
|
-
*/
|
|
95
|
-
forceShow() {
|
|
96
|
-
console.log('ActivityTree.forceShow() called');
|
|
97
|
-
|
|
98
|
-
if (!this.container) {
|
|
99
|
-
this.container = document.getElementById('activity-tree-container') || document.getElementById('activity-tree');
|
|
100
|
-
if (!this.container) {
|
|
101
|
-
console.error('Cannot find activity tree container');
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
this.createLinearTreeView();
|
|
107
|
-
this.renderTree();
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Render the visualization when tab becomes visible
|
|
112
|
-
*/
|
|
113
|
-
renderWhenVisible() {
|
|
114
|
-
console.log('ActivityTree.renderWhenVisible() called');
|
|
115
|
-
|
|
116
|
-
if (!this.initialized) {
|
|
117
|
-
console.log('Not initialized yet, calling initialize...');
|
|
118
|
-
this.initialize();
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
this.createLinearTreeView();
|
|
123
|
-
this.renderTree();
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Setup control handlers
|
|
128
|
-
*/
|
|
129
|
-
setupControls() {
|
|
130
|
-
// Time range filter dropdown
|
|
131
|
-
const timeRangeSelect = document.getElementById('time-range');
|
|
132
|
-
if (timeRangeSelect) {
|
|
133
|
-
timeRangeSelect.addEventListener('change', (e) => {
|
|
134
|
-
this.timeRange = e.target.value;
|
|
135
|
-
console.log(`ActivityTree: Time range changed to: ${this.timeRange}`);
|
|
136
|
-
this.renderTree();
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Listen for session filter changes from SessionManager
|
|
141
|
-
document.addEventListener('sessionFilterChanged', (e) => {
|
|
142
|
-
this.selectedSessionFilter = e.detail.sessionId || 'all';
|
|
143
|
-
console.log(`ActivityTree: Session filter changed to: ${this.selectedSessionFilter} (from SessionManager)`);
|
|
144
|
-
this.renderTree();
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
// Also listen for sessionChanged for backward compatibility
|
|
148
|
-
document.addEventListener('sessionChanged', (e) => {
|
|
149
|
-
this.selectedSessionFilter = e.detail.sessionId || 'all';
|
|
150
|
-
console.log(`ActivityTree: Session changed to: ${this.selectedSessionFilter} (from SessionManager - backward compat)`);
|
|
151
|
-
this.renderTree();
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
// Initialize with current session filter from SessionManager (prevent loop)
|
|
155
|
-
setTimeout(() => {
|
|
156
|
-
if (window.sessionManager && !this.sessionFilterInitialized) {
|
|
157
|
-
const currentFilter = window.sessionManager.getCurrentFilter();
|
|
158
|
-
if (currentFilter !== this.selectedSessionFilter) {
|
|
159
|
-
this.selectedSessionFilter = currentFilter || 'all';
|
|
160
|
-
console.log(`ActivityTree: Initialized with current session filter: ${this.selectedSessionFilter}`);
|
|
161
|
-
this.sessionFilterInitialized = true; // Prevent re-initialization
|
|
162
|
-
this.renderTree();
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
}, 100); // Small delay to ensure SessionManager is initialized
|
|
166
|
-
|
|
167
|
-
// Expand all button - expand all sessions
|
|
168
|
-
const expandAllBtn = document.getElementById('expand-all');
|
|
169
|
-
if (expandAllBtn) {
|
|
170
|
-
expandAllBtn.addEventListener('click', () => this.expandAllSessions());
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// Collapse all button - collapse all sessions
|
|
174
|
-
const collapseAllBtn = document.getElementById('collapse-all');
|
|
175
|
-
if (collapseAllBtn) {
|
|
176
|
-
collapseAllBtn.addEventListener('click', () => this.collapseAllSessions());
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// Reset zoom button functionality
|
|
180
|
-
const resetZoomBtn = document.getElementById('reset-zoom');
|
|
181
|
-
if (resetZoomBtn) {
|
|
182
|
-
resetZoomBtn.style.display = 'inline-block';
|
|
183
|
-
resetZoomBtn.addEventListener('click', () => this.resetZoom());
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// Search input
|
|
187
|
-
const searchInput = document.getElementById('activity-search');
|
|
188
|
-
if (searchInput) {
|
|
189
|
-
searchInput.addEventListener('input', (e) => {
|
|
190
|
-
this.searchTerm = e.target.value.toLowerCase();
|
|
191
|
-
this.renderTree();
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* Create the linear tree view container
|
|
198
|
-
*/
|
|
199
|
-
createLinearTreeView() {
|
|
200
|
-
console.log('Creating linear tree view');
|
|
201
|
-
|
|
202
|
-
// Clear container
|
|
203
|
-
this.container.innerHTML = '';
|
|
204
|
-
|
|
205
|
-
// Create main tree container
|
|
206
|
-
const treeContainer = document.createElement('div');
|
|
207
|
-
treeContainer.id = 'linear-tree';
|
|
208
|
-
treeContainer.className = 'linear-tree';
|
|
209
|
-
|
|
210
|
-
this.container.appendChild(treeContainer);
|
|
211
|
-
|
|
212
|
-
console.log('Linear tree view created');
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
/**
|
|
216
|
-
* Subscribe to socket events
|
|
217
|
-
*/
|
|
218
|
-
subscribeToEvents() {
|
|
219
|
-
if (!window.socketClient) {
|
|
220
|
-
console.warn('Socket client not available for activity tree');
|
|
221
|
-
setTimeout(() => this.subscribeToEvents(), 1000);
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
console.log('ActivityTree: Setting up event subscription');
|
|
226
|
-
|
|
227
|
-
// Subscribe to event updates from the socket client
|
|
228
|
-
// FIXED: Now correctly receives both events AND sessions from socket client
|
|
229
|
-
window.socketClient.onEventUpdate((events, sessions) => {
|
|
230
|
-
console.log(`ActivityTree: onEventUpdate called with ${events.length} total events and ${sessions.size} sessions`);
|
|
231
|
-
|
|
232
|
-
// IMPORTANT: Don't clear sessions! We need to preserve the accumulated agent data
|
|
233
|
-
// Only create new sessions if they don't exist yet
|
|
234
|
-
for (const [sessionId, sessionData] of sessions.entries()) {
|
|
235
|
-
if (!this.sessions.has(sessionId)) {
|
|
236
|
-
// Create new session only if it doesn't exist
|
|
237
|
-
const activitySession = {
|
|
238
|
-
id: sessionId,
|
|
239
|
-
timestamp: new Date(sessionData.lastActivity || sessionData.startTime || new Date()),
|
|
240
|
-
expanded: this.expandedSessions.has(sessionId) || true, // Preserve expansion state
|
|
241
|
-
agents: new Map(),
|
|
242
|
-
todos: [],
|
|
243
|
-
userInstructions: [],
|
|
244
|
-
tools: [],
|
|
245
|
-
toolsMap: new Map(),
|
|
246
|
-
status: 'active',
|
|
247
|
-
currentTodoTool: null,
|
|
248
|
-
// Preserve additional session metadata
|
|
249
|
-
working_directory: sessionData.working_directory,
|
|
250
|
-
git_branch: sessionData.git_branch,
|
|
251
|
-
eventCount: sessionData.eventCount
|
|
252
|
-
};
|
|
253
|
-
this.sessions.set(sessionId, activitySession);
|
|
254
|
-
} else {
|
|
255
|
-
// Update existing session metadata without clearing accumulated data
|
|
256
|
-
// CRITICAL: Preserve all accumulated data (tools, agents, todos, etc.)
|
|
257
|
-
const existingSession = this.sessions.get(sessionId);
|
|
258
|
-
existingSession.timestamp = new Date(sessionData.lastActivity || sessionData.startTime || existingSession.timestamp);
|
|
259
|
-
existingSession.eventCount = sessionData.eventCount;
|
|
260
|
-
existingSession.status = sessionData.status || existingSession.status;
|
|
261
|
-
// Update metadata without losing accumulated data
|
|
262
|
-
existingSession.working_directory = sessionData.working_directory || existingSession.working_directory;
|
|
263
|
-
existingSession.git_branch = sessionData.git_branch || existingSession.git_branch;
|
|
264
|
-
// DO NOT reset tools, agents, todos, userInstructions, toolsMap, etc.
|
|
265
|
-
// These are built up from events and must be preserved!
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// Process only events we haven't seen before
|
|
270
|
-
const newEvents = events.filter(event => {
|
|
271
|
-
const eventId = event.id || `${event.type}-${event.timestamp}-${Math.random()}`;
|
|
272
|
-
return !this.processedEventIds.has(eventId);
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
if (newEvents.length > 0) {
|
|
276
|
-
console.log(`ActivityTree: Processing ${newEvents.length} new events`, newEvents);
|
|
277
|
-
|
|
278
|
-
newEvents.forEach(event => {
|
|
279
|
-
const eventId = event.id || `${event.type}-${event.timestamp}-${Math.random()}`;
|
|
280
|
-
this.processedEventIds.add(eventId);
|
|
281
|
-
this.processEvent(event);
|
|
282
|
-
});
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
this.events = [...events];
|
|
286
|
-
// Use debounced render to prevent excessive DOM rebuilds
|
|
287
|
-
this.renderTreeDebounced();
|
|
288
|
-
|
|
289
|
-
// Debug: Log session state after processing
|
|
290
|
-
console.log(`ActivityTree: Sessions after sync with socket client:`, Array.from(this.sessions.entries()));
|
|
291
|
-
});
|
|
292
|
-
|
|
293
|
-
// Load existing data from socket client
|
|
294
|
-
const socketState = window.socketClient?.getState();
|
|
295
|
-
|
|
296
|
-
if (socketState && socketState.events.length > 0) {
|
|
297
|
-
console.log(`ActivityTree: Loading existing data - ${socketState.events.length} events, ${socketState.sessions.size} sessions`);
|
|
298
|
-
|
|
299
|
-
// Initialize from existing socket client data
|
|
300
|
-
// Don't clear existing sessions - preserve accumulated data
|
|
301
|
-
|
|
302
|
-
// Convert authoritative sessions Map to our format
|
|
303
|
-
for (const [sessionId, sessionData] of socketState.sessions.entries()) {
|
|
304
|
-
if (!this.sessions.has(sessionId)) {
|
|
305
|
-
const activitySession = {
|
|
306
|
-
id: sessionId,
|
|
307
|
-
timestamp: new Date(sessionData.lastActivity || sessionData.startTime || new Date()),
|
|
308
|
-
expanded: this.expandedSessions.has(sessionId) || true,
|
|
309
|
-
agents: new Map(),
|
|
310
|
-
todos: [],
|
|
311
|
-
userInstructions: [],
|
|
312
|
-
tools: [],
|
|
313
|
-
toolsMap: new Map(),
|
|
314
|
-
status: 'active',
|
|
315
|
-
currentTodoTool: null,
|
|
316
|
-
working_directory: sessionData.working_directory,
|
|
317
|
-
git_branch: sessionData.git_branch,
|
|
318
|
-
eventCount: sessionData.eventCount
|
|
319
|
-
};
|
|
320
|
-
this.sessions.set(sessionId, activitySession);
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
// Process only events we haven't seen before
|
|
325
|
-
const unprocessedEvents = socketState.events.filter(event => {
|
|
326
|
-
const eventId = event.id || `${event.type}-${event.timestamp}-${Math.random()}`;
|
|
327
|
-
return !this.processedEventIds.has(eventId);
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
if (unprocessedEvents.length > 0) {
|
|
331
|
-
console.log(`ActivityTree: Processing ${unprocessedEvents.length} unprocessed events from initial load`);
|
|
332
|
-
unprocessedEvents.forEach(event => {
|
|
333
|
-
const eventId = event.id || `${event.type}-${event.timestamp}-${Math.random()}`;
|
|
334
|
-
this.processedEventIds.add(eventId);
|
|
335
|
-
this.processEvent(event);
|
|
336
|
-
});
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
this.events = [...socketState.events];
|
|
340
|
-
// Initial render can be immediate
|
|
341
|
-
this.renderTree();
|
|
342
|
-
|
|
343
|
-
// Debug: Log initial session state
|
|
344
|
-
console.log(`ActivityTree: Initial sessions state:`, Array.from(this.sessions.entries()));
|
|
345
|
-
} else {
|
|
346
|
-
console.log('ActivityTree: No existing events found');
|
|
347
|
-
this.events = [];
|
|
348
|
-
this.sessions.clear();
|
|
349
|
-
this.renderTree();
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
/**
|
|
354
|
-
* Process an event and update the session structure
|
|
355
|
-
*/
|
|
356
|
-
processEvent(event) {
|
|
357
|
-
if (!event) {
|
|
358
|
-
console.log('ActivityTree: Ignoring null event');
|
|
359
|
-
return;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
// Determine event type
|
|
363
|
-
let eventType = this.getEventType(event);
|
|
364
|
-
if (!eventType) {
|
|
365
|
-
return;
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
console.log(`ActivityTree: Processing event: ${eventType}`, event);
|
|
369
|
-
|
|
370
|
-
// Fix timestamp processing - ensure we get a valid date
|
|
371
|
-
let timestamp;
|
|
372
|
-
if (event.timestamp) {
|
|
373
|
-
// Handle both ISO strings and already parsed dates
|
|
374
|
-
timestamp = new Date(event.timestamp);
|
|
375
|
-
// Check if date is valid
|
|
376
|
-
if (isNaN(timestamp.getTime())) {
|
|
377
|
-
console.warn('ActivityTree: Invalid timestamp, using current time:', event.timestamp);
|
|
378
|
-
timestamp = new Date();
|
|
379
|
-
}
|
|
380
|
-
} else {
|
|
381
|
-
console.warn('ActivityTree: No timestamp found, using current time');
|
|
382
|
-
timestamp = new Date();
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
// Get session ID from event - this should match the authoritative sessions
|
|
386
|
-
const sessionId = event.session_id || event.data?.session_id;
|
|
387
|
-
|
|
388
|
-
// Skip events without session ID - they can't be properly categorized
|
|
389
|
-
if (!sessionId) {
|
|
390
|
-
console.log(`ActivityTree: Skipping event without session_id: ${eventType}`);
|
|
391
|
-
return;
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
// Find the session - it should already exist from authoritative sessions
|
|
395
|
-
if (!this.sessions.has(sessionId)) {
|
|
396
|
-
console.warn(`ActivityTree: Session ${sessionId} not found in authoritative sessions - skipping event`);
|
|
397
|
-
return;
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
const session = this.sessions.get(sessionId);
|
|
401
|
-
|
|
402
|
-
switch (eventType) {
|
|
403
|
-
case 'Start':
|
|
404
|
-
// New PM session started
|
|
405
|
-
this.currentSession = session;
|
|
406
|
-
break;
|
|
407
|
-
case 'user_prompt':
|
|
408
|
-
this.processUserInstruction(event, session);
|
|
409
|
-
break;
|
|
410
|
-
case 'TodoWrite':
|
|
411
|
-
// TodoWrite is now handled as a tool in 'tool_use' events
|
|
412
|
-
// Skip separate TodoWrite processing to avoid duplication
|
|
413
|
-
break;
|
|
414
|
-
case 'SubagentStart':
|
|
415
|
-
this.processSubagentStart(event, session);
|
|
416
|
-
break;
|
|
417
|
-
case 'SubagentStop':
|
|
418
|
-
this.processSubagentStop(event, session);
|
|
419
|
-
break;
|
|
420
|
-
case 'PreToolUse':
|
|
421
|
-
this.processToolUse(event, session);
|
|
422
|
-
break;
|
|
423
|
-
case 'PostToolUse':
|
|
424
|
-
this.updateToolStatus(event, session, 'completed');
|
|
425
|
-
break;
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
this.updateStats();
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
/**
|
|
432
|
-
* Get event type from event data
|
|
433
|
-
*/
|
|
434
|
-
getEventType(event) {
|
|
435
|
-
if (event.hook_event_name) {
|
|
436
|
-
return event.hook_event_name;
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
if (event.type === 'hook' && event.subtype) {
|
|
440
|
-
const mapping = {
|
|
441
|
-
'pre_tool': 'PreToolUse',
|
|
442
|
-
'post_tool': 'PostToolUse',
|
|
443
|
-
'subagent_start': 'SubagentStart',
|
|
444
|
-
'subagent_stop': 'SubagentStop',
|
|
445
|
-
'todo_write': 'TodoWrite'
|
|
446
|
-
};
|
|
447
|
-
return mapping[event.subtype];
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
if (event.type === 'todo' && event.subtype === 'updated') {
|
|
451
|
-
return 'TodoWrite';
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
if (event.type === 'subagent') {
|
|
455
|
-
if (event.subtype === 'started') return 'SubagentStart';
|
|
456
|
-
if (event.subtype === 'stopped') return 'SubagentStop';
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
if (event.type === 'start') {
|
|
460
|
-
return 'Start';
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
if (event.type === 'user_prompt' || event.subtype === 'user_prompt') {
|
|
464
|
-
return 'user_prompt';
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
return null;
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
// getSessionId method removed - now using authoritative session IDs directly from socket client
|
|
471
|
-
|
|
472
|
-
/**
|
|
473
|
-
* Process user instruction/prompt event
|
|
474
|
-
*/
|
|
475
|
-
processUserInstruction(event, session) {
|
|
476
|
-
const promptText = event.prompt_text || event.data?.prompt_text || event.prompt || '';
|
|
477
|
-
if (!promptText) return;
|
|
478
|
-
|
|
479
|
-
const instruction = {
|
|
480
|
-
id: `instruction-${session.id}-${Date.now()}`,
|
|
481
|
-
text: promptText,
|
|
482
|
-
preview: promptText.length > 100 ? promptText.substring(0, 100) + '...' : promptText,
|
|
483
|
-
timestamp: event.timestamp || new Date().toISOString(),
|
|
484
|
-
type: 'user_instruction'
|
|
485
|
-
};
|
|
486
|
-
|
|
487
|
-
// NEW USER PROMPT: Only collapse agents if we have existing ones
|
|
488
|
-
// Don't clear - we want to keep the history!
|
|
489
|
-
if (session.agents.size > 0) {
|
|
490
|
-
console.log('ActivityTree: New user prompt detected, collapsing previous agents');
|
|
491
|
-
|
|
492
|
-
// Mark all existing agents as completed (not active)
|
|
493
|
-
for (let agent of session.agents.values()) {
|
|
494
|
-
if (agent.status === 'active') {
|
|
495
|
-
agent.status = 'completed';
|
|
496
|
-
}
|
|
497
|
-
// Collapse all existing agents
|
|
498
|
-
this.expandedAgents.delete(agent.id);
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
// Reset current active agent for new work
|
|
503
|
-
session.currentActiveAgent = null;
|
|
504
|
-
|
|
505
|
-
// Add to session's user instructions
|
|
506
|
-
session.userInstructions.push(instruction);
|
|
507
|
-
|
|
508
|
-
// Keep only last 5 instructions to prevent memory bloat
|
|
509
|
-
if (session.userInstructions.length > 5) {
|
|
510
|
-
session.userInstructions = session.userInstructions.slice(-5);
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
/**
|
|
515
|
-
* Process TodoWrite event - attach TODOs to session and active agent
|
|
516
|
-
*/
|
|
517
|
-
processTodoWrite(event, session) {
|
|
518
|
-
let todos = event.todos || event.data?.todos || event.data || [];
|
|
519
|
-
|
|
520
|
-
if (todos && typeof todos === 'object' && todos.todos) {
|
|
521
|
-
todos = todos.todos;
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
if (!Array.isArray(todos) || todos.length === 0) {
|
|
525
|
-
return;
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
// Update session's current todos for latest state tracking
|
|
529
|
-
session.currentTodos = todos.map(todo => ({
|
|
530
|
-
content: todo.content,
|
|
531
|
-
activeForm: todo.activeForm,
|
|
532
|
-
status: todo.status,
|
|
533
|
-
timestamp: event.timestamp
|
|
534
|
-
}));
|
|
535
|
-
|
|
536
|
-
// Find the appropriate agent to attach this TodoWrite to
|
|
537
|
-
let targetAgent = session.currentActiveAgent;
|
|
538
|
-
|
|
539
|
-
if (!targetAgent) {
|
|
540
|
-
// Fall back to most recent active agent
|
|
541
|
-
const activeAgents = this.getAllAgents(session)
|
|
542
|
-
.filter(agent => agent.status === 'active' || agent.status === 'in_progress')
|
|
543
|
-
.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
|
|
544
|
-
|
|
545
|
-
if (activeAgents.length > 0) {
|
|
546
|
-
targetAgent = activeAgents[0];
|
|
547
|
-
} else {
|
|
548
|
-
// If no active agents, check if this is PM-level
|
|
549
|
-
const allAgents = this.getAllAgents(session);
|
|
550
|
-
const pmAgent = allAgents.find(a => a.isPM);
|
|
551
|
-
if (pmAgent) {
|
|
552
|
-
targetAgent = pmAgent;
|
|
553
|
-
} else if (allAgents.length > 0) {
|
|
554
|
-
targetAgent = allAgents[0];
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
// Attach or update TodoWrite for the agent
|
|
560
|
-
if (targetAgent) {
|
|
561
|
-
if (!targetAgent.todoWritesMap) {
|
|
562
|
-
targetAgent.todoWritesMap = new Map();
|
|
563
|
-
}
|
|
564
|
-
if (!targetAgent.todoWrites) {
|
|
565
|
-
targetAgent.todoWrites = [];
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
// Check if we already have a TodoWrite instance
|
|
569
|
-
const existingTodoWrite = targetAgent.todoWritesMap.get('TodoWrite');
|
|
570
|
-
|
|
571
|
-
if (existingTodoWrite) {
|
|
572
|
-
// Update existing TodoWrite instance
|
|
573
|
-
existingTodoWrite.todos = todos;
|
|
574
|
-
existingTodoWrite.timestamp = event.timestamp;
|
|
575
|
-
existingTodoWrite.updateCount = (existingTodoWrite.updateCount || 1) + 1;
|
|
576
|
-
} else {
|
|
577
|
-
// Create new TodoWrite instance
|
|
578
|
-
const todoWriteInstance = {
|
|
579
|
-
id: `todowrite-${targetAgent.id}-${Date.now()}`,
|
|
580
|
-
name: 'TodoWrite',
|
|
581
|
-
type: 'todowrite',
|
|
582
|
-
icon: '📝',
|
|
583
|
-
timestamp: event.timestamp,
|
|
584
|
-
status: 'completed',
|
|
585
|
-
todos: todos,
|
|
586
|
-
params: {
|
|
587
|
-
todos: todos
|
|
588
|
-
},
|
|
589
|
-
updateCount: 1
|
|
590
|
-
};
|
|
591
|
-
|
|
592
|
-
targetAgent.todoWritesMap.set('TodoWrite', todoWriteInstance);
|
|
593
|
-
targetAgent.todoWrites = [todoWriteInstance]; // Keep single instance
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
// Update agent's current todos for display when collapsed
|
|
597
|
-
targetAgent.currentTodos = todos;
|
|
598
|
-
} else {
|
|
599
|
-
// No agent found, attach to session level
|
|
600
|
-
if (!session.todoWrites) {
|
|
601
|
-
session.todoWrites = [];
|
|
602
|
-
}
|
|
603
|
-
if (!session.todoWritesMap) {
|
|
604
|
-
session.todoWritesMap = new Map();
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
const existingTodoWrite = session.todoWritesMap.get('TodoWrite');
|
|
608
|
-
if (existingTodoWrite) {
|
|
609
|
-
existingTodoWrite.todos = todos;
|
|
610
|
-
existingTodoWrite.timestamp = event.timestamp;
|
|
611
|
-
existingTodoWrite.updateCount = (existingTodoWrite.updateCount || 1) + 1;
|
|
612
|
-
} else {
|
|
613
|
-
const todoWriteInstance = {
|
|
614
|
-
id: `todowrite-session-${Date.now()}`,
|
|
615
|
-
name: 'TodoWrite',
|
|
616
|
-
type: 'todowrite',
|
|
617
|
-
icon: '📝',
|
|
618
|
-
timestamp: event.timestamp,
|
|
619
|
-
status: 'completed',
|
|
620
|
-
todos: todos,
|
|
621
|
-
updateCount: 1
|
|
622
|
-
};
|
|
623
|
-
session.todoWritesMap.set('TodoWrite', todoWriteInstance);
|
|
624
|
-
session.todoWrites = [todoWriteInstance];
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
/**
|
|
630
|
-
* Process SubagentStart event
|
|
631
|
-
*/
|
|
632
|
-
processSubagentStart(event, session) {
|
|
633
|
-
const agentName = event.agent_name || event.data?.agent_name || event.data?.agent_type || event.agent_type || event.agent || 'unknown';
|
|
634
|
-
const agentSessionId = event.session_id || event.data?.session_id;
|
|
635
|
-
const parentAgent = event.parent_agent || event.data?.parent_agent;
|
|
636
|
-
|
|
637
|
-
// Use a composite key based on agent name and session to find existing instances
|
|
638
|
-
// This ensures we track unique agent instances per session
|
|
639
|
-
const agentKey = `${agentName}-${agentSessionId || 'no-session'}`;
|
|
640
|
-
|
|
641
|
-
// Check if this exact agent already exists (same name and session)
|
|
642
|
-
let existingAgent = null;
|
|
643
|
-
const allAgents = this.getAllAgents(session);
|
|
644
|
-
existingAgent = allAgents.find(a =>
|
|
645
|
-
a.name === agentName &&
|
|
646
|
-
a.sessionId === agentSessionId &&
|
|
647
|
-
a.status === 'active' // Only reuse if still active
|
|
648
|
-
);
|
|
649
|
-
|
|
650
|
-
let agent;
|
|
651
|
-
if (existingAgent) {
|
|
652
|
-
// Update existing active agent
|
|
653
|
-
agent = existingAgent;
|
|
654
|
-
agent.timestamp = event.timestamp;
|
|
655
|
-
agent.instanceCount = (agent.instanceCount || 1) + 1;
|
|
656
|
-
// Auto-expand the active agent
|
|
657
|
-
this.expandedAgents.add(agent.id);
|
|
658
|
-
} else {
|
|
659
|
-
// Create new agent instance for first occurrence
|
|
660
|
-
const agentId = `agent-${agentKey}-${Date.now()}`;
|
|
661
|
-
agent = {
|
|
662
|
-
id: agentId,
|
|
663
|
-
name: agentName,
|
|
664
|
-
type: 'agent',
|
|
665
|
-
icon: this.getAgentIcon(agentName),
|
|
666
|
-
timestamp: event.timestamp,
|
|
667
|
-
status: 'active',
|
|
668
|
-
tools: [],
|
|
669
|
-
subagents: new Map(), // Store nested subagents
|
|
670
|
-
sessionId: agentSessionId,
|
|
671
|
-
parentAgent: parentAgent,
|
|
672
|
-
isPM: agentName.toLowerCase() === 'pm' || agentName.toLowerCase().includes('project manager'),
|
|
673
|
-
instanceCount: 1,
|
|
674
|
-
toolsMap: new Map() // Track unique tools by name
|
|
675
|
-
};
|
|
676
|
-
|
|
677
|
-
// If this is a subagent, nest it under the parent agent
|
|
678
|
-
if (parentAgent) {
|
|
679
|
-
// Find the parent agent in the session
|
|
680
|
-
let parent = null;
|
|
681
|
-
for (let [id, ag] of session.agents.entries()) {
|
|
682
|
-
if (ag.sessionId === parentAgent || ag.name === parentAgent) {
|
|
683
|
-
parent = ag;
|
|
684
|
-
break;
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
if (parent) {
|
|
689
|
-
// Add as nested subagent
|
|
690
|
-
if (!parent.subagents) {
|
|
691
|
-
parent.subagents = new Map();
|
|
692
|
-
}
|
|
693
|
-
parent.subagents.set(agent.id, agent);
|
|
694
|
-
} else {
|
|
695
|
-
// No parent found, add to session level
|
|
696
|
-
session.agents.set(agent.id, agent);
|
|
697
|
-
}
|
|
698
|
-
} else {
|
|
699
|
-
// Top-level agent, add to session
|
|
700
|
-
session.agents.set(agent.id, agent);
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
// Auto-expand new agents
|
|
704
|
-
this.expandedAgents.add(agent.id);
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
// Track the currently active agent for tool/todo association
|
|
708
|
-
session.currentActiveAgent = agent;
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
/**
|
|
712
|
-
* Process SubagentStop event
|
|
713
|
-
*/
|
|
714
|
-
processSubagentStop(event, session) {
|
|
715
|
-
const agentSessionId = event.session_id || event.data?.session_id;
|
|
716
|
-
|
|
717
|
-
// Find and mark agent as completed
|
|
718
|
-
if (agentSessionId && session.agents.has(agentSessionId)) {
|
|
719
|
-
const agent = session.agents.get(agentSessionId);
|
|
720
|
-
agent.status = 'completed';
|
|
721
|
-
}
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
/**
|
|
725
|
-
* Process tool use event
|
|
726
|
-
*
|
|
727
|
-
* DISPLAY RULES:
|
|
728
|
-
* 1. TodoWrite is a privileged tool that ALWAYS appears first under the agent/PM
|
|
729
|
-
* 2. Each tool appears only once per unique instance (updated in place)
|
|
730
|
-
* 3. Tools are listed in order of creation (after TodoWrite)
|
|
731
|
-
* 4. Tool instances are updated with new events as they arrive
|
|
732
|
-
*/
|
|
733
|
-
processToolUse(event, session) {
|
|
734
|
-
const toolName = event.tool_name || event.data?.tool_name || event.tool || event.data?.tool || 'unknown';
|
|
735
|
-
const params = event.tool_parameters || event.data?.tool_parameters || event.parameters || event.data?.parameters || {};
|
|
736
|
-
const agentSessionId = event.session_id || event.data?.session_id;
|
|
737
|
-
|
|
738
|
-
// Find the appropriate agent to attach this tool to
|
|
739
|
-
let targetAgent = session.currentActiveAgent;
|
|
740
|
-
|
|
741
|
-
if (!targetAgent) {
|
|
742
|
-
// Fall back to finding by session ID or most recent active
|
|
743
|
-
const allAgents = this.getAllAgents(session);
|
|
744
|
-
targetAgent = allAgents.find(a => a.sessionId === agentSessionId) ||
|
|
745
|
-
allAgents.find(a => a.status === 'active') ||
|
|
746
|
-
allAgents[0];
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
if (targetAgent) {
|
|
750
|
-
if (!targetAgent.toolsMap) {
|
|
751
|
-
targetAgent.toolsMap = new Map();
|
|
752
|
-
}
|
|
753
|
-
if (!targetAgent.tools) {
|
|
754
|
-
targetAgent.tools = [];
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
// Check if we already have this tool instance
|
|
758
|
-
// Use tool name + params hash for unique identification
|
|
759
|
-
const toolKey = this.getToolKey(toolName, params);
|
|
760
|
-
let existingTool = targetAgent.toolsMap.get(toolKey);
|
|
761
|
-
|
|
762
|
-
if (existingTool) {
|
|
763
|
-
// UPDATE RULE: Update existing tool instance in place
|
|
764
|
-
existingTool.params = params;
|
|
765
|
-
existingTool.timestamp = event.timestamp;
|
|
766
|
-
existingTool.status = 'in_progress';
|
|
767
|
-
existingTool.eventId = event.id;
|
|
768
|
-
existingTool.callCount = (existingTool.callCount || 1) + 1;
|
|
769
|
-
|
|
770
|
-
// Update current tool for collapsed display
|
|
771
|
-
targetAgent.currentTool = existingTool;
|
|
772
|
-
} else {
|
|
773
|
-
// CREATE RULE: Create new tool instance
|
|
774
|
-
const tool = {
|
|
775
|
-
id: `tool-${targetAgent.id}-${toolName}-${Date.now()}`,
|
|
776
|
-
name: toolName,
|
|
777
|
-
type: 'tool',
|
|
778
|
-
icon: this.getToolIcon(toolName),
|
|
779
|
-
timestamp: event.timestamp,
|
|
780
|
-
status: 'in_progress',
|
|
781
|
-
params: params,
|
|
782
|
-
eventId: event.id,
|
|
783
|
-
callCount: 1,
|
|
784
|
-
createdAt: event.timestamp // Track creation order
|
|
785
|
-
};
|
|
786
|
-
|
|
787
|
-
// Special handling for Task tool (subagent delegation)
|
|
788
|
-
if (toolName === 'Task' && params.subagent_type) {
|
|
789
|
-
tool.isSubagentTask = true;
|
|
790
|
-
tool.subagentType = params.subagent_type;
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
targetAgent.toolsMap.set(toolKey, tool);
|
|
794
|
-
|
|
795
|
-
// ORDERING RULE: TodoWrite always goes first, others in creation order
|
|
796
|
-
if (toolName === 'TodoWrite') {
|
|
797
|
-
// Insert TodoWrite at the beginning
|
|
798
|
-
targetAgent.tools.unshift(tool);
|
|
799
|
-
} else {
|
|
800
|
-
// Append other tools in creation order
|
|
801
|
-
targetAgent.tools.push(tool);
|
|
802
|
-
}
|
|
803
|
-
|
|
804
|
-
targetAgent.currentTool = tool;
|
|
805
|
-
}
|
|
806
|
-
} else {
|
|
807
|
-
// No agent found, attach to session (PM level)
|
|
808
|
-
// PM RULE: Same display rules apply - TodoWrite first, others in creation order
|
|
809
|
-
if (!session.tools) {
|
|
810
|
-
session.tools = [];
|
|
811
|
-
}
|
|
812
|
-
if (!session.toolsMap) {
|
|
813
|
-
session.toolsMap = new Map();
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
const toolKey = this.getToolKey(toolName, params);
|
|
817
|
-
let existingTool = session.toolsMap.get(toolKey);
|
|
818
|
-
|
|
819
|
-
if (existingTool) {
|
|
820
|
-
// UPDATE RULE: Update existing tool instance in place
|
|
821
|
-
existingTool.params = params;
|
|
822
|
-
existingTool.timestamp = event.timestamp;
|
|
823
|
-
existingTool.status = 'in_progress';
|
|
824
|
-
existingTool.eventId = event.id;
|
|
825
|
-
existingTool.callCount = (existingTool.callCount || 1) + 1;
|
|
826
|
-
session.currentTool = existingTool;
|
|
827
|
-
} else {
|
|
828
|
-
const tool = {
|
|
829
|
-
id: `tool-session-${toolName}-${Date.now()}`,
|
|
830
|
-
name: toolName,
|
|
831
|
-
type: 'tool',
|
|
832
|
-
icon: this.getToolIcon(toolName),
|
|
833
|
-
timestamp: event.timestamp,
|
|
834
|
-
status: 'in_progress',
|
|
835
|
-
params: params,
|
|
836
|
-
eventId: event.id,
|
|
837
|
-
callCount: 1,
|
|
838
|
-
createdAt: event.timestamp // Track creation order
|
|
839
|
-
};
|
|
840
|
-
|
|
841
|
-
session.toolsMap.set(toolKey, tool);
|
|
842
|
-
|
|
843
|
-
// ORDERING RULE: TodoWrite always goes first for PM too
|
|
844
|
-
if (toolName === 'TodoWrite') {
|
|
845
|
-
session.tools.unshift(tool);
|
|
846
|
-
} else {
|
|
847
|
-
session.tools.push(tool);
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
session.currentTool = tool;
|
|
851
|
-
}
|
|
852
|
-
}
|
|
853
|
-
}
|
|
854
|
-
|
|
855
|
-
/**
|
|
856
|
-
* Generate unique key for tool instance identification
|
|
857
|
-
* Tools are unique per name + certain parameter combinations
|
|
858
|
-
*/
|
|
859
|
-
getToolKey(toolName, params) {
|
|
860
|
-
// For TodoWrite, we want ONE instance per agent/PM that updates in place
|
|
861
|
-
// So we use just the tool name as the key
|
|
862
|
-
if (toolName === 'TodoWrite') {
|
|
863
|
-
return 'TodoWrite'; // Single instance per agent/PM
|
|
864
|
-
}
|
|
865
|
-
|
|
866
|
-
// For other tools, we generally want one instance per tool type
|
|
867
|
-
// that gets updated with each call (not creating new instances)
|
|
868
|
-
let key = toolName;
|
|
869
|
-
|
|
870
|
-
// Only add distinguishing params if we need multiple instances
|
|
871
|
-
// For example, multiple files being edited simultaneously
|
|
872
|
-
if (toolName === 'Edit' || toolName === 'Write' || toolName === 'Read') {
|
|
873
|
-
if (params.file_path) {
|
|
874
|
-
key += `-${params.file_path}`;
|
|
875
|
-
}
|
|
876
|
-
}
|
|
877
|
-
|
|
878
|
-
// For search tools, we might want separate instances for different searches
|
|
879
|
-
if ((toolName === 'Grep' || toolName === 'Glob') && params.pattern) {
|
|
880
|
-
// Only add pattern if significantly different
|
|
881
|
-
key += `-${params.pattern.substring(0, 20)}`;
|
|
882
|
-
}
|
|
883
|
-
|
|
884
|
-
// Most tools should have a single instance that updates
|
|
885
|
-
// This prevents the tool list from growing unbounded
|
|
886
|
-
return key;
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
/**
|
|
890
|
-
* Update tool status after completion
|
|
891
|
-
*/
|
|
892
|
-
updateToolStatus(event, session, status) {
|
|
893
|
-
const toolName = event.tool_name || event.data?.tool_name || event.tool || 'unknown';
|
|
894
|
-
const params = event.tool_parameters || event.data?.tool_parameters || event.parameters || event.data?.parameters || {};
|
|
895
|
-
const agentSessionId = event.session_id || event.data?.session_id;
|
|
896
|
-
|
|
897
|
-
// Generate the same key we used to store the tool
|
|
898
|
-
const toolKey = this.getToolKey(toolName, params);
|
|
899
|
-
|
|
900
|
-
// Find the appropriate agent
|
|
901
|
-
let targetAgent = session.currentActiveAgent;
|
|
902
|
-
|
|
903
|
-
if (!targetAgent) {
|
|
904
|
-
const allAgents = this.getAllAgents(session);
|
|
905
|
-
targetAgent = allAgents.find(a => a.sessionId === agentSessionId) ||
|
|
906
|
-
allAgents.find(a => a.status === 'active');
|
|
907
|
-
}
|
|
908
|
-
|
|
909
|
-
if (targetAgent && targetAgent.toolsMap) {
|
|
910
|
-
const tool = targetAgent.toolsMap.get(toolKey);
|
|
911
|
-
if (tool) {
|
|
912
|
-
tool.status = status;
|
|
913
|
-
tool.completedAt = event.timestamp;
|
|
914
|
-
if (event.data?.result || event.result) {
|
|
915
|
-
tool.result = event.data?.result || event.result;
|
|
916
|
-
}
|
|
917
|
-
if (event.data?.duration_ms) {
|
|
918
|
-
tool.duration = event.data.duration_ms;
|
|
919
|
-
}
|
|
920
|
-
return;
|
|
921
|
-
}
|
|
922
|
-
}
|
|
923
|
-
|
|
924
|
-
// Check session-level tools
|
|
925
|
-
if (session.toolsMap) {
|
|
926
|
-
const tool = session.toolsMap.get(toolKey);
|
|
927
|
-
if (tool) {
|
|
928
|
-
tool.status = status;
|
|
929
|
-
tool.completedAt = event.timestamp;
|
|
930
|
-
if (event.data?.result || event.result) {
|
|
931
|
-
tool.result = event.data?.result || event.result;
|
|
932
|
-
}
|
|
933
|
-
if (event.data?.duration_ms) {
|
|
934
|
-
tool.duration = event.data.duration_ms;
|
|
935
|
-
}
|
|
936
|
-
return;
|
|
937
|
-
}
|
|
938
|
-
}
|
|
939
|
-
|
|
940
|
-
console.log(`ActivityTree: Could not find tool to update status for ${toolName} with key ${toolKey} (event ${event.id})`);
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
/**
|
|
944
|
-
* Render the linear tree view
|
|
945
|
-
*/
|
|
946
|
-
renderTree() {
|
|
947
|
-
const treeContainer = document.getElementById('linear-tree');
|
|
948
|
-
if (!treeContainer) return;
|
|
949
|
-
|
|
950
|
-
// Clear tree
|
|
951
|
-
treeContainer.innerHTML = '';
|
|
952
|
-
|
|
953
|
-
// Add sessions directly (no project root)
|
|
954
|
-
const sortedSessions = Array.from(this.sessions.values())
|
|
955
|
-
.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
|
|
956
|
-
|
|
957
|
-
for (let session of sortedSessions) {
|
|
958
|
-
if (this.selectedSessionFilter !== 'all' && this.selectedSessionFilter !== session.id) {
|
|
959
|
-
continue;
|
|
960
|
-
}
|
|
961
|
-
|
|
962
|
-
const sessionElement = this.createSessionElement(session);
|
|
963
|
-
treeContainer.appendChild(sessionElement);
|
|
964
|
-
}
|
|
965
|
-
|
|
966
|
-
// Session filtering is now handled by the main session selector via event listeners
|
|
967
|
-
}
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
/**
|
|
971
|
-
* Create session element
|
|
972
|
-
*/
|
|
973
|
-
createSessionElement(session) {
|
|
974
|
-
const isExpanded = this.expandedSessions.has(session.id) || session.expanded;
|
|
975
|
-
|
|
976
|
-
// Ensure timestamp is valid and format it consistently
|
|
977
|
-
let sessionTime;
|
|
978
|
-
try {
|
|
979
|
-
const sessionDate = session.timestamp instanceof Date ? session.timestamp : new Date(session.timestamp);
|
|
980
|
-
if (isNaN(sessionDate.getTime())) {
|
|
981
|
-
sessionTime = 'Invalid Date';
|
|
982
|
-
console.warn('ActivityTree: Invalid session timestamp:', session.timestamp);
|
|
983
|
-
} else {
|
|
984
|
-
sessionTime = sessionDate.toLocaleString();
|
|
985
|
-
}
|
|
986
|
-
} catch (error) {
|
|
987
|
-
sessionTime = 'Invalid Date';
|
|
988
|
-
console.error('ActivityTree: Error formatting session timestamp:', error, session.timestamp);
|
|
989
|
-
}
|
|
990
|
-
|
|
991
|
-
const element = document.createElement('div');
|
|
992
|
-
element.className = 'tree-node session';
|
|
993
|
-
element.dataset.sessionId = session.id;
|
|
994
|
-
|
|
995
|
-
const expandIcon = isExpanded ? '▼' : '▶';
|
|
996
|
-
// Count ALL agents including nested ones
|
|
997
|
-
const agentCount = this.getAllAgents(session).length;
|
|
998
|
-
const todoCount = session.currentTodos ? session.currentTodos.length : 0;
|
|
999
|
-
const instructionCount = session.userInstructions ? session.userInstructions.length : 0;
|
|
1000
|
-
|
|
1001
|
-
console.log(`ActivityTree: Rendering session ${session.id}: ${agentCount} agents, ${instructionCount} instructions, ${todoCount} todos at ${sessionTime}`);
|
|
1002
|
-
|
|
1003
|
-
element.innerHTML = `
|
|
1004
|
-
<div class="tree-node-content" onclick="window.activityTreeInstance.toggleSession('${session.id}')">
|
|
1005
|
-
<span class="tree-expand-icon">${expandIcon}</span>
|
|
1006
|
-
<span class="tree-icon">🎯</span>
|
|
1007
|
-
<span class="tree-label">PM Session</span>
|
|
1008
|
-
<span class="tree-meta">${sessionTime} • ${agentCount} agent(s) • ${instructionCount} instruction(s) • ${todoCount} todo(s)</span>
|
|
1009
|
-
</div>
|
|
1010
|
-
<div class="tree-children" style="display: ${isExpanded ? 'block' : 'none'}">
|
|
1011
|
-
${this.renderSessionContent(session)}
|
|
1012
|
-
</div>
|
|
1013
|
-
`;
|
|
1014
|
-
|
|
1015
|
-
return element;
|
|
1016
|
-
}
|
|
1017
|
-
|
|
1018
|
-
/**
|
|
1019
|
-
* Render session content (user instructions, todos, agents, tools)
|
|
1020
|
-
*
|
|
1021
|
-
* PM DISPLAY RULES (documented inline):
|
|
1022
|
-
* 1. User instructions appear first (context)
|
|
1023
|
-
* 2. PM-level tools follow the same rules as agent tools:
|
|
1024
|
-
* - TodoWrite is privileged and appears first
|
|
1025
|
-
* - Other tools appear in creation order
|
|
1026
|
-
* - Each unique instance is updated in place
|
|
1027
|
-
* 3. Agents appear after PM tools
|
|
1028
|
-
*/
|
|
1029
|
-
renderSessionContent(session) {
|
|
1030
|
-
let html = '';
|
|
1031
|
-
|
|
1032
|
-
// Render user instructions first
|
|
1033
|
-
if (session.userInstructions && session.userInstructions.length > 0) {
|
|
1034
|
-
for (let instruction of session.userInstructions.slice(-3)) { // Show last 3 instructions
|
|
1035
|
-
html += this.renderUserInstructionElement(instruction, 1);
|
|
1036
|
-
}
|
|
1037
|
-
}
|
|
1038
|
-
|
|
1039
|
-
// PM TOOL DISPLAY RULES:
|
|
1040
|
-
// Render PM-level tools (TodoWrite first, then others in creation order)
|
|
1041
|
-
// The session.tools array is already properly ordered by processToolUse
|
|
1042
|
-
if (session.tools && session.tools.length > 0) {
|
|
1043
|
-
for (let tool of session.tools) {
|
|
1044
|
-
html += this.renderToolElement(tool, 1);
|
|
1045
|
-
}
|
|
1046
|
-
}
|
|
1047
|
-
|
|
1048
|
-
// Render agents (they will have their own TodoWrite at the top)
|
|
1049
|
-
const agents = Array.from(session.agents.values())
|
|
1050
|
-
.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
|
|
1051
|
-
|
|
1052
|
-
for (let agent of agents) {
|
|
1053
|
-
html += this.renderAgentElement(agent, 1);
|
|
1054
|
-
}
|
|
1055
|
-
|
|
1056
|
-
return html;
|
|
1057
|
-
}
|
|
1058
|
-
|
|
1059
|
-
/**
|
|
1060
|
-
* Render user instruction element
|
|
1061
|
-
*/
|
|
1062
|
-
renderUserInstructionElement(instruction, level) {
|
|
1063
|
-
const isSelected = this.selectedItem && this.selectedItem.type === 'instruction' && this.selectedItem.data.id === instruction.id;
|
|
1064
|
-
const selectedClass = isSelected ? 'selected' : '';
|
|
1065
|
-
|
|
1066
|
-
return `
|
|
1067
|
-
<div class="tree-node user-instruction ${selectedClass}" data-level="${level}">
|
|
1068
|
-
<div class="tree-node-content">
|
|
1069
|
-
<span class="tree-expand-icon"></span>
|
|
1070
|
-
<span class="tree-icon">💬</span>
|
|
1071
|
-
<span class="tree-label clickable" onclick="window.activityTreeInstance.selectItem(${this.escapeJson(instruction)}, 'instruction', event)">User: "${this.escapeHtml(instruction.preview)}"</span>
|
|
1072
|
-
<span class="tree-status status-active">instruction</span>
|
|
1073
|
-
</div>
|
|
1074
|
-
</div>
|
|
1075
|
-
`;
|
|
1076
|
-
}
|
|
1077
|
-
|
|
1078
|
-
/**
|
|
1079
|
-
* Render TODO checklist element
|
|
1080
|
-
*/
|
|
1081
|
-
renderTodoChecklistElement(todos, level) {
|
|
1082
|
-
const checklistId = `checklist-${Date.now()}`;
|
|
1083
|
-
const isExpanded = this.expandedTools.has(checklistId) !== false; // Default to expanded
|
|
1084
|
-
const expandIcon = isExpanded ? '▼' : '▶';
|
|
1085
|
-
|
|
1086
|
-
// Calculate status summary
|
|
1087
|
-
let completedCount = 0;
|
|
1088
|
-
let inProgressCount = 0;
|
|
1089
|
-
let pendingCount = 0;
|
|
1090
|
-
|
|
1091
|
-
todos.forEach(todo => {
|
|
1092
|
-
if (todo.status === 'completed') completedCount++;
|
|
1093
|
-
else if (todo.status === 'in_progress') inProgressCount++;
|
|
1094
|
-
else pendingCount++;
|
|
1095
|
-
});
|
|
1096
|
-
|
|
1097
|
-
let statusSummary = '';
|
|
1098
|
-
if (inProgressCount > 0) {
|
|
1099
|
-
statusSummary = `${inProgressCount} in progress, ${completedCount} completed`;
|
|
1100
|
-
} else if (completedCount === todos.length && todos.length > 0) {
|
|
1101
|
-
statusSummary = `All ${todos.length} completed`;
|
|
1102
|
-
} else {
|
|
1103
|
-
statusSummary = `${todos.length} todo(s)`;
|
|
1104
|
-
}
|
|
1105
|
-
|
|
1106
|
-
let html = `
|
|
1107
|
-
<div class="tree-node todo-checklist" data-level="${level}">
|
|
1108
|
-
<div class="tree-node-content">
|
|
1109
|
-
<span class="tree-expand-icon" onclick="window.activityTreeInstance.toggleTodoChecklist('${checklistId}'); event.stopPropagation();">${expandIcon}</span>
|
|
1110
|
-
<span class="tree-icon">☑️</span>
|
|
1111
|
-
<span class="tree-label">TODOs</span>
|
|
1112
|
-
<span class="tree-params">${statusSummary}</span>
|
|
1113
|
-
<span class="tree-status status-active">checklist</span>
|
|
1114
|
-
</div>
|
|
1115
|
-
`;
|
|
1116
|
-
|
|
1117
|
-
// Show expanded todo items if expanded
|
|
1118
|
-
if (isExpanded) {
|
|
1119
|
-
html += '<div class="tree-children">';
|
|
1120
|
-
for (let todo of todos) {
|
|
1121
|
-
const statusIcon = this.getCheckboxIcon(todo.status);
|
|
1122
|
-
const statusClass = `status-${todo.status}`;
|
|
1123
|
-
const displayText = todo.status === 'in_progress' ? todo.activeForm : todo.content;
|
|
1124
|
-
|
|
1125
|
-
html += `
|
|
1126
|
-
<div class="tree-node todo-item ${statusClass}" data-level="${level + 1}">
|
|
1127
|
-
<div class="tree-node-content">
|
|
1128
|
-
<span class="tree-expand-icon"></span>
|
|
1129
|
-
<span class="tree-icon">${statusIcon}</span>
|
|
1130
|
-
<span class="tree-label">${this.escapeHtml(displayText)}</span>
|
|
1131
|
-
<span class="tree-status ${statusClass}">${todo.status.replace('_', ' ')}</span>
|
|
1132
|
-
</div>
|
|
1133
|
-
</div>
|
|
1134
|
-
`;
|
|
1135
|
-
}
|
|
1136
|
-
html += '</div>';
|
|
1137
|
-
}
|
|
1138
|
-
|
|
1139
|
-
html += '</div>';
|
|
1140
|
-
return html;
|
|
1141
|
-
}
|
|
1142
|
-
|
|
1143
|
-
/**
|
|
1144
|
-
* Render agent element with proper nesting
|
|
1145
|
-
*/
|
|
1146
|
-
renderAgentElement(agent, level) {
|
|
1147
|
-
const statusClass = agent.status === 'active' ? 'status-active' : 'status-completed';
|
|
1148
|
-
const isExpanded = this.expandedAgents.has(agent.id);
|
|
1149
|
-
const hasTools = agent.tools && agent.tools.length > 0;
|
|
1150
|
-
const hasSubagents = agent.subagents && agent.subagents.size > 0;
|
|
1151
|
-
const hasContent = hasTools || hasSubagents;
|
|
1152
|
-
const isSelected = this.selectedItem && this.selectedItem.type === 'agent' && this.selectedItem.data.id === agent.id;
|
|
1153
|
-
|
|
1154
|
-
const expandIcon = hasContent ? (isExpanded ? '▼' : '▶') : '';
|
|
1155
|
-
const selectedClass = isSelected ? 'selected' : '';
|
|
1156
|
-
|
|
1157
|
-
// Add instance count if called multiple times
|
|
1158
|
-
const instanceIndicator = agent.instanceCount > 1 ? ` (${agent.instanceCount}x)` : '';
|
|
1159
|
-
|
|
1160
|
-
// Build status display for collapsed state
|
|
1161
|
-
let collapsedStatus = '';
|
|
1162
|
-
if (!isExpanded && hasContent) {
|
|
1163
|
-
const parts = [];
|
|
1164
|
-
if (agent.currentTodos && agent.currentTodos.length > 0) {
|
|
1165
|
-
const inProgress = agent.currentTodos.find(t => t.status === 'in_progress');
|
|
1166
|
-
if (inProgress) {
|
|
1167
|
-
parts.push(`📝 ${inProgress.activeForm || inProgress.content}`);
|
|
1168
|
-
}
|
|
1169
|
-
}
|
|
1170
|
-
if (agent.currentTool) {
|
|
1171
|
-
parts.push(`${agent.currentTool.icon} ${agent.currentTool.name}`);
|
|
1172
|
-
}
|
|
1173
|
-
if (parts.length > 0) {
|
|
1174
|
-
collapsedStatus = ` • ${parts.join(' • ')}`;
|
|
1175
|
-
}
|
|
1176
|
-
}
|
|
1177
|
-
|
|
1178
|
-
let html = `
|
|
1179
|
-
<div class="tree-node agent ${statusClass} ${selectedClass}" data-level="${level}">
|
|
1180
|
-
<div class="tree-node-content">
|
|
1181
|
-
${expandIcon ? `<span class="tree-expand-icon" onclick="window.activityTreeInstance.toggleAgent('${agent.id}'); event.stopPropagation();">${expandIcon}</span>` : '<span class="tree-expand-icon"></span>'}
|
|
1182
|
-
<span class="tree-icon">${agent.icon}</span>
|
|
1183
|
-
<span class="tree-label clickable" onclick="window.activityTreeInstance.selectItem(${this.escapeJson(agent)}, 'agent', event)">${agent.name}${instanceIndicator}${collapsedStatus}</span>
|
|
1184
|
-
<span class="tree-status ${statusClass}">${agent.status}</span>
|
|
1185
|
-
</div>
|
|
1186
|
-
`;
|
|
1187
|
-
|
|
1188
|
-
// Render nested content when expanded
|
|
1189
|
-
if (hasContent && isExpanded) {
|
|
1190
|
-
html += '<div class="tree-children">';
|
|
1191
|
-
|
|
1192
|
-
// DISPLAY ORDER RULES (documented inline):
|
|
1193
|
-
// 1. TodoWrite is a privileged tool - ALWAYS appears first
|
|
1194
|
-
// 2. Each tool appears only once per unique instance
|
|
1195
|
-
// 3. Tools are displayed in order of creation (after TodoWrite)
|
|
1196
|
-
// 4. Tool instances are updated in place as new events arrive
|
|
1197
|
-
|
|
1198
|
-
// Render all tools in their proper order
|
|
1199
|
-
// The tools array is already ordered: TodoWrite first, then others by creation
|
|
1200
|
-
if (hasTools) {
|
|
1201
|
-
for (let tool of agent.tools) {
|
|
1202
|
-
html += this.renderToolElement(tool, level + 1);
|
|
1203
|
-
}
|
|
1204
|
-
}
|
|
1205
|
-
|
|
1206
|
-
// Then render subagents (they will have their own TodoWrite at the top)
|
|
1207
|
-
if (hasSubagents) {
|
|
1208
|
-
const subagents = Array.from(agent.subagents.values());
|
|
1209
|
-
for (let subagent of subagents) {
|
|
1210
|
-
html += this.renderAgentElement(subagent, level + 1);
|
|
1211
|
-
}
|
|
1212
|
-
}
|
|
1213
|
-
|
|
1214
|
-
html += '</div>';
|
|
1215
|
-
}
|
|
1216
|
-
|
|
1217
|
-
html += '</div>';
|
|
1218
|
-
return html;
|
|
1219
|
-
}
|
|
1220
|
-
|
|
1221
|
-
/**
|
|
1222
|
-
* Render tool element (non-expandable, clickable to show data)
|
|
1223
|
-
*/
|
|
1224
|
-
renderToolElement(tool, level) {
|
|
1225
|
-
const statusClass = `status-${tool.status}`;
|
|
1226
|
-
const params = this.getToolParams(tool);
|
|
1227
|
-
const isSelected = this.selectedItem && this.selectedItem.type === 'tool' && this.selectedItem.data.id === tool.id;
|
|
1228
|
-
const selectedClass = isSelected ? 'selected' : '';
|
|
1229
|
-
|
|
1230
|
-
// Add visual status indicators
|
|
1231
|
-
const statusIcon = this.getToolStatusIcon(tool.status);
|
|
1232
|
-
const statusLabel = this.getToolStatusLabel(tool.status);
|
|
1233
|
-
|
|
1234
|
-
// Add call count if more than 1
|
|
1235
|
-
const callIndicator = tool.callCount > 1 ? ` (${tool.callCount} calls)` : '';
|
|
1236
|
-
|
|
1237
|
-
let html = `
|
|
1238
|
-
<div class="tree-node tool ${statusClass} ${selectedClass}" data-level="${level}">
|
|
1239
|
-
<div class="tree-node-content">
|
|
1240
|
-
<span class="tree-expand-icon"></span>
|
|
1241
|
-
<span class="tree-icon">${tool.icon}</span>
|
|
1242
|
-
<span class="tree-status-icon">${statusIcon}</span>
|
|
1243
|
-
<span class="tree-label clickable" onclick="window.activityTreeInstance.selectItem(${this.escapeJson(tool)}, 'tool', event)">${tool.name}${callIndicator}</span>
|
|
1244
|
-
<span class="tree-params">${params}</span>
|
|
1245
|
-
<span class="tree-status ${statusClass}">${statusLabel}</span>
|
|
1246
|
-
</div>
|
|
1247
|
-
</div>
|
|
1248
|
-
`;
|
|
1249
|
-
|
|
1250
|
-
return html;
|
|
1251
|
-
}
|
|
1252
|
-
|
|
1253
|
-
/**
|
|
1254
|
-
* Get formatted tool parameters
|
|
1255
|
-
*/
|
|
1256
|
-
getToolParams(tool) {
|
|
1257
|
-
if (!tool.params) return '';
|
|
1258
|
-
|
|
1259
|
-
if (tool.name === 'Read' && tool.params.file_path) {
|
|
1260
|
-
return tool.params.file_path;
|
|
1261
|
-
}
|
|
1262
|
-
if (tool.name === 'Edit' && tool.params.file_path) {
|
|
1263
|
-
return tool.params.file_path;
|
|
1264
|
-
}
|
|
1265
|
-
if (tool.name === 'Write' && tool.params.file_path) {
|
|
1266
|
-
return tool.params.file_path;
|
|
1267
|
-
}
|
|
1268
|
-
if (tool.name === 'Bash' && tool.params.command) {
|
|
1269
|
-
const cmd = tool.params.command;
|
|
1270
|
-
return cmd.length > 50 ? cmd.substring(0, 50) + '...' : cmd;
|
|
1271
|
-
}
|
|
1272
|
-
if (tool.name === 'WebFetch' && tool.params.url) {
|
|
1273
|
-
return tool.params.url;
|
|
1274
|
-
}
|
|
1275
|
-
|
|
1276
|
-
return '';
|
|
1277
|
-
}
|
|
1278
|
-
|
|
1279
|
-
/**
|
|
1280
|
-
* Get status icon for todo status
|
|
1281
|
-
*/
|
|
1282
|
-
getStatusIcon(status) {
|
|
1283
|
-
const icons = {
|
|
1284
|
-
'pending': '⏸️',
|
|
1285
|
-
'in_progress': '🔄',
|
|
1286
|
-
'completed': '✅'
|
|
1287
|
-
};
|
|
1288
|
-
return icons[status] || '❓';
|
|
1289
|
-
}
|
|
1290
|
-
|
|
1291
|
-
/**
|
|
1292
|
-
* Get checkbox icon for todo checklist items
|
|
1293
|
-
*/
|
|
1294
|
-
getCheckboxIcon(status) {
|
|
1295
|
-
const icons = {
|
|
1296
|
-
'pending': '⏳',
|
|
1297
|
-
'in_progress': '🔄',
|
|
1298
|
-
'completed': '✅'
|
|
1299
|
-
};
|
|
1300
|
-
return icons[status] || '❓';
|
|
1301
|
-
}
|
|
1302
|
-
|
|
1303
|
-
/**
|
|
1304
|
-
* Get agent icon based on name
|
|
1305
|
-
*/
|
|
1306
|
-
getAgentIcon(agentName) {
|
|
1307
|
-
const icons = {
|
|
1308
|
-
'engineer': '👷',
|
|
1309
|
-
'research': '🔬',
|
|
1310
|
-
'qa': '🧪',
|
|
1311
|
-
'ops': '⚙️',
|
|
1312
|
-
'pm': '📊',
|
|
1313
|
-
'architect': '🏗️',
|
|
1314
|
-
'project manager': '📊'
|
|
1315
|
-
};
|
|
1316
|
-
return icons[agentName.toLowerCase()] || '🤖';
|
|
1317
|
-
}
|
|
1318
|
-
|
|
1319
|
-
/**
|
|
1320
|
-
* Helper to get all agents including nested subagents
|
|
1321
|
-
*/
|
|
1322
|
-
getAllAgents(session) {
|
|
1323
|
-
const agents = [];
|
|
1324
|
-
|
|
1325
|
-
const collectAgents = (agentMap) => {
|
|
1326
|
-
if (!agentMap) return;
|
|
1327
|
-
|
|
1328
|
-
for (let agent of agentMap.values()) {
|
|
1329
|
-
agents.push(agent);
|
|
1330
|
-
if (agent.subagents && agent.subagents.size > 0) {
|
|
1331
|
-
collectAgents(agent.subagents);
|
|
1332
|
-
}
|
|
1333
|
-
}
|
|
1334
|
-
};
|
|
1335
|
-
|
|
1336
|
-
collectAgents(session.agents);
|
|
1337
|
-
return agents;
|
|
1338
|
-
}
|
|
1339
|
-
|
|
1340
|
-
/**
|
|
1341
|
-
* Render TodoWrite element
|
|
1342
|
-
*/
|
|
1343
|
-
renderTodoWriteElement(todoWrite, level) {
|
|
1344
|
-
const todoWriteId = todoWrite.id;
|
|
1345
|
-
const isExpanded = this.expandedTools.has(todoWriteId);
|
|
1346
|
-
const expandIcon = isExpanded ? '▼' : '▶';
|
|
1347
|
-
const todos = todoWrite.todos || [];
|
|
1348
|
-
|
|
1349
|
-
// Calculate status summary
|
|
1350
|
-
let completedCount = 0;
|
|
1351
|
-
let inProgressCount = 0;
|
|
1352
|
-
let pendingCount = 0;
|
|
1353
|
-
|
|
1354
|
-
todos.forEach(todo => {
|
|
1355
|
-
if (todo.status === 'completed') completedCount++;
|
|
1356
|
-
else if (todo.status === 'in_progress') inProgressCount++;
|
|
1357
|
-
else pendingCount++;
|
|
1358
|
-
});
|
|
1359
|
-
|
|
1360
|
-
// Find current in-progress todo for highlighting
|
|
1361
|
-
const currentTodo = todos.find(t => t.status === 'in_progress');
|
|
1362
|
-
const currentIndicator = currentTodo ? ` • 🔄 ${currentTodo.activeForm || currentTodo.content}` : '';
|
|
1363
|
-
|
|
1364
|
-
let statusSummary = '';
|
|
1365
|
-
if (inProgressCount > 0) {
|
|
1366
|
-
statusSummary = `${inProgressCount} in progress, ${completedCount}/${todos.length} done`;
|
|
1367
|
-
} else if (completedCount === todos.length && todos.length > 0) {
|
|
1368
|
-
statusSummary = `All ${todos.length} completed ✅`;
|
|
1369
|
-
} else {
|
|
1370
|
-
statusSummary = `${completedCount}/${todos.length} done`;
|
|
1371
|
-
}
|
|
1372
|
-
|
|
1373
|
-
// Add update count if more than 1
|
|
1374
|
-
const updateIndicator = todoWrite.updateCount > 1 ? ` (${todoWrite.updateCount} updates)` : '';
|
|
1375
|
-
|
|
1376
|
-
let html = `
|
|
1377
|
-
<div class="tree-node todowrite ${currentTodo ? 'has-active' : ''}" data-level="${level}">
|
|
1378
|
-
<div class="tree-node-content">
|
|
1379
|
-
<span class="tree-expand-icon" onclick="window.activityTreeInstance.toggleTodoWrite('${todoWriteId}'); event.stopPropagation();">${expandIcon}</span>
|
|
1380
|
-
<span class="tree-icon">📝</span>
|
|
1381
|
-
<span class="tree-label">TodoWrite${updateIndicator}${!isExpanded ? currentIndicator : ''}</span>
|
|
1382
|
-
<span class="tree-params">${statusSummary}</span>
|
|
1383
|
-
<span class="tree-status status-active">todos</span>
|
|
1384
|
-
</div>
|
|
1385
|
-
`;
|
|
1386
|
-
|
|
1387
|
-
// Show expanded todo items if expanded
|
|
1388
|
-
if (isExpanded && todos.length > 0) {
|
|
1389
|
-
html += '<div class="tree-children">';
|
|
1390
|
-
for (let todo of todos) {
|
|
1391
|
-
const statusIcon = this.getCheckboxIcon(todo.status);
|
|
1392
|
-
const statusClass = `status-${todo.status}`;
|
|
1393
|
-
const displayText = todo.status === 'in_progress' ? todo.activeForm : todo.content;
|
|
1394
|
-
const isCurrentTodo = todo === currentTodo;
|
|
1395
|
-
|
|
1396
|
-
html += `
|
|
1397
|
-
<div class="tree-node todo-item ${statusClass} ${isCurrentTodo ? 'current-active' : ''}" data-level="${level + 1}">
|
|
1398
|
-
<div class="tree-node-content">
|
|
1399
|
-
<span class="tree-expand-icon"></span>
|
|
1400
|
-
<span class="tree-icon">${statusIcon}</span>
|
|
1401
|
-
<span class="tree-label">${this.escapeHtml(displayText)}</span>
|
|
1402
|
-
<span class="tree-status ${statusClass}">${todo.status.replace('_', ' ')}</span>
|
|
1403
|
-
</div>
|
|
1404
|
-
</div>
|
|
1405
|
-
`;
|
|
1406
|
-
}
|
|
1407
|
-
html += '</div>';
|
|
1408
|
-
}
|
|
1409
|
-
|
|
1410
|
-
html += '</div>';
|
|
1411
|
-
return html;
|
|
1412
|
-
}
|
|
1413
|
-
|
|
1414
|
-
/**
|
|
1415
|
-
* Toggle TodoWrite expansion
|
|
1416
|
-
*/
|
|
1417
|
-
toggleTodoWrite(todoWriteId) {
|
|
1418
|
-
if (this.expandedTools.has(todoWriteId)) {
|
|
1419
|
-
this.expandedTools.delete(todoWriteId);
|
|
1420
|
-
} else {
|
|
1421
|
-
this.expandedTools.add(todoWriteId);
|
|
1422
|
-
}
|
|
1423
|
-
this.renderTree();
|
|
1424
|
-
}
|
|
1425
|
-
|
|
1426
|
-
/**
|
|
1427
|
-
* Get tool icon based on name
|
|
1428
|
-
*/
|
|
1429
|
-
getToolIcon(toolName) {
|
|
1430
|
-
const icons = {
|
|
1431
|
-
'read': '👁️',
|
|
1432
|
-
'write': '✍️',
|
|
1433
|
-
'edit': '✏️',
|
|
1434
|
-
'bash': '💻',
|
|
1435
|
-
'webfetch': '🌐',
|
|
1436
|
-
'grep': '🔍',
|
|
1437
|
-
'glob': '📂',
|
|
1438
|
-
'todowrite': '📝'
|
|
1439
|
-
};
|
|
1440
|
-
return icons[toolName.toLowerCase()] || '🔧';
|
|
1441
|
-
}
|
|
1442
|
-
|
|
1443
|
-
/**
|
|
1444
|
-
* Get status icon for tool status
|
|
1445
|
-
*/
|
|
1446
|
-
getToolStatusIcon(status) {
|
|
1447
|
-
const icons = {
|
|
1448
|
-
'in_progress': '⏳',
|
|
1449
|
-
'completed': '✅',
|
|
1450
|
-
'failed': '❌',
|
|
1451
|
-
'error': '❌',
|
|
1452
|
-
'pending': '⏸️',
|
|
1453
|
-
'active': '🔄'
|
|
1454
|
-
};
|
|
1455
|
-
return icons[status] || '❓';
|
|
1456
|
-
}
|
|
1457
|
-
|
|
1458
|
-
/**
|
|
1459
|
-
* Get formatted status label for tool
|
|
1460
|
-
*/
|
|
1461
|
-
getToolStatusLabel(status) {
|
|
1462
|
-
const labels = {
|
|
1463
|
-
'in_progress': 'in progress',
|
|
1464
|
-
'completed': 'completed',
|
|
1465
|
-
'failed': 'failed',
|
|
1466
|
-
'error': 'error',
|
|
1467
|
-
'pending': 'pending',
|
|
1468
|
-
'active': 'active'
|
|
1469
|
-
};
|
|
1470
|
-
return labels[status] || status;
|
|
1471
|
-
}
|
|
1472
|
-
|
|
1473
|
-
/**
|
|
1474
|
-
* Toggle session expansion
|
|
1475
|
-
*/
|
|
1476
|
-
toggleSession(sessionId) {
|
|
1477
|
-
if (this.expandedSessions.has(sessionId)) {
|
|
1478
|
-
this.expandedSessions.delete(sessionId);
|
|
1479
|
-
} else {
|
|
1480
|
-
this.expandedSessions.add(sessionId);
|
|
1481
|
-
}
|
|
1482
|
-
|
|
1483
|
-
// Update the session in the data structure
|
|
1484
|
-
const session = this.sessions.get(sessionId);
|
|
1485
|
-
if (session) {
|
|
1486
|
-
session.expanded = this.expandedSessions.has(sessionId);
|
|
1487
|
-
}
|
|
1488
|
-
|
|
1489
|
-
this.renderTree();
|
|
1490
|
-
}
|
|
1491
|
-
|
|
1492
|
-
/**
|
|
1493
|
-
* Expand all sessions
|
|
1494
|
-
*/
|
|
1495
|
-
expandAllSessions() {
|
|
1496
|
-
for (let sessionId of this.sessions.keys()) {
|
|
1497
|
-
this.expandedSessions.add(sessionId);
|
|
1498
|
-
const session = this.sessions.get(sessionId);
|
|
1499
|
-
if (session) session.expanded = true;
|
|
1500
|
-
}
|
|
1501
|
-
this.renderTree();
|
|
1502
|
-
}
|
|
1503
|
-
|
|
1504
|
-
/**
|
|
1505
|
-
* Collapse all sessions
|
|
1506
|
-
*/
|
|
1507
|
-
collapseAllSessions() {
|
|
1508
|
-
this.expandedSessions.clear();
|
|
1509
|
-
for (let session of this.sessions.values()) {
|
|
1510
|
-
session.expanded = false;
|
|
1511
|
-
}
|
|
1512
|
-
this.renderTree();
|
|
1513
|
-
}
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
/**
|
|
1517
|
-
* Update statistics
|
|
1518
|
-
*/
|
|
1519
|
-
updateStats() {
|
|
1520
|
-
const totalNodes = this.countTotalNodes();
|
|
1521
|
-
const activeNodes = this.countActiveNodes();
|
|
1522
|
-
const maxDepth = this.calculateMaxDepth();
|
|
1523
|
-
|
|
1524
|
-
const nodeCountEl = document.getElementById('node-count');
|
|
1525
|
-
const activeCountEl = document.getElementById('active-count');
|
|
1526
|
-
const depthEl = document.getElementById('tree-depth');
|
|
1527
|
-
|
|
1528
|
-
if (nodeCountEl) nodeCountEl.textContent = totalNodes;
|
|
1529
|
-
if (activeCountEl) activeCountEl.textContent = activeNodes;
|
|
1530
|
-
if (depthEl) depthEl.textContent = maxDepth;
|
|
1531
|
-
|
|
1532
|
-
console.log(`ActivityTree: Stats updated - Nodes: ${totalNodes}, Active: ${activeNodes}, Depth: ${maxDepth}`);
|
|
1533
|
-
}
|
|
1534
|
-
|
|
1535
|
-
/**
|
|
1536
|
-
* Count total nodes across all sessions
|
|
1537
|
-
*/
|
|
1538
|
-
countTotalNodes() {
|
|
1539
|
-
let count = 0; // No project root anymore
|
|
1540
|
-
for (let session of this.sessions.values()) {
|
|
1541
|
-
count += 1; // Session
|
|
1542
|
-
count += session.agents.size; // Agents
|
|
1543
|
-
|
|
1544
|
-
// Count user instructions
|
|
1545
|
-
if (session.userInstructions) {
|
|
1546
|
-
count += session.userInstructions.length;
|
|
1547
|
-
}
|
|
1548
|
-
|
|
1549
|
-
// Count todos
|
|
1550
|
-
if (session.todos) {
|
|
1551
|
-
count += session.todos.length;
|
|
1552
|
-
}
|
|
1553
|
-
|
|
1554
|
-
// Count session-level tools
|
|
1555
|
-
if (session.tools) {
|
|
1556
|
-
count += session.tools.length;
|
|
1557
|
-
}
|
|
1558
|
-
|
|
1559
|
-
// Count tools in agents
|
|
1560
|
-
for (let agent of session.agents.values()) {
|
|
1561
|
-
if (agent.tools) {
|
|
1562
|
-
count += agent.tools.length;
|
|
1563
|
-
}
|
|
1564
|
-
}
|
|
1565
|
-
}
|
|
1566
|
-
return count;
|
|
1567
|
-
}
|
|
1568
|
-
|
|
1569
|
-
/**
|
|
1570
|
-
* Count active nodes (in progress)
|
|
1571
|
-
*/
|
|
1572
|
-
countActiveNodes() {
|
|
1573
|
-
let count = 0;
|
|
1574
|
-
for (let session of this.sessions.values()) {
|
|
1575
|
-
// Count active session
|
|
1576
|
-
if (session.status === 'active') count++;
|
|
1577
|
-
|
|
1578
|
-
// Count active todos
|
|
1579
|
-
if (session.todos) {
|
|
1580
|
-
for (let todo of session.todos) {
|
|
1581
|
-
if (todo.status === 'in_progress') count++;
|
|
1582
|
-
}
|
|
1583
|
-
}
|
|
1584
|
-
|
|
1585
|
-
// Count session-level tools
|
|
1586
|
-
if (session.tools) {
|
|
1587
|
-
for (let tool of session.tools) {
|
|
1588
|
-
if (tool.status === 'in_progress') count++;
|
|
1589
|
-
}
|
|
1590
|
-
}
|
|
1591
|
-
|
|
1592
|
-
// Count agents and their tools
|
|
1593
|
-
for (let agent of session.agents.values()) {
|
|
1594
|
-
if (agent.status === 'active') count++;
|
|
1595
|
-
if (agent.tools) {
|
|
1596
|
-
for (let tool of agent.tools) {
|
|
1597
|
-
if (tool.status === 'in_progress') count++;
|
|
1598
|
-
}
|
|
1599
|
-
}
|
|
1600
|
-
}
|
|
1601
|
-
}
|
|
1602
|
-
return count;
|
|
1603
|
-
}
|
|
1604
|
-
|
|
1605
|
-
/**
|
|
1606
|
-
* Calculate maximum depth
|
|
1607
|
-
*/
|
|
1608
|
-
calculateMaxDepth() {
|
|
1609
|
-
let maxDepth = 0; // No project root anymore
|
|
1610
|
-
for (let session of this.sessions.values()) {
|
|
1611
|
-
let sessionDepth = 1; // Session level (now root level)
|
|
1612
|
-
|
|
1613
|
-
// Check session content (instructions, todos, tools)
|
|
1614
|
-
if (session.userInstructions && session.userInstructions.length > 0) {
|
|
1615
|
-
sessionDepth = Math.max(sessionDepth, 2); // Instruction level
|
|
1616
|
-
}
|
|
1617
|
-
|
|
1618
|
-
if (session.todos && session.todos.length > 0) {
|
|
1619
|
-
sessionDepth = Math.max(sessionDepth, 3); // Todo checklist -> todo items
|
|
1620
|
-
}
|
|
1621
|
-
|
|
1622
|
-
if (session.tools && session.tools.length > 0) {
|
|
1623
|
-
sessionDepth = Math.max(sessionDepth, 2); // Tool level
|
|
1624
|
-
}
|
|
1625
|
-
|
|
1626
|
-
// Check agents
|
|
1627
|
-
for (let agent of session.agents.values()) {
|
|
1628
|
-
if (agent.tools && agent.tools.length > 0) {
|
|
1629
|
-
sessionDepth = Math.max(sessionDepth, 3); // Tool level under agents
|
|
1630
|
-
}
|
|
1631
|
-
}
|
|
1632
|
-
|
|
1633
|
-
maxDepth = Math.max(maxDepth, sessionDepth);
|
|
1634
|
-
}
|
|
1635
|
-
return maxDepth;
|
|
1636
|
-
}
|
|
1637
|
-
|
|
1638
|
-
/**
|
|
1639
|
-
* Toggle agent expansion
|
|
1640
|
-
*/
|
|
1641
|
-
toggleAgent(agentId) {
|
|
1642
|
-
if (this.expandedAgents.has(agentId)) {
|
|
1643
|
-
this.expandedAgents.delete(agentId);
|
|
1644
|
-
} else {
|
|
1645
|
-
this.expandedAgents.add(agentId);
|
|
1646
|
-
}
|
|
1647
|
-
this.renderTree();
|
|
1648
|
-
}
|
|
1649
|
-
|
|
1650
|
-
/**
|
|
1651
|
-
* Toggle tool expansion (deprecated - tools are no longer expandable)
|
|
1652
|
-
*/
|
|
1653
|
-
toggleTool(toolId) {
|
|
1654
|
-
// Tools are no longer expandable - this method is kept for compatibility
|
|
1655
|
-
console.log('Tool expansion is disabled. Tools now show data in the left pane when clicked.');
|
|
1656
|
-
}
|
|
1657
|
-
|
|
1658
|
-
/**
|
|
1659
|
-
* Toggle TODO checklist expansion
|
|
1660
|
-
*/
|
|
1661
|
-
toggleTodoChecklist(checklistId) {
|
|
1662
|
-
if (this.expandedTools.has(checklistId)) {
|
|
1663
|
-
this.expandedTools.delete(checklistId);
|
|
1664
|
-
} else {
|
|
1665
|
-
this.expandedTools.add(checklistId);
|
|
1666
|
-
}
|
|
1667
|
-
this.renderTree();
|
|
1668
|
-
}
|
|
1669
|
-
|
|
1670
|
-
/**
|
|
1671
|
-
* Render pinned TODOs element under agent
|
|
1672
|
-
*/
|
|
1673
|
-
renderPinnedTodosElement(pinnedTodos, level) {
|
|
1674
|
-
const checklistId = `pinned-todos-${Date.now()}`;
|
|
1675
|
-
const isExpanded = this.expandedTools.has(checklistId) !== false; // Default to expanded
|
|
1676
|
-
const expandIcon = isExpanded ? '▼' : '▶';
|
|
1677
|
-
const todos = pinnedTodos.todos || [];
|
|
1678
|
-
|
|
1679
|
-
// Calculate status summary
|
|
1680
|
-
let completedCount = 0;
|
|
1681
|
-
let inProgressCount = 0;
|
|
1682
|
-
let pendingCount = 0;
|
|
1683
|
-
|
|
1684
|
-
todos.forEach(todo => {
|
|
1685
|
-
if (todo.status === 'completed') completedCount++;
|
|
1686
|
-
else if (todo.status === 'in_progress') inProgressCount++;
|
|
1687
|
-
else pendingCount++;
|
|
1688
|
-
});
|
|
1689
|
-
|
|
1690
|
-
let statusSummary = '';
|
|
1691
|
-
if (inProgressCount > 0) {
|
|
1692
|
-
statusSummary = `${inProgressCount} in progress, ${completedCount} completed`;
|
|
1693
|
-
} else if (completedCount === todos.length && todos.length > 0) {
|
|
1694
|
-
statusSummary = `All ${todos.length} completed`;
|
|
1695
|
-
} else {
|
|
1696
|
-
statusSummary = `${todos.length} todo(s)`;
|
|
1697
|
-
}
|
|
1698
|
-
|
|
1699
|
-
let html = `
|
|
1700
|
-
<div class="tree-node pinned-todos" data-level="${level}">
|
|
1701
|
-
<div class="tree-node-content">
|
|
1702
|
-
<span class="tree-expand-icon" onclick="window.activityTreeInstance.toggleTodoChecklist('${checklistId}'); event.stopPropagation();">${expandIcon}</span>
|
|
1703
|
-
<span class="tree-icon">📌</span>
|
|
1704
|
-
<span class="tree-label">Pinned TODOs</span>
|
|
1705
|
-
<span class="tree-params">${statusSummary}</span>
|
|
1706
|
-
<span class="tree-status status-active">pinned</span>
|
|
1707
|
-
</div>
|
|
1708
|
-
`;
|
|
1709
|
-
|
|
1710
|
-
// Show expanded todo items if expanded
|
|
1711
|
-
if (isExpanded) {
|
|
1712
|
-
html += '<div class="tree-children">';
|
|
1713
|
-
for (let todo of todos) {
|
|
1714
|
-
const statusIcon = this.getCheckboxIcon(todo.status);
|
|
1715
|
-
const statusClass = `status-${todo.status}`;
|
|
1716
|
-
const displayText = todo.status === 'in_progress' ? todo.activeForm : todo.content;
|
|
1717
|
-
|
|
1718
|
-
html += `
|
|
1719
|
-
<div class="tree-node todo-item ${statusClass}" data-level="${level + 1}">
|
|
1720
|
-
<div class="tree-node-content">
|
|
1721
|
-
<span class="tree-expand-icon"></span>
|
|
1722
|
-
<span class="tree-icon">${statusIcon}</span>
|
|
1723
|
-
<span class="tree-label">${this.escapeHtml(displayText)}</span>
|
|
1724
|
-
<span class="tree-status ${statusClass}">${todo.status.replace('_', ' ')}</span>
|
|
1725
|
-
</div>
|
|
1726
|
-
</div>
|
|
1727
|
-
`;
|
|
1728
|
-
}
|
|
1729
|
-
html += '</div>';
|
|
1730
|
-
}
|
|
1731
|
-
|
|
1732
|
-
html += '</div>';
|
|
1733
|
-
return html;
|
|
1734
|
-
}
|
|
1735
|
-
|
|
1736
|
-
/**
|
|
1737
|
-
* Handle item click to show data in left pane
|
|
1738
|
-
*/
|
|
1739
|
-
selectItem(item, itemType, event) {
|
|
1740
|
-
// Stop event propagation to prevent expand/collapse when clicking on label
|
|
1741
|
-
if (event) {
|
|
1742
|
-
event.stopPropagation();
|
|
1743
|
-
}
|
|
1744
|
-
|
|
1745
|
-
this.selectedItem = { data: item, type: itemType };
|
|
1746
|
-
this.displayItemData(item, itemType);
|
|
1747
|
-
this.renderTree(); // Re-render to show selection highlight
|
|
1748
|
-
}
|
|
1749
|
-
|
|
1750
|
-
/**
|
|
1751
|
-
* Display item data in left pane using UnifiedDataViewer for consistency with Tools viewer
|
|
1752
|
-
*/
|
|
1753
|
-
displayItemData(item, itemType) {
|
|
1754
|
-
// Initialize UnifiedDataViewer if not already available
|
|
1755
|
-
if (!this.unifiedViewer) {
|
|
1756
|
-
this.unifiedViewer = new UnifiedDataViewer('module-data-content');
|
|
1757
|
-
}
|
|
1758
|
-
|
|
1759
|
-
// Use the same UnifiedDataViewer as Tools viewer for consistent display
|
|
1760
|
-
this.unifiedViewer.display(item, itemType);
|
|
1761
|
-
|
|
1762
|
-
// Update module header for consistency
|
|
1763
|
-
const moduleHeader = document.querySelector('.module-data-header h5');
|
|
1764
|
-
if (moduleHeader) {
|
|
1765
|
-
const icons = {
|
|
1766
|
-
'agent': '🤖',
|
|
1767
|
-
'tool': '🔧',
|
|
1768
|
-
'instruction': '💬',
|
|
1769
|
-
'session': '🎯',
|
|
1770
|
-
'todo': '📝'
|
|
1771
|
-
};
|
|
1772
|
-
const icon = icons[itemType] || '📊';
|
|
1773
|
-
const name = item.name || item.agentName || item.tool_name || 'Item';
|
|
1774
|
-
moduleHeader.textContent = `${icon} ${itemType}: ${name}`;
|
|
1775
|
-
}
|
|
1776
|
-
}
|
|
1777
|
-
|
|
1778
|
-
// Display methods removed - now using UnifiedDataViewer for consistency
|
|
1779
|
-
|
|
1780
|
-
/**
|
|
1781
|
-
* Escape HTML for safe display
|
|
1782
|
-
*/
|
|
1783
|
-
escapeHtml(text) {
|
|
1784
|
-
const div = document.createElement('div');
|
|
1785
|
-
div.textContent = text;
|
|
1786
|
-
return div.innerHTML;
|
|
1787
|
-
}
|
|
1788
|
-
|
|
1789
|
-
/**
|
|
1790
|
-
* Reset zoom and pan to initial state
|
|
1791
|
-
*/
|
|
1792
|
-
resetZoom() {
|
|
1793
|
-
if (this.svg && this.zoom) {
|
|
1794
|
-
this.svg.transition()
|
|
1795
|
-
.duration(this.duration)
|
|
1796
|
-
.call(this.zoom.transform, d3.zoomIdentity);
|
|
1797
|
-
}
|
|
1798
|
-
}
|
|
1799
|
-
|
|
1800
|
-
/**
|
|
1801
|
-
* Escape JSON for safe inclusion in HTML attributes
|
|
1802
|
-
*/
|
|
1803
|
-
escapeJson(obj) {
|
|
1804
|
-
return JSON.stringify(obj).replace(/'/g, ''').replace(/"/g, '"');
|
|
1805
|
-
}
|
|
1806
|
-
}
|
|
1807
|
-
|
|
1808
|
-
// Make ActivityTree globally available
|
|
1809
|
-
window.ActivityTree = ActivityTree;
|
|
1810
|
-
|
|
1811
|
-
// Initialize when the Activity tab is selected
|
|
1812
|
-
const setupActivityTreeListeners = () => {
|
|
1813
|
-
let activityTree = null;
|
|
1814
|
-
|
|
1815
|
-
const initializeActivityTree = () => {
|
|
1816
|
-
if (!activityTree) {
|
|
1817
|
-
console.log('Creating new Activity Tree instance...');
|
|
1818
|
-
activityTree = new ActivityTree();
|
|
1819
|
-
window.activityTreeInstance = activityTree;
|
|
1820
|
-
window.activityTree = () => activityTree; // For debugging
|
|
1821
|
-
}
|
|
1822
|
-
|
|
1823
|
-
setTimeout(() => {
|
|
1824
|
-
console.log('Attempting to initialize Activity Tree visualization...');
|
|
1825
|
-
activityTree.initialize();
|
|
1826
|
-
}, 100);
|
|
1827
|
-
};
|
|
1828
|
-
|
|
1829
|
-
// REMOVED: Conflicting tab click handlers that were interfering with UIStateManager
|
|
1830
|
-
// Tab switching is now handled entirely through the 'tabChanged' event listener below
|
|
1831
|
-
// This prevents conflicts with the UIStateManager's hash-based navigation system
|
|
1832
|
-
|
|
1833
|
-
// Listen for custom tab change events
|
|
1834
|
-
document.addEventListener('tabChanged', (e) => {
|
|
1835
|
-
if (e.detail && e.detail.newTab === 'activity') {
|
|
1836
|
-
console.log('Tab changed to activity, initializing tree...');
|
|
1837
|
-
initializeActivityTree();
|
|
1838
|
-
if (activityTree) {
|
|
1839
|
-
setTimeout(() => {
|
|
1840
|
-
activityTree.renderWhenVisible();
|
|
1841
|
-
activityTree.forceShow();
|
|
1842
|
-
}, 150);
|
|
1843
|
-
}
|
|
1844
|
-
}
|
|
1845
|
-
});
|
|
1846
|
-
|
|
1847
|
-
// Check if activity tab is already active on load
|
|
1848
|
-
const activeTab = document.querySelector('.tab-button.active');
|
|
1849
|
-
if (activeTab && activeTab.getAttribute('data-tab') === 'activity') {
|
|
1850
|
-
console.log('Activity tab is active on load, initializing tree...');
|
|
1851
|
-
initializeActivityTree();
|
|
1852
|
-
}
|
|
1853
|
-
|
|
1854
|
-
const activityPanel = document.getElementById('activity-tab');
|
|
1855
|
-
if (activityPanel && activityPanel.classList.contains('active')) {
|
|
1856
|
-
console.log('Activity panel is active on load, initializing tree...');
|
|
1857
|
-
if (!activityTree) {
|
|
1858
|
-
initializeActivityTree();
|
|
1859
|
-
}
|
|
1860
|
-
}
|
|
1861
|
-
};
|
|
1862
|
-
|
|
1863
|
-
// Set up listeners when DOM is ready
|
|
1864
|
-
if (document.readyState === 'loading') {
|
|
1865
|
-
document.addEventListener('DOMContentLoaded', setupActivityTreeListeners);
|
|
1866
|
-
} else {
|
|
1867
|
-
setupActivityTreeListeners();
|
|
1868
|
-
}
|
|
1869
|
-
|
|
1870
|
-
export { ActivityTree };
|
|
1871
|
-
export default ActivityTree;
|