claude-mpm 4.1.15__py3-none-any.whl → 4.1.17__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,9 +3,12 @@
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;
@@ -205,27 +208,36 @@ class ActivityTree {
205
208
  window.socketClient.onEventUpdate((events, sessions) => {
206
209
  console.log(`ActivityTree: onEventUpdate called with ${events.length} total events and ${sessions.size} sessions`);
207
210
 
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
211
+ // IMPORTANT: Don't clear sessions! We need to preserve the accumulated agent data
212
+ // Only create new sessions if they don't exist yet
212
213
  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);
214
+ if (!this.sessions.has(sessionId)) {
215
+ // Create new session only if it doesn't exist
216
+ const activitySession = {
217
+ id: sessionId,
218
+ timestamp: new Date(sessionData.lastActivity || sessionData.startTime || new Date()),
219
+ expanded: this.expandedSessions.has(sessionId) || true, // Preserve expansion state
220
+ agents: new Map(),
221
+ todos: [],
222
+ userInstructions: [],
223
+ tools: [],
224
+ toolsMap: new Map(),
225
+ todoWritesMap: new Map(),
226
+ status: 'active',
227
+ currentTodoTool: null,
228
+ // Preserve additional session metadata
229
+ working_directory: sessionData.working_directory,
230
+ git_branch: sessionData.git_branch,
231
+ eventCount: sessionData.eventCount
232
+ };
233
+ this.sessions.set(sessionId, activitySession);
234
+ } else {
235
+ // Update existing session metadata without clearing accumulated data
236
+ const existingSession = this.sessions.get(sessionId);
237
+ existingSession.timestamp = new Date(sessionData.lastActivity || sessionData.startTime || existingSession.timestamp);
238
+ existingSession.eventCount = sessionData.eventCount;
239
+ existingSession.status = sessionData.status || existingSession.status;
240
+ }
229
241
  }
230
242
 
231
243
  // Process only the new events since last update
@@ -253,25 +265,29 @@ class ActivityTree {
253
265
  console.log(`ActivityTree: Loading existing data - ${socketState.events.length} events, ${socketState.sessions.size} sessions`);
254
266
 
255
267
  // Initialize from existing socket client data
256
- this.sessions.clear();
268
+ // Don't clear existing sessions - preserve accumulated data
257
269
 
258
270
  // Convert authoritative sessions Map to our format
259
271
  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);
272
+ if (!this.sessions.has(sessionId)) {
273
+ const activitySession = {
274
+ id: sessionId,
275
+ timestamp: new Date(sessionData.lastActivity || sessionData.startTime || new Date()),
276
+ expanded: this.expandedSessions.has(sessionId) || true,
277
+ agents: new Map(),
278
+ todos: [],
279
+ userInstructions: [],
280
+ tools: [],
281
+ toolsMap: new Map(),
282
+ todoWritesMap: new Map(),
283
+ status: 'active',
284
+ currentTodoTool: null,
285
+ working_directory: sessionData.working_directory,
286
+ git_branch: sessionData.git_branch,
287
+ eventCount: sessionData.eventCount
288
+ };
289
+ this.sessions.set(sessionId, activitySession);
290
+ }
275
291
  }
276
292
 
277
293
  // Process existing events to populate activity data
@@ -424,6 +440,15 @@ class ActivityTree {
424
440
  type: 'user_instruction'
425
441
  };
426
442
 
443
+ // NEW USER PROMPT: This is when we should clear/reset agent activity
444
+ // Clear previous agents for this session as we're starting fresh
445
+ console.log('ActivityTree: New user prompt detected, clearing previous agent activity');
446
+ session.agents.clear();
447
+ session.tools = [];
448
+ session.toolsMap = new Map();
449
+ session.todoWritesMap = new Map();
450
+ session.currentActiveAgent = null;
451
+
427
452
  // Add to session's user instructions
428
453
  session.userInstructions.push(instruction);
429
454
 
@@ -447,77 +472,104 @@ class ActivityTree {
447
472
  return;
448
473
  }
449
474
 
