illuma-agents 1.0.26 → 1.0.28

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.
Files changed (42) hide show
  1. package/dist/cjs/graphs/Graph.cjs +28 -2
  2. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  3. package/dist/cjs/graphs/MultiAgentGraph.cjs +108 -0
  4. package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
  5. package/dist/cjs/messages/format.cjs.map +1 -1
  6. package/dist/cjs/stream.cjs +27 -0
  7. package/dist/cjs/stream.cjs.map +1 -1
  8. package/dist/cjs/tools/BrowserTools.cjs +125 -113
  9. package/dist/cjs/tools/BrowserTools.cjs.map +1 -1
  10. package/dist/esm/graphs/Graph.mjs +28 -2
  11. package/dist/esm/graphs/Graph.mjs.map +1 -1
  12. package/dist/esm/graphs/MultiAgentGraph.mjs +108 -0
  13. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
  14. package/dist/esm/messages/format.mjs.map +1 -1
  15. package/dist/esm/stream.mjs +27 -0
  16. package/dist/esm/stream.mjs.map +1 -1
  17. package/dist/esm/tools/BrowserTools.mjs +125 -113
  18. package/dist/esm/tools/BrowserTools.mjs.map +1 -1
  19. package/dist/types/graphs/Graph.d.ts +14 -0
  20. package/dist/types/graphs/MultiAgentGraph.d.ts +41 -0
  21. package/dist/types/messages/format.d.ts +1 -1
  22. package/dist/types/tools/BrowserTools.d.ts +45 -5
  23. package/dist/types/types/stream.d.ts +13 -0
  24. package/package.json +4 -2
  25. package/src/graphs/Graph.ts +30 -2
  26. package/src/graphs/MultiAgentGraph.ts +119 -0
  27. package/src/messages/format.ts +2 -2
  28. package/src/scripts/multi-agent-chain.ts +59 -6
  29. package/src/scripts/multi-agent-parallel-start.ts +265 -0
  30. package/src/scripts/multi-agent-parallel.ts +61 -10
  31. package/src/scripts/multi-agent-sequence.ts +6 -1
  32. package/src/scripts/parallel-asymmetric-tools-test.ts +274 -0
  33. package/src/scripts/parallel-full-metadata-test.ts +240 -0
  34. package/src/scripts/parallel-tools-test.ts +340 -0
  35. package/src/scripts/sequential-full-metadata-test.ts +197 -0
  36. package/src/scripts/single-agent-metadata-test.ts +198 -0
  37. package/src/scripts/test-thinking-handoff.ts +8 -0
  38. package/src/scripts/tools.ts +31 -11
  39. package/src/stream.ts +32 -0
  40. package/src/tools/BrowserTools.ts +356 -310
  41. package/src/tools/__tests__/BrowserTools.test.ts +263 -257
  42. package/src/types/stream.ts +15 -0
