claude-mpm 4.1.15__py3-none-any.whl → 4.1.19__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.
@@ -3,13 +3,17 @@
3
3
  *
4
4
  * HTML/CSS-based linear tree visualization for showing PM activity hierarchy.
5
5
  * Replaces D3.js with simpler, cleaner linear tree structure.
6
- * Uses simple display methods for data visualization.
6
+ * Uses UnifiedDataViewer for consistent data display with Tools viewer.
7
7
  */
8
8
 
9
+ // Import UnifiedDataViewer for consistent data display
10
+ import { UnifiedDataViewer } from './unified-data-viewer.js';
11
+
9
12
  class ActivityTree {
10
13
  constructor() {
11
14
  this.container = null;
12
15
  this.events = [];
16
+ this.processedEventIds = new Set(); // Track which events we've already processed
13
17
  this.sessions = new Map();
14
18
  this.currentSession = null;
15
19
  this.selectedSessionFilter = 'all';
@@ -205,36 +209,50 @@ class ActivityTree {
205
209
  window.socketClient.onEventUpdate((events, sessions) => {
206
210
  console.log(`ActivityTree: onEventUpdate called with ${events.length} total events and ${sessions.size} sessions`);
207
211
 
208
- // Use the authoritative sessions from socket client instead of building our own
209
- this.sessions.clear();
210
-
211
- // Convert authoritative sessions Map to our format
212
+ // IMPORTANT: Don't clear sessions! We need to preserve the accumulated agent data
213
+ // Only create new sessions if they don't exist yet
212
214
  for (const [sessionId, sessionData] of sessions.entries()) {
213
- const activitySession = {
214
- id: sessionId,
215
- timestamp: new Date(sessionData.lastActivity || sessionData.startTime || new Date()),
216
- expanded: this.expandedSessions.has(sessionId) || true, // Preserve expansion state
217
- agents: new Map(),
218
- todos: [],
219
- userInstructions: [],
220
- tools: [],
221
- status: 'active',
222
- currentTodoTool: null,
223
- // Preserve additional session metadata
224
- working_directory: sessionData.working_directory,
225
- git_branch: sessionData.git_branch,
226
- eventCount: sessionData.eventCount
227
- };
228
- this.sessions.set(sessionId, activitySession);
215
+ if (!this.sessions.has(sessionId)) {
216
+ // Create new session only if it doesn't exist
217
+ const activitySession = {
218
+ id: sessionId,
219
+ timestamp: new Date(sessionData.lastActivity || sessionData.startTime || new Date()),
220
+ expanded: this.expandedSessions.has(sessionId) || true, // Preserve expansion state
221
+ agents: new Map(),
222
+ todos: [],
223
+ userInstructions: [],
224
+ tools: [],
225
+ toolsMap: new Map(),
226
+ todoWritesMap: new Map(),
227
+ status: 'active',
228
+ currentTodoTool: null,
229
+ // Preserve additional session metadata
230
+ working_directory: sessionData.working_directory,
231
+ git_branch: sessionData.git_branch,
232
+ eventCount: sessionData.eventCount
233
+ };
234
+ this.sessions.set(sessionId, activitySession);
235
+ } else {
236
+ // Update existing session metadata without clearing accumulated data
237
+ const existingSession = this.sessions.get(sessionId);
238
+ existingSession.timestamp = new Date(sessionData.lastActivity || sessionData.startTime || existingSession.timestamp);
239
+ existingSession.eventCount = sessionData.eventCount;
240
+ existingSession.status = sessionData.status || existingSession.status;
241
+ }
229
242
  }
230
243
 
231
- // Process only the new events since last update
232
- const newEventCount = events.length - this.events.length;
233
- if (newEventCount > 0) {
234
- const newEvents = events.slice(this.events.length);
235
- console.log(`ActivityTree: Processing ${newEventCount} new events`, newEvents);
244
+ // Process only events we haven't seen before
245
+ const newEvents = events.filter(event => {
246
+ const eventId = event.id || `${event.type}-${event.timestamp}-${Math.random()}`;
247
+ return !this.processedEventIds.has(eventId);
248
+ });
249
+
250
+ if (newEvents.length > 0) {
251
+ console.log(`ActivityTree: Processing ${newEvents.length} new events`, newEvents);
236
252
 
237
253
  newEvents.forEach(event => {
254
+ const eventId = event.id || `${event.type}-${event.timestamp}-${Math.random()}`;
255
+ this.processedEventIds.add(eventId);
238
256
  this.processEvent(event);
239
257
  });
240
258
  }
@@ -253,31 +271,46 @@ class ActivityTree {
253
271
  console.log(`ActivityTree: Loading existing data - ${socketState.events.length} events, ${socketState.sessions.size} sessions`);
254
272
 
255
273
  // Initialize from existing socket client data
256
- this.sessions.clear();
274
+ // Don't clear existing sessions - preserve accumulated data
257
275
 
258
276
  // Convert authoritative sessions Map to our format
259
277
  for (const [sessionId, sessionData] of socketState.sessions.entries()) {
260
- const activitySession = {
261
- id: sessionId,
262
- timestamp: new Date(sessionData.lastActivity || sessionData.startTime || new Date()),
263
- expanded: this.expandedSessions.has(sessionId) || true,
264
- agents: new Map(),
265
- todos: [],
266
- userInstructions: [],
267
- tools: [],
268
- status: 'active',
269
- currentTodoTool: null,
270
- working_directory: sessionData.working_directory,
271
- git_branch: sessionData.git_branch,
272
- eventCount: sessionData.eventCount
273
- };
274
- this.sessions.set(sessionId, activitySession);
278
+ if (!this.sessions.has(sessionId)) {
279
+ const activitySession = {
280
+ id: sessionId,
281
+ timestamp: new Date(sessionData.lastActivity || sessionData.startTime || new Date()),
282
+ expanded: this.expandedSessions.has(sessionId) || true,
283
+ agents: new Map(),
284
+ todos: [],
285
+ userInstructions: [],
286
+ tools: [],
287
+ toolsMap: new Map(),
288
+ todoWritesMap: new Map(),
289
+ status: 'active',
290
+ currentTodoTool: null,
291
+ working_directory: sessionData.working_directory,
292
+ git_branch: sessionData.git_branch,
293
+ eventCount: sessionData.eventCount
294
+ };
295
+ this.sessions.set(sessionId, activitySession);
296
+ }
275
297
  }
276
298
 
277
- // Process existing events to populate activity data
278
- socketState.events.forEach(event => {
279
- this.processEvent(event);
299
+ // Process only events we haven't seen before
300
+ const unprocessedEvents = socketState.events.filter(event => {
301
+ const eventId = event.id || `${event.type}-${event.timestamp}-${Math.random()}`;
302
+ return !this.processedEventIds.has(eventId);
280
303
  });
304
+
305
+ if (unprocessedEvents.length > 0) {
306
+ console.log(`ActivityTree: Processing ${unprocessedEvents.length} unprocessed events from initial load`);
307
+ unprocessedEvents.forEach(event => {
308
+ const eventId = event.id || `${event.type}-${event.timestamp}-${Math.random()}`;
309
+ this.processedEventIds.add(eventId);
310
+ this.processEvent(event);
311
+ });
312
+ }
313
+
281
314
  this.events = [...socketState.events];
282
315
  this.renderTree();
283
316
 
@@ -424,6 +457,24 @@ class ActivityTree {
424
457
  type: 'user_instruction'
425
458
  };
426
459
 
460
+ // NEW USER PROMPT: Only collapse agents if we have existing ones
461
+ // Don't clear - we want to keep the history!
462
+ if (session.agents.size > 0) {
463
+ console.log('ActivityTree: New user prompt detected, collapsing previous agents');
464
+
465
+ // Mark all existing agents as completed (not active)
466
+ for (let agent of session.agents.values()) {
467
+ if (agent.status === 'active') {
468
+ agent.status = 'completed';
469
+ }
470
+ // Collapse all existing agents
471
+ this.expandedAgents.delete(agent.id);
472
+ }
473
+ }
474
+
475
+ // Reset current active agent for new work
476
+ session.currentActiveAgent = null;
477
+
427
478
  // Add to session's user instructions
428
479
  session.userInstructions.push(instruction);
429
480
 
@@ -447,77 +498,104 @@ class ActivityTree {
447
498
  return;
448
499
  }
449
500
 
450
- // Update session's todos directly for overall checklist view
451
- session.todos = todos.map(todo => ({
501
+ // Update session's current todos for latest state tracking
502
+ session.currentTodos = todos.map(todo => ({
452
503
  content: todo.content,
453
504
  activeForm: todo.activeForm,
454
505
  status: todo.status,
455
506
  timestamp: event.timestamp
456
507
  }));
457
-
458
- // Create TodoWrite tool for session-level display
459
- const sessionTodoTool = {
460
- id: `todo-session-${session.id}-${Date.now()}`,
461
- name: 'TodoWrite',
462
- type: 'tool',
463
- icon: '📝',
464
- timestamp: event.timestamp,
465
- status: 'active',
466
- params: {
467
- todos: todos
468
- },
469
- isPrioritizedTool: true
470
- };
471
-
472
- // Update session-level TodoWrite tool
473
- session.tools = session.tools.filter(t => t.name !== 'TodoWrite');
474
- session.tools.unshift(sessionTodoTool);
475
- session.currentTodoTool = sessionTodoTool;
476
508
 
477
- // ALSO attach TodoWrite to the active agent that triggered it
478
- const agentSessionId = event.session_id || event.data?.session_id;
479
- let targetAgent = null;
480
-
481
509
  // Find the appropriate agent to attach this TodoWrite to
482
- // First try to find by session ID
483
- if (agentSessionId && session.agents.has(agentSessionId)) {
484
- targetAgent = session.agents.get(agentSessionId);
485
- } else {
510
+ let targetAgent = session.currentActiveAgent;
511
+
512
+ if (!targetAgent) {
486
513
  // Fall back to most recent active agent
487
- const activeAgents = Array.from(session.agents.values())
514
+ const activeAgents = this.getAllAgents(session)
488
515
  .filter(agent => agent.status === 'active' || agent.status === 'in_progress')
489
516
  .sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
490
517
 
491
518
  if (activeAgents.length > 0) {
492
519
  targetAgent = activeAgents[0];
493
520
  } else {
494
- // If no active agents, use the most recently used agent
495
- const allAgents = Array.from(session.agents.values())
496
- .sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
497
- if (allAgents.length > 0) {
521
+ // If no active agents, check if this is PM-level
522
+ const allAgents = this.getAllAgents(session);
523
+ const pmAgent = allAgents.find(a => a.isPM);
524
+ if (pmAgent) {
525
+ targetAgent = pmAgent;
526
+ } else if (allAgents.length > 0) {
498
527
  targetAgent = allAgents[0];
499
528
  }
500
529
  }
501
530
  }
502
531
 
503
- // Create agent-specific TodoWrite tool
532
+ // Attach or update TodoWrite for the agent
504
533
  if (targetAgent) {
505
- const agentTodoTool = {
506
- id: `todo-agent-${targetAgent.id}-${Date.now()}`,
507
- name: 'TodoWrite',
508
- type: 'tool',
509
- icon: '📝',
510
- timestamp: event.timestamp,
511
- status: 'active',
512
- params: {
513
- todos: todos
514
- },
515
- isPrioritizedTool: true
516
- };
534
+ if (!targetAgent.todoWritesMap) {
535
+ targetAgent.todoWritesMap = new Map();
536
+ }
537
+ if (!targetAgent.todoWrites) {
538
+ targetAgent.todoWrites = [];
539
+ }
517
540
 
518
- // Remove existing TodoWrite tool from agent and add the updated one
519
- targetAgent.tools = targetAgent.tools.filter(t => t.name !== 'TodoWrite');
520
- targetAgent.tools.unshift(agentTodoTool);
541
+ // Check if we already have a TodoWrite instance
542
+ const existingTodoWrite = targetAgent.todoWritesMap.get('TodoWrite');
543
+
544
+ if (existingTodoWrite) {
545
+ // Update existing TodoWrite instance
546
+ existingTodoWrite.todos = todos;
547
+ existingTodoWrite.timestamp = event.timestamp;
548
+ existingTodoWrite.updateCount = (existingTodoWrite.updateCount || 1) + 1;
549
+ } else {
550
+ // Create new TodoWrite instance
551
+ const todoWriteInstance = {
552
+ id: `todowrite-${targetAgent.id}-${Date.now()}`,
553
+ name: 'TodoWrite',
554
+ type: 'todowrite',
555
+ icon: '📝',
556
+ timestamp: event.timestamp,
557
+ status: 'completed',
558
+ todos: todos,
559
+ params: {
560
+ todos: todos
561
+ },
562
+ updateCount: 1
563
+ };
564
+
565
+ targetAgent.todoWritesMap.set('TodoWrite', todoWriteInstance);
566
+ targetAgent.todoWrites = [todoWriteInstance]; // Keep single instance
567
+ }
568
+
569
+ // Update agent's current todos for display when collapsed
570
+ targetAgent.currentTodos = todos;
571
+ } else {
572
+ // No agent found, attach to session level
573
+ if (!session.todoWrites) {
574
+ session.todoWrites = [];
575
+ }
576
+ if (!session.todoWritesMap) {
577
+ session.todoWritesMap = new Map();
578
+ }
579
+
580
+ const existingTodoWrite = session.todoWritesMap.get('TodoWrite');
581
+ if (existingTodoWrite) {
582
+ existingTodoWrite.todos = todos;
583
+ existingTodoWrite.timestamp = event.timestamp;
584
+ existingTodoWrite.updateCount = (existingTodoWrite.updateCount || 1) + 1;
585
+ } else {
586
+ const todoWriteInstance = {
587
+ id: `todowrite-session-${Date.now()}`,
588
+ name: 'TodoWrite',
589
+ type: 'todowrite',
590
+ icon: '📝',
591
+ timestamp: event.timestamp,
592
+ status: 'completed',
593
+ todos: todos,
594
+ updateCount: 1
595
+ };
596
+ session.todoWritesMap.set('TodoWrite', todoWriteInstance);
597
+ session.todoWrites = [todoWriteInstance];
598
+ }
521
599
  }
522
600
  }
523
601
 
@@ -527,13 +605,33 @@ class ActivityTree {
527
605
  processSubagentStart(event, session) {
528
606
  const agentName = event.agent_name || event.data?.agent_name || event.data?.agent_type || event.agent_type || event.agent || 'unknown';
529
607
  const agentSessionId = event.session_id || event.data?.session_id;
530
-
531
- // Use session ID as unique agent identifier, or create unique ID
532
- const agentId = agentSessionId || `agent-${Date.now()}-${Math.random()}`;
533
-
534
- // Check if agent already exists in this session
535
- if (!session.agents.has(agentId)) {
536
- const agent = {
608
+ const parentAgent = event.parent_agent || event.data?.parent_agent;
609
+
610
+ // Use a composite key based on agent name and session to find existing instances
611
+ // This ensures we track unique agent instances per session
612
+ const agentKey = `${agentName}-${agentSessionId || 'no-session'}`;
613
+
614
+ // Check if this exact agent already exists (same name and session)
615
+ let existingAgent = null;
616
+ const allAgents = this.getAllAgents(session);
617
+ existingAgent = allAgents.find(a =>
618
+ a.name === agentName &&
619
+ a.sessionId === agentSessionId &&
620
+ a.status === 'active' // Only reuse if still active
621
+ );
622
+
623
+ let agent;
624
+ if (existingAgent) {
625
+ // Update existing active agent
626
+ agent = existingAgent;
627
+ agent.timestamp = event.timestamp;
628
+ agent.instanceCount = (agent.instanceCount || 1) + 1;
629
+ // Auto-expand the active agent
630
+ this.expandedAgents.add(agent.id);
631
+ } else {
632
+ // Create new agent instance for first occurrence
633
+ const agentId = `agent-${agentKey}-${Date.now()}`;
634
+ agent = {
537
635
  id: agentId,
538
636
  name: agentName,
539
637
  type: 'agent',
@@ -541,17 +639,48 @@ class ActivityTree {
541
639
  timestamp: event.timestamp,
542
640
  status: 'active',
543
641
  tools: [],
642
+ todoWrites: [], // Store TodoWrite instances
643
+ subagents: new Map(), // Store nested subagents
544
644
  sessionId: agentSessionId,
545
- isPM: false
645
+ parentAgent: parentAgent,
646
+ isPM: agentName.toLowerCase() === 'pm' || agentName.toLowerCase().includes('project manager'),
647
+ instanceCount: 1,
648
+ toolsMap: new Map(), // Track unique tools by name
649
+ todoWritesMap: new Map() // Track unique TodoWrites
546
650
  };
547
651
 
548
- session.agents.set(agentId, agent);
549
- } else {
550
- // Update existing agent status to active
551
- const existingAgent = session.agents.get(agentId);
552
- existingAgent.status = 'active';
553
- existingAgent.timestamp = event.timestamp; // Update timestamp
652
+ // If this is a subagent, nest it under the parent agent
653
+ if (parentAgent) {
654
+ // Find the parent agent in the session
655
+ let parent = null;
656
+ for (let [id, ag] of session.agents.entries()) {
657
+ if (ag.sessionId === parentAgent || ag.name === parentAgent) {
658
+ parent = ag;
659
+ break;
660
+ }
661
+ }
662
+
663
+ if (parent) {
664
+ // Add as nested subagent
665
+ if (!parent.subagents) {
666
+ parent.subagents = new Map();
667
+ }
668
+ parent.subagents.set(agent.id, agent);
669
+ } else {
670
+ // No parent found, add to session level
671
+ session.agents.set(agent.id, agent);
672
+ }
673
+ } else {
674
+ // Top-level agent, add to session
675
+ session.agents.set(agent.id, agent);
676
+ }
677
+
678
+ // Auto-expand new agents
679
+ this.expandedAgents.add(agent.id);
554
680
  }
681
+
682
+ // Track the currently active agent for tool/todo association
683
+ session.currentActiveAgent = agent;
555
684
  }
556
685
 
557
686
  /**
@@ -574,67 +703,149 @@ class ActivityTree {
574
703
  const toolName = event.tool_name || event.data?.tool_name || event.tool || event.data?.tool || 'unknown';
575
704
  const params = event.tool_parameters || event.data?.tool_parameters || event.parameters || event.data?.parameters || {};
576
705
  const agentSessionId = event.session_id || event.data?.session_id;
577
-
578
- const tool = {
579
- id: `tool-${Date.now()}-${Math.random()}`,
580
- name: toolName,
581
- type: 'tool',
582
- icon: this.getToolIcon(toolName),
583
- timestamp: event.timestamp,
584
- status: 'in_progress',
585
- params: params,
586
- eventId: event.id
587
- };
588
706
 
589
707
  // Find the appropriate agent to attach this tool to
590
- let targetAgent = null;
708
+ let targetAgent = session.currentActiveAgent;
591
709
 
592
- // First try to find by session ID
593
- if (agentSessionId && session.agents.has(agentSessionId)) {
594
- targetAgent = session.agents.get(agentSessionId);
710
+ if (!targetAgent) {
711
+ // Fall back to finding by session ID or most recent active
712
+ const allAgents = this.getAllAgents(session);
713
+ targetAgent = allAgents.find(a => a.sessionId === agentSessionId) ||
714
+ allAgents.find(a => a.status === 'active') ||
715
+ allAgents[0];
716
+ }
717
+
718
+ if (targetAgent) {
719
+ if (!targetAgent.toolsMap) {
720
+ targetAgent.toolsMap = new Map();
721
+ }
722
+ if (!targetAgent.tools) {
723
+ targetAgent.tools = [];
724
+ }
725
+
726
+ // Check if we already have this tool type
727
+ let existingTool = targetAgent.toolsMap.get(toolName);
728
+
729
+ if (existingTool) {
730
+ // Update existing tool instance
731
+ existingTool.params = params;
732
+ existingTool.timestamp = event.timestamp;
733
+ existingTool.status = 'in_progress';
734
+ existingTool.eventId = event.id;
735
+ existingTool.callCount = (existingTool.callCount || 1) + 1;
736
+
737
+ // Update current tool for collapsed display
738
+ targetAgent.currentTool = existingTool;
739
+ } else {
740
+ // Create new tool instance
741
+ const tool = {
742
+ id: `tool-${targetAgent.id}-${toolName}-${Date.now()}`,
743
+ name: toolName,
744
+ type: 'tool',
745
+ icon: this.getToolIcon(toolName),
746
+ timestamp: event.timestamp,
747
+ status: 'in_progress',
748
+ params: params,
749
+ eventId: event.id,
750
+ callCount: 1
751
+ };
752
+
753
+ // Special handling for Task tool (subagent delegation)
754
+ if (toolName === 'Task' && params.subagent_type) {
755
+ tool.isSubagentTask = true;
756
+ tool.subagentType = params.subagent_type;
757
+ }
758
+
759
+ targetAgent.toolsMap.set(toolName, tool);
760
+ targetAgent.tools.push(tool);
761
+ targetAgent.currentTool = tool;
762
+ }
595
763
  } else {
596
- // Fall back to most recent active agent
597
- const activeAgents = Array.from(session.agents.values())
598
- .filter(agent => agent.status === 'active')
599
- .sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
764
+ // No agent found, attach to session (PM level)
765
+ if (!session.tools) {
766
+ session.tools = [];
767
+ }
768
+ if (!session.toolsMap) {
769
+ session.toolsMap = new Map();
770
+ }
600
771
 
601
- if (activeAgents.length > 0) {
602
- targetAgent = activeAgents[0];
772
+ let existingTool = session.toolsMap.get(toolName);
773
+
774
+ if (existingTool) {
775
+ existingTool.params = params;
776
+ existingTool.timestamp = event.timestamp;
777
+ existingTool.status = 'in_progress';
778
+ existingTool.eventId = event.id;
779
+ existingTool.callCount = (existingTool.callCount || 1) + 1;
780
+ session.currentTool = existingTool;
603
781
  } else {
604
- // If no active agents, attach to session (PM level)
782
+ const tool = {
783
+ id: `tool-session-${toolName}-${Date.now()}`,
784
+ name: toolName,
785
+ type: 'tool',
786
+ icon: this.getToolIcon(toolName),
787
+ timestamp: event.timestamp,
788
+ status: 'in_progress',
789
+ params: params,
790
+ eventId: event.id,
791
+ callCount: 1
792
+ };
793
+
794
+ session.toolsMap.set(toolName, tool);
605
795
  session.tools.push(tool);
606
- return;
796
+ session.currentTool = tool;
607
797
  }
608
798
  }
609
-
610
- if (targetAgent) {
611
- targetAgent.tools.push(tool);
612
- }
613
799
  }
614
800
 
615
801
  /**
616
802
  * Update tool status after completion
617
803
  */
618
804
  updateToolStatus(event, session, status) {
619
- // Find and update tool status across all agents
620
- const findAndUpdateTool = (agent) => {
621
- if (agent.tools) {
622
- const tool = agent.tools.find(t => t.eventId === event.id);
623
- if (tool) {
624
- tool.status = status;
625
- return true;
805
+ const toolName = event.tool_name || event.data?.tool_name || event.tool || 'unknown';
806
+ const agentSessionId = event.session_id || event.data?.session_id;
807
+
808
+ // Find the appropriate agent
809
+ let targetAgent = session.currentActiveAgent;
810
+
811
+ if (!targetAgent) {
812
+ const allAgents = this.getAllAgents(session);
813
+ targetAgent = allAgents.find(a => a.sessionId === agentSessionId) ||
814
+ allAgents.find(a => a.status === 'active');
815
+ }
816
+
817
+ if (targetAgent && targetAgent.toolsMap) {
818
+ const tool = targetAgent.toolsMap.get(toolName);
819
+ if (tool) {
820
+ tool.status = status;
821
+ tool.completedAt = event.timestamp;
822
+ if (event.data?.result || event.result) {
823
+ tool.result = event.data?.result || event.result;
824
+ }
825
+ if (event.data?.duration_ms) {
826
+ tool.duration = event.data.duration_ms;
626
827
  }
828
+ return;
829
+ }
830
+ }
831
+
832
+ // Check session-level tools
833
+ if (session.toolsMap) {
834
+ const tool = session.toolsMap.get(toolName);
835
+ if (tool) {
836
+ tool.status = status;
837
+ tool.completedAt = event.timestamp;
838
+ if (event.data?.result || event.result) {
839
+ tool.result = event.data?.result || event.result;
840
+ }
841
+ if (event.data?.duration_ms) {
842
+ tool.duration = event.data.duration_ms;
843
+ }
844
+ return;
627
845
  }
628
- return false;
629
- };
630
-
631
- // Check all agents in session
632
- for (let agent of session.agents.values()) {
633
- if (findAndUpdateTool(agent)) return;
634
846
  }
635
847
 
636
- // Check session-level tools (PM level)
637
- if (session.tools && findAndUpdateTool(session)) return;
848
+ console.log(`ActivityTree: Could not find tool to update status for ${toolName} (event ${event.id})`);
638
849
  }
639
850
 
640
851
  /**
@@ -690,8 +901,9 @@ class ActivityTree {
690
901
  element.dataset.sessionId = session.id;
691
902
 
692
903
  const expandIcon = isExpanded ? '▼' : '▶';
693
- const agentCount = session.agents ? session.agents.size : 0;
694
- const todoCount = session.todos ? session.todos.length : 0;
904
+ // Count ALL agents including nested ones
905
+ const agentCount = this.getAllAgents(session).length;
906
+ const todoCount = session.currentTodos ? session.currentTodos.length : 0;
695
907
  const instructionCount = session.userInstructions ? session.userInstructions.length : 0;
696
908
 
697
909
  console.log(`ActivityTree: Rendering session ${session.id}: ${agentCount} agents, ${instructionCount} instructions, ${todoCount} todos at ${sessionTime}`);
@@ -724,20 +936,13 @@ class ActivityTree {
724
936
  }
725
937
  }
726
938
 
727
- // Render TODOs as checklist directly under session
728
- if (session.todos && session.todos.length > 0) {
729
- html += this.renderTodoChecklistElement(session.todos, 1);
939
+ // Render session-level TodoWrite FIRST (if exists)
940
+ if (session.todoWrites && session.todoWrites.length > 0) {
941
+ // Show the first (and should be only) TodoWrite at session level
942
+ html += this.renderTodoWriteElement(session.todoWrites[0], 1);
730
943
  }
731
944
 
732
- // Render session-level tools (PM tools)
733
- if (session.tools && session.tools.length > 0) {
734
- for (let tool of session.tools) {
735
- // Show all tools including TodoWrite - both checklist and tool views are useful
736
- html += this.renderToolElement(tool, 1);
737
- }
738
- }
739
-
740
- // Render agents
945
+ // Render agents (they will have their own TodoWrite)
741
946
  const agents = Array.from(session.agents.values())
742
947
  .sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
743
948
 
@@ -745,6 +950,15 @@ class ActivityTree {
745
950
  html += this.renderAgentElement(agent, 1);
746
951
  }
747
952
 
953
+ // Render session-level tools LAST (excluding TodoWrite since we show it first)
954
+ if (session.tools && session.tools.length > 0) {
955
+ for (let tool of session.tools) {
956
+ if (tool.name !== 'TodoWrite') {
957
+ html += this.renderToolElement(tool, 1);
958
+ }
959
+ }
960
+ }
961
+
748
962
  return html;
749
963
  }
750
964
 
@@ -833,33 +1047,79 @@ class ActivityTree {
833
1047
  }
834
1048
 
835
1049
  /**
836
- * Render agent element
1050
+ * Render agent element with proper nesting
837
1051
  */
838
1052
  renderAgentElement(agent, level) {
839
1053
  const statusClass = agent.status === 'active' ? 'status-active' : 'status-completed';
840
1054
  const isExpanded = this.expandedAgents.has(agent.id);
1055
+ const hasTodoWrites = agent.todoWrites && agent.todoWrites.length > 0;
841
1056
  const hasTools = agent.tools && agent.tools.length > 0;
1057
+ const hasSubagents = agent.subagents && agent.subagents.size > 0;
1058
+ const hasContent = hasTodoWrites || hasTools || hasSubagents;
842
1059
  const isSelected = this.selectedItem && this.selectedItem.type === 'agent' && this.selectedItem.data.id === agent.id;
843
1060
 
844
- const expandIcon = hasTools ? (isExpanded ? '▼' : '▶') : '';
1061
+ const expandIcon = hasContent ? (isExpanded ? '▼' : '▶') : '';
845
1062
  const selectedClass = isSelected ? 'selected' : '';
846
1063
 
1064
+ // Add instance count if called multiple times
1065
+ const instanceIndicator = agent.instanceCount > 1 ? ` (${agent.instanceCount}x)` : '';
1066
+
1067
+ // Build status display for collapsed state
1068
+ let collapsedStatus = '';
1069
+ if (!isExpanded && hasContent) {
1070
+ const parts = [];
1071
+ if (agent.currentTodos && agent.currentTodos.length > 0) {
1072
+ const inProgress = agent.currentTodos.find(t => t.status === 'in_progress');
1073
+ if (inProgress) {
1074
+ parts.push(`📝 ${inProgress.activeForm || inProgress.content}`);
1075
+ }
1076
+ }
1077
+ if (agent.currentTool) {
1078
+ parts.push(`${agent.currentTool.icon} ${agent.currentTool.name}`);
1079
+ }
1080
+ if (parts.length > 0) {
1081
+ collapsedStatus = ` • ${parts.join(' • ')}`;
1082
+ }
1083
+ }
1084
+
847
1085
  let html = `
848
1086
  <div class="tree-node agent ${statusClass} ${selectedClass}" data-level="${level}">
849
1087
  <div class="tree-node-content">
850
1088
  ${expandIcon ? `<span class="tree-expand-icon" onclick="window.activityTreeInstance.toggleAgent('${agent.id}'); event.stopPropagation();">${expandIcon}</span>` : '<span class="tree-expand-icon"></span>'}
851
1089
  <span class="tree-icon">${agent.icon}</span>
852
- <span class="tree-label clickable" onclick="window.activityTreeInstance.selectItem(${this.escapeJson(agent)}, 'agent', event)">${agent.name}</span>
1090
+ <span class="tree-label clickable" onclick="window.activityTreeInstance.selectItem(${this.escapeJson(agent)}, 'agent', event)">${agent.name}${instanceIndicator}${collapsedStatus}</span>
853
1091
  <span class="tree-status ${statusClass}">${agent.status}</span>
854
1092
  </div>
855
1093
  `;
856
1094
 
857
- // Render tools under this agent
858
- if (hasTools && isExpanded) {
1095
+ // Render nested content when expanded
1096
+ if (hasContent && isExpanded) {
859
1097
  html += '<div class="tree-children">';
860
- for (let tool of agent.tools) {
861
- html += this.renderToolElement(tool, level + 1);
1098
+
1099
+ // ALWAYS render TodoWrite FIRST (single instance)
1100
+ if (hasTodoWrites) {
1101
+ // Only render the first (and should be only) TodoWrite instance
1102
+ html += this.renderTodoWriteElement(agent.todoWrites[0], level + 1);
1103
+ }
1104
+
1105
+ // Then render subagents (they will have their own TodoWrite at the top)
1106
+ if (hasSubagents) {
1107
+ const subagents = Array.from(agent.subagents.values());
1108
+ for (let subagent of subagents) {
1109
+ html += this.renderAgentElement(subagent, level + 1);
1110
+ }
862
1111
  }
1112
+
1113
+ // Finally render other tools (excluding TodoWrite)
1114
+ if (hasTools) {
1115
+ for (let tool of agent.tools) {
1116
+ // Skip TodoWrite tools since we show them separately at the top
1117
+ if (tool.name !== 'TodoWrite') {
1118
+ html += this.renderToolElement(tool, level + 1);
1119
+ }
1120
+ }
1121
+ }
1122
+
863
1123
  html += '</div>';
864
1124
  }
865
1125
 
@@ -876,14 +1136,22 @@ class ActivityTree {
876
1136
  const isSelected = this.selectedItem && this.selectedItem.type === 'tool' && this.selectedItem.data.id === tool.id;
877
1137
  const selectedClass = isSelected ? 'selected' : '';
878
1138
 
1139
+ // Add visual status indicators
1140
+ const statusIcon = this.getToolStatusIcon(tool.status);
1141
+ const statusLabel = this.getToolStatusLabel(tool.status);
1142
+
1143
+ // Add call count if more than 1
1144
+ const callIndicator = tool.callCount > 1 ? ` (${tool.callCount} calls)` : '';
1145
+
879
1146
  let html = `
880
1147
  <div class="tree-node tool ${statusClass} ${selectedClass}" data-level="${level}">
881
1148
  <div class="tree-node-content">
882
1149
  <span class="tree-expand-icon"></span>
883
1150
  <span class="tree-icon">${tool.icon}</span>
884
- <span class="tree-label clickable" onclick="window.activityTreeInstance.selectItem(${this.escapeJson(tool)}, 'tool', event)">${tool.name} (click to view details)</span>
1151
+ <span class="tree-status-icon">${statusIcon}</span>
1152
+ <span class="tree-label clickable" onclick="window.activityTreeInstance.selectItem(${this.escapeJson(tool)}, 'tool', event)">${tool.name}${callIndicator}</span>
885
1153
  <span class="tree-params">${params}</span>
886
- <span class="tree-status ${statusClass}">${tool.status}</span>
1154
+ <span class="tree-status ${statusClass}">${statusLabel}</span>
887
1155
  </div>
888
1156
  </div>
889
1157
  `;
@@ -951,11 +1219,119 @@ class ActivityTree {
951
1219
  'qa': '🧪',
952
1220
  'ops': '⚙️',
953
1221
  'pm': '📊',
954
- 'architect': '🏗️'
1222
+ 'architect': '🏗️',
1223
+ 'project manager': '📊'
955
1224
  };
956
1225
  return icons[agentName.toLowerCase()] || '🤖';
957
1226
  }
958
1227
 
1228
+ /**
1229
+ * Helper to get all agents including nested subagents
1230
+ */
1231
+ getAllAgents(session) {
1232
+ const agents = [];
1233
+
1234
+ const collectAgents = (agentMap) => {
1235
+ if (!agentMap) return;
1236
+
1237
+ for (let agent of agentMap.values()) {
1238
+ agents.push(agent);
1239
+ if (agent.subagents && agent.subagents.size > 0) {
1240
+ collectAgents(agent.subagents);
1241
+ }
1242
+ }
1243
+ };
1244
+
1245
+ collectAgents(session.agents);
1246
+ return agents;
1247
+ }
1248
+
1249
+ /**
1250
+ * Render TodoWrite element
1251
+ */
1252
+ renderTodoWriteElement(todoWrite, level) {
1253
+ const todoWriteId = todoWrite.id;
1254
+ const isExpanded = this.expandedTools.has(todoWriteId);
1255
+ const expandIcon = isExpanded ? '▼' : '▶';
1256
+ const todos = todoWrite.todos || [];
1257
+
1258
+ // Calculate status summary
1259
+ let completedCount = 0;
1260
+ let inProgressCount = 0;
1261
+ let pendingCount = 0;
1262
+
1263
+ todos.forEach(todo => {
1264
+ if (todo.status === 'completed') completedCount++;
1265
+ else if (todo.status === 'in_progress') inProgressCount++;
1266
+ else pendingCount++;
1267
+ });
1268
+
1269
+ // Find current in-progress todo for highlighting
1270
+ const currentTodo = todos.find(t => t.status === 'in_progress');
1271
+ const currentIndicator = currentTodo ? ` • 🔄 ${currentTodo.activeForm || currentTodo.content}` : '';
1272
+
1273
+ let statusSummary = '';
1274
+ if (inProgressCount > 0) {
1275
+ statusSummary = `${inProgressCount} in progress, ${completedCount}/${todos.length} done`;
1276
+ } else if (completedCount === todos.length && todos.length > 0) {
1277
+ statusSummary = `All ${todos.length} completed ✅`;
1278
+ } else {
1279
+ statusSummary = `${completedCount}/${todos.length} done`;
1280
+ }
1281
+
1282
+ // Add update count if more than 1
1283
+ const updateIndicator = todoWrite.updateCount > 1 ? ` (${todoWrite.updateCount} updates)` : '';
1284
+
1285
+ let html = `
1286
+ <div class="tree-node todowrite ${currentTodo ? 'has-active' : ''}" data-level="${level}">
1287
+ <div class="tree-node-content">
1288
+ <span class="tree-expand-icon" onclick="window.activityTreeInstance.toggleTodoWrite('${todoWriteId}'); event.stopPropagation();">${expandIcon}</span>
1289
+ <span class="tree-icon">📝</span>
1290
+ <span class="tree-label">TodoWrite${updateIndicator}${!isExpanded ? currentIndicator : ''}</span>
1291
+ <span class="tree-params">${statusSummary}</span>
1292
+ <span class="tree-status status-active">todos</span>
1293
+ </div>
1294
+ `;
1295
+
1296
+ // Show expanded todo items if expanded
1297
+ if (isExpanded && todos.length > 0) {
1298
+ html += '<div class="tree-children">';
1299
+ for (let todo of todos) {
1300
+ const statusIcon = this.getCheckboxIcon(todo.status);
1301
+ const statusClass = `status-${todo.status}`;
1302
+ const displayText = todo.status === 'in_progress' ? todo.activeForm : todo.content;
1303
+ const isCurrentTodo = todo === currentTodo;
1304
+
1305
+ html += `
1306
+ <div class="tree-node todo-item ${statusClass} ${isCurrentTodo ? 'current-active' : ''}" data-level="${level + 1}">
1307
+ <div class="tree-node-content">
1308
+ <span class="tree-expand-icon"></span>
1309
+ <span class="tree-icon">${statusIcon}</span>
1310
+ <span class="tree-label">${this.escapeHtml(displayText)}</span>
1311
+ <span class="tree-status ${statusClass}">${todo.status.replace('_', ' ')}</span>
1312
+ </div>
1313
+ </div>
1314
+ `;
1315
+ }
1316
+ html += '</div>';
1317
+ }
1318
+
1319
+ html += '</div>';
1320
+ return html;
1321
+ }
1322
+
1323
+ /**
1324
+ * Toggle TodoWrite expansion
1325
+ */
1326
+ toggleTodoWrite(todoWriteId) {
1327
+ if (this.expandedTools.has(todoWriteId)) {
1328
+ this.expandedTools.delete(todoWriteId);
1329
+ } else {
1330
+ this.expandedTools.add(todoWriteId);
1331
+ }
1332
+ this.renderTree();
1333
+ }
1334
+
959
1335
  /**
960
1336
  * Get tool icon based on name
961
1337
  */
@@ -973,6 +1349,36 @@ class ActivityTree {
973
1349
  return icons[toolName.toLowerCase()] || '🔧';
974
1350
  }
975
1351
 
1352
+ /**
1353
+ * Get status icon for tool status
1354
+ */
1355
+ getToolStatusIcon(status) {
1356
+ const icons = {
1357
+ 'in_progress': '⏳',
1358
+ 'completed': '✅',
1359
+ 'failed': '❌',
1360
+ 'error': '❌',
1361
+ 'pending': '⏸️',
1362
+ 'active': '🔄'
1363
+ };
1364
+ return icons[status] || '❓';
1365
+ }
1366
+
1367
+ /**
1368
+ * Get formatted status label for tool
1369
+ */
1370
+ getToolStatusLabel(status) {
1371
+ const labels = {
1372
+ 'in_progress': 'in progress',
1373
+ 'completed': 'completed',
1374
+ 'failed': 'failed',
1375
+ 'error': 'error',
1376
+ 'pending': 'pending',
1377
+ 'active': 'active'
1378
+ };
1379
+ return labels[status] || status;
1380
+ }
1381
+
976
1382
  /**
977
1383
  * Toggle session expansion
978
1384
  */
@@ -1171,66 +1577,13 @@ class ActivityTree {
1171
1577
  }
1172
1578
 
1173
1579
  /**
1174
- * Handle item click to show data in left pane
1175
- */
1176
- selectItem(item, itemType, event) {
1177
- // Stop event propagation to prevent expand/collapse when clicking on label
1178
- if (event) {
1179
- event.stopPropagation();
1180
- }
1181
-
1182
- this.selectedItem = { data: item, type: itemType };
1183
- this.displayItemData(item, itemType);
1184
- this.renderTree(); // Re-render to show selection highlight
1185
- }
1186
-
1187
- /**
1188
- * Display item data in left pane using simple display methods
1189
- */
1190
- displayItemData(item, itemType) {
1191
- // Special handling for TodoWrite tools to match Tools view display
1192
- if (itemType === 'tool' && item.name === 'TodoWrite' && item.params && item.params.todos) {
1193
- this.displayTodoWriteData(item);
1194
- return;
1195
- }
1196
-
1197
- // Use simple display methods based on item type
1198
- switch(itemType) {
1199
- case 'agent':
1200
- this.displayAgentData(item);
1201
- break;
1202
- case 'tool':
1203
- this.displayToolData(item);
1204
- break;
1205
- case 'instruction':
1206
- this.displayInstructionData(item);
1207
- break;
1208
- default:
1209
- this.displayGenericData(item, itemType);
1210
- break;
1211
- }
1212
-
1213
- // Update module header for consistency
1214
- const moduleHeader = document.querySelector('.module-data-header h5');
1215
- if (moduleHeader) {
1216
- const icons = {
1217
- 'agent': '🤖',
1218
- 'tool': '🔧',
1219
- 'instruction': '💬',
1220
- 'session': '🎯'
1221
- };
1222
- const icon = icons[itemType] || '📊';
1223
- const name = item.name || item.agentName || item.tool_name || 'Item';
1224
- moduleHeader.textContent = `${icon} ${itemType}: ${name}`;
1225
- }
1226
- }
1227
-
1228
- /**
1229
- * Display TodoWrite data in the same clean format as Tools view
1580
+ * Render pinned TODOs element under agent
1230
1581
  */
1231
- displayTodoWriteData(item) {
1232
- const todos = item.params.todos || [];
1233
- const timestamp = this.formatTimestamp(item.timestamp);
1582
+ renderPinnedTodosElement(pinnedTodos, level) {
1583
+ const checklistId = `pinned-todos-${Date.now()}`;
1584
+ const isExpanded = this.expandedTools.has(checklistId) !== false; // Default to expanded
1585
+ const expandIcon = isExpanded ? '▼' : '▶';
1586
+ const todos = pinnedTodos.todos || [];
1234
1587
 
1235
1588
  // Calculate status summary
1236
1589
  let completedCount = 0;
@@ -1243,293 +1596,95 @@ class ActivityTree {
1243
1596
  else pendingCount++;
1244
1597
  });
1245
1598
 
1599
+ let statusSummary = '';
1600
+ if (inProgressCount > 0) {
1601
+ statusSummary = `${inProgressCount} in progress, ${completedCount} completed`;
1602
+ } else if (completedCount === todos.length && todos.length > 0) {
1603
+ statusSummary = `All ${todos.length} completed`;
1604
+ } else {
1605
+ statusSummary = `${todos.length} todo(s)`;
1606
+ }
1607
+
1246
1608
  let html = `
1247
- <div class="unified-viewer-header">
1248
- <h6>📝 TodoWrite: PM ${timestamp}</h6>
1249
- <span class="unified-viewer-status">${this.formatStatus(item.status)}</span>
1250
- </div>
1251
- <div class="unified-viewer-content">
1252
- `;
1253
-
1254
- if (todos.length > 0) {
1255
- // Status summary
1256
- html += `
1257
- <div class="detail-section">
1258
- <span class="detail-section-title">Todo Summary</span>
1259
- <div class="todo-summary">
1260
- <div class="summary-item completed">
1261
- <span class="summary-icon">✅</span>
1262
- <span class="summary-count">${completedCount}</span>
1263
- <span class="summary-label">Completed</span>
1264
- </div>
1265
- <div class="summary-item in_progress">
1266
- <span class="summary-icon">🔄</span>
1267
- <span class="summary-count">${inProgressCount}</span>
1268
- <span class="summary-label">In Progress</span>
1269
- </div>
1270
- <div class="summary-item pending">
1271
- <span class="summary-icon">⏳</span>
1272
- <span class="summary-count">${pendingCount}</span>
1273
- <span class="summary-label">Pending</span>
1274
- </div>
1275
- </div>
1609
+ <div class="tree-node pinned-todos" data-level="${level}">
1610
+ <div class="tree-node-content">
1611
+ <span class="tree-expand-icon" onclick="window.activityTreeInstance.toggleTodoChecklist('${checklistId}'); event.stopPropagation();">${expandIcon}</span>
1612
+ <span class="tree-icon">📌</span>
1613
+ <span class="tree-label">Pinned TODOs</span>
1614
+ <span class="tree-params">${statusSummary}</span>
1615
+ <span class="tree-status status-active">pinned</span>
1276
1616
  </div>
1277
- `;
1278
-
1279
- // Todo list display (same as Tools view)
1280
- html += `
1281
- <div class="detail-section">
1282
- <span class="detail-section-title">Todo List (${todos.length} items)</span>
1283
- <div class="todo-checklist">
1284
- `;
1285
-
1286
- todos.forEach((todo, index) => {
1617
+ `;
1618
+
1619
+ // Show expanded todo items if expanded
1620
+ if (isExpanded) {
1621
+ html += '<div class="tree-children">';
1622
+ for (let todo of todos) {
1287
1623
  const statusIcon = this.getCheckboxIcon(todo.status);
1288
- const displayText = todo.status === 'in_progress' ?
1289
- (todo.activeForm || todo.content) : todo.content;
1290
- const statusClass = this.formatStatusClass(todo.status);
1624
+ const statusClass = `status-${todo.status}`;
1625
+ const displayText = todo.status === 'in_progress' ? todo.activeForm : todo.content;
1291
1626
 
1292
1627
  html += `
1293
- <div class="todo-checklist-item ${todo.status}">
1294
- <div class="todo-checkbox">
1295
- <span class="checkbox-icon ${statusClass}">${statusIcon}</span>
1296
- </div>
1297
- <div class="todo-text">
1298
- <span class="todo-content">${this.escapeHtml(displayText)}</span>
1299
- <span class="todo-status-badge ${statusClass}">${todo.status.replace('_', ' ')}</span>
1628
+ <div class="tree-node todo-item ${statusClass}" data-level="${level + 1}">
1629
+ <div class="tree-node-content">
1630
+ <span class="tree-expand-icon"></span>
1631
+ <span class="tree-icon">${statusIcon}</span>
1632
+ <span class="tree-label">${this.escapeHtml(displayText)}</span>
1633
+ <span class="tree-status ${statusClass}">${todo.status.replace('_', ' ')}</span>
1300
1634
  </div>
1301
1635
  </div>
1302
1636
  `;
1303
- });
1304
-
1305
- html += `
1306
- </div>
1307
- </div>
1308
- `;
1309
- } else {
1310
- html += `
1311
- <div class="detail-section">
1312
- <div class="no-todos">No todo items found</div>
1313
- </div>
1314
- `;
1315
- }
1316
-
1317
- // Add raw JSON section at the bottom
1318
- html += `
1319
- <div class="detail-section">
1320
- <span class="detail-section-title">Parameters (${Object.keys(item.params).length})</span>
1321
- <div class="params-list">
1322
- <div class="param-item">
1323
- <div class="param-key">todos:</div>
1324
- <div class="param-value">
1325
- <pre class="param-json">${this.escapeHtml(JSON.stringify(todos, null, 2))}</pre>
1326
- </div>
1327
- </div>
1328
- </div>
1329
- </div>
1330
- `;
1331
-
1332
- html += '</div>';
1333
-
1334
- // Set the content directly
1335
- const container = document.getElementById('module-data-content');
1336
- if (container) {
1337
- container.innerHTML = html;
1338
- }
1339
-
1340
- // Update module header
1341
- const moduleHeader = document.querySelector('.module-data-header h5');
1342
- if (moduleHeader) {
1343
- moduleHeader.textContent = `📝 tool: TodoWrite`;
1637
+ }
1638
+ html += '</div>';
1344
1639
  }
1345
- }
1346
-
1347
- // Utility methods for TodoWrite display
1348
- formatStatus(status) {
1349
- if (!status) return 'unknown';
1350
-
1351
- const statusMap = {
1352
- 'active': '🟢 Active',
1353
- 'completed': '✅ Completed',
1354
- 'in_progress': '🔄 In Progress',
1355
- 'pending': '⏳ Pending',
1356
- 'error': '❌ Error',
1357
- 'failed': '❌ Failed'
1358
- };
1359
-
1360
- return statusMap[status] || status;
1361
- }
1362
-
1363
- formatStatusClass(status) {
1364
- return `status-${status}`;
1365
- }
1366
-
1367
- formatTimestamp(timestamp) {
1368
- if (!timestamp) return '';
1369
1640
 
1370
- try {
1371
- const date = new Date(timestamp);
1372
- if (isNaN(date.getTime())) return '';
1373
- return date.toLocaleTimeString();
1374
- } catch (error) {
1375
- return '';
1376
- }
1641
+ html += '</div>';
1642
+ return html;
1377
1643
  }
1378
1644
 
1379
1645
  /**
1380
- * Display agent data in a simple format
1646
+ * Handle item click to show data in left pane
1381
1647
  */
1382
- displayAgentData(agent) {
1383
- const timestamp = this.formatTimestamp(agent.timestamp);
1384
- const container = document.getElementById('module-data-content');
1385
- if (!container) return;
1386
-
1387
- let html = `
1388
- <div class="detail-section">
1389
- <span class="detail-section-title">Agent Information</span>
1390
- <div class="agent-info">
1391
- <div class="info-item">
1392
- <span class="info-label">Name:</span>
1393
- <span class="info-value">${this.escapeHtml(agent.name)}</span>
1394
- </div>
1395
- <div class="info-item">
1396
- <span class="info-label">Status:</span>
1397
- <span class="info-value status-${agent.status}">${agent.status}</span>
1398
- </div>
1399
- <div class="info-item">
1400
- <span class="info-label">Timestamp:</span>
1401
- <span class="info-value">${timestamp}</span>
1402
- </div>
1403
- <div class="info-item">
1404
- <span class="info-label">Session ID:</span>
1405
- <span class="info-value">${agent.sessionId || 'N/A'}</span>
1406
- </div>
1407
- </div>
1408
- </div>
1409
- `;
1410
-
1411
- if (agent.tools && agent.tools.length > 0) {
1412
- html += `
1413
- <div class="detail-section">
1414
- <span class="detail-section-title">Tools (${agent.tools.length})</span>
1415
- <div class="tool-list">
1416
- `;
1417
-
1418
- agent.tools.forEach(tool => {
1419
- html += `
1420
- <div class="tool-item">
1421
- <span class="tool-name">${this.escapeHtml(tool.name)}</span>
1422
- <span class="tool-status status-${tool.status}">${tool.status}</span>
1423
- </div>
1424
- `;
1425
- });
1426
-
1427
- html += `
1428
- </div>
1429
- </div>
1430
- `;
1648
+ selectItem(item, itemType, event) {
1649
+ // Stop event propagation to prevent expand/collapse when clicking on label
1650
+ if (event) {
1651
+ event.stopPropagation();
1431
1652
  }
1432
-
1433
- container.innerHTML = html;
1653
+
1654
+ this.selectedItem = { data: item, type: itemType };
1655
+ this.displayItemData(item, itemType);
1656
+ this.renderTree(); // Re-render to show selection highlight
1434
1657
  }
1435
1658
 
1436
1659
  /**
1437
- * Display tool data in a simple format
1660
+ * Display item data in left pane using UnifiedDataViewer for consistency with Tools viewer
1438
1661
  */
1439
- displayToolData(tool) {
1440
- const timestamp = this.formatTimestamp(tool.timestamp);
1441
- const container = document.getElementById('module-data-content');
1442
- if (!container) return;
1443
-
1444
- let html = `
1445
- <div class="detail-section">
1446
- <span class="detail-section-title">Tool Information</span>
1447
- <div class="tool-info">
1448
- <div class="info-item">
1449
- <span class="info-label">Name:</span>
1450
- <span class="info-value">${this.escapeHtml(tool.name)}</span>
1451
- </div>
1452
- <div class="info-item">
1453
- <span class="info-label">Status:</span>
1454
- <span class="info-value status-${tool.status}">${tool.status}</span>
1455
- </div>
1456
- <div class="info-item">
1457
- <span class="info-label">Timestamp:</span>
1458
- <span class="info-value">${timestamp}</span>
1459
- </div>
1460
- </div>
1461
- </div>
1462
- `;
1463
-
1464
- if (tool.params && Object.keys(tool.params).length > 0) {
1465
- html += `
1466
- <div class="detail-section">
1467
- <span class="detail-section-title">Parameters</span>
1468
- <div class="params-list">
1469
- `;
1470
-
1471
- Object.entries(tool.params).forEach(([key, value]) => {
1472
- html += `
1473
- <div class="param-item">
1474
- <div class="param-key">${this.escapeHtml(key)}:</div>
1475
- <div class="param-value">${this.escapeHtml(String(value))}</div>
1476
- </div>
1477
- `;
1478
- });
1479
-
1480
- html += `
1481
- </div>
1482
- </div>
1483
- `;
1662
+ displayItemData(item, itemType) {
1663
+ // Initialize UnifiedDataViewer if not already available
1664
+ if (!this.unifiedViewer) {
1665
+ this.unifiedViewer = new UnifiedDataViewer('module-data-content');
1666
+ }
1667
+
1668
+ // Use the same UnifiedDataViewer as Tools viewer for consistent display
1669
+ this.unifiedViewer.display(item, itemType);
1670
+
1671
+ // Update module header for consistency
1672
+ const moduleHeader = document.querySelector('.module-data-header h5');
1673
+ if (moduleHeader) {
1674
+ const icons = {
1675
+ 'agent': '🤖',
1676
+ 'tool': '🔧',
1677
+ 'instruction': '💬',
1678
+ 'session': '🎯',
1679
+ 'todo': '📝'
1680
+ };
1681
+ const icon = icons[itemType] || '📊';
1682
+ const name = item.name || item.agentName || item.tool_name || 'Item';
1683
+ moduleHeader.textContent = `${icon} ${itemType}: ${name}`;
1484
1684
  }
1485
-
1486
- container.innerHTML = html;
1487
- }
1488
-
1489
- /**
1490
- * Display instruction data in a simple format
1491
- */
1492
- displayInstructionData(instruction) {
1493
- const timestamp = this.formatTimestamp(instruction.timestamp);
1494
- const container = document.getElementById('module-data-content');
1495
- if (!container) return;
1496
-
1497
- const html = `
1498
- <div class="detail-section">
1499
- <span class="detail-section-title">User Instruction</span>
1500
- <div class="instruction-info">
1501
- <div class="info-item">
1502
- <span class="info-label">Timestamp:</span>
1503
- <span class="info-value">${timestamp}</span>
1504
- </div>
1505
- <div class="instruction-content">
1506
- <div class="instruction-text">${this.escapeHtml(instruction.text)}</div>
1507
- </div>
1508
- </div>
1509
- </div>
1510
- `;
1511
-
1512
- container.innerHTML = html;
1513
1685
  }
1514
1686
 
1515
- /**
1516
- * Display generic data for unknown types
1517
- */
1518
- displayGenericData(item, itemType) {
1519
- const container = document.getElementById('module-data-content');
1520
- if (!container) return;
1521
-
1522
- let html = `
1523
- <div class="detail-section">
1524
- <span class="detail-section-title">${itemType || 'Item'} Data</span>
1525
- <div class="generic-data">
1526
- <pre>${this.escapeHtml(JSON.stringify(item, null, 2))}</pre>
1527
- </div>
1528
- </div>
1529
- `;
1530
-
1531
- container.innerHTML = html;
1532
- }
1687
+ // Display methods removed - now using UnifiedDataViewer for consistency
1533
1688
 
1534
1689
  /**
1535
1690
  * Escape HTML for safe display