450
- // Update session's todos directly for overall checklist view
451
- session.todos = todos.map(todo => ({
475
+ // Update session's current todos for latest state tracking
476
+ session.currentTodos = todos.map(todo => ({
452
477
  content: todo.content,
453
478
  activeForm: todo.activeForm,
454
479
  status: todo.status,
455
480
  timestamp: event.timestamp
456
481
  }));
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
482
 
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
483
  // 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 {
484
+ let targetAgent = session.currentActiveAgent;
485
+
486
+ if (!targetAgent) {
486
487
  // Fall back to most recent active agent
487
- const activeAgents = Array.from(session.agents.values())
488
+ const activeAgents = this.getAllAgents(session)
488
489
  .filter(agent => agent.status === 'active' || agent.status === 'in_progress')
489
490
  .sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
490
491
 
491
492
  if (activeAgents.length > 0) {
492
493
  targetAgent = activeAgents[0];
493
494
  } 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) {
495
+ // If no active agents, check if this is PM-level
496
+ const allAgents = this.getAllAgents(session);
497
+ const pmAgent = allAgents.find(a => a.isPM);
498
+ if (pmAgent) {
499
+ targetAgent = pmAgent;
500
+ } else if (allAgents.length > 0) {
498
501
  targetAgent = allAgents[0];
499
502
  }
500
503
  }
501
504
  }
502
505
 
503
- // Create agent-specific TodoWrite tool
506
+ // Attach or update TodoWrite for the agent
504
507
  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
- };
508
+ if (!targetAgent.todoWritesMap) {
509
+ targetAgent.todoWritesMap = new Map();
510
+ }
511
+ if (!targetAgent.todoWrites) {
512
+ targetAgent.todoWrites = [];
513
+ }
514
+
515
+ // Check if we already have a TodoWrite instance
516
+ const existingTodoWrite = targetAgent.todoWritesMap.get('TodoWrite');
517
517
 
518
- // Remove existing TodoWrite tool from agent and add the updated one
519
- targetAgent.tools = targetAgent.tools.filter(t => t.name !== 'TodoWrite');
520
- targetAgent.tools.unshift(agentTodoTool);
518
+ if (existingTodoWrite) {
519
+ // Update existing TodoWrite instance
520
+ existingTodoWrite.todos = todos;
521
+ existingTodoWrite.timestamp = event.timestamp;
522
+ existingTodoWrite.updateCount = (existingTodoWrite.updateCount || 1) + 1;
523
+ } else {
524
+ // Create new TodoWrite instance
525
+ const todoWriteInstance = {
526
+ id: `todowrite-${targetAgent.id}-${Date.now()}`,
527
+ name: 'TodoWrite',
528
+ type: 'todowrite',
529
+ icon: '📝',
530
+ timestamp: event.timestamp,
531
+ status: 'completed',
532
+ todos: todos,
533
+ params: {
534
+ todos: todos
535
+ },
536
+ updateCount: 1
537
+ };
538
+
539
+ targetAgent.todoWritesMap.set('TodoWrite', todoWriteInstance);
540
+ targetAgent.todoWrites = [todoWriteInstance]; // Keep single instance
541
+ }
542
+
543
+ // Update agent's current todos for display when collapsed
544
+ targetAgent.currentTodos = todos;
545
+ } else {
546
+ // No agent found, attach to session level
547
+ if (!session.todoWrites) {
548
+ session.todoWrites = [];
549
+ }
550
+ if (!session.todoWritesMap) {
551
+ session.todoWritesMap = new Map();
552
+ }
553
+
554
+ const existingTodoWrite = session.todoWritesMap.get('TodoWrite');
555
+ if (existingTodoWrite) {
556
+ existingTodoWrite.todos = todos;
557
+ existingTodoWrite.timestamp = event.timestamp;
558
+ existingTodoWrite.updateCount = (existingTodoWrite.updateCount || 1) + 1;
559
+ } else {
560
+ const todoWriteInstance = {
561
+ id: `todowrite-session-${Date.now()}`,
562
+ name: 'TodoWrite',
563
+ type: 'todowrite',
564
+ icon: '📝',
565
+ timestamp: event.timestamp,
566
+ status: 'completed',
567
+ todos: todos,
568
+ updateCount: 1
569
+ };
570
+ session.todoWritesMap.set('TodoWrite', todoWriteInstance);
571
+ session.todoWrites = [todoWriteInstance];
572
+ }
521
573
  }
522
574
  }
523
575
 
