@illuma-ai/agents 1.1.15 → 1.1.17
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.
- package/dist/cjs/common/enum.cjs +15 -13
- package/dist/cjs/common/enum.cjs.map +1 -1
- package/dist/cjs/graphs/MultiAgentGraph.cjs +173 -150
- package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
- package/dist/cjs/main.cjs +2 -2
- package/dist/cjs/types/graph.cjs.map +1 -1
- package/dist/esm/common/enum.mjs +14 -12
- package/dist/esm/common/enum.mjs.map +1 -1
- package/dist/esm/graphs/MultiAgentGraph.mjs +174 -151
- package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
- package/dist/esm/main.mjs +1 -1
- package/dist/esm/types/graph.mjs.map +1 -1
- package/dist/types/common/enum.d.ts +13 -11
- package/dist/types/graphs/MultiAgentGraph.d.ts +38 -36
- package/dist/types/types/graph.d.ts +22 -7
- package/package.json +1 -1
- package/src/common/__tests__/enum.test.ts +14 -6
- package/src/common/enum.ts +13 -11
- package/src/graphs/MultiAgentGraph.ts +190 -152
- package/src/graphs/__tests__/multi-agent-delegate.test.ts +44 -44
- package/src/graphs/__tests__/multi-agent-edges.test.ts +83 -85
- package/src/scripts/multi-agent-chain.js +1 -1
- package/src/scripts/multi-agent-chain.ts +1 -1
- package/src/scripts/multi-agent-document-review-chain.js +1 -1
- package/src/scripts/multi-agent-document-review-chain.ts +1 -1
- package/src/scripts/multi-agent-hybrid-flow.js +3 -3
- package/src/scripts/multi-agent-hybrid-flow.ts +3 -3
- package/src/scripts/multi-agent-parallel.js +2 -2
- package/src/scripts/multi-agent-parallel.ts +2 -2
- package/src/scripts/multi-agent-sequence.js +2 -2
- package/src/scripts/multi-agent-sequence.ts +2 -2
- package/src/scripts/multi-agent-supervisor.js +5 -5
- package/src/scripts/multi-agent-supervisor.ts +5 -5
- package/src/scripts/poc-multi-agent-comprehensive.ts +7 -7
- package/src/scripts/sequential-full-metadata-test.js +1 -1
- package/src/scripts/sequential-full-metadata-test.ts +1 -1
- package/src/scripts/test-custom-prompt-key.js +3 -3
- package/src/scripts/test-custom-prompt-key.ts +3 -3
- package/src/scripts/test-handoff-input.js +1 -1
- package/src/scripts/test-handoff-input.ts +1 -1
- package/src/scripts/test-handoff-preamble.js +1 -1
- package/src/scripts/test-handoff-preamble.ts +1 -1
- package/src/scripts/test-handoff-steering.js +3 -3
- package/src/scripts/test-handoff-steering.ts +3 -3
- package/src/scripts/test-multi-agent-list-handoff.js +1 -1
- package/src/scripts/test-multi-agent-list-handoff.ts +1 -1
- package/src/scripts/test-parallel-agent-labeling.js +2 -2
- package/src/scripts/test-parallel-agent-labeling.ts +2 -2
- package/src/scripts/test-parallel-handoffs.js +2 -2
- package/src/scripts/test-parallel-handoffs.ts +2 -2
- package/src/scripts/test-thinking-handoff-bedrock.js +1 -1
- package/src/scripts/test-thinking-handoff-bedrock.ts +1 -1
- package/src/scripts/test-thinking-handoff.js +1 -1
- package/src/scripts/test-thinking-handoff.ts +1 -1
- package/src/scripts/test-thinking-to-thinking-handoff-bedrock.js +1 -1
- package/src/scripts/test-thinking-to-thinking-handoff-bedrock.ts +1 -1
- package/src/scripts/test-tool-before-handoff-role-order.js +1 -1
- package/src/scripts/test-tool-before-handoff-role-order.ts +1 -1
- package/src/scripts/test-tools-before-handoff.js +1 -1
- package/src/scripts/test-tools-before-handoff.ts +1 -1
- package/src/specs/agent-handoffs-bedrock.integration.test.ts +6 -6
- package/src/specs/agent-handoffs.test.ts +35 -35
- package/src/specs/thinking-handoff.test.ts +9 -9
- package/src/types/graph.ts +23 -7
|
@@ -25,35 +25,37 @@ import { StandardGraph } from './Graph';
|
|
|
25
25
|
import {
|
|
26
26
|
Constants,
|
|
27
27
|
EdgeType,
|
|
28
|
-
|
|
28
|
+
GraphEvents,
|
|
29
|
+
DEFAULT_HANDOFF_MAX_RESULT_CHARS,
|
|
29
30
|
} from '@/common';
|
|
31
|
+
import { safeDispatchCustomEvent } from '@/utils/events';
|
|
30
32
|
|
|
31
33
|
/** Pattern to extract instructions from transfer ToolMessage content */
|
|
32
|
-
const
|
|
34
|
+
const TRANSFER_INSTRUCTIONS_PATTERN = /(?:Instructions?|Context):\s*(.+)/is;
|
|
33
35
|
|
|
34
36
|
/**
|
|
35
37
|
* MultiAgentGraph extends StandardGraph to support dynamic multi-agent workflows
|
|
36
38
|
* with handoffs, fan-in/fan-out, and other composable patterns.
|
|
37
39
|
*
|
|
38
40
|
* Key behavior:
|
|
39
|
-
* - Agents with ONLY
|
|
40
|
-
* - Agents with ONLY
|
|
41
|
-
* - Agents with BOTH: Use Command for exclusive routing (
|
|
42
|
-
* - If
|
|
43
|
-
* - If no
|
|
41
|
+
* - Agents with ONLY transfer edges: Can dynamically route to any transfer destination
|
|
42
|
+
* - Agents with ONLY sequence edges: Always follow their sequence edges
|
|
43
|
+
* - Agents with BOTH: Use Command for exclusive routing (transfer OR sequence, not both)
|
|
44
|
+
* - If transfer occurs: Only the transfer destination executes
|
|
45
|
+
* - If no transfer: Sequence edges execute (potentially in parallel)
|
|
44
46
|
*
|
|
45
|
-
* This enables the common pattern where an agent either
|
|
46
|
-
* OR continues its workflow (
|
|
47
|
+
* This enables the common pattern where an agent either transfers (one-way)
|
|
48
|
+
* OR continues its workflow (sequence edges), but not both simultaneously.
|
|
47
49
|
*/
|
|
48
50
|
export class MultiAgentGraph extends StandardGraph {
|
|
49
51
|
private edges: t.GraphEdge[];
|
|
50
52
|
private startingNodes: Set<string> = new Set();
|
|
51
|
-
private
|
|
53
|
+
private sequenceEdges: t.GraphEdge[] = [];
|
|
54
|
+
private transferEdges: t.GraphEdge[] = [];
|
|
52
55
|
private handoffEdges: t.GraphEdge[] = [];
|
|
53
|
-
private delegateEdges: t.GraphEdge[] = [];
|
|
54
56
|
/**
|
|
55
57
|
* Lazily populated registry of compiled subgraphs, keyed by agentId.
|
|
56
|
-
*
|
|
58
|
+
* Handoff tools are created in the constructor but reference subgraphs
|
|
57
59
|
* that are only created in createWorkflow(). This Map bridges that gap —
|
|
58
60
|
* tools capture the Map reference in their closure, and createWorkflow()
|
|
59
61
|
* populates it before any tool invocation occurs.
|
|
@@ -81,42 +83,40 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
81
83
|
this.edges = input.edges;
|
|
82
84
|
this.categorizeEdges();
|
|
83
85
|
this.analyzeGraph();
|
|
86
|
+
this.createTransferTools();
|
|
84
87
|
this.createHandoffTools();
|
|
85
|
-
this.createDelegateTools();
|
|
86
88
|
console.debug(
|
|
87
89
|
`[MultiAgentGraph] Constructor complete: ${this.agentContexts.size} agents, ${this.edges.length} edges`
|
|
88
90
|
);
|
|
89
91
|
}
|
|
90
92
|
|
|
91
93
|
/**
|
|
92
|
-
* Categorize edges into handoff and
|
|
94
|
+
* Categorize edges into handoff, transfer, and sequence types
|
|
93
95
|
*/
|
|
94
96
|
private categorizeEdges(): void {
|
|
95
97
|
for (const edge of this.edges) {
|
|
96
|
-
|
|
97
|
-
// Edges with explicit 'direct' type or multi-destination without conditions are direct edges
|
|
98
|
-
if (edge.edgeType === EdgeType.DELEGATE) {
|
|
99
|
-
this.delegateEdges.push(edge);
|
|
100
|
-
} else if (edge.edgeType === EdgeType.DIRECT) {
|
|
101
|
-
this.directEdges.push(edge);
|
|
102
|
-
} else if (edge.edgeType === EdgeType.HANDOFF || edge.condition != null) {
|
|
98
|
+
if (edge.edgeType === EdgeType.HANDOFF) {
|
|
103
99
|
this.handoffEdges.push(edge);
|
|
100
|
+
} else if (edge.edgeType === EdgeType.SEQUENCE) {
|
|
101
|
+
this.sequenceEdges.push(edge);
|
|
102
|
+
} else if (edge.edgeType === EdgeType.TRANSFER || edge.condition != null) {
|
|
103
|
+
this.transferEdges.push(edge);
|
|
104
104
|
} else {
|
|
105
|
-
// Default: single-to-single edges are
|
|
105
|
+
// Default: single-to-single edges are transfer, single-to-multiple are sequence
|
|
106
106
|
const destinations = Array.isArray(edge.to) ? edge.to : [edge.to];
|
|
107
107
|
const sources = Array.isArray(edge.from) ? edge.from : [edge.from];
|
|
108
108
|
|
|
109
109
|
if (sources.length === 1 && destinations.length > 1) {
|
|
110
|
-
// Fan-out pattern defaults to
|
|
111
|
-
this.
|
|
110
|
+
// Fan-out pattern defaults to sequence
|
|
111
|
+
this.sequenceEdges.push(edge);
|
|
112
112
|
} else {
|
|
113
|
-
// Everything else defaults to
|
|
114
|
-
this.
|
|
113
|
+
// Everything else defaults to transfer
|
|
114
|
+
this.transferEdges.push(edge);
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
118
|
console.debug(
|
|
119
|
-
`[MultiAgentGraph] Edge categorization: ${this.handoffEdges.length} handoff, ${this.
|
|
119
|
+
`[MultiAgentGraph] Edge categorization: ${this.handoffEdges.length} handoff, ${this.transferEdges.length} transfer, ${this.sequenceEdges.length} sequence (of ${this.edges.length} total)`
|
|
120
120
|
);
|
|
121
121
|
}
|
|
122
122
|
|
|
@@ -187,8 +187,8 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
187
187
|
if (visited.has(current)) continue;
|
|
188
188
|
visited.add(current);
|
|
189
189
|
|
|
190
|
-
// Find
|
|
191
|
-
for (const edge of this.
|
|
190
|
+
// Find sequence edges from this node
|
|
191
|
+
for (const edge of this.sequenceEdges) {
|
|
192
192
|
const sources = Array.isArray(edge.from) ? edge.from : [edge.from];
|
|
193
193
|
if (!sources.includes(current)) continue;
|
|
194
194
|
|
|
@@ -216,8 +216,8 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
216
216
|
}
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
-
// Also follow
|
|
220
|
-
for (const edge of [...this.
|
|
219
|
+
// Also follow transfer and handoff edges for traversal (they don't create parallel groups)
|
|
220
|
+
for (const edge of [...this.transferEdges, ...this.handoffEdges]) {
|
|
221
221
|
const sources = Array.isArray(edge.from) ? edge.from : [edge.from];
|
|
222
222
|
if (!sources.includes(current)) continue;
|
|
223
223
|
|
|
@@ -267,54 +267,52 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
267
267
|
}
|
|
268
268
|
|
|
269
269
|
/**
|
|
270
|
-
* Create
|
|
270
|
+
* Create transfer tools for agents based on transfer edges only.
|
|
271
|
+
* Transfer tools return Command for one-way routing — parent exits, child takes over.
|
|
271
272
|
*/
|
|
272
|
-
private
|
|
273
|
-
|
|
274
|
-
const handoffsByAgent = new Map<string, t.GraphEdge[]>();
|
|
273
|
+
private createTransferTools(): void {
|
|
274
|
+
const transfersByAgent = new Map<string, t.GraphEdge[]>();
|
|
275
275
|
|
|
276
|
-
|
|
277
|
-
for (const edge of this.handoffEdges) {
|
|
276
|
+
for (const edge of this.transferEdges) {
|
|
278
277
|
const sources = Array.isArray(edge.from) ? edge.from : [edge.from];
|
|
279
278
|
sources.forEach((source) => {
|
|
280
|
-
if (!
|
|
281
|
-
|
|
279
|
+
if (!transfersByAgent.has(source)) {
|
|
280
|
+
transfersByAgent.set(source, []);
|
|
282
281
|
}
|
|
283
|
-
|
|
282
|
+
transfersByAgent.get(source)!.push(edge);
|
|
284
283
|
});
|
|
285
284
|
}
|
|
286
285
|
|
|
287
|
-
|
|
288
|
-
for (const [agentId, edges] of handoffsByAgent) {
|
|
286
|
+
for (const [agentId, edges] of transfersByAgent) {
|
|
289
287
|
const agentContext = this.agentContexts.get(agentId);
|
|
290
288
|
if (!agentContext) continue;
|
|
291
289
|
|
|
292
|
-
|
|
293
|
-
const handoffTools: t.GenericTool[] = [];
|
|
290
|
+
const transferTools: t.GenericTool[] = [];
|
|
294
291
|
const sourceAgentName = agentContext.name ?? agentId;
|
|
295
292
|
for (const edge of edges) {
|
|
296
|
-
|
|
297
|
-
...this.
|
|
293
|
+
transferTools.push(
|
|
294
|
+
...this.createTransferToolsForEdge(edge, agentId, sourceAgentName)
|
|
298
295
|
);
|
|
299
296
|
}
|
|
300
297
|
|
|
301
298
|
if (!agentContext.graphTools) {
|
|
302
299
|
agentContext.graphTools = [];
|
|
303
300
|
}
|
|
304
|
-
agentContext.graphTools.push(...
|
|
301
|
+
agentContext.graphTools.push(...transferTools);
|
|
305
302
|
console.debug(
|
|
306
|
-
`[MultiAgentGraph]
|
|
303
|
+
`[MultiAgentGraph] Transfer tools for "${agentId}": [${transferTools.map((t) => t.name).join(', ')}]`
|
|
307
304
|
);
|
|
308
305
|
}
|
|
309
306
|
}
|
|
310
307
|
|
|
311
308
|
/**
|
|
312
|
-
* Create
|
|
313
|
-
*
|
|
314
|
-
* @param
|
|
309
|
+
* Create transfer tools for an edge (handles multiple destinations).
|
|
310
|
+
* Transfer tools return Command for one-way routing — parent exits, child takes over.
|
|
311
|
+
* @param edge - The graph edge defining the transfer
|
|
312
|
+
* @param sourceAgentId - The ID of the agent that will perform the transfer
|
|
315
313
|
* @param sourceAgentName - The human-readable name of the source agent
|
|
316
314
|
*/
|
|
317
|
-
private
|
|
315
|
+
private createTransferToolsForEdge(
|
|
318
316
|
edge: t.GraphEdge,
|
|
319
317
|
sourceAgentId: string,
|
|
320
318
|
sourceAgentName: string
|
|
@@ -329,9 +327,9 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
329
327
|
edge.description ?? 'Conditionally transfer control based on state';
|
|
330
328
|
|
|
331
329
|
/** Check if we have a prompt for handoff input */
|
|
332
|
-
const
|
|
330
|
+
const hasTransferInput =
|
|
333
331
|
edge.prompt != null && typeof edge.prompt === 'string';
|
|
334
|
-
const
|
|
332
|
+
const transferInputDescription = hasTransferInput ? edge.prompt : undefined;
|
|
335
333
|
const promptKey = edge.promptKey ?? 'instructions';
|
|
336
334
|
|
|
337
335
|
tools.push(
|
|
@@ -360,7 +358,7 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
360
358
|
|
|
361
359
|
let content = `Conditionally transferred to ${destination}`;
|
|
362
360
|
if (
|
|
363
|
-
|
|
361
|
+
hasTransferInput &&
|
|
364
362
|
promptKey in input &&
|
|
365
363
|
input[promptKey] != null
|
|
366
364
|
) {
|
|
@@ -387,13 +385,13 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
387
385
|
},
|
|
388
386
|
{
|
|
389
387
|
name: toolName,
|
|
390
|
-
schema:
|
|
388
|
+
schema: hasTransferInput
|
|
391
389
|
? {
|
|
392
390
|
type: 'object',
|
|
393
391
|
properties: {
|
|
394
392
|
[promptKey]: {
|
|
395
393
|
type: 'string',
|
|
396
|
-
description:
|
|
394
|
+
description: transferInputDescription as string,
|
|
397
395
|
},
|
|
398
396
|
},
|
|
399
397
|
required: [],
|
|
@@ -410,12 +408,12 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
410
408
|
const destContext = this.agentContexts.get(destination);
|
|
411
409
|
const toolDescription =
|
|
412
410
|
edge.description ??
|
|
413
|
-
this.
|
|
411
|
+
this.buildDefaultTransferDescription(destContext, destination);
|
|
414
412
|
|
|
415
413
|
/** Check if we have a prompt for handoff input */
|
|
416
|
-
const
|
|
414
|
+
const hasTransferInput =
|
|
417
415
|
edge.prompt != null && typeof edge.prompt === 'string';
|
|
418
|
-
const
|
|
416
|
+
const transferInputDescription = hasTransferInput
|
|
419
417
|
? edge.prompt
|
|
420
418
|
: undefined;
|
|
421
419
|
const promptKey = edge.promptKey ?? 'instructions';
|
|
@@ -430,7 +428,7 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
430
428
|
|
|
431
429
|
let content = `Successfully transferred to ${destination}`;
|
|
432
430
|
if (
|
|
433
|
-
|
|
431
|
+
hasTransferInput &&
|
|
434
432
|
promptKey in input &&
|
|
435
433
|
input[promptKey] != null
|
|
436
434
|
) {
|
|
@@ -521,13 +519,13 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
521
519
|
},
|
|
522
520
|
{
|
|
523
521
|
name: toolName,
|
|
524
|
-
schema:
|
|
522
|
+
schema: hasTransferInput
|
|
525
523
|
? {
|
|
526
524
|
type: 'object',
|
|
527
525
|
properties: {
|
|
528
526
|
[promptKey]: {
|
|
529
527
|
type: 'string',
|
|
530
|
-
description:
|
|
528
|
+
description: transferInputDescription as string,
|
|
531
529
|
},
|
|
532
530
|
},
|
|
533
531
|
required: [],
|
|
@@ -544,13 +542,13 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
544
542
|
}
|
|
545
543
|
|
|
546
544
|
/**
|
|
547
|
-
* Builds a meaningful default description for a
|
|
545
|
+
* Builds a meaningful default description for a transfer tool when no explicit
|
|
548
546
|
* edge.description is provided. Uses the destination agent's name and description
|
|
549
547
|
* so the LLM can make informed routing decisions.
|
|
550
548
|
* @param destContext - AgentContext of the destination agent (may be undefined)
|
|
551
549
|
* @param destinationId - Raw agent ID (fallback when context unavailable)
|
|
552
550
|
*/
|
|
553
|
-
private
|
|
551
|
+
private buildDefaultTransferDescription(
|
|
554
552
|
destContext: import('@/agents/AgentContext').AgentContext | undefined,
|
|
555
553
|
destinationId: string
|
|
556
554
|
): string {
|
|
@@ -564,72 +562,72 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
564
562
|
}
|
|
565
563
|
|
|
566
564
|
/**
|
|
567
|
-
* Create
|
|
568
|
-
*
|
|
569
|
-
* as a string to the parent agent's context. Unlike
|
|
570
|
-
* return Command for
|
|
571
|
-
*
|
|
565
|
+
* Create handoff tools for agents based on handoff edges.
|
|
566
|
+
* Handoff tools invoke child agent subgraphs inline and return the result
|
|
567
|
+
* as a string to the parent agent's context. Unlike transfer tools (which
|
|
568
|
+
* return Command for one-way routing), handoff tools execute the child,
|
|
569
|
+
* extract the final text, and return it within the parent's agent loop.
|
|
572
570
|
*
|
|
573
571
|
* This enables the supervisor pattern: parent calls child → gets result → thinks → calls another.
|
|
574
572
|
*/
|
|
575
|
-
private
|
|
576
|
-
const
|
|
573
|
+
private createHandoffTools(): void {
|
|
574
|
+
const handoffsByAgent = new Map<string, t.GraphEdge[]>();
|
|
577
575
|
|
|
578
|
-
for (const edge of this.
|
|
576
|
+
for (const edge of this.handoffEdges) {
|
|
579
577
|
const sources = Array.isArray(edge.from) ? edge.from : [edge.from];
|
|
580
578
|
sources.forEach((source) => {
|
|
581
|
-
if (!
|
|
582
|
-
|
|
579
|
+
if (!handoffsByAgent.has(source)) {
|
|
580
|
+
handoffsByAgent.set(source, []);
|
|
583
581
|
}
|
|
584
|
-
|
|
582
|
+
handoffsByAgent.get(source)!.push(edge);
|
|
585
583
|
});
|
|
586
584
|
}
|
|
587
585
|
|
|
588
|
-
for (const [agentId, edges] of
|
|
586
|
+
for (const [agentId, edges] of handoffsByAgent) {
|
|
589
587
|
const agentContext = this.agentContexts.get(agentId);
|
|
590
588
|
if (!agentContext) continue;
|
|
591
589
|
|
|
592
|
-
const
|
|
590
|
+
const handoffTools: t.GenericTool[] = [];
|
|
593
591
|
for (const edge of edges) {
|
|
594
|
-
|
|
595
|
-
...this.
|
|
592
|
+
handoffTools.push(
|
|
593
|
+
...this.createHandoffToolsForEdge(edge, agentId)
|
|
596
594
|
);
|
|
597
595
|
}
|
|
598
596
|
|
|
599
597
|
if (!agentContext.graphTools) {
|
|
600
598
|
agentContext.graphTools = [];
|
|
601
599
|
}
|
|
602
|
-
agentContext.graphTools.push(...
|
|
600
|
+
agentContext.graphTools.push(...handoffTools);
|
|
603
601
|
console.debug(
|
|
604
|
-
`[MultiAgentGraph]
|
|
602
|
+
`[MultiAgentGraph] Handoff tools for "${agentId}": [${handoffTools.map((t) => t.name).join(', ')}]`
|
|
605
603
|
);
|
|
606
604
|
}
|
|
607
605
|
}
|
|
608
606
|
|
|
609
607
|
/**
|
|
610
|
-
* Create
|
|
611
|
-
* Each
|
|
608
|
+
* Create handoff tools for an edge (handles multiple destinations).
|
|
609
|
+
* Each handoff tool invokes the child agent's compiled subgraph inline,
|
|
612
610
|
* extracts the final AI message text, truncates it, and returns it as
|
|
613
611
|
* a string (which becomes a ToolMessage in the parent's context).
|
|
614
612
|
*
|
|
615
|
-
* @param edge - The graph edge defining the
|
|
613
|
+
* @param edge - The graph edge defining the handoff
|
|
616
614
|
* @param sourceAgentId - The ID of the parent/supervisor agent
|
|
617
615
|
*/
|
|
618
|
-
private
|
|
616
|
+
private createHandoffToolsForEdge(
|
|
619
617
|
edge: t.GraphEdge,
|
|
620
618
|
sourceAgentId: string
|
|
621
619
|
): t.GenericTool[] {
|
|
622
620
|
const tools: t.GenericTool[] = [];
|
|
623
621
|
const destinations = Array.isArray(edge.to) ? edge.to : [edge.to];
|
|
624
622
|
const maxResultChars =
|
|
625
|
-
edge.maxResultChars ??
|
|
623
|
+
edge.maxResultChars ?? DEFAULT_HANDOFF_MAX_RESULT_CHARS;
|
|
626
624
|
|
|
627
625
|
for (const destination of destinations) {
|
|
628
|
-
const toolName = `${Constants.
|
|
626
|
+
const toolName = `${Constants.LC_HANDOFF_TO_}${destination}`;
|
|
629
627
|
const destContext = this.agentContexts.get(destination);
|
|
630
628
|
const toolDescription =
|
|
631
629
|
edge.description ??
|
|
632
|
-
this.
|
|
630
|
+
this.buildDefaultHandoffDescription(destContext, destination);
|
|
633
631
|
|
|
634
632
|
const hasPromptInput =
|
|
635
633
|
edge.prompt != null && typeof edge.prompt === 'string';
|
|
@@ -646,7 +644,7 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
646
644
|
const subgraph = registry.get(destination);
|
|
647
645
|
if (!subgraph) {
|
|
648
646
|
throw new Error(
|
|
649
|
-
`
|
|
647
|
+
`Handoff target "${destination}" subgraph not found in registry. ` +
|
|
650
648
|
'This is a bug: createWorkflow() should have populated the subgraph registry.'
|
|
651
649
|
);
|
|
652
650
|
}
|
|
@@ -671,7 +669,7 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
671
669
|
};
|
|
672
670
|
|
|
673
671
|
console.debug(
|
|
674
|
-
`[MultiAgentGraph]
|
|
672
|
+
`[MultiAgentGraph] Handoff "${sourceAgentId}" -> "${destination}" START ` +
|
|
675
673
|
`(messages: ${childMessages.length})`
|
|
676
674
|
);
|
|
677
675
|
|
|
@@ -683,30 +681,43 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
683
681
|
*/
|
|
684
682
|
const result = await subgraph.invoke(childState, config);
|
|
685
683
|
|
|
686
|
-
const resultText = MultiAgentGraph.
|
|
684
|
+
const resultText = MultiAgentGraph.extractHandoffResult(
|
|
687
685
|
result.messages,
|
|
688
686
|
destination
|
|
689
687
|
);
|
|
690
|
-
const truncatedResult = MultiAgentGraph.
|
|
688
|
+
const truncatedResult = MultiAgentGraph.truncateHandoffResult(
|
|
691
689
|
resultText,
|
|
692
690
|
maxResultChars
|
|
693
691
|
);
|
|
694
692
|
|
|
695
693
|
console.debug(
|
|
696
|
-
`[MultiAgentGraph]
|
|
694
|
+
`[MultiAgentGraph] Handoff "${sourceAgentId}" -> "${destination}" DONE ` +
|
|
697
695
|
`(result: ${resultText.length} chars` +
|
|
698
696
|
`${truncatedResult.length < resultText.length ? `, truncated to ${truncatedResult.length}` : ''})`
|
|
699
697
|
);
|
|
700
698
|
|
|
699
|
+
await safeDispatchCustomEvent(
|
|
700
|
+
GraphEvents.ON_AGENT_TRANSITION,
|
|
701
|
+
{
|
|
702
|
+
sourceAgentId: sourceAgentId,
|
|
703
|
+
sourceAgentName: this.agentContexts.get(sourceAgentId)?.name ?? sourceAgentId,
|
|
704
|
+
destinationAgentId: destination,
|
|
705
|
+
destinationAgentName: destContext?.name ?? destination,
|
|
706
|
+
edgeType: EdgeType.HANDOFF,
|
|
707
|
+
timestamp: Date.now(),
|
|
708
|
+
},
|
|
709
|
+
config
|
|
710
|
+
);
|
|
711
|
+
|
|
701
712
|
return truncatedResult;
|
|
702
713
|
} catch (err) {
|
|
703
714
|
const errorMessage =
|
|
704
715
|
err instanceof Error ? err.message : String(err);
|
|
705
716
|
console.error(
|
|
706
|
-
`[MultiAgentGraph]
|
|
717
|
+
`[MultiAgentGraph] Handoff "${sourceAgentId}" -> "${destination}" ERROR:`,
|
|
707
718
|
errorMessage
|
|
708
719
|
);
|
|
709
|
-
return `[
|
|
720
|
+
return `[Handoff to "${destination}" failed: ${errorMessage}]`;
|
|
710
721
|
}
|
|
711
722
|
},
|
|
712
723
|
{
|
|
@@ -739,7 +750,7 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
739
750
|
* @param messages - The child agent's output messages
|
|
740
751
|
* @param agentId - The child agent ID (for fallback message)
|
|
741
752
|
*/
|
|
742
|
-
static
|
|
753
|
+
static extractHandoffResult(
|
|
743
754
|
messages: BaseMessage[],
|
|
744
755
|
agentId: string
|
|
745
756
|
): string {
|
|
@@ -780,19 +791,19 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
780
791
|
}
|
|
781
792
|
|
|
782
793
|
/**
|
|
783
|
-
* Truncate
|
|
794
|
+
* Truncate handoff result using head/tail strategy (60/40 split).
|
|
784
795
|
* Preserves the beginning (key findings) and end (conclusions).
|
|
785
796
|
* Matches the TaskTool.truncateResult pattern from Ranger.
|
|
786
797
|
* @param result - The full result text
|
|
787
798
|
* @param maxChars - Maximum allowed characters
|
|
788
799
|
*/
|
|
789
|
-
static
|
|
800
|
+
static truncateHandoffResult(result: string, maxChars: number): string {
|
|
790
801
|
if (!result || result.length <= maxChars) {
|
|
791
802
|
return result;
|
|
792
803
|
}
|
|
793
804
|
|
|
794
805
|
const truncationNotice =
|
|
795
|
-
'\n\n[...
|
|
806
|
+
'\n\n[... handoff output truncated — middle section omitted to fit parent context ...]\n\n';
|
|
796
807
|
const available = maxChars - truncationNotice.length;
|
|
797
808
|
if (available <= 0) {
|
|
798
809
|
return result.substring(0, maxChars);
|
|
@@ -809,11 +820,11 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
809
820
|
}
|
|
810
821
|
|
|
811
822
|
/**
|
|
812
|
-
* Build a meaningful default description for a
|
|
823
|
+
* Build a meaningful default description for a handoff tool.
|
|
813
824
|
* @param destContext - AgentContext of the destination agent
|
|
814
825
|
* @param destinationId - Raw agent ID (fallback)
|
|
815
826
|
*/
|
|
816
|
-
private
|
|
827
|
+
private buildDefaultHandoffDescription(
|
|
817
828
|
destContext: import('@/agents/AgentContext').AgentContext | undefined,
|
|
818
829
|
destinationId: string
|
|
819
830
|
): string {
|
|
@@ -821,9 +832,9 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
821
832
|
const agentDescription = destContext?.description;
|
|
822
833
|
|
|
823
834
|
if (agentDescription != null && agentDescription !== '') {
|
|
824
|
-
return `
|
|
835
|
+
return `Hand off task to "${displayName}": ${agentDescription}. The agent will execute and return its result.`;
|
|
825
836
|
}
|
|
826
|
-
return `
|
|
837
|
+
return `Hand off task to "${displayName}" and receive its result.`;
|
|
827
838
|
}
|
|
828
839
|
|
|
829
840
|
/**
|
|
@@ -846,7 +857,7 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
846
857
|
* @param agentId - The agent ID to check for handoff reception
|
|
847
858
|
* @returns Object with filtered messages, extracted instructions, source agent, and parallel siblings
|
|
848
859
|
*/
|
|
849
|
-
private
|
|
860
|
+
private processTransferReception(
|
|
850
861
|
messages: BaseMessage[],
|
|
851
862
|
agentId: string
|
|
852
863
|
): {
|
|
@@ -886,8 +897,8 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
886
897
|
if (isTransferMessage) {
|
|
887
898
|
destinationAgent = toolName.replace(Constants.LC_TRANSFER_TO_, '');
|
|
888
899
|
} else if (isConditionalTransfer) {
|
|
889
|
-
const
|
|
890
|
-
destinationAgent = typeof
|
|
900
|
+
const transferDest = candidateMsg.additional_kwargs.handoff_destination;
|
|
901
|
+
destinationAgent = typeof transferDest === 'string' ? transferDest : null;
|
|
891
902
|
}
|
|
892
903
|
|
|
893
904
|
/** Check if this transfer targets our agent */
|
|
@@ -907,7 +918,7 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
907
918
|
? toolMessage.content
|
|
908
919
|
: JSON.stringify(toolMessage.content);
|
|
909
920
|
|
|
910
|
-
const instructionsMatch = contentStr.match(
|
|
921
|
+
const instructionsMatch = contentStr.match(TRANSFER_INSTRUCTIONS_PATTERN);
|
|
911
922
|
const instructions = instructionsMatch?.[1]?.trim() ?? null;
|
|
912
923
|
|
|
913
924
|
/** Extract source agent name from additional_kwargs */
|
|
@@ -1138,7 +1149,7 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
1138
1149
|
}
|
|
1139
1150
|
|
|
1140
1151
|
/**
|
|
1141
|
-
* Create the multi-agent workflow with
|
|
1152
|
+
* Create the multi-agent workflow with handoffs, transfers, and sequences
|
|
1142
1153
|
*/
|
|
1143
1154
|
override createWorkflow(): t.CompiledMultiAgentWorkflow {
|
|
1144
1155
|
const StateAnnotation = Annotation.Root({
|
|
@@ -1166,45 +1177,45 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
1166
1177
|
// Add all agents as complete subgraphs
|
|
1167
1178
|
for (const [agentId] of this.agentContexts) {
|
|
1168
1179
|
// Get all possible destinations for this agent
|
|
1169
|
-
const
|
|
1170
|
-
const
|
|
1180
|
+
const transferDestinations = new Set<string>();
|
|
1181
|
+
const sequenceDestinations = new Set<string>();
|
|
1171
1182
|
|
|
1172
|
-
// Check
|
|
1173
|
-
for (const edge of this.
|
|
1183
|
+
// Check transfer edges for destinations
|
|
1184
|
+
for (const edge of this.transferEdges) {
|
|
1174
1185
|
const sources = Array.isArray(edge.from) ? edge.from : [edge.from];
|
|
1175
1186
|
if (sources.includes(agentId) === true) {
|
|
1176
1187
|
const dests = Array.isArray(edge.to) ? edge.to : [edge.to];
|
|
1177
|
-
dests.forEach((dest) =>
|
|
1188
|
+
dests.forEach((dest) => transferDestinations.add(dest));
|
|
1178
1189
|
}
|
|
1179
1190
|
}
|
|
1180
1191
|
|
|
1181
|
-
// Check
|
|
1182
|
-
for (const edge of this.
|
|
1192
|
+
// Check sequence edges for destinations
|
|
1193
|
+
for (const edge of this.sequenceEdges) {
|
|
1183
1194
|
const sources = Array.isArray(edge.from) ? edge.from : [edge.from];
|
|
1184
1195
|
if (sources.includes(agentId) === true) {
|
|
1185
1196
|
const dests = Array.isArray(edge.to) ? edge.to : [edge.to];
|
|
1186
|
-
dests.forEach((dest) =>
|
|
1197
|
+
dests.forEach((dest) => sequenceDestinations.add(dest));
|
|
1187
1198
|
}
|
|
1188
1199
|
}
|
|
1189
1200
|
|
|
1190
|
-
/** Check if this agent has BOTH
|
|
1191
|
-
const
|
|
1192
|
-
const
|
|
1193
|
-
const needsCommandRouting =
|
|
1201
|
+
/** Check if this agent has BOTH transfer and sequence edges */
|
|
1202
|
+
const hasTransferEdges = transferDestinations.size > 0;
|
|
1203
|
+
const hasSequenceEdges = sequenceDestinations.size > 0;
|
|
1204
|
+
const needsCommandRouting = hasTransferEdges && hasSequenceEdges;
|
|
1194
1205
|
|
|
1195
1206
|
/** Collect all possible destinations for this agent */
|
|
1196
1207
|
const allDestinations = new Set([
|
|
1197
|
-
...
|
|
1198
|
-
...
|
|
1208
|
+
...transferDestinations,
|
|
1209
|
+
...sequenceDestinations,
|
|
1199
1210
|
]);
|
|
1200
|
-
if (
|
|
1211
|
+
if (transferDestinations.size > 0 || sequenceDestinations.size === 0) {
|
|
1201
1212
|
allDestinations.add(END);
|
|
1202
1213
|
}
|
|
1203
1214
|
|
|
1204
1215
|
/** Agent subgraph (includes agent + tools) */
|
|
1205
1216
|
const agentSubgraph = this.createAgentSubgraph(agentId);
|
|
1206
1217
|
|
|
1207
|
-
/** Register subgraph for
|
|
1218
|
+
/** Register subgraph for handoff tools (lazy reference resolution) */
|
|
1208
1219
|
this.subgraphRegistry.set(agentId, agentSubgraph);
|
|
1209
1220
|
|
|
1210
1221
|
/** Wrapper function that handles agentMessages channel, handoff reception, and conditional routing */
|
|
@@ -1218,25 +1229,25 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
1218
1229
|
let result: t.MultiAgentGraphState;
|
|
1219
1230
|
|
|
1220
1231
|
/**
|
|
1221
|
-
* Check if this agent is receiving a
|
|
1232
|
+
* Check if this agent is receiving a transfer.
|
|
1222
1233
|
* If so, filter out the transfer messages and inject instructions as preamble.
|
|
1223
1234
|
* This prevents the receiving agent from seeing the transfer as "completed work"
|
|
1224
1235
|
* and prematurely producing an end token.
|
|
1225
1236
|
*/
|
|
1226
|
-
const
|
|
1237
|
+
const transferContext = this.processTransferReception(
|
|
1227
1238
|
state.messages,
|
|
1228
1239
|
agentId
|
|
1229
1240
|
);
|
|
1230
1241
|
|
|
1231
|
-
if (
|
|
1242
|
+
if (transferContext !== null) {
|
|
1232
1243
|
const {
|
|
1233
1244
|
filteredMessages,
|
|
1234
1245
|
instructions,
|
|
1235
1246
|
sourceAgentName,
|
|
1236
1247
|
parallelSiblings,
|
|
1237
|
-
} =
|
|
1248
|
+
} = transferContext;
|
|
1238
1249
|
console.debug(
|
|
1239
|
-
`[MultiAgentGraph] Agent "${agentId}" receiving
|
|
1250
|
+
`[MultiAgentGraph] Agent "${agentId}" receiving transfer from "${sourceAgentName}" (instructions: ${instructions != null}, parallelSiblings: ${parallelSiblings.length})`
|
|
1240
1251
|
);
|
|
1241
1252
|
|
|
1242
1253
|
/**
|
|
@@ -1381,9 +1392,9 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
1381
1392
|
`[MultiAgentGraph] Agent "${agentId}" wrapper EXIT (result messages: ${result.messages.length})`
|
|
1382
1393
|
);
|
|
1383
1394
|
|
|
1384
|
-
/** If agent has both
|
|
1395
|
+
/** If agent has both transfer and sequence edges, use Command for exclusive routing */
|
|
1385
1396
|
if (needsCommandRouting) {
|
|
1386
|
-
/** Check if a
|
|
1397
|
+
/** Check if a transfer occurred */
|
|
1387
1398
|
const lastMessage = result.messages[
|
|
1388
1399
|
result.messages.length - 1
|
|
1389
1400
|
] as BaseMessage | null;
|
|
@@ -1393,26 +1404,26 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
1393
1404
|
typeof lastMessage.name === 'string' &&
|
|
1394
1405
|
lastMessage.name.startsWith(Constants.LC_TRANSFER_TO_)
|
|
1395
1406
|
) {
|
|
1396
|
-
/**
|
|
1397
|
-
const
|
|
1407
|
+
/** Transfer occurred - extract destination and navigate there exclusively */
|
|
1408
|
+
const transferDest = lastMessage.name.replace(
|
|
1398
1409
|
Constants.LC_TRANSFER_TO_,
|
|
1399
1410
|
''
|
|
1400
1411
|
);
|
|
1401
1412
|
console.debug(
|
|
1402
|
-
`[MultiAgentGraph] Command routing: "${agentId}" ->
|
|
1413
|
+
`[MultiAgentGraph] Command routing: "${agentId}" -> transfer to "${transferDest}" (sequence edges skipped: [${Array.from(sequenceDestinations).join(', ')}])`
|
|
1403
1414
|
);
|
|
1404
1415
|
|
|
1405
1416
|
/** Validate destination agent exists */
|
|
1406
|
-
if (!this.agentContexts.has(
|
|
1417
|
+
if (!this.agentContexts.has(transferDest)) {
|
|
1407
1418
|
const availableAgents = Array.from(
|
|
1408
1419
|
this.agentContexts.keys()
|
|
1409
1420
|
).join(', ');
|
|
1410
1421
|
console.error(
|
|
1411
|
-
`[MultiAgentGraph]
|
|
1422
|
+
`[MultiAgentGraph] Transfer to non-existent agent "${transferDest}". Available: ${availableAgents}`
|
|
1412
1423
|
);
|
|
1413
1424
|
/** Return error to model so it can self-correct */
|
|
1414
1425
|
const errorMsg = new ToolMessage({
|
|
1415
|
-
content: `Transfer failed: agent "${
|
|
1426
|
+
content: `Transfer failed: agent "${transferDest}" does not exist. Available agents: ${availableAgents}. Please choose a valid agent to transfer to.`,
|
|
1416
1427
|
tool_call_id: (lastMessage as ToolMessage).tool_call_id,
|
|
1417
1428
|
name: lastMessage.name,
|
|
1418
1429
|
});
|
|
@@ -1423,7 +1434,7 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
1423
1434
|
}
|
|
1424
1435
|
|
|
1425
1436
|
/** Pre-handoff context compaction: if receiving agent has smaller budget */
|
|
1426
|
-
const receiverContext = this.agentContexts.get(
|
|
1437
|
+
const receiverContext = this.agentContexts.get(transferDest);
|
|
1427
1438
|
const senderContext = this.agentContexts.get(agentId);
|
|
1428
1439
|
if (
|
|
1429
1440
|
receiverContext?.maxContextTokens != null &&
|
|
@@ -1439,7 +1450,7 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
1439
1450
|
if (currentSize > receiverBudget * 0.7) {
|
|
1440
1451
|
console.warn(
|
|
1441
1452
|
`[MultiAgentGraph] Pre-handoff compaction: context (${currentSize} tokens) exceeds ` +
|
|
1442
|
-
`70% of receiver "${
|
|
1453
|
+
`70% of receiver "${transferDest}" budget (${receiverBudget} tokens)`
|
|
1443
1454
|
);
|
|
1444
1455
|
|
|
1445
1456
|
/** Generate handoff briefing */
|
|
@@ -1457,8 +1468,8 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
1457
1468
|
summaryBudget: Math.floor(receiverBudget * 0.2),
|
|
1458
1469
|
isMultiAgent: true,
|
|
1459
1470
|
agentWorkflowState: {
|
|
1460
|
-
currentAgentId:
|
|
1461
|
-
agentChain: [agentId,
|
|
1471
|
+
currentAgentId: transferDest,
|
|
1472
|
+
agentChain: [agentId, transferDest],
|
|
1462
1473
|
pendingAgents: [],
|
|
1463
1474
|
},
|
|
1464
1475
|
}
|
|
@@ -1512,16 +1523,43 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
1512
1523
|
}
|
|
1513
1524
|
}
|
|
1514
1525
|
|
|
1526
|
+
await safeDispatchCustomEvent(
|
|
1527
|
+
GraphEvents.ON_AGENT_TRANSITION,
|
|
1528
|
+
{
|
|
1529
|
+
sourceAgentId: agentId,
|
|
1530
|
+
sourceAgentName: this.agentContexts.get(agentId)?.name ?? agentId,
|
|
1531
|
+
destinationAgentId: transferDest,
|
|
1532
|
+
destinationAgentName: this.agentContexts.get(transferDest)?.name ?? transferDest,
|
|
1533
|
+
edgeType: EdgeType.TRANSFER,
|
|
1534
|
+
timestamp: Date.now(),
|
|
1535
|
+
},
|
|
1536
|
+
config
|
|
1537
|
+
);
|
|
1538
|
+
|
|
1515
1539
|
return new Command({
|
|
1516
1540
|
update: result,
|
|
1517
|
-
goto:
|
|
1541
|
+
goto: transferDest,
|
|
1518
1542
|
});
|
|
1519
1543
|
} else {
|
|
1520
|
-
/** No
|
|
1544
|
+
/** No transfer - proceed with sequence edges */
|
|
1521
1545
|
console.debug(
|
|
1522
|
-
`[MultiAgentGraph] Command routing: "${agentId}" -> no
|
|
1546
|
+
`[MultiAgentGraph] Command routing: "${agentId}" -> no transfer, following sequence edges: [${Array.from(sequenceDestinations).join(', ')}]`
|
|
1523
1547
|
);
|
|
1524
|
-
const directDests = Array.from(
|
|
1548
|
+
const directDests = Array.from(sequenceDestinations);
|
|
1549
|
+
for (const dest of directDests) {
|
|
1550
|
+
await safeDispatchCustomEvent(
|
|
1551
|
+
GraphEvents.ON_AGENT_TRANSITION,
|
|
1552
|
+
{
|
|
1553
|
+
sourceAgentId: agentId,
|
|
1554
|
+
sourceAgentName: this.agentContexts.get(agentId)?.name ?? agentId,
|
|
1555
|
+
destinationAgentId: dest,
|
|
1556
|
+
destinationAgentName: this.agentContexts.get(dest)?.name ?? dest,
|
|
1557
|
+
edgeType: EdgeType.SEQUENCE,
|
|
1558
|
+
timestamp: Date.now(),
|
|
1559
|
+
},
|
|
1560
|
+
config
|
|
1561
|
+
);
|
|
1562
|
+
}
|
|
1525
1563
|
if (directDests.length === 1) {
|
|
1526
1564
|
return new Command({
|
|
1527
1565
|
update: result,
|
|
@@ -1555,12 +1593,12 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
1555
1593
|
}
|
|
1556
1594
|
|
|
1557
1595
|
/**
|
|
1558
|
-
* Add
|
|
1596
|
+
* Add sequence edges for automatic transitions
|
|
1559
1597
|
* Group edges by destination to handle fan-in scenarios
|
|
1560
1598
|
*/
|
|
1561
1599
|
const edgesByDestination = new Map<string, t.GraphEdge[]>();
|
|
1562
1600
|
|
|
1563
|
-
for (const edge of this.
|
|
1601
|
+
for (const edge of this.sequenceEdges) {
|
|
1564
1602
|
const destinations = Array.isArray(edge.to) ? edge.to : [edge.to];
|
|
1565
1603
|
for (const destination of destinations) {
|
|
1566
1604
|
if (!edgesByDestination.has(destination)) {
|
|
@@ -1659,18 +1697,18 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
1659
1697
|
for (const edge of edges) {
|
|
1660
1698
|
const sources = Array.isArray(edge.from) ? edge.from : [edge.from];
|
|
1661
1699
|
for (const source of sources) {
|
|
1662
|
-
/** Check if this source node has both
|
|
1663
|
-
const
|
|
1700
|
+
/** Check if this source node has both transfer and sequence edges */
|
|
1701
|
+
const sourceTransferEdges = this.transferEdges.filter((e) => {
|
|
1664
1702
|
const eSources = Array.isArray(e.from) ? e.from : [e.from];
|
|
1665
1703
|
return eSources.includes(source);
|
|
1666
1704
|
});
|
|
1667
|
-
const
|
|
1705
|
+
const sourceSequenceEdges = this.sequenceEdges.filter((e) => {
|
|
1668
1706
|
const eSources = Array.isArray(e.from) ? e.from : [e.from];
|
|
1669
1707
|
return eSources.includes(source);
|
|
1670
1708
|
});
|
|
1671
1709
|
|
|
1672
1710
|
/** Skip adding edge if source uses Command routing (has both types) */
|
|
1673
|
-
if (
|
|
1711
|
+
if (sourceTransferEdges.length > 0 && sourceSequenceEdges.length > 0) {
|
|
1674
1712
|
continue;
|
|
1675
1713
|
}
|
|
1676
1714
|
|