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.
- claude_mpm/VERSION +1 -1
- claude_mpm/cli/commands/mpm_init.py +154 -7
- claude_mpm/cli/commands/mpm_init_handler.py +1 -0
- claude_mpm/cli/parsers/mpm_init_parser.py +13 -0
- claude_mpm/commands/mpm-init.md +162 -0
- claude_mpm/dashboard/static/css/activity.css +165 -0
- claude_mpm/dashboard/static/css/dashboard.css +18 -15
- claude_mpm/dashboard/static/js/components/activity-tree.js +603 -475
- {claude_mpm-4.1.15.dist-info → claude_mpm-4.1.17.dist-info}/METADATA +1 -1
- {claude_mpm-4.1.15.dist-info → claude_mpm-4.1.17.dist-info}/RECORD +14 -13
- {claude_mpm-4.1.15.dist-info → claude_mpm-4.1.17.dist-info}/WHEEL +0 -0
- {claude_mpm-4.1.15.dist-info → claude_mpm-4.1.17.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.1.15.dist-info → claude_mpm-4.1.17.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.1.15.dist-info → claude_mpm-4.1.17.dist-info}/top_level.txt +0 -0
|
@@ -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
|
|
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
|
-
//
|
|
209
|
-
|
|
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
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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
|
-
|
|
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
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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
|
|
451
|
-
session.
|
|
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
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
} else {
|
|
484
|
+
let targetAgent = session.currentActiveAgent;
|
|
485
|
+
|
|
486
|
+
if (!targetAgent) {
|
|
486
487
|
// Fall back to most recent active agent
|
|
487
|
-
const activeAgents =
|
|
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,
|
|
495
|
-
const allAgents =
|
|
496
|
-
|
|
497
|
-
if (
|
|
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
|
-
//
|
|
506
|
+
// Attach or update TodoWrite for the agent
|
|
504
507
|
if (targetAgent) {
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
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
|
-
|
|
519
|
-
|
|
520
|
-
|
|
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
|
-
//
|
|
532
|
-
|
|
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
|
-
//
|
|
535
|
-
if (!
|
|
536
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
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 =
|
|
684
|
+
let targetAgent = session.currentActiveAgent;
|
|
591
685
|
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
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
|
-
//
|
|
597
|
-
|
|
598
|
-
.
|
|
599
|
-
|
|
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
|
-
|
|
602
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
620
|
-
const
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
858
|
-
if (
|
|
1068
|
+
// Render nested content when expanded
|
|
1069
|
+
if (hasContent && isExpanded) {
|
|
859
1070
|
html += '<div class="tree-children">';
|
|
860
|
-
|
|
861
|
-
|
|
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-
|
|
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}">${
|
|
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
|
-
*
|
|
1553
|
+
* Render pinned TODOs element under agent
|
|
1175
1554
|
*/
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
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="
|
|
1248
|
-
<
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
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
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
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
|
|
1289
|
-
|
|
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-
|
|
1294
|
-
<div class="
|
|
1295
|
-
<span class="
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
<span class="
|
|
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
|
-
|
|
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
|
-
*
|
|
1619
|
+
* Handle item click to show data in left pane
|
|
1381
1620
|
*/
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
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
|
-
|
|
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
|
|
1633
|
+
* Display item data in left pane using UnifiedDataViewer for consistency with Tools viewer
|
|
1438
1634
|
*/
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
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
|