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