@@ -527,13 +579,38 @@ class ActivityTree {
527
579
  processSubagentStart(event, session) {
528
580
  const agentName = event.agent_name || event.data?.agent_name || event.data?.agent_type || event.agent_type || event.agent || 'unknown';
529
581
  const agentSessionId = event.session_id || event.data?.session_id;
582
+ const parentAgent = event.parent_agent || event.data?.parent_agent;
583
+
584
+ // Use a composite key based on agent name and session to find existing instances
585
+ // This ensures we track unique agent instances per session
586
+ const agentKey = `${agentName}-${agentSessionId || 'no-session'}`;
530
587
 
531
- // Use session ID as unique agent identifier, or create unique ID
532
- const agentId = agentSessionId || `agent-${Date.now()}-${Math.random()}`;
588
+ // Check if this agent already exists in the session
589
+ let existingAgent = null;
590
+ for (let [id, ag] of session.agents.entries()) {
591
+ if (ag.name === agentName && (ag.sessionId === agentSessionId || (!ag.sessionId && !agentSessionId))) {
592
+ existingAgent = ag;
593
+ break;
594
+ }
595
+ }
533
596
 
534
- // Check if agent already exists in this session
535
- if (!session.agents.has(agentId)) {
536
- const agent = {
597
+ // Also check in subagents of all agents
598
+ if (!existingAgent) {
599
+ const allAgents = this.getAllAgents(session);
600
+ existingAgent = allAgents.find(a => a.name === agentName && a.sessionId === agentSessionId);
601
+ }
602
+
603
+ let agent;
604
+ if (existingAgent) {
605
+ // Update existing agent
606
+ agent = existingAgent;
607
+ agent.status = 'active';
608
+ agent.timestamp = event.timestamp;
609
+ agent.instanceCount = (agent.instanceCount || 1) + 1;
610
+ } else {
611
+ // Create new agent instance for first occurrence
612
+ const agentId = `agent-${agentKey}-${Date.now()}`;
613
+ agent = {
537
614
  id: agentId,
538
615
  name: agentName,
539
616
  type: 'agent',
@@ -541,17 +618,45 @@ class ActivityTree {
541
618
  timestamp: event.timestamp,
542
619
  status: 'active',
543
620
  tools: [],
621
+ todoWrites: [], // Store TodoWrite instances
622
+ subagents: new Map(), // Store nested subagents
544
623
  sessionId: agentSessionId,
545
- isPM: false
624
+ parentAgent: parentAgent,
625
+ isPM: agentName.toLowerCase() === 'pm' || agentName.toLowerCase().includes('project manager'),
626
+ instanceCount: 1,
627
+ toolsMap: new Map(), // Track unique tools by name
628
+ todoWritesMap: new Map() // Track unique TodoWrites
546
629
  };
547
630
 
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
631
+ // If this is a subagent, nest it under the parent agent
632
+ if (parentAgent) {
633
+ // Find the parent agent in the session
634
+ let parent = null;
635
+ for (let [id, ag] of session.agents.entries()) {
636
+ if (ag.sessionId === parentAgent || ag.name === parentAgent) {
637
+ parent = ag;
638
+ break;
639
+ }
640
+ }
641
+
642
+ if (parent) {
643
+ // Add as nested subagent
644
+ if (!parent.subagents) {
645
+ parent.subagents = new Map();
646
+ }
647
+ parent.subagents.set(agent.id, agent);
648
+ } else {
649
+ // No parent found, add to session level
650
+ session.agents.set(agent.id, agent);
651
+ }
652
+ } else {
653
+ // Top-level agent, add to session
654
+ session.agents.set(agent.id, agent);
655
+ }
554
656
  }
657
+
658
+ // Track the currently active agent for tool/todo association
659
+ session.currentActiveAgent = agent;
555
660
  }
556
661
 
557
662
  /**
@@ -574,67 +679,149 @@ class ActivityTree {
574
679
  const toolName = event.tool_name || event.data?.tool_name || event.tool || event.data?.tool || 'unknown';
575
680
  const params = event.tool_parameters || event.data?.tool_parameters || event.parameters || event.data?.parameters || {};
576
681
  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
682
 
589
683
  // Find the appropriate agent to attach this tool to
590
- let targetAgent = null;
684
+ let targetAgent = session.currentActiveAgent;
591
685
 
592
- // First try to find by session ID
593
- if (agentSessionId && session.agents.has(agentSessionId)) {
594
- targetAgent = session.agents.get(agentSessionId);
686
+ if (!targetAgent) {
687
+ // Fall back to finding by session ID or most recent active
688
+ const allAgents = this.getAllAgents(session);
689
+ targetAgent = allAgents.find(a => a.sessionId === agentSessionId) ||
690
+ allAgents.find(a => a.status === 'active') ||
691
+ allAgents[0];
692
+ }
693
+
694
+ if (targetAgent) {
695
+ if (!targetAgent.toolsMap) {
696
+ targetAgent.toolsMap = new Map();
697
+ }
698
+ if (!targetAgent.tools) {
699
+ targetAgent.tools = [];
700
+ }
701
+
702
+ // Check if we already have this tool type
703
+ let existingTool = targetAgent.toolsMap.get(toolName);
704
+
705
+ if (existingTool) {
706
+ // Update existing tool instance
707
+ existingTool.params = params;
708
+ existingTool.timestamp = event.timestamp;
709
+ existingTool.status = 'in_progress';
710
+ existingTool.eventId = event.id;
711
+ existingTool.callCount = (existingTool.callCount || 1) + 1;
712
+
713
+ // Update current tool for collapsed display
714
+ targetAgent.currentTool = existingTool;
715
+ } else {
716
+ // Create new tool instance
717
+ const tool = {
718
+ id: `tool-${targetAgent.id}-${toolName}-${Date.now()}`,
719
+ name: toolName,
720
+ type: 'tool',
721
+ icon: this.getToolIcon(toolName),
722
+ timestamp: event.timestamp,
723
+ status: 'in_progress',
724
+ params: params,
725
+ eventId: event.id,
726
+ callCount: 1
727
+ };
728
+
729
+ // Special handling for Task tool (subagent delegation)
730
+ if (toolName === 'Task' && params.subagent_type) {
731
+ tool.isSubagentTask = true;
732
+ tool.subagentType = params.subagent_type;
733
+ }
734
+
735
+ targetAgent.toolsMap.set(toolName, tool);
736
+ targetAgent.tools.push(tool);
737
+ targetAgent.currentTool = tool;
738
+ }
595
739
  } 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));
740
+ // No agent found, attach to session (PM level)
741
+ if (!session.tools) {
742
+ session.tools = [];
743
+ }
744
+ if (!session.toolsMap) {
745
+ session.toolsMap = new Map();
746
+ }
600
747
 
601
- if (activeAgents.length > 0) {
602
- targetAgent = activeAgents[0];
748
+ let existingTool = session.toolsMap.get(toolName);
749
+
750
+ if (existingTool) {
751
+ existingTool.params = params;
752
+ existingTool.timestamp = event.timestamp;
753
+ existingTool.status = 'in_progress';
754
+ existingTool.eventId = event.id;
755
+ existingTool.callCount = (existingTool.callCount || 1) + 1;
756
+ session.currentTool = existingTool;
603
757
  } else {
604
- // If no active agents, attach to session (PM level)
758
+ const tool = {
759
+ id: `tool-session-${toolName}-${Date.now()}`,
760
+ name: toolName,
761
+ type: 'tool',
762
+ icon: this.getToolIcon(toolName),
763
+ timestamp: event.timestamp,
764
+ status: 'in_progress',
765
+ params: params,
766
+ eventId: event.id,
767
+ callCount: 1
768
+ };
769
+
770
+ session.toolsMap.set(toolName, tool);
605
771
  session.tools.push(tool);
606
- return;
772
+ session.currentTool = tool;
607
773
  }
608
774
  }
609
-
610
- if (targetAgent) {
611
- targetAgent.tools.push(tool);
612
- }
613
775
  }
614
776
 
615
777
  /**
616
778
  * Update tool status after completion
617
779
  */
618
780
  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;
781
+ const toolName = event.tool_name || event.data?.tool_name || event.tool || 'unknown';
782
+ const agentSessionId = event.session_id || event.data?.session_id;
783
+
784
+ // Find the appropriate agent
785
+ let targetAgent = session.currentActiveAgent;
786
+
787
+ if (!targetAgent) {
788
+ const allAgents = this.getAllAgents(session);
789
+ targetAgent = allAgents.find(a => a.sessionId === agentSessionId) ||
790
+ allAgents.find(a => a.status === 'active');
791
+ }
792
+
793
+ if (targetAgent && targetAgent.toolsMap) {
794
+ const tool = targetAgent.toolsMap.get(toolName);
795
+ if (tool) {
796
+ tool.status = status;
797
+ tool.completedAt = event.timestamp;
798
+ if (event.data?.result || event.result) {
799
+ tool.result = event.data?.result || event.result;
800
+ }
801
+ if (event.data?.duration_ms) {
802
+ tool.duration = event.data.duration_ms;
626
803
  }
804
+ return;
805
+ }
806
+ }
807
+
808
+ // Check session-level tools
809
+ if (session.toolsMap) {
810
+ const tool = session.toolsMap.get(toolName);
811
+ if (tool) {
812
+ tool.status = status;
813
+ tool.completedAt = event.timestamp;
814
+ if (event.data?.result || event.result) {
815
+ tool.result = event.data?.result || event.result;
816
+ }
817
+ if (event.data?.duration_ms) {
818
+ tool.duration = event.data.duration_ms;
819
+ }
820
+ return;
627
821
  }
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
822
  }
635
823
 
636
- // Check session-level tools (PM level)
637
- if (session.tools && findAndUpdateTool(session)) return;
824
+ console.log(`ActivityTree: Could not find tool to update status for ${toolName} (event ${event.id})`);
638
825
  }
639
826
 
640
827
  /**
@@ -833,33 +1020,79 @@ class ActivityTree {
833
1020
  }
834
1021
 
835
1022
  /**
836
- * Render agent element
1023
+ * Render agent element with proper nesting
837
1024
  */
838
1025
  renderAgentElement(agent, level) {
839
1026
  const statusClass = agent.status === 'active' ? 'status-active' : 'status-completed';
840
1027
  const isExpanded = this.expandedAgents.has(agent.id);
1028
+ const hasTodoWrites = agent.todoWrites && agent.todoWrites.length > 0;
841
1029
  const hasTools = agent.tools && agent.tools.length > 0;
1030
+ const hasSubagents = agent.subagents && agent.subagents.size > 0;
1031
+ const hasContent = hasTodoWrites || hasTools || hasSubagents;
842
1032
  const isSelected = this.selectedItem && this.selectedItem.type === 'agent' && this.selectedItem.data.id === agent.id;
843
1033
 
844
- const expandIcon = hasTools ? (isExpanded ? '▼' : '▶') : '';
1034
+ const expandIcon = hasContent ? (isExpanded ? '▼' : '▶') : '';
845
1035
  const selectedClass = isSelected ? 'selected' : '';
846
1036
 
1037
+ // Add instance count if called multiple times
1038
+ const instanceIndicator = agent.instanceCount > 1 ? ` (${agent.instanceCount}x)` : '';
1039
+
1040
+ // Build status display for collapsed state
1041
+ let collapsedStatus = '';
1042
+ if (!isExpanded && hasContent) {
1043
+ const parts = [];
1044
+ if (agent.currentTodos && agent.currentTodos.length > 0) {
1045
+ const inProgress = agent.currentTodos.find(t => t.status === 'in_progress');
1046
+ if (inProgress) {
1047
+ parts.push(`📝 ${inProgress.activeForm || inProgress.content}`);
1048
+ }
1049
+ }
1050
+ if (agent.currentTool) {
1051
+ parts.push(`${agent.currentTool.icon} ${agent.currentTool.name}`);
1052
+ }
1053
+ if (parts.length > 0) {
1054
+ collapsedStatus = ` • ${parts.join(' • ')}`;
1055
+ }
1056
+ }
1057
+
847
1058
  let html = `
848
1059
  <div class="tree-node agent ${statusClass} ${selectedClass}" data-level="${level}">
849
1060
  <div class="tree-node-content">
850
1061
  ${expandIcon ? `<span class="tree-expand-icon" onclick="window.activityTreeInstance.toggleAgent('${agent.id}'); event.stopPropagation();">${expandIcon}</span>` : '<span class="tree-expand-icon"></span>'}
851
1062
  <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>
1063
+ <span class="tree-label clickable" onclick="window.activityTreeInstance.selectItem(${this.escapeJson(agent)}, 'agent', event)">${agent.name}${instanceIndicator}${collapsedStatus}</span>
853
1064
  <span class="tree-status ${statusClass}">${agent.status}</span>
854
1065
  </div>
855
1066
  `;
856
1067
 
857
- // Render tools under this agent
858
- if (hasTools && isExpanded) {
1068
+ // Render nested content when expanded
1069
+ if (hasContent && isExpanded) {
859
1070
  html += '<div class="tree-children">';
860
- for (let tool of agent.tools) {
861
- html += this.renderToolElement(tool, level + 1);
1071
+
1072
+ // ALWAYS render TodoWrite FIRST (single instance)
1073
+ if (hasTodoWrites) {
1074
+ // Only render the first (and should be only) TodoWrite instance
1075
+ html += this.renderTodoWriteElement(agent.todoWrites[0], level + 1);
862
1076
  }
1077
+
1078
+ // Then render subagents (they will have their own TodoWrite at the top)
1079
+ if (hasSubagents) {
1080
+ const subagents = Array.from(agent.subagents.values());
1081
+ for (let subagent of subagents) {
1082
+ html += this.renderAgentElement(subagent, level + 1);
1083
+ }
1084
+ }
1085
+
1086
+ // Finally render other tools (excluding TodoWrite)
1087
+ if (hasTools) {
1088
+ for (let tool of agent.tools) {
1089
+ // Skip TodoWrite tools since we show them separately at the top
1090
+ if (tool.name !== 'TodoWrite') {
1091
+ html += this.renderToolElement(tool, level + 1);
1092
+ }
1093
+ }
1094
+ }
1095
+
863
1096
  html += '</div>';
864
1097
  }
865
1098
 
@@ -876,14 +1109,22 @@ class ActivityTree {
876
1109
  const isSelected = this.selectedItem && this.selectedItem.type === 'tool' && this.selectedItem.data.id === tool.id;
877
1110
  const selectedClass = isSelected ? 'selected' : '';
878
1111
 
1112
+ // Add visual status indicators
1113
+ const statusIcon = this.getToolStatusIcon(tool.status);
1114
+ const statusLabel = this.getToolStatusLabel(tool.status);
1115
+
1116
+ // Add call count if more than 1
1117
+ const callIndicator = tool.callCount > 1 ? ` (${tool.callCount} calls)` : '';
1118
+
879
1119
  let html = `
880
1120
  <div class="tree-node tool ${statusClass} ${selectedClass}" data-level="${level}">
881
1121
  <div class="tree-node-content">
882
1122
  <span class="tree-expand-icon"></span>
883
1123
  <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>
1124
+ <span class="tree-status-icon">${statusIcon}</span>
1125
+ <span class="tree-label clickable" onclick="window.activityTreeInstance.selectItem(${this.escapeJson(tool)}, 'tool', event)">${tool.name}${callIndicator}</span>
885
1126
  <span class="tree-params">${params}</span>
886
- <span class="tree-status ${statusClass}">${tool.status}</span>
1127
+ <span class="tree-status ${statusClass}">${statusLabel}</span>
887
1128
  </div>
888
1129
  </div>
889
1130
  `;
@@ -951,11 +1192,119 @@ class ActivityTree {
951
1192
  'qa': '🧪',
952
1193
  'ops': '⚙️',
953
1194
  'pm': '📊',
954
- 'architect': '🏗️'
1195
+ 'architect': '🏗️',
1196
+ 'project manager': '📊'
955
1197
  };
956
1198
  return icons[agentName.toLowerCase()] || '🤖';
957
1199
  }
958
1200
 
1201
+ /**
1202
+ * Helper to get all agents including nested subagents
1203
+ */
1204
+ getAllAgents(session) {
1205
+ const agents = [];
1206
+
1207
+ const collectAgents = (agentMap) => {
1208
+ if (!agentMap) return;
1209
+
1210
+ for (let agent of agentMap.values()) {
1211
+ agents.push(agent);
1212
+ if (agent.subagents && agent.subagents.size > 0) {
1213
+ collectAgents(agent.subagents);
1214
+ }
1215
+ }
1216
+ };
1217
+
1218
+ collectAgents(session.agents);
1219
+ return agents;
1220
+ }
1221
+
1222
+ /**
1223
+ * Render TodoWrite element
1224
+ */
1225
+ renderTodoWriteElement(todoWrite, level) {
1226
+ const todoWriteId = todoWrite.id;
1227
+ const isExpanded = this.expandedTools.has(todoWriteId);
1228
+ const expandIcon = isExpanded ? '▼' : '▶';
1229
+ const todos = todoWrite.todos || [];
1230
+
1231
+ // Calculate status summary
1232
+ let completedCount = 0;
1233
+ let inProgressCount = 0;
1234
+ let pendingCount = 0;
1235
+
1236
+ todos.forEach(todo => {
1237
+ if (todo.status === 'completed') completedCount++;
1238
+ else if (todo.status === 'in_progress') inProgressCount++;
1239
+ else pendingCount++;
1240
+ });
1241
+
1242
+ // Find current in-progress todo for highlighting
1243
+ const currentTodo = todos.find(t => t.status === 'in_progress');
1244
+ const currentIndicator = currentTodo ? ` • 🔄 ${currentTodo.activeForm || currentTodo.content}` : '';
1245
+
1246
+ let statusSummary = '';
1247
+ if (inProgressCount > 0) {
1248
+ statusSummary = `${inProgressCount} in progress, ${completedCount}/${todos.length} done`;
1249
+ } else if (completedCount === todos.length && todos.length > 0) {
1250
+ statusSummary = `All ${todos.length} completed ✅`;
1251
+ } else {
1252
+ statusSummary = `${completedCount}/${todos.length} done`;
1253
+ }
1254
+
1255
+ // Add update count if more than 1
1256
+ const updateIndicator = todoWrite.updateCount > 1 ? ` (${todoWrite.updateCount} updates)` : '';
1257
+
1258
+ let html = `
1259
+ <div class="tree-node todowrite ${currentTodo ? 'has-active' : ''}" data-level="${level}">
1260
+ <div class="tree-node-content">
1261
+ <span class="tree-expand-icon" onclick="window.activityTreeInstance.toggleTodoWrite('${todoWriteId}'); event.stopPropagation();">${expandIcon}</span>
1262
+ <span class="tree-icon">📝</span>
1263
+ <span class="tree-label">TodoWrite${updateIndicator}${!isExpanded ? currentIndicator : ''}</span>
1264
+ <span class="tree-params">${statusSummary}</span>
1265
+ <span class="tree-status status-active">todos</span>
1266
+ </div>
1267
+ `;
1268
+
1269
+ // Show expanded todo items if expanded
1270
+ if (isExpanded && todos.length > 0) {
1271
+ html += '<div class="tree-children">';
1272
+ for (let todo of todos) {
1273
+ const statusIcon = this.getCheckboxIcon(todo.status);
1274
+ const statusClass = `status-${todo.status}`;
1275
+ const displayText = todo.status === 'in_progress' ? todo.activeForm : todo.content;
1276
+ const isCurrentTodo = todo === currentTodo;
1277
+
1278
+ html += `
1279
+ <div class="tree-node todo-item ${statusClass} ${isCurrentTodo ? 'current-active' : ''}" data-level="${level + 1}">
1280
+ <div class="tree-node-content">
1281
+ <span class="tree-expand-icon"></span>
1282
+ <span class="tree-icon">${statusIcon}</span>
1283
+ <span class="tree-label">${this.escapeHtml(displayText)}</span>
1284
+ <span class="tree-status ${statusClass}">${todo.status.replace('_', ' ')}</span>
1285
+ </div>
1286
+ </div>
1287
+ `;
1288
+ }
1289
+ html += '</div>';
1290
+ }
1291
+
1292
+ html += '</div>';
1293
+ return html;
1294
+ }
1295
+
1296
+ /**
1297
+ * Toggle TodoWrite expansion
1298
+ */
1299
+ toggleTodoWrite(todoWriteId) {
1300
+ if (this.expandedTools.has(todoWriteId)) {
1301
+ this.expandedTools.delete(todoWriteId);
1302
+ } else {
1303
+ this.expandedTools.add(todoWriteId);
1304
+ }
1305
+ this.renderTree();
1306
+ }
1307
+
959
1308
  /**
960
1309
  * Get tool icon based on name
961
1310
  */
@@ -973,6 +1322,36 @@ class ActivityTree {
973
1322
  return icons[toolName.toLowerCase()] || '🔧';
974
1323
  }
975
1324
 
1325
+ /**
1326
+ * Get status icon for tool status
1327
+ */
1328
+ getToolStatusIcon(status) {
1329
+ const icons = {
1330
+ 'in_progress': '⏳',
1331
+ 'completed': '✅',
1332
+ 'failed': '❌',
1333
+ 'error': '❌',
1334
+ 'pending': '⏸️',
1335
+ 'active': '🔄'
1336
+ };
1337
+ return icons[status] || '❓';
1338
+ }
1339
+
1340
+ /**
1341
+ * Get formatted status label for tool
1342
+ */
1343
+ getToolStatusLabel(status) {
1344
+ const labels = {
1345
+ 'in_progress': 'in progress',
1346
+ 'completed': 'completed',
1347
+ 'failed': 'failed',
1348
+ 'error': 'error',
1349
+ 'pending': 'pending',
1350
+ 'active': 'active'
1351
+ };
1352
+ return labels[status] || status;
1353
+ }
1354
+
976
1355
  /**
977
1356
  * Toggle session expansion
978
1357
  */
@@ -1171,66 +1550,13 @@ class ActivityTree {
1171
1550
  }
1172
1551
 
1173
1552
  /**
1174
- * Handle item click to show data in left pane
1553
+ * Render pinned TODOs element under agent
1175
1554
  */
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
1230
- */
1231
- displayTodoWriteData(item) {
1232
- const todos = item.params.todos || [];
1233
- const timestamp = this.formatTimestamp(item.timestamp);
1555
+ renderPinnedTodosElement(pinnedTodos, level) {
1556
+ const checklistId = `pinned-todos-${Date.now()}`;
1557
+ const isExpanded = this.expandedTools.has(checklistId) !== false; // Default to expanded
1558
+ const expandIcon = isExpanded ? '▼' : '▶';
1559
+ const todos = pinnedTodos.todos || [];
1234
1560
 
1235
1561
  // Calculate status summary
1236
1562
  let completedCount = 0;
@@ -1243,293 +1569,95 @@ class ActivityTree {
1243
1569
  else pendingCount++;
1244
1570
  });
1245
1571
 
1572
+ let statusSummary = '';
1573
+ if (inProgressCount > 0) {
1574
+ statusSummary = `${inProgressCount} in progress, ${completedCount} completed`;
1575
+ } else if (completedCount === todos.length && todos.length > 0) {
1576
+ statusSummary = `All ${todos.length} completed`;
1577
+ } else {
1578
+ statusSummary = `${todos.length} todo(s)`;
1579
+ }
1580
+
1246
1581
  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>
1582
+ <div class="tree-node pinned-todos" data-level="${level}">
1583
+ <div class="tree-node-content">
1584
+ <span class="tree-expand-icon" onclick="window.activityTreeInstance.toggleTodoChecklist('${checklistId}'); event.stopPropagation();">${expandIcon}</span>
1585
+ <span class="tree-icon">📌</span>
1586
+ <span class="tree-label">Pinned TODOs</span>
1587
+ <span class="tree-params">${statusSummary}</span>
1588
+ <span class="tree-status status-active">pinned</span>
1276
1589
  </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) => {
1590
+ `;
1591
+
1592
+ // Show expanded todo items if expanded
1593
+ if (isExpanded) {
1594
+ html += '<div class="tree-children">';
1595
+ for (let todo of todos) {
1287
1596
  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);
1597
+ const statusClass = `status-${todo.status}`;
1598
+ const displayText = todo.status === 'in_progress' ? todo.activeForm : todo.content;
1291
1599
 
1292
1600
  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>
1601
+ <div class="tree-node todo-item ${statusClass}" data-level="${level + 1}">
1602
+ <div class="tree-node-content">
1603
+ <span class="tree-expand-icon"></span>
1604
+ <span class="tree-icon">${statusIcon}</span>
1605
+ <span class="tree-label">${this.escapeHtml(displayText)}</span>
1606
+ <span class="tree-status ${statusClass}">${todo.status.replace('_', ' ')}</span>
1300
1607
  </div>
1301
1608
  </div>
1302
1609
  `;
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`;
1610
+ }
1611
+ html += '</div>';
1344
1612
  }
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
1613
 
1360
- return statusMap[status] || status;
1361
- }
1362
-
1363
- formatStatusClass(status) {
1364
- return `status-${status}`;
1365
- }
1366
-
1367
- formatTimestamp(timestamp) {
1368
- if (!timestamp) return '';
1369
-
1370
- try {
1371
- const date = new Date(timestamp);
1372
- if (isNaN(date.getTime())) return '';
1373
- return date.toLocaleTimeString();
1374
- } catch (error) {
1375
- return '';
1376
- }
1614
+ html += '</div>';
1615
+ return html;
1377
1616
  }
1378
1617
 
1379
1618
  /**
1380
- * Display agent data in a simple format
1619
+ * Handle item click to show data in left pane
1381
1620
  */
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
- `;
1621
+ selectItem(item, itemType, event) {
1622
+ // Stop event propagation to prevent expand/collapse when clicking on label
1623
+ if (event) {
1624
+ event.stopPropagation();
1431
1625
  }
1432
-
1433
- container.innerHTML = html;
1626
+
1627
+ this.selectedItem = { data: item, type: itemType };
1628
+ this.displayItemData(item, itemType);
1629
+ this.renderTree(); // Re-render to show selection highlight
1434
1630
  }
1435
1631
 
1436
1632
  /**
1437
- * Display tool data in a simple format
1633
+ * Display item data in left pane using UnifiedDataViewer for consistency with Tools viewer
1438
1634
  */
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
- `;
1635
+ displayItemData(item, itemType) {
1636
+ // Initialize UnifiedDataViewer if not already available
1637
+ if (!this.unifiedViewer) {
1638
+ this.unifiedViewer = new UnifiedDataViewer('module-data-content');
1639
+ }
1640
+
1641
+ // Use the same UnifiedDataViewer as Tools viewer for consistent display
1642
+ this.unifiedViewer.display(item, itemType);
1643
+
1644
+ // Update module header for consistency
1645
+ const moduleHeader = document.querySelector('.module-data-header h5');
1646
+ if (moduleHeader) {
1647
+ const icons = {
1648
+ 'agent': '🤖',
1649
+ 'tool': '🔧',
1650
+ 'instruction': '💬',
1651
+ 'session': '🎯',
1652
+ 'todo': '📝'
1653
+ };
1654
+ const icon = icons[itemType] || '📊';
1655
+ const name = item.name || item.agentName || item.tool_name || 'Item';
1656
+ moduleHeader.textContent = `${icon} ${itemType}: ${name}`;
1484
1657
  }
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
1658
  }
1514
1659
 
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
- }
1660
+ // Display methods removed - now using UnifiedDataViewer for consistency
1533
1661
 
1534
1662
  /**
1535
1663
  * Escape HTML for safe display