@@ -0,0 +1,197 @@
1
+ import { config } from 'dotenv';
2
+ config();
3
+
4
+ import { HumanMessage, BaseMessage } from '@langchain/core/messages';
5
+ import type * as t from '@/types';
6
+ import { ChatModelStreamHandler, createContentAggregator } from '@/stream';
7
+ import { Providers, GraphEvents } from '@/common';
8
+ import { sleep } from '@/utils/run';
9
+ import { Run } from '@/run';
10
+
11
+ const conversationHistory: BaseMessage[] = [];
12
+
13
+ /**
14
+ * Dump ALL metadata for SEQUENTIAL execution to compare with parallel
15
+ */
16
+ async function testSequentialMetadata() {
17
+ console.log('Dumping FULL metadata for SEQUENTIAL execution...\n');
18
+
19
+ const { contentParts, aggregateContent } = createContentAggregator();
20
+
21
+ const allMetadata: Array<{
22
+ event: string;
23
+ timestamp: number;
24
+ metadata: Record<string, unknown>;
25
+ }> = [];
26
+ const startTime = Date.now();
27
+
28
+ // Sequential chain: agent_a -> agent_b
29
+ const agents: t.AgentInputs[] = [
30
+ {
31
+ agentId: 'agent_a',
32
+ provider: Providers.ANTHROPIC,
33
+ clientOptions: {
34
+ modelName: 'claude-haiku-4-5',
35
+ apiKey: process.env.ANTHROPIC_API_KEY,
36
+ },
37
+ instructions: `You are Agent A. Just say "Hello from A" in one sentence.`,
38
+ },
39
+ {
40
+ agentId: 'agent_b',
41
+ provider: Providers.ANTHROPIC,
42
+ clientOptions: {
43
+ modelName: 'claude-haiku-4-5',
44
+ apiKey: process.env.ANTHROPIC_API_KEY,
45
+ },
46
+ instructions: `You are Agent B. Just say "Hello from B" in one sentence.`,
47
+ },
48
+ ];
49
+
50
+ // Sequential edge: A -> B (using edgeType not type)
51
+ const edges: t.GraphEdge[] = [
52
+ { from: 'agent_a', to: 'agent_b', edgeType: 'direct' },
53
+ ];
54
+
55
+ const captureMetadata = (
56
+ eventName: string,
57
+ metadata?: Record<string, unknown>
58
+ ) => {
59
+ if (metadata) {
60
+ allMetadata.push({
61
+ event: eventName,
62
+ timestamp: Date.now() - startTime,
63
+ metadata: { ...metadata },
64
+ });
65
+ }
66
+ };
67
+
68
+ const customHandlers = {
69
+ [GraphEvents.CHAT_MODEL_END]: {
70
+ handle: (
71
+ _event: string,
72
+ _data: t.StreamEventData,
73
+ metadata?: Record<string, unknown>
74
+ ): void => {
75
+ captureMetadata('CHAT_MODEL_END', metadata);
76
+ },
77
+ },
78
+ [GraphEvents.CHAT_MODEL_START]: {
79
+ handle: (
80
+ _event: string,
81
+ _data: t.StreamEventData,
82
+ metadata?: Record<string, unknown>
83
+ ): void => {
84
+ captureMetadata('CHAT_MODEL_START', metadata);
85
+ },
86
+ },
87
+ [GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
88
+ [GraphEvents.ON_RUN_STEP]: {
89
+ handle: (
90
+ event: GraphEvents.ON_RUN_STEP,
91
+ data: t.StreamEventData,
92
+ metadata?: Record<string, unknown>
93
+ ): void => {
94
+ captureMetadata('ON_RUN_STEP', metadata);
95
+ aggregateContent({ event, data: data as t.RunStep });
96
+ },
97
+ },
98
+ [GraphEvents.ON_RUN_STEP_DELTA]: {
99
+ handle: (
100
+ event: GraphEvents.ON_RUN_STEP_DELTA,
101
+ data: t.StreamEventData,
102
+ metadata?: Record<string, unknown>
103
+ ): void => {
104
+ aggregateContent({ event, data: data as t.RunStepDeltaEvent });
105
+ },
106
+ },
107
+ [GraphEvents.ON_MESSAGE_DELTA]: {
108
+ handle: (
109
+ event: GraphEvents.ON_MESSAGE_DELTA,
110
+ data: t.StreamEventData,
111
+ metadata?: Record<string, unknown>
112
+ ): void => {
113
+ aggregateContent({ event, data: data as t.MessageDeltaEvent });
114
+ },
115
+ },
116
+ };
117
+
118
+ const runConfig: t.RunConfig = {
119
+ runId: `sequential-metadata-${Date.now()}`,
120
+ graphConfig: {
121
+ type: 'multi-agent',
122
+ agents,
123
+ edges,
124
+ },
125
+ customHandlers,
126
+ returnContent: true,
127
+ };
128
+
129
+ try {
130
+ const run = await Run.create(runConfig);
131
+
132
+ const userMessage = `Hi`;
133
+ conversationHistory.push(new HumanMessage(userMessage));
134
+
135
+ const config = {
136
+ configurable: {
137
+ thread_id: 'sequential-metadata-test-1',
138
+ },
139
+ streamMode: 'values',
140
+ version: 'v2' as const,
141
+ };
142
+
143
+ await run.processStream({ messages: conversationHistory }, config);
144
+
145
+ // Print ALL CHAT_MODEL_START metadata (don't dedupe)
146
+ console.log(
147
+ '\n\n========== ALL CHAT_MODEL_START EVENTS (SEQUENTIAL) ==========\n'
148
+ );
149
+ for (const entry of allMetadata) {
150
+ if (entry.event === 'CHAT_MODEL_START') {
151
+ const node = entry.metadata.langgraph_node as string;
152
+ console.log(`\n--- ${node} (at ${entry.timestamp}ms) ---`);
153
+ console.dir(entry.metadata, { depth: null });
154
+ }
155
+ }
156
+
157
+ console.log('\n\n========== ALL EVENTS ==========\n');
158
+ for (const entry of allMetadata) {
159
+ console.log(
160
+ `[${entry.timestamp}ms] ${entry.event}: ${entry.metadata.langgraph_node}`
161
+ );
162
+ }
163
+
164
+ // Key comparison
165
+ console.log(
166
+ '\n\n========== KEY FIELDS COMPARISON (SEQUENTIAL) ==========\n'
167
+ );
168
+
169
+ const agentMetadataMap = new Map<string, Record<string, unknown>>();
170
+ for (const entry of allMetadata) {
171
+ if (entry.event === 'CHAT_MODEL_START') {
172
+ const node = entry.metadata.langgraph_node as string;
173
+ if (!agentMetadataMap.has(node)) {
174
+ agentMetadataMap.set(node, entry.metadata);
175
+ }
176
+ }
177
+ }
178
+
179
+ for (const [node, meta] of agentMetadataMap) {
180
+ console.log(`${node}:`);
181
+ console.log(` langgraph_step: ${meta.langgraph_step}`);
182
+ console.log(
183
+ ` langgraph_triggers: ${JSON.stringify(meta.langgraph_triggers)}`
184
+ );
185
+ console.log(` checkpoint_ns: ${meta.checkpoint_ns}`);
186
+ console.log(` __pregel_task_id: ${meta.__pregel_task_id}`);
187
+ console.log(` langgraph_path: ${JSON.stringify(meta.langgraph_path)}`);
188
+ console.log();
189
+ }
190
+
191
+ await sleep(1000);
192
+ } catch (error) {
193
+ console.error('Error:', error);
194
+ }
195
+ }
196
+
197
+ testSequentialMetadata();
@@ -0,0 +1,198 @@
1
+ import { config } from 'dotenv';
2
+ config();
3
+
4
+ import { HumanMessage, BaseMessage } from '@langchain/core/messages';
5
+ import { v4 as uuidv4 } from 'uuid';
6
+ import type * as t from '@/types';
7
+ import { ChatModelStreamHandler, createContentAggregator } from '@/stream';
8
+ import { ToolEndHandler, ModelEndHandler } from '@/events';
9
+ import { Providers, GraphEvents } from '@/common';
10
+ import { sleep } from '@/utils/run';
11
+ import { Run } from '@/run';
12
+
13
+ const conversationHistory: BaseMessage[] = [];
14
+
15
+ /**
16
+ * Single agent test with extensive metadata logging
17
+ * Compare with multi-agent-parallel-start.ts to see metadata differences
18
+ */
19
+ async function testSingleAgent() {
20
+ console.log('Testing Single Agent with Metadata Logging...\n');
21
+
22
+ // Set up content aggregator
23
+ const { contentParts, aggregateContent, contentMetadataMap } =
24
+ createContentAggregator();
25
+
26
+ const startTime = Date.now();
27
+
28
+ // Create custom handlers with extensive metadata logging
29
+ const customHandlers = {
30
+ [GraphEvents.TOOL_END]: new ToolEndHandler(),
31
+ [GraphEvents.CHAT_MODEL_END]: {
32
+ handle: (
33
+ _event: string,
34
+ _data: t.StreamEventData,
35
+ metadata?: Record<string, unknown>
36
+ ): void => {
37
+ console.log('\n====== CHAT_MODEL_END METADATA ======');
38
+ console.dir(metadata, { depth: null });
39
+ const elapsed = Date.now() - startTime;
40
+ console.log(`⏱️ COMPLETED at ${elapsed}ms`);
41
+ },
42
+ },
43
+ [GraphEvents.CHAT_MODEL_START]: {
44
+ handle: (
45
+ _event: string,
46
+ _data: t.StreamEventData,
47
+ metadata?: Record<string, unknown>
48
+ ): void => {
49
+ console.log('\n====== CHAT_MODEL_START METADATA ======');
50
+ console.dir(metadata, { depth: null });
51
+ const elapsed = Date.now() - startTime;
52
+ console.log(`⏱️ STARTED at ${elapsed}ms`);
53
+ },
54
+ },
55
+ [GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
56
+ [GraphEvents.ON_RUN_STEP_COMPLETED]: {
57
+ handle: (
58
+ event: GraphEvents.ON_RUN_STEP_COMPLETED,
59
+ data: t.StreamEventData,
60
+ metadata?: Record<string, unknown>
61
+ ): void => {
62
+ console.log('\n====== ON_RUN_STEP_COMPLETED ======');
63
+ console.log('DATA:');
64
+ console.dir(data, { depth: null });
65
+ console.log('METADATA:');
66
+ console.dir(metadata, { depth: null });
67
+ aggregateContent({
68
+ event,
69
+ data: data as unknown as { result: t.ToolEndEvent },
70
+ });
71
+ },
72
+ },
73
+ [GraphEvents.ON_RUN_STEP]: {
74
+ handle: (
75
+ event: GraphEvents.ON_RUN_STEP,
76
+ data: t.StreamEventData,
77
+ metadata?: Record<string, unknown>
78
+ ): void => {
79
+ console.log('\n====== ON_RUN_STEP ======');
80
+ console.log('DATA:');
81
+ console.dir(data, { depth: null });
82
+ console.log('METADATA:');
83
+ console.dir(metadata, { depth: null });
84
+ aggregateContent({ event, data: data as t.RunStep });
85
+ },
86
+ },
87
+ [GraphEvents.ON_RUN_STEP_DELTA]: {
88
+ handle: (
89
+ event: GraphEvents.ON_RUN_STEP_DELTA,
90
+ data: t.StreamEventData,
91
+ metadata?: Record<string, unknown>
92
+ ): void => {
93
+ console.log('\n====== ON_RUN_STEP_DELTA ======');
94
+ console.log('DATA:');
95
+ console.dir(data, { depth: null });
96
+ console.log('METADATA:');
97
+ console.dir(metadata, { depth: null });
98
+ aggregateContent({ event, data: data as t.RunStepDeltaEvent });
99
+ },
100
+ },
101
+ [GraphEvents.ON_MESSAGE_DELTA]: {
102
+ handle: (
103
+ event: GraphEvents.ON_MESSAGE_DELTA,
104
+ data: t.StreamEventData,
105
+ metadata?: Record<string, unknown>
106
+ ): void => {
107
+ console.log('\n====== ON_MESSAGE_DELTA ======');
108
+ console.log('DATA:');
109
+ console.dir(data, { depth: null });
110
+ console.log('METADATA:');
111
+ console.dir(metadata, { depth: null });
112
+ aggregateContent({ event, data: data as t.MessageDeltaEvent });
113
+ },
114
+ },
115
+ };
116
+
117
+ // Create single-agent run configuration (standard graph, not multi-agent)
118
+ const runConfig: t.RunConfig = {
119
+ runId: `single-agent-${Date.now()}`,
120
+ graphConfig: {
121
+ type: 'standard',
122
+ llmConfig: {
123
+ provider: Providers.ANTHROPIC,
124
+ modelName: 'claude-haiku-4-5',
125
+ apiKey: process.env.ANTHROPIC_API_KEY,
126
+ },
127
+ instructions: `You are a helpful AI assistant. Keep your response concise (50-100 words).`,
128
+ },
129
+ customHandlers,
130
+ returnContent: true,
131
+ };
132
+
133
+ try {
134
+ // Create and execute the run
135
+ const run = await Run.create(runConfig);
136
+
137
+ // Debug: Log the graph structure
138
+ console.log('=== DEBUG: Graph Structure ===');
139
+ const graph = (run as any).Graph;
140
+ console.log('Graph exists:', !!graph);
141
+ if (graph) {
142
+ console.log('Graph type:', graph.constructor.name);
143
+ console.log('AgentContexts exists:', !!graph.agentContexts);
144
+ if (graph.agentContexts) {
145
+ console.log('AgentContexts size:', graph.agentContexts.size);
146
+ for (const [agentId, context] of graph.agentContexts) {
147
+ console.log(`\nAgent: ${agentId}`);
148
+ console.log(
149
+ `Tools: ${context.tools?.map((t: any) => t.name || 'unnamed').join(', ') || 'none'}`
150
+ );
151
+ }
152
+ }
153
+ }
154
+ console.log('=== END DEBUG ===\n');
155
+
156
+ const userMessage = `What are the best approaches to learning a new programming language?`;
157
+ conversationHistory.push(new HumanMessage(userMessage));
158
+
159
+ console.log('Invoking single-agent graph...\n');
160
+
161
+ const config = {
162
+ configurable: {
163
+ thread_id: 'single-agent-conversation-1',
164
+ },
165
+ streamMode: 'values',
166
+ version: 'v2' as const,
167
+ };
168
+
169
+ // Process with streaming
170
+ const inputs = {
171
+ messages: conversationHistory,
172
+ };
173
+
174
+ const finalContentParts = await run.processStream(inputs, config);
175
+ const finalMessages = run.getRunMessages();
176
+
177
+ if (finalMessages) {
178
+ conversationHistory.push(...finalMessages);
179
+ }
180
+
181
+ console.log('\n\n========== SUMMARY ==========');
182
+ console.log('Final content parts:', contentParts.length, 'parts');
183
+ console.log('\n=== Content Parts (clean, no metadata) ===');
184
+ console.dir(contentParts, { depth: null });
185
+ console.log(
186
+ '\n=== Content Metadata Map (should be empty for single-agent) ==='
187
+ );
188
+ console.dir(Object.fromEntries(contentMetadataMap), { depth: null });
189
+ console.log('====================================\n');
190
+
191
+ await sleep(3000);
192
+ } catch (error) {
193
+ console.error('Error in single-agent test:', error);
194
+ }
195
+ }
196
+
197
+ // Run the test
198
+ testSingleAgent();
@@ -32,6 +32,14 @@ async function testThinkingHandoff() {
32
32
  }
33
33
  },
34
34
  },
