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.

Files changed (191) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/__init__.py +4 -0
  3. claude_mpm/agents/{PM_INSTRUCTIONS_TEACH.md → CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md} +721 -41
  4. claude_mpm/agents/PM_INSTRUCTIONS.md +290 -34
  5. claude_mpm/agents/agent_loader.py +13 -44
  6. claude_mpm/agents/frontmatter_validator.py +68 -0
  7. claude_mpm/agents/templates/circuit-breakers.md +138 -1
  8. claude_mpm/cli/__main__.py +4 -0
  9. claude_mpm/cli/chrome_devtools_installer.py +175 -0
  10. claude_mpm/cli/commands/agent_state_manager.py +8 -17
  11. claude_mpm/cli/commands/agents.py +169 -31
  12. claude_mpm/cli/commands/auto_configure.py +210 -25
  13. claude_mpm/cli/commands/config.py +88 -2
  14. claude_mpm/cli/commands/configure.py +1111 -161
  15. claude_mpm/cli/commands/configure_agent_display.py +15 -6
  16. claude_mpm/cli/commands/mpm_init/core.py +160 -46
  17. claude_mpm/cli/commands/mpm_init/knowledge_extractor.py +481 -0
  18. claude_mpm/cli/commands/mpm_init/prompts.py +280 -0
  19. claude_mpm/cli/commands/skills.py +214 -189
  20. claude_mpm/cli/commands/summarize.py +413 -0
  21. claude_mpm/cli/executor.py +11 -3
  22. claude_mpm/cli/parsers/agents_parser.py +54 -9
  23. claude_mpm/cli/parsers/auto_configure_parser.py +0 -138
  24. claude_mpm/cli/parsers/base_parser.py +5 -0
  25. claude_mpm/cli/parsers/config_parser.py +153 -83
  26. claude_mpm/cli/parsers/skills_parser.py +3 -2
  27. claude_mpm/cli/startup.py +550 -94
  28. claude_mpm/commands/mpm-config.md +265 -0
  29. claude_mpm/commands/mpm-help.md +14 -95
  30. claude_mpm/commands/mpm-organize.md +500 -0
  31. claude_mpm/config/agent_sources.py +27 -0
  32. claude_mpm/core/framework/formatters/content_formatter.py +3 -13
  33. claude_mpm/core/framework/loaders/agent_loader.py +8 -5
  34. claude_mpm/core/framework_loader.py +4 -2
  35. claude_mpm/core/logger.py +13 -0
  36. claude_mpm/core/output_style_manager.py +173 -43
  37. claude_mpm/core/socketio_pool.py +3 -3
  38. claude_mpm/core/unified_agent_registry.py +134 -16
  39. claude_mpm/hooks/claude_hooks/correlation_manager.py +60 -0
  40. claude_mpm/hooks/claude_hooks/event_handlers.py +211 -78
  41. claude_mpm/hooks/claude_hooks/hook_handler.py +6 -0
  42. claude_mpm/hooks/claude_hooks/installer.py +33 -10
  43. claude_mpm/hooks/claude_hooks/memory_integration.py +26 -9
  44. claude_mpm/hooks/claude_hooks/response_tracking.py +2 -3
  45. claude_mpm/hooks/claude_hooks/services/connection_manager.py +4 -0
  46. claude_mpm/hooks/memory_integration_hook.py +46 -1
  47. claude_mpm/init.py +0 -19
  48. claude_mpm/models/agent_definition.py +7 -0
  49. claude_mpm/scripts/claude-hook-handler.sh +58 -18
  50. claude_mpm/scripts/launch_monitor.py +93 -13
  51. claude_mpm/scripts/start_activity_logging.py +0 -0
  52. claude_mpm/services/agents/agent_recommendation_service.py +278 -0
  53. claude_mpm/services/agents/agent_review_service.py +280 -0
  54. claude_mpm/services/agents/deployment/agent_discovery_service.py +2 -3
  55. claude_mpm/services/agents/deployment/agent_template_builder.py +4 -2
  56. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +188 -12
  57. claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +531 -55
  58. claude_mpm/services/agents/git_source_manager.py +34 -0
  59. claude_mpm/services/agents/loading/base_agent_manager.py +1 -13
  60. claude_mpm/services/agents/sources/git_source_sync_service.py +8 -1
  61. claude_mpm/services/agents/toolchain_detector.py +10 -6
  62. claude_mpm/services/analysis/__init__.py +11 -1
  63. claude_mpm/services/analysis/clone_detector.py +1030 -0
  64. claude_mpm/services/command_deployment_service.py +81 -10
  65. claude_mpm/services/event_bus/config.py +3 -1
  66. claude_mpm/services/git/git_operations_service.py +93 -8
  67. claude_mpm/services/monitor/daemon.py +9 -2
  68. claude_mpm/services/monitor/daemon_manager.py +39 -3
  69. claude_mpm/services/monitor/server.py +225 -19
  70. claude_mpm/services/self_upgrade_service.py +120 -12
  71. claude_mpm/services/skills/__init__.py +3 -0
  72. claude_mpm/services/skills/git_skill_source_manager.py +32 -2
  73. claude_mpm/services/skills/selective_skill_deployer.py +704 -0
  74. claude_mpm/services/skills/skill_to_agent_mapper.py +406 -0
  75. claude_mpm/services/skills_deployer.py +126 -9
  76. claude_mpm/services/socketio/event_normalizer.py +15 -1
  77. claude_mpm/services/socketio/server/core.py +160 -21
  78. claude_mpm/services/version_control/git_operations.py +103 -0
  79. claude_mpm/utils/agent_filters.py +17 -44
  80. {claude_mpm-5.1.8.dist-info → claude_mpm-5.4.22.dist-info}/METADATA +47 -84
  81. {claude_mpm-5.1.8.dist-info → claude_mpm-5.4.22.dist-info}/RECORD +86 -176
  82. claude_mpm-5.4.22.dist-info/entry_points.txt +5 -0
  83. claude_mpm-5.4.22.dist-info/licenses/LICENSE +94 -0
  84. claude_mpm-5.4.22.dist-info/licenses/LICENSE-FAQ.md +153 -0
  85. claude_mpm/agents/BASE_AGENT_TEMPLATE.md +0 -292
  86. claude_mpm/agents/BASE_DOCUMENTATION.md +0 -53
  87. claude_mpm/agents/BASE_ENGINEER.md +0 -658
  88. claude_mpm/agents/BASE_OPS.md +0 -219
  89. claude_mpm/agents/BASE_PM.md +0 -480
  90. claude_mpm/agents/BASE_PROMPT_ENGINEER.md +0 -787
  91. claude_mpm/agents/BASE_QA.md +0 -167
  92. claude_mpm/agents/BASE_RESEARCH.md +0 -53
  93. claude_mpm/agents/base_agent.json +0 -31
  94. claude_mpm/agents/base_agent_loader.py +0 -601
  95. claude_mpm/cli/commands/agents_detect.py +0 -380
  96. claude_mpm/cli/commands/agents_recommend.py +0 -309
  97. claude_mpm/cli/ticket_cli.py +0 -35
  98. claude_mpm/commands/mpm-agents-auto-configure.md +0 -278
  99. claude_mpm/commands/mpm-agents-detect.md +0 -177
  100. claude_mpm/commands/mpm-agents-list.md +0 -131
  101. claude_mpm/commands/mpm-agents-recommend.md +0 -223
  102. claude_mpm/commands/mpm-config-view.md +0 -150
  103. claude_mpm/commands/mpm-ticket-organize.md +0 -304
  104. claude_mpm/dashboard/analysis_runner.py +0 -455
  105. claude_mpm/dashboard/index.html +0 -13
  106. claude_mpm/dashboard/open_dashboard.py +0 -66
  107. claude_mpm/dashboard/static/css/activity.css +0 -1958
  108. claude_mpm/dashboard/static/css/connection-status.css +0 -370
  109. claude_mpm/dashboard/static/css/dashboard.css +0 -4701
  110. claude_mpm/dashboard/static/js/components/activity-tree.js +0 -1871
  111. claude_mpm/dashboard/static/js/components/agent-hierarchy.js +0 -777
  112. claude_mpm/dashboard/static/js/components/agent-inference.js +0 -956
  113. claude_mpm/dashboard/static/js/components/build-tracker.js +0 -333
  114. claude_mpm/dashboard/static/js/components/code-simple.js +0 -857
  115. claude_mpm/dashboard/static/js/components/connection-debug.js +0 -654
  116. claude_mpm/dashboard/static/js/components/diff-viewer.js +0 -891
  117. claude_mpm/dashboard/static/js/components/event-processor.js +0 -542
  118. claude_mpm/dashboard/static/js/components/event-viewer.js +0 -1155
  119. claude_mpm/dashboard/static/js/components/export-manager.js +0 -368
  120. claude_mpm/dashboard/static/js/components/file-change-tracker.js +0 -443
  121. claude_mpm/dashboard/static/js/components/file-change-viewer.js +0 -690
  122. claude_mpm/dashboard/static/js/components/file-tool-tracker.js +0 -724
  123. claude_mpm/dashboard/static/js/components/file-viewer.js +0 -580
  124. claude_mpm/dashboard/static/js/components/hud-library-loader.js +0 -211
  125. claude_mpm/dashboard/static/js/components/hud-manager.js +0 -671
  126. claude_mpm/dashboard/static/js/components/hud-visualizer.js +0 -1718
  127. claude_mpm/dashboard/static/js/components/module-viewer.js +0 -2764
  128. claude_mpm/dashboard/static/js/components/session-manager.js +0 -579
  129. claude_mpm/dashboard/static/js/components/socket-manager.js +0 -368
  130. claude_mpm/dashboard/static/js/components/ui-state-manager.js +0 -749
  131. claude_mpm/dashboard/static/js/components/unified-data-viewer.js +0 -1824
  132. claude_mpm/dashboard/static/js/components/working-directory.js +0 -920
  133. claude_mpm/dashboard/static/js/connection-manager.js +0 -536
  134. claude_mpm/dashboard/static/js/dashboard.js +0 -1914
  135. claude_mpm/dashboard/static/js/extension-error-handler.js +0 -164
  136. claude_mpm/dashboard/static/js/socket-client.js +0 -1474
  137. claude_mpm/dashboard/static/js/tab-isolation-fix.js +0 -185
  138. claude_mpm/dashboard/static/socket.io.min.js +0 -7
  139. claude_mpm/dashboard/static/socket.io.v4.8.1.backup.js +0 -7
  140. claude_mpm/dashboard/templates/code_simple.html +0 -153
  141. claude_mpm/dashboard/templates/index.html +0 -606
  142. claude_mpm/dashboard/test_dashboard.html +0 -372
  143. claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-313.pyc +0 -0
  144. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-313.pyc +0 -0
  145. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-313.pyc +0 -0
  146. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-313.pyc +0 -0
  147. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-313.pyc +0 -0
  148. claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-313.pyc +0 -0
  149. claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-313.pyc +0 -0
  150. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-313.pyc +0 -0
  151. claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-313.pyc +0 -0
  152. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-313.pyc +0 -0
  153. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-313.pyc +0 -0
  154. claude_mpm/scripts/mcp_server.py +0 -75
  155. claude_mpm/scripts/mcp_wrapper.py +0 -39
  156. claude_mpm/services/mcp_gateway/__init__.py +0 -159
  157. claude_mpm/services/mcp_gateway/auto_configure.py +0 -369
  158. claude_mpm/services/mcp_gateway/config/__init__.py +0 -17
  159. claude_mpm/services/mcp_gateway/config/config_loader.py +0 -296
  160. claude_mpm/services/mcp_gateway/config/config_schema.py +0 -243
  161. claude_mpm/services/mcp_gateway/config/configuration.py +0 -429
  162. claude_mpm/services/mcp_gateway/core/__init__.py +0 -43
  163. claude_mpm/services/mcp_gateway/core/base.py +0 -312
  164. claude_mpm/services/mcp_gateway/core/exceptions.py +0 -253
  165. claude_mpm/services/mcp_gateway/core/interfaces.py +0 -443
  166. claude_mpm/services/mcp_gateway/core/process_pool.py +0 -977
  167. claude_mpm/services/mcp_gateway/core/singleton_manager.py +0 -315
  168. claude_mpm/services/mcp_gateway/core/startup_verification.py +0 -316
  169. claude_mpm/services/mcp_gateway/main.py +0 -589
  170. claude_mpm/services/mcp_gateway/registry/__init__.py +0 -12
  171. claude_mpm/services/mcp_gateway/registry/service_registry.py +0 -412
  172. claude_mpm/services/mcp_gateway/registry/tool_registry.py +0 -489
  173. claude_mpm/services/mcp_gateway/server/__init__.py +0 -15
  174. claude_mpm/services/mcp_gateway/server/mcp_gateway.py +0 -414
  175. claude_mpm/services/mcp_gateway/server/stdio_handler.py +0 -372
  176. claude_mpm/services/mcp_gateway/server/stdio_server.py +0 -712
  177. claude_mpm/services/mcp_gateway/tools/__init__.py +0 -36
  178. claude_mpm/services/mcp_gateway/tools/base_adapter.py +0 -485
  179. claude_mpm/services/mcp_gateway/tools/document_summarizer.py +0 -789
  180. claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +0 -654
  181. claude_mpm/services/mcp_gateway/tools/health_check_tool.py +0 -456
  182. claude_mpm/services/mcp_gateway/tools/hello_world.py +0 -551
  183. claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +0 -555
  184. claude_mpm/services/mcp_gateway/utils/__init__.py +0 -14
  185. claude_mpm/services/mcp_gateway/utils/package_version_checker.py +0 -160
  186. claude_mpm/services/mcp_gateway/utils/update_preferences.py +0 -170
  187. claude_mpm-5.1.8.dist-info/entry_points.txt +0 -10
  188. claude_mpm-5.1.8.dist-info/licenses/LICENSE +0 -21
  189. /claude_mpm/agents/{OUTPUT_STYLE.md → CLAUDE_MPM_OUTPUT_STYLE.md} +0 -0
  190. {claude_mpm-5.1.8.dist-info → claude_mpm-5.4.22.dist-info}/WHEEL +0 -0
  191. {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, '&apos;').replace(/"/g, '&quot;');
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;