35
+ [GraphEvents.ON_RUN_STEP]: {
36
+ handle: (_event: string, data: t.StreamEventData): void => {
37
+ const runStep = data as t.RunStep;
38
+ console.log(
39
+ `\n📍 ON_RUN_STEP: agentId=${runStep.agentId}, groupId=${runStep.groupId}`
40
+ );
41
+ },
42
+ },
35
43
  };
36
44
 
37
45
  // Create the graph configuration
@@ -21,15 +21,36 @@ async function testStandardStreaming(): Promise<void> {
21
21
  [GraphEvents.TOOL_END]: new ToolEndHandler(undefined, (name?: string) => {
22
22
  return true;
23
23
  }),
24
- [GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(),
24
+ [GraphEvents.CHAT_MODEL_END]: {
25
+ handle: (
26
+ _event: string,
27
+ _data: t.StreamEventData,
28
+ metadata?: Record<string, unknown>
29
+ ): void => {
30
+ console.log('\n====== CHAT_MODEL_END METADATA ======');
31
+ console.dir(metadata, { depth: null });
32
+ },
33
+ },
34
+ [GraphEvents.CHAT_MODEL_START]: {
35
+ handle: (
36
+ _event: string,
37
+ _data: t.StreamEventData,
38
+ metadata?: Record<string, unknown>
39
+ ): void => {
40
+ console.log('\n====== CHAT_MODEL_START METADATA ======');
41
+ console.dir(metadata, { depth: null });
42
+ },
43
+ },
25
44
  [GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
26
45
  [GraphEvents.ON_RUN_STEP_COMPLETED]: {
27
46
  handle: (
28
47
  event: GraphEvents.ON_RUN_STEP_COMPLETED,
29
- data: t.StreamEventData
48
+ data: t.StreamEventData,
49
+ metadata?: Record<string, unknown>
30
50
  ): void => {
31
51
  console.log('====== ON_RUN_STEP_COMPLETED ======');
32
- console.dir(data, { depth: null });
52
+ console.log('METADATA:');
53
+ console.dir(metadata, { depth: null });
33
54
  aggregateContent({
34
55
  event,
35
56
  data: data as unknown as { result: t.ToolEndEvent },
@@ -39,10 +60,14 @@ async function testStandardStreaming(): Promise<void> {
39
60
  [GraphEvents.ON_RUN_STEP]: {
40
61
  handle: (
41
62
  event: GraphEvents.ON_RUN_STEP,
42
- data: t.StreamEventData
63
+ data: t.StreamEventData,
64
+ metadata?: Record<string, unknown>
43
65
  ): void => {
44
66
  console.log('====== ON_RUN_STEP ======');
67
+ console.log('DATA:');
45
68
  console.dir(data, { depth: null });
69
+ console.log('METADATA:');
70
+ console.dir(metadata, { depth: null });
46
71
  aggregateContent({ event, data: data as t.RunStep });
47
72
  },
48
73
  },
@@ -51,8 +76,6 @@ async function testStandardStreaming(): Promise<void> {
51
76
  event: GraphEvents.ON_RUN_STEP_DELTA,
52
77
  data: t.StreamEventData
53
78
  ): void => {
54
- console.log('====== ON_RUN_STEP_DELTA ======');
55
- console.dir(data, { depth: null });
56
79
  aggregateContent({ event, data: data as t.RunStepDeltaEvent });
57
80
  },
58
81
  },
@@ -61,8 +84,6 @@ async function testStandardStreaming(): Promise<void> {
61
84
  event: GraphEvents.ON_MESSAGE_DELTA,
62
85
  data: t.StreamEventData
63
86
  ): void => {
64
- console.log('====== ON_MESSAGE_DELTA ======');
65
- console.dir(data, { depth: null });
66
87
  aggregateContent({ event, data: data as t.MessageDeltaEvent });
67
88
  },
68
89
  },
@@ -71,8 +92,6 @@ async function testStandardStreaming(): Promise<void> {
71
92
  event: GraphEvents.ON_REASONING_DELTA,
72
93
  data: t.StreamEventData
73
94
  ) => {
74
- console.log('====== ON_REASONING_DELTA ======');
75
- console.dir(data, { depth: null });
76
95
  aggregateContent({ event, data: data as t.ReasoningDeltaEvent });
77
96
  },
78
97
  },
@@ -83,7 +102,8 @@ async function testStandardStreaming(): Promise<void> {
83
102
  metadata?: Record<string, unknown>
84
103
  ): void => {
85
104
  console.log('====== TOOL_START ======');
86
- console.dir(data, { depth: null });
105
+ console.log('METADATA:');
106
+ console.dir(metadata, { depth: null });
87
107
  },
88
108
  },
89
109
  };
package/src/stream.ts CHANGED
@@ -442,6 +442,11 @@ export function createContentAggregator(): t.ContentAggregatorResult {
442
442
  const contentParts: Array<t.MessageContentComplex | undefined> = [];
443
443
  const stepMap = new Map<string, t.RunStep>();
444
444
  const toolCallIdMap = new Map<string, string>();
445
+ // Track agentId and groupId for each content index (applied to content parts)
446
+ const contentMetaMap = new Map<
447
+ number,
448
+ { agentId?: string; groupId?: number }
449
+ >();
445
450
 
446
451
  const updateContent = (
447
452
  index: number,
@@ -578,6 +583,17 @@ export function createContentAggregator(): t.ContentAggregatorResult {
578
583
  tool_call: newToolCall,
579
584
  };
580
585
  }
586
+
587
+ // Apply agentId (for MultiAgentGraph) and groupId (for parallel execution) to content parts
588
+ // - agentId present → MultiAgentGraph (show agent labels)
589
+ // - groupId present → parallel execution (render columns)
590
+ const meta = contentMetaMap.get(index);
591
+ if (meta?.agentId != null) {
592
+ (contentParts[index] as t.MessageContentComplex).agentId = meta.agentId;
593
+ }
594
+ if (meta?.groupId != null) {
595
+ (contentParts[index] as t.MessageContentComplex).groupId = meta.groupId;
596
+ }
581
597
  };
582
598
 
583
599
  const aggregateContent = ({
@@ -596,6 +612,22 @@ export function createContentAggregator(): t.ContentAggregatorResult {
596
612
  const runStep = data as t.RunStep;
597
613
  stepMap.set(runStep.id, runStep);
598
614
 
615
+ // Track agentId (MultiAgentGraph) and groupId (parallel execution) separately
616
+ // - agentId: present for all MultiAgentGraph runs (enables agent labels in UI)
617
+ // - groupId: present only for parallel execution (enables column rendering)
618
+ const hasAgentId = runStep.agentId != null && runStep.agentId !== '';
619
+ const hasGroupId = runStep.groupId != null;
620
+ if (hasAgentId || hasGroupId) {
621
+ const existingMeta = contentMetaMap.get(runStep.index) ?? {};
622
+ if (hasAgentId) {
623
+ existingMeta.agentId = runStep.agentId;
624
+ }
625
+ if (hasGroupId) {
626
+ existingMeta.groupId = runStep.groupId;
627
+ }
628
+ contentMetaMap.set(runStep.index, existingMeta);
629
+ }
630
+
599
631
  // Store tool call IDs if present
600
632
  if (
601
633
  runStep.stepDetails.type === StepTypes.TOOL_CALLS &&