@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
|
@@ -11,32 +11,33 @@ require('../tools/approval/constants.cjs');
|
|
|
11
11
|
require('../utils/toonFormat.cjs');
|
|
12
12
|
var summarize = require('../messages/summarize.cjs');
|
|
13
13
|
var Graph = require('./Graph.cjs');
|
|
14
|
+
var events = require('../utils/events.cjs');
|
|
14
15
|
|
|
15
16
|
/** Pattern to extract instructions from transfer ToolMessage content */
|
|
16
|
-
const
|
|
17
|
+
const TRANSFER_INSTRUCTIONS_PATTERN = /(?:Instructions?|Context):\s*(.+)/is;
|
|
17
18
|
/**
|
|
18
19
|
* MultiAgentGraph extends StandardGraph to support dynamic multi-agent workflows
|
|
19
20
|
* with handoffs, fan-in/fan-out, and other composable patterns.
|
|
20
21
|
*
|
|
21
22
|
* Key behavior:
|
|
22
|
-
* - Agents with ONLY
|
|
23
|
-
* - Agents with ONLY
|
|
24
|
-
* - Agents with BOTH: Use Command for exclusive routing (
|
|
25
|
-
* - If
|
|
26
|
-
* - If no
|
|
23
|
+
* - Agents with ONLY transfer edges: Can dynamically route to any transfer destination
|
|
24
|
+
* - Agents with ONLY sequence edges: Always follow their sequence edges
|
|
25
|
+
* - Agents with BOTH: Use Command for exclusive routing (transfer OR sequence, not both)
|
|
26
|
+
* - If transfer occurs: Only the transfer destination executes
|
|
27
|
+
* - If no transfer: Sequence edges execute (potentially in parallel)
|
|
27
28
|
*
|
|
28
|
-
* This enables the common pattern where an agent either
|
|
29
|
-
* OR continues its workflow (
|
|
29
|
+
* This enables the common pattern where an agent either transfers (one-way)
|
|
30
|
+
* OR continues its workflow (sequence edges), but not both simultaneously.
|
|
30
31
|
*/
|
|
31
32
|
class MultiAgentGraph extends Graph.StandardGraph {
|
|
32
33
|
edges;
|
|
33
34
|
startingNodes = new Set();
|
|
34
|
-
|
|
35
|
+
sequenceEdges = [];
|
|
36
|
+
transferEdges = [];
|
|
35
37
|
handoffEdges = [];
|
|
36
|
-
delegateEdges = [];
|
|
37
38
|
/**
|
|
38
39
|
* Lazily populated registry of compiled subgraphs, keyed by agentId.
|
|
39
|
-
*
|
|
40
|
+
* Handoff tools are created in the constructor but reference subgraphs
|
|
40
41
|
* that are only created in createWorkflow(). This Map bridges that gap —
|
|
41
42
|
* tools capture the Map reference in their closure, and createWorkflow()
|
|
42
43
|
* populates it before any tool invocation occurs.
|
|
@@ -63,41 +64,39 @@ class MultiAgentGraph extends Graph.StandardGraph {
|
|
|
63
64
|
this.edges = input.edges;
|
|
64
65
|
this.categorizeEdges();
|
|
65
66
|
this.analyzeGraph();
|
|
67
|
+
this.createTransferTools();
|
|
66
68
|
this.createHandoffTools();
|
|
67
|
-
this.createDelegateTools();
|
|
68
69
|
console.debug(`[MultiAgentGraph] Constructor complete: ${this.agentContexts.size} agents, ${this.edges.length} edges`);
|
|
69
70
|
}
|
|
70
71
|
/**
|
|
71
|
-
* Categorize edges into handoff and
|
|
72
|
+
* Categorize edges into handoff, transfer, and sequence types
|
|
72
73
|
*/
|
|
73
74
|
categorizeEdges() {
|
|
74
75
|
for (const edge of this.edges) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if (edge.edgeType === _enum.EdgeType.DELEGATE) {
|
|
78
|
-
this.delegateEdges.push(edge);
|
|
76
|
+
if (edge.edgeType === _enum.EdgeType.HANDOFF) {
|
|
77
|
+
this.handoffEdges.push(edge);
|
|
79
78
|
}
|
|
80
|
-
else if (edge.edgeType === _enum.EdgeType.
|
|
81
|
-
this.
|
|
79
|
+
else if (edge.edgeType === _enum.EdgeType.SEQUENCE) {
|
|
80
|
+
this.sequenceEdges.push(edge);
|
|
82
81
|
}
|
|
83
|
-
else if (edge.edgeType === _enum.EdgeType.
|
|
84
|
-
this.
|
|
82
|
+
else if (edge.edgeType === _enum.EdgeType.TRANSFER || edge.condition != null) {
|
|
83
|
+
this.transferEdges.push(edge);
|
|
85
84
|
}
|
|
86
85
|
else {
|
|
87
|
-
// Default: single-to-single edges are
|
|
86
|
+
// Default: single-to-single edges are transfer, single-to-multiple are sequence
|
|
88
87
|
const destinations = Array.isArray(edge.to) ? edge.to : [edge.to];
|
|
89
88
|
const sources = Array.isArray(edge.from) ? edge.from : [edge.from];
|
|
90
89
|
if (sources.length === 1 && destinations.length > 1) {
|
|
91
|
-
// Fan-out pattern defaults to
|
|
92
|
-
this.
|
|
90
|
+
// Fan-out pattern defaults to sequence
|
|
91
|
+
this.sequenceEdges.push(edge);
|
|
93
92
|
}
|
|
94
93
|
else {
|
|
95
|
-
// Everything else defaults to
|
|
96
|
-
this.
|
|
94
|
+
// Everything else defaults to transfer
|
|
95
|
+
this.transferEdges.push(edge);
|
|
97
96
|
}
|
|
98
97
|
}
|
|
99
98
|
}
|
|
100
|
-
console.debug(`[MultiAgentGraph] Edge categorization: ${this.handoffEdges.length} handoff, ${this.
|
|
99
|
+
console.debug(`[MultiAgentGraph] Edge categorization: ${this.handoffEdges.length} handoff, ${this.transferEdges.length} transfer, ${this.sequenceEdges.length} sequence (of ${this.edges.length} total)`);
|
|
101
100
|
}
|
|
102
101
|
/**
|
|
103
102
|
* Analyze graph structure to determine starting nodes and connections
|
|
@@ -155,8 +154,8 @@ class MultiAgentGraph extends Graph.StandardGraph {
|
|
|
155
154
|
if (visited.has(current))
|
|
156
155
|
continue;
|
|
157
156
|
visited.add(current);
|
|
158
|
-
// Find
|
|
159
|
-
for (const edge of this.
|
|
157
|
+
// Find sequence edges from this node
|
|
158
|
+
for (const edge of this.sequenceEdges) {
|
|
160
159
|
const sources = Array.isArray(edge.from) ? edge.from : [edge.from];
|
|
161
160
|
if (!sources.includes(current))
|
|
162
161
|
continue;
|
|
@@ -183,8 +182,8 @@ class MultiAgentGraph extends Graph.StandardGraph {
|
|
|
183
182
|
}
|
|
184
183
|
}
|
|
185
184
|
}
|
|
186
|
-
// Also follow
|
|
187
|
-
for (const edge of [...this.
|
|
185
|
+
// Also follow transfer and handoff edges for traversal (they don't create parallel groups)
|
|
186
|
+
for (const edge of [...this.transferEdges, ...this.handoffEdges]) {
|
|
188
187
|
const sources = Array.isArray(edge.from) ? edge.from : [edge.from];
|
|
189
188
|
if (!sources.includes(current))
|
|
190
189
|
continue;
|
|
@@ -227,46 +226,44 @@ class MultiAgentGraph extends Graph.StandardGraph {
|
|
|
227
226
|
return this.agentParallelGroups.get(agentId);
|
|
228
227
|
}
|
|
229
228
|
/**
|
|
230
|
-
* Create
|
|
229
|
+
* Create transfer tools for agents based on transfer edges only.
|
|
230
|
+
* Transfer tools return Command for one-way routing — parent exits, child takes over.
|
|
231
231
|
*/
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
const
|
|
235
|
-
// Only process handoff edges for tool creation
|
|
236
|
-
for (const edge of this.handoffEdges) {
|
|
232
|
+
createTransferTools() {
|
|
233
|
+
const transfersByAgent = new Map();
|
|
234
|
+
for (const edge of this.transferEdges) {
|
|
237
235
|
const sources = Array.isArray(edge.from) ? edge.from : [edge.from];
|
|
238
236
|
sources.forEach((source) => {
|
|
239
|
-
if (!
|
|
240
|
-
|
|
237
|
+
if (!transfersByAgent.has(source)) {
|
|
238
|
+
transfersByAgent.set(source, []);
|
|
241
239
|
}
|
|
242
|
-
|
|
240
|
+
transfersByAgent.get(source).push(edge);
|
|
243
241
|
});
|
|
244
242
|
}
|
|
245
|
-
|
|
246
|
-
for (const [agentId, edges] of handoffsByAgent) {
|
|
243
|
+
for (const [agentId, edges] of transfersByAgent) {
|
|
247
244
|
const agentContext = this.agentContexts.get(agentId);
|
|
248
245
|
if (!agentContext)
|
|
249
246
|
continue;
|
|
250
|
-
|
|
251
|
-
const handoffTools = [];
|
|
247
|
+
const transferTools = [];
|
|
252
248
|
const sourceAgentName = agentContext.name ?? agentId;
|
|
253
249
|
for (const edge of edges) {
|
|
254
|
-
|
|
250
|
+
transferTools.push(...this.createTransferToolsForEdge(edge, agentId, sourceAgentName));
|
|
255
251
|
}
|
|
256
252
|
if (!agentContext.graphTools) {
|
|
257
253
|
agentContext.graphTools = [];
|
|
258
254
|
}
|
|
259
|
-
agentContext.graphTools.push(...
|
|
260
|
-
console.debug(`[MultiAgentGraph]
|
|
255
|
+
agentContext.graphTools.push(...transferTools);
|
|
256
|
+
console.debug(`[MultiAgentGraph] Transfer tools for "${agentId}": [${transferTools.map((t) => t.name).join(', ')}]`);
|
|
261
257
|
}
|
|
262
258
|
}
|
|
263
259
|
/**
|
|
264
|
-
* Create
|
|
265
|
-
*
|
|
266
|
-
* @param
|
|
260
|
+
* Create transfer tools for an edge (handles multiple destinations).
|
|
261
|
+
* Transfer tools return Command for one-way routing — parent exits, child takes over.
|
|
262
|
+
* @param edge - The graph edge defining the transfer
|
|
263
|
+
* @param sourceAgentId - The ID of the agent that will perform the transfer
|
|
267
264
|
* @param sourceAgentName - The human-readable name of the source agent
|
|
268
265
|
*/
|
|
269
|
-
|
|
266
|
+
createTransferToolsForEdge(edge, sourceAgentId, sourceAgentName) {
|
|
270
267
|
const tools$1 = [];
|
|
271
268
|
const destinations = Array.isArray(edge.to) ? edge.to : [edge.to];
|
|
272
269
|
/** If there's a condition, create a single conditional handoff tool */
|
|
@@ -274,8 +271,8 @@ class MultiAgentGraph extends Graph.StandardGraph {
|
|
|
274
271
|
const toolName = 'conditional_transfer';
|
|
275
272
|
const toolDescription = edge.description ?? 'Conditionally transfer control based on state';
|
|
276
273
|
/** Check if we have a prompt for handoff input */
|
|
277
|
-
const
|
|
278
|
-
const
|
|
274
|
+
const hasTransferInput = edge.prompt != null && typeof edge.prompt === 'string';
|
|
275
|
+
const transferInputDescription = hasTransferInput ? edge.prompt : undefined;
|
|
279
276
|
const promptKey = edge.promptKey ?? 'instructions';
|
|
280
277
|
tools$1.push(tools.tool(async (rawInput, config) => {
|
|
281
278
|
const input = rawInput;
|
|
@@ -299,7 +296,7 @@ class MultiAgentGraph extends Graph.StandardGraph {
|
|
|
299
296
|
destination = Array.isArray(result) ? result[0] : destinations[0];
|
|
300
297
|
}
|
|
301
298
|
let content = `Conditionally transferred to ${destination}`;
|
|
302
|
-
if (
|
|
299
|
+
if (hasTransferInput &&
|
|
303
300
|
promptKey in input &&
|
|
304
301
|
input[promptKey] != null) {
|
|
305
302
|
content += `\n\n${promptKey.charAt(0).toUpperCase() + promptKey.slice(1)}: ${input[promptKey]}`;
|
|
@@ -322,13 +319,13 @@ class MultiAgentGraph extends Graph.StandardGraph {
|
|
|
322
319
|
});
|
|
323
320
|
}, {
|
|
324
321
|
name: toolName,
|
|
325
|
-
schema:
|
|
322
|
+
schema: hasTransferInput
|
|
326
323
|
? {
|
|
327
324
|
type: 'object',
|
|
328
325
|
properties: {
|
|
329
326
|
[promptKey]: {
|
|
330
327
|
type: 'string',
|
|
331
|
-
description:
|
|
328
|
+
description: transferInputDescription,
|
|
332
329
|
},
|
|
333
330
|
},
|
|
334
331
|
required: [],
|
|
@@ -343,10 +340,10 @@ class MultiAgentGraph extends Graph.StandardGraph {
|
|
|
343
340
|
const toolName = `${_enum.Constants.LC_TRANSFER_TO_}${destination}`;
|
|
344
341
|
const destContext = this.agentContexts.get(destination);
|
|
345
342
|
const toolDescription = edge.description ??
|
|
346
|
-
this.
|
|
343
|
+
this.buildDefaultTransferDescription(destContext, destination);
|
|
347
344
|
/** Check if we have a prompt for handoff input */
|
|
348
|
-
const
|
|
349
|
-
const
|
|
345
|
+
const hasTransferInput = edge.prompt != null && typeof edge.prompt === 'string';
|
|
346
|
+
const transferInputDescription = hasTransferInput
|
|
350
347
|
? edge.prompt
|
|
351
348
|
: undefined;
|
|
352
349
|
const promptKey = edge.promptKey ?? 'instructions';
|
|
@@ -355,7 +352,7 @@ class MultiAgentGraph extends Graph.StandardGraph {
|
|
|
355
352
|
const toolCallId = config?.toolCall?.id ??
|
|
356
353
|
'unknown';
|
|
357
354
|
let content = `Successfully transferred to ${destination}`;
|
|
358
|
-
if (
|
|
355
|
+
if (hasTransferInput &&
|
|
359
356
|
promptKey in input &&
|
|
360
357
|
input[promptKey] != null) {
|
|
361
358
|
content += `\n\n${promptKey.charAt(0).toUpperCase() + promptKey.slice(1)}: ${input[promptKey]}`;
|
|
@@ -432,13 +429,13 @@ class MultiAgentGraph extends Graph.StandardGraph {
|
|
|
432
429
|
});
|
|
433
430
|
}, {
|
|
434
431
|
name: toolName,
|
|
435
|
-
schema:
|
|
432
|
+
schema: hasTransferInput
|
|
436
433
|
? {
|
|
437
434
|
type: 'object',
|
|
438
435
|
properties: {
|
|
439
436
|
[promptKey]: {
|
|
440
437
|
type: 'string',
|
|
441
|
-
description:
|
|
438
|
+
description: transferInputDescription,
|
|
442
439
|
},
|
|
443
440
|
},
|
|
444
441
|
required: [],
|
|
@@ -451,13 +448,13 @@ class MultiAgentGraph extends Graph.StandardGraph {
|
|
|
451
448
|
return tools$1;
|
|
452
449
|
}
|
|
453
450
|
/**
|
|
454
|
-
* Builds a meaningful default description for a
|
|
451
|
+
* Builds a meaningful default description for a transfer tool when no explicit
|
|
455
452
|
* edge.description is provided. Uses the destination agent's name and description
|
|
456
453
|
* so the LLM can make informed routing decisions.
|
|
457
454
|
* @param destContext - AgentContext of the destination agent (may be undefined)
|
|
458
455
|
* @param destinationId - Raw agent ID (fallback when context unavailable)
|
|
459
456
|
*/
|
|
460
|
-
|
|
457
|
+
buildDefaultTransferDescription(destContext, destinationId) {
|
|
461
458
|
const displayName = destContext?.name ?? destinationId;
|
|
462
459
|
const agentDescription = destContext?.description;
|
|
463
460
|
if (agentDescription != null && agentDescription !== '') {
|
|
@@ -466,58 +463,58 @@ class MultiAgentGraph extends Graph.StandardGraph {
|
|
|
466
463
|
return `Transfer control to "${displayName}"`;
|
|
467
464
|
}
|
|
468
465
|
/**
|
|
469
|
-
* Create
|
|
470
|
-
*
|
|
471
|
-
* as a string to the parent agent's context. Unlike
|
|
472
|
-
* return Command for
|
|
473
|
-
*
|
|
466
|
+
* Create handoff tools for agents based on handoff edges.
|
|
467
|
+
* Handoff tools invoke child agent subgraphs inline and return the result
|
|
468
|
+
* as a string to the parent agent's context. Unlike transfer tools (which
|
|
469
|
+
* return Command for one-way routing), handoff tools execute the child,
|
|
470
|
+
* extract the final text, and return it within the parent's agent loop.
|
|
474
471
|
*
|
|
475
472
|
* This enables the supervisor pattern: parent calls child → gets result → thinks → calls another.
|
|
476
473
|
*/
|
|
477
|
-
|
|
478
|
-
const
|
|
479
|
-
for (const edge of this.
|
|
474
|
+
createHandoffTools() {
|
|
475
|
+
const handoffsByAgent = new Map();
|
|
476
|
+
for (const edge of this.handoffEdges) {
|
|
480
477
|
const sources = Array.isArray(edge.from) ? edge.from : [edge.from];
|
|
481
478
|
sources.forEach((source) => {
|
|
482
|
-
if (!
|
|
483
|
-
|
|
479
|
+
if (!handoffsByAgent.has(source)) {
|
|
480
|
+
handoffsByAgent.set(source, []);
|
|
484
481
|
}
|
|
485
|
-
|
|
482
|
+
handoffsByAgent.get(source).push(edge);
|
|
486
483
|
});
|
|
487
484
|
}
|
|
488
|
-
for (const [agentId, edges] of
|
|
485
|
+
for (const [agentId, edges] of handoffsByAgent) {
|
|
489
486
|
const agentContext = this.agentContexts.get(agentId);
|
|
490
487
|
if (!agentContext)
|
|
491
488
|
continue;
|
|
492
|
-
const
|
|
489
|
+
const handoffTools = [];
|
|
493
490
|
for (const edge of edges) {
|
|
494
|
-
|
|
491
|
+
handoffTools.push(...this.createHandoffToolsForEdge(edge, agentId));
|
|
495
492
|
}
|
|
496
493
|
if (!agentContext.graphTools) {
|
|
497
494
|
agentContext.graphTools = [];
|
|
498
495
|
}
|
|
499
|
-
agentContext.graphTools.push(...
|
|
500
|
-
console.debug(`[MultiAgentGraph]
|
|
496
|
+
agentContext.graphTools.push(...handoffTools);
|
|
497
|
+
console.debug(`[MultiAgentGraph] Handoff tools for "${agentId}": [${handoffTools.map((t) => t.name).join(', ')}]`);
|
|
501
498
|
}
|
|
502
499
|
}
|
|
503
500
|
/**
|
|
504
|
-
* Create
|
|
505
|
-
* Each
|
|
501
|
+
* Create handoff tools for an edge (handles multiple destinations).
|
|
502
|
+
* Each handoff tool invokes the child agent's compiled subgraph inline,
|
|
506
503
|
* extracts the final AI message text, truncates it, and returns it as
|
|
507
504
|
* a string (which becomes a ToolMessage in the parent's context).
|
|
508
505
|
*
|
|
509
|
-
* @param edge - The graph edge defining the
|
|
506
|
+
* @param edge - The graph edge defining the handoff
|
|
510
507
|
* @param sourceAgentId - The ID of the parent/supervisor agent
|
|
511
508
|
*/
|
|
512
|
-
|
|
509
|
+
createHandoffToolsForEdge(edge, sourceAgentId) {
|
|
513
510
|
const tools$1 = [];
|
|
514
511
|
const destinations = Array.isArray(edge.to) ? edge.to : [edge.to];
|
|
515
|
-
const maxResultChars = edge.maxResultChars ?? _enum.
|
|
512
|
+
const maxResultChars = edge.maxResultChars ?? _enum.DEFAULT_HANDOFF_MAX_RESULT_CHARS;
|
|
516
513
|
for (const destination of destinations) {
|
|
517
|
-
const toolName = `${_enum.Constants.
|
|
514
|
+
const toolName = `${_enum.Constants.LC_HANDOFF_TO_}${destination}`;
|
|
518
515
|
const destContext = this.agentContexts.get(destination);
|
|
519
516
|
const toolDescription = edge.description ??
|
|
520
|
-
this.
|
|
517
|
+
this.buildDefaultHandoffDescription(destContext, destination);
|
|
521
518
|
const hasPromptInput = edge.prompt != null && typeof edge.prompt === 'string';
|
|
522
519
|
const promptInputDescription = hasPromptInput ? edge.prompt : undefined;
|
|
523
520
|
const promptKey = edge.promptKey ?? 'instructions';
|
|
@@ -527,7 +524,7 @@ class MultiAgentGraph extends Graph.StandardGraph {
|
|
|
527
524
|
const input = rawInput;
|
|
528
525
|
const subgraph = registry.get(destination);
|
|
529
526
|
if (!subgraph) {
|
|
530
|
-
throw new Error(`
|
|
527
|
+
throw new Error(`Handoff target "${destination}" subgraph not found in registry. ` +
|
|
531
528
|
'This is a bug: createWorkflow() should have populated the subgraph registry.');
|
|
532
529
|
}
|
|
533
530
|
const state = langgraph.getCurrentTaskInput();
|
|
@@ -544,7 +541,7 @@ class MultiAgentGraph extends Graph.StandardGraph {
|
|
|
544
541
|
const childState = {
|
|
545
542
|
messages: childMessages,
|
|
546
543
|
};
|
|
547
|
-
console.debug(`[MultiAgentGraph]
|
|
544
|
+
console.debug(`[MultiAgentGraph] Handoff "${sourceAgentId}" -> "${destination}" START ` +
|
|
548
545
|
`(messages: ${childMessages.length})`);
|
|
549
546
|
try {
|
|
550
547
|
/**
|
|
@@ -553,17 +550,25 @@ class MultiAgentGraph extends Graph.StandardGraph {
|
|
|
553
550
|
* and configurable data (thread_id, user_id) to the child.
|
|
554
551
|
*/
|
|
555
552
|
const result = await subgraph.invoke(childState, config);
|
|
556
|
-
const resultText = MultiAgentGraph.
|
|
557
|
-
const truncatedResult = MultiAgentGraph.
|
|
558
|
-
console.debug(`[MultiAgentGraph]
|
|
553
|
+
const resultText = MultiAgentGraph.extractHandoffResult(result.messages, destination);
|
|
554
|
+
const truncatedResult = MultiAgentGraph.truncateHandoffResult(resultText, maxResultChars);
|
|
555
|
+
console.debug(`[MultiAgentGraph] Handoff "${sourceAgentId}" -> "${destination}" DONE ` +
|
|
559
556
|
`(result: ${resultText.length} chars` +
|
|
560
557
|
`${truncatedResult.length < resultText.length ? `, truncated to ${truncatedResult.length}` : ''})`);
|
|
558
|
+
await events.safeDispatchCustomEvent(_enum.GraphEvents.ON_AGENT_TRANSITION, {
|
|
559
|
+
sourceAgentId: sourceAgentId,
|
|
560
|
+
sourceAgentName: this.agentContexts.get(sourceAgentId)?.name ?? sourceAgentId,
|
|
561
|
+
destinationAgentId: destination,
|
|
562
|
+
destinationAgentName: destContext?.name ?? destination,
|
|
563
|
+
edgeType: _enum.EdgeType.HANDOFF,
|
|
564
|
+
timestamp: Date.now(),
|
|
565
|
+
}, config);
|
|
561
566
|
return truncatedResult;
|
|
562
567
|
}
|
|
563
568
|
catch (err) {
|
|
564
569
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
565
|
-
console.error(`[MultiAgentGraph]
|
|
566
|
-
return `[
|
|
570
|
+
console.error(`[MultiAgentGraph] Handoff "${sourceAgentId}" -> "${destination}" ERROR:`, errorMessage);
|
|
571
|
+
return `[Handoff to "${destination}" failed: ${errorMessage}]`;
|
|
567
572
|
}
|
|
568
573
|
}, {
|
|
569
574
|
name: toolName,
|
|
@@ -591,7 +596,7 @@ class MultiAgentGraph extends Graph.StandardGraph {
|
|
|
591
596
|
* @param messages - The child agent's output messages
|
|
592
597
|
* @param agentId - The child agent ID (for fallback message)
|
|
593
598
|
*/
|
|
594
|
-
static
|
|
599
|
+
static extractHandoffResult(messages, agentId) {
|
|
595
600
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
596
601
|
const msg = messages[i];
|
|
597
602
|
if (msg.getType() !== 'ai')
|
|
@@ -618,17 +623,17 @@ class MultiAgentGraph extends Graph.StandardGraph {
|
|
|
618
623
|
return `[Agent "${agentId}" completed but produced no text output]`;
|
|
619
624
|
}
|
|
620
625
|
/**
|
|
621
|
-
* Truncate
|
|
626
|
+
* Truncate handoff result using head/tail strategy (60/40 split).
|
|
622
627
|
* Preserves the beginning (key findings) and end (conclusions).
|
|
623
628
|
* Matches the TaskTool.truncateResult pattern from Ranger.
|
|
624
629
|
* @param result - The full result text
|
|
625
630
|
* @param maxChars - Maximum allowed characters
|
|
626
631
|
*/
|
|
627
|
-
static
|
|
632
|
+
static truncateHandoffResult(result, maxChars) {
|
|
628
633
|
if (!result || result.length <= maxChars) {
|
|
629
634
|
return result;
|
|
630
635
|
}
|
|
631
|
-
const truncationNotice = '\n\n[...
|
|
636
|
+
const truncationNotice = '\n\n[... handoff output truncated — middle section omitted to fit parent context ...]\n\n';
|
|
632
637
|
const available = maxChars - truncationNotice.length;
|
|
633
638
|
if (available <= 0) {
|
|
634
639
|
return result.substring(0, maxChars);
|
|
@@ -640,17 +645,17 @@ class MultiAgentGraph extends Graph.StandardGraph {
|
|
|
640
645
|
result.substring(result.length - tailSize));
|
|
641
646
|
}
|
|
642
647
|
/**
|
|
643
|
-
* Build a meaningful default description for a
|
|
648
|
+
* Build a meaningful default description for a handoff tool.
|
|
644
649
|
* @param destContext - AgentContext of the destination agent
|
|
645
650
|
* @param destinationId - Raw agent ID (fallback)
|
|
646
651
|
*/
|
|
647
|
-
|
|
652
|
+
buildDefaultHandoffDescription(destContext, destinationId) {
|
|
648
653
|
const displayName = destContext?.name ?? destinationId;
|
|
649
654
|
const agentDescription = destContext?.description;
|
|
650
655
|
if (agentDescription != null && agentDescription !== '') {
|
|
651
|
-
return `
|
|
656
|
+
return `Hand off task to "${displayName}": ${agentDescription}. The agent will execute and return its result.`;
|
|
652
657
|
}
|
|
653
|
-
return `
|
|
658
|
+
return `Hand off task to "${displayName}" and receive its result.`;
|
|
654
659
|
}
|
|
655
660
|
/**
|
|
656
661
|
* Create a complete agent subgraph (similar to createReactAgent)
|
|
@@ -671,7 +676,7 @@ class MultiAgentGraph extends Graph.StandardGraph {
|
|
|
671
676
|
* @param agentId - The agent ID to check for handoff reception
|
|
672
677
|
* @returns Object with filtered messages, extracted instructions, source agent, and parallel siblings
|
|
673
678
|
*/
|
|
674
|
-
|
|
679
|
+
processTransferReception(messages$1, agentId) {
|
|
675
680
|
if (messages$1.length === 0)
|
|
676
681
|
return null;
|
|
677
682
|
/**
|
|
@@ -700,8 +705,8 @@ class MultiAgentGraph extends Graph.StandardGraph {
|
|
|
700
705
|
destinationAgent = toolName.replace(_enum.Constants.LC_TRANSFER_TO_, '');
|
|
701
706
|
}
|
|
702
707
|
else if (isConditionalTransfer) {
|
|
703
|
-
const
|
|
704
|
-
destinationAgent = typeof
|
|
708
|
+
const transferDest = candidateMsg.additional_kwargs.handoff_destination;
|
|
709
|
+
destinationAgent = typeof transferDest === 'string' ? transferDest : null;
|
|
705
710
|
}
|
|
706
711
|
/** Check if this transfer targets our agent */
|
|
707
712
|
if (destinationAgent === agentId) {
|
|
@@ -717,7 +722,7 @@ class MultiAgentGraph extends Graph.StandardGraph {
|
|
|
717
722
|
const contentStr = typeof toolMessage.content === 'string'
|
|
718
723
|
? toolMessage.content
|
|
719
724
|
: JSON.stringify(toolMessage.content);
|
|
720
|
-
const instructionsMatch = contentStr.match(
|
|
725
|
+
const instructionsMatch = contentStr.match(TRANSFER_INSTRUCTIONS_PATTERN);
|
|
721
726
|
const instructions = instructionsMatch?.[1]?.trim() ?? null;
|
|
722
727
|
/** Extract source agent name from additional_kwargs */
|
|
723
728
|
const handoffSourceName = toolMessage.additional_kwargs.handoff_source_name;
|
|
@@ -895,7 +900,7 @@ class MultiAgentGraph extends Graph.StandardGraph {
|
|
|
895
900
|
};
|
|
896
901
|
}
|
|
897
902
|
/**
|
|
898
|
-
* Create the multi-agent workflow with
|
|
903
|
+
* Create the multi-agent workflow with handoffs, transfers, and sequences
|
|
899
904
|
*/
|
|
900
905
|
createWorkflow() {
|
|
901
906
|
const StateAnnotation = langgraph.Annotation.Root({
|
|
@@ -921,54 +926,54 @@ class MultiAgentGraph extends Graph.StandardGraph {
|
|
|
921
926
|
// Add all agents as complete subgraphs
|
|
922
927
|
for (const [agentId] of this.agentContexts) {
|
|
923
928
|
// Get all possible destinations for this agent
|
|
924
|
-
const
|
|
925
|
-
const
|
|
926
|
-
// Check
|
|
927
|
-
for (const edge of this.
|
|
929
|
+
const transferDestinations = new Set();
|
|
930
|
+
const sequenceDestinations = new Set();
|
|
931
|
+
// Check transfer edges for destinations
|
|
932
|
+
for (const edge of this.transferEdges) {
|
|
928
933
|
const sources = Array.isArray(edge.from) ? edge.from : [edge.from];
|
|
929
934
|
if (sources.includes(agentId) === true) {
|
|
930
935
|
const dests = Array.isArray(edge.to) ? edge.to : [edge.to];
|
|
931
|
-
dests.forEach((dest) =>
|
|
936
|
+
dests.forEach((dest) => transferDestinations.add(dest));
|
|
932
937
|
}
|
|
933
938
|
}
|
|
934
|
-
// Check
|
|
935
|
-
for (const edge of this.
|
|
939
|
+
// Check sequence edges for destinations
|
|
940
|
+
for (const edge of this.sequenceEdges) {
|
|
936
941
|
const sources = Array.isArray(edge.from) ? edge.from : [edge.from];
|
|
937
942
|
if (sources.includes(agentId) === true) {
|
|
938
943
|
const dests = Array.isArray(edge.to) ? edge.to : [edge.to];
|
|
939
|
-
dests.forEach((dest) =>
|
|
944
|
+
dests.forEach((dest) => sequenceDestinations.add(dest));
|
|
940
945
|
}
|
|
941
946
|
}
|
|
942
|
-
/** Check if this agent has BOTH
|
|
943
|
-
const
|
|
944
|
-
const
|
|
945
|
-
const needsCommandRouting =
|
|
947
|
+
/** Check if this agent has BOTH transfer and sequence edges */
|
|
948
|
+
const hasTransferEdges = transferDestinations.size > 0;
|
|
949
|
+
const hasSequenceEdges = sequenceDestinations.size > 0;
|
|
950
|
+
const needsCommandRouting = hasTransferEdges && hasSequenceEdges;
|
|
946
951
|
/** Collect all possible destinations for this agent */
|
|
947
952
|
const allDestinations = new Set([
|
|
948
|
-
...
|
|
949
|
-
...
|
|
953
|
+
...transferDestinations,
|
|
954
|
+
...sequenceDestinations,
|
|
950
955
|
]);
|
|
951
|
-
if (
|
|
956
|
+
if (transferDestinations.size > 0 || sequenceDestinations.size === 0) {
|
|
952
957
|
allDestinations.add(langgraph.END);
|
|
953
958
|
}
|
|
954
959
|
/** Agent subgraph (includes agent + tools) */
|
|
955
960
|
const agentSubgraph = this.createAgentSubgraph(agentId);
|
|
956
|
-
/** Register subgraph for
|
|
961
|
+
/** Register subgraph for handoff tools (lazy reference resolution) */
|
|
957
962
|
this.subgraphRegistry.set(agentId, agentSubgraph);
|
|
958
963
|
/** Wrapper function that handles agentMessages channel, handoff reception, and conditional routing */
|
|
959
964
|
const agentWrapper = async (state, config) => {
|
|
960
965
|
console.debug(`[MultiAgentGraph] Agent "${agentId}" wrapper ENTRY (messages: ${state.messages.length}, needsCommandRouting: ${needsCommandRouting})`);
|
|
961
966
|
let result;
|
|
962
967
|
/**
|
|
963
|
-
* Check if this agent is receiving a
|
|
968
|
+
* Check if this agent is receiving a transfer.
|
|
964
969
|
* If so, filter out the transfer messages and inject instructions as preamble.
|
|
965
970
|
* This prevents the receiving agent from seeing the transfer as "completed work"
|
|
966
971
|
* and prematurely producing an end token.
|
|
967
972
|
*/
|
|
968
|
-
const
|
|
969
|
-
if (
|
|
970
|
-
const { filteredMessages, instructions, sourceAgentName, parallelSiblings, } =
|
|
971
|
-
console.debug(`[MultiAgentGraph] Agent "${agentId}" receiving
|
|
973
|
+
const transferContext = this.processTransferReception(state.messages, agentId);
|
|
974
|
+
if (transferContext !== null) {
|
|
975
|
+
const { filteredMessages, instructions, sourceAgentName, parallelSiblings, } = transferContext;
|
|
976
|
+
console.debug(`[MultiAgentGraph] Agent "${agentId}" receiving transfer from "${sourceAgentName}" (instructions: ${instructions != null}, parallelSiblings: ${parallelSiblings.length})`);
|
|
972
977
|
/**
|
|
973
978
|
* Set handoff context on the receiving agent.
|
|
974
979
|
* Uses pre-computed graph position for depth and parallel info.
|
|
@@ -1085,24 +1090,24 @@ class MultiAgentGraph extends Graph.StandardGraph {
|
|
|
1085
1090
|
/** Track the last agent that produced output for continuation support */
|
|
1086
1091
|
this.lastActiveAgentId = agentId;
|
|
1087
1092
|
console.debug(`[MultiAgentGraph] Agent "${agentId}" wrapper EXIT (result messages: ${result.messages.length})`);
|
|
1088
|
-
/** If agent has both
|
|
1093
|
+
/** If agent has both transfer and sequence edges, use Command for exclusive routing */
|
|
1089
1094
|
if (needsCommandRouting) {
|
|
1090
|
-
/** Check if a
|
|
1095
|
+
/** Check if a transfer occurred */
|
|
1091
1096
|
const lastMessage = result.messages[result.messages.length - 1];
|
|
1092
1097
|
if (lastMessage != null &&
|
|
1093
1098
|
lastMessage.getType() === 'tool' &&
|
|
1094
1099
|
typeof lastMessage.name === 'string' &&
|
|
1095
1100
|
lastMessage.name.startsWith(_enum.Constants.LC_TRANSFER_TO_)) {
|
|
1096
|
-
/**
|
|
1097
|
-
const
|
|
1098
|
-
console.debug(`[MultiAgentGraph] Command routing: "${agentId}" ->
|
|
1101
|
+
/** Transfer occurred - extract destination and navigate there exclusively */
|
|
1102
|
+
const transferDest = lastMessage.name.replace(_enum.Constants.LC_TRANSFER_TO_, '');
|
|
1103
|
+
console.debug(`[MultiAgentGraph] Command routing: "${agentId}" -> transfer to "${transferDest}" (sequence edges skipped: [${Array.from(sequenceDestinations).join(', ')}])`);
|
|
1099
1104
|
/** Validate destination agent exists */
|
|
1100
|
-
if (!this.agentContexts.has(
|
|
1105
|
+
if (!this.agentContexts.has(transferDest)) {
|
|
1101
1106
|
const availableAgents = Array.from(this.agentContexts.keys()).join(', ');
|
|
1102
|
-
console.error(`[MultiAgentGraph]
|
|
1107
|
+
console.error(`[MultiAgentGraph] Transfer to non-existent agent "${transferDest}". Available: ${availableAgents}`);
|
|
1103
1108
|
/** Return error to model so it can self-correct */
|
|
1104
1109
|
const errorMsg = new messages.ToolMessage({
|
|
1105
|
-
content: `Transfer failed: agent "${
|
|
1110
|
+
content: `Transfer failed: agent "${transferDest}" does not exist. Available agents: ${availableAgents}. Please choose a valid agent to transfer to.`,
|
|
1106
1111
|
tool_call_id: lastMessage.tool_call_id,
|
|
1107
1112
|
name: lastMessage.name,
|
|
1108
1113
|
});
|
|
@@ -1112,7 +1117,7 @@ class MultiAgentGraph extends Graph.StandardGraph {
|
|
|
1112
1117
|
};
|
|
1113
1118
|
}
|
|
1114
1119
|
/** Pre-handoff context compaction: if receiving agent has smaller budget */
|
|
1115
|
-
const receiverContext = this.agentContexts.get(
|
|
1120
|
+
const receiverContext = this.agentContexts.get(transferDest);
|
|
1116
1121
|
const senderContext = this.agentContexts.get(agentId);
|
|
1117
1122
|
if (receiverContext?.maxContextTokens != null &&
|
|
1118
1123
|
senderContext?.tokenCounter != null &&
|
|
@@ -1124,7 +1129,7 @@ class MultiAgentGraph extends Graph.StandardGraph {
|
|
|
1124
1129
|
const receiverBudget = receiverContext.maxContextTokens;
|
|
1125
1130
|
if (currentSize > receiverBudget * 0.7) {
|
|
1126
1131
|
console.warn(`[MultiAgentGraph] Pre-handoff compaction: context (${currentSize} tokens) exceeds ` +
|
|
1127
|
-
`70% of receiver "${
|
|
1132
|
+
`70% of receiver "${transferDest}" budget (${receiverBudget} tokens)`);
|
|
1128
1133
|
/** Generate handoff briefing */
|
|
1129
1134
|
const senderName = senderContext.name ?? agentId;
|
|
1130
1135
|
if (senderContext.summarizeCallback) {
|
|
@@ -1136,8 +1141,8 @@ class MultiAgentGraph extends Graph.StandardGraph {
|
|
|
1136
1141
|
summaryBudget: Math.floor(receiverBudget * 0.2),
|
|
1137
1142
|
isMultiAgent: true,
|
|
1138
1143
|
agentWorkflowState: {
|
|
1139
|
-
currentAgentId:
|
|
1140
|
-
agentChain: [agentId,
|
|
1144
|
+
currentAgentId: transferDest,
|
|
1145
|
+
agentChain: [agentId, transferDest],
|
|
1141
1146
|
pendingAgents: [],
|
|
1142
1147
|
},
|
|
1143
1148
|
});
|
|
@@ -1173,15 +1178,33 @@ class MultiAgentGraph extends Graph.StandardGraph {
|
|
|
1173
1178
|
}
|
|
1174
1179
|
}
|
|
1175
1180
|
}
|
|
1181
|
+
await events.safeDispatchCustomEvent(_enum.GraphEvents.ON_AGENT_TRANSITION, {
|
|
1182
|
+
sourceAgentId: agentId,
|
|
1183
|
+
sourceAgentName: this.agentContexts.get(agentId)?.name ?? agentId,
|
|
1184
|
+
destinationAgentId: transferDest,
|
|
1185
|
+
destinationAgentName: this.agentContexts.get(transferDest)?.name ?? transferDest,
|
|
1186
|
+
edgeType: _enum.EdgeType.TRANSFER,
|
|
1187
|
+
timestamp: Date.now(),
|
|
1188
|
+
}, config);
|
|
1176
1189
|
return new langgraph.Command({
|
|
1177
1190
|
update: result,
|
|
1178
|
-
goto:
|
|
1191
|
+
goto: transferDest,
|
|
1179
1192
|
});
|
|
1180
1193
|
}
|
|
1181
1194
|
else {
|
|
1182
|
-
/** No
|
|
1183
|
-
console.debug(`[MultiAgentGraph] Command routing: "${agentId}" -> no
|
|
1184
|
-
const directDests = Array.from(
|
|
1195
|
+
/** No transfer - proceed with sequence edges */
|
|
1196
|
+
console.debug(`[MultiAgentGraph] Command routing: "${agentId}" -> no transfer, following sequence edges: [${Array.from(sequenceDestinations).join(', ')}]`);
|
|
1197
|
+
const directDests = Array.from(sequenceDestinations);
|
|
1198
|
+
for (const dest of directDests) {
|
|
1199
|
+
await events.safeDispatchCustomEvent(_enum.GraphEvents.ON_AGENT_TRANSITION, {
|
|
1200
|
+
sourceAgentId: agentId,
|
|
1201
|
+
sourceAgentName: this.agentContexts.get(agentId)?.name ?? agentId,
|
|
1202
|
+
destinationAgentId: dest,
|
|
1203
|
+
destinationAgentName: this.agentContexts.get(dest)?.name ?? dest,
|
|
1204
|
+
edgeType: _enum.EdgeType.SEQUENCE,
|
|
1205
|
+
timestamp: Date.now(),
|
|
1206
|
+
}, config);
|
|
1207
|
+
}
|
|
1185
1208
|
if (directDests.length === 1) {
|
|
1186
1209
|
return new langgraph.Command({
|
|
1187
1210
|
update: result,
|
|
@@ -1212,11 +1235,11 @@ class MultiAgentGraph extends Graph.StandardGraph {
|
|
|
1212
1235
|
builder.addEdge(langgraph.START, startNode);
|
|
1213
1236
|
}
|
|
1214
1237
|
/**
|
|
1215
|
-
* Add
|
|
1238
|
+
* Add sequence edges for automatic transitions
|
|
1216
1239
|
* Group edges by destination to handle fan-in scenarios
|
|
1217
1240
|
*/
|
|
1218
1241
|
const edgesByDestination = new Map();
|
|
1219
|
-
for (const edge of this.
|
|
1242
|
+
for (const edge of this.sequenceEdges) {
|
|
1220
1243
|
const destinations = Array.isArray(edge.to) ? edge.to : [edge.to];
|
|
1221
1244
|
for (const destination of destinations) {
|
|
1222
1245
|
if (!edgesByDestination.has(destination)) {
|
|
@@ -1305,17 +1328,17 @@ class MultiAgentGraph extends Graph.StandardGraph {
|
|
|
1305
1328
|
for (const edge of edges) {
|
|
1306
1329
|
const sources = Array.isArray(edge.from) ? edge.from : [edge.from];
|
|
1307
1330
|
for (const source of sources) {
|
|
1308
|
-
/** Check if this source node has both
|
|
1309
|
-
const
|
|
1331
|
+
/** Check if this source node has both transfer and sequence edges */
|
|
1332
|
+
const sourceTransferEdges = this.transferEdges.filter((e) => {
|
|
1310
1333
|
const eSources = Array.isArray(e.from) ? e.from : [e.from];
|
|
1311
1334
|
return eSources.includes(source);
|
|
1312
1335
|
});
|
|
1313
|
-
const
|
|
1336
|
+
const sourceSequenceEdges = this.sequenceEdges.filter((e) => {
|
|
1314
1337
|
const eSources = Array.isArray(e.from) ? e.from : [e.from];
|
|
1315
1338
|
return eSources.includes(source);
|
|
1316
1339
|
});
|
|
1317
1340
|
/** Skip adding edge if source uses Command routing (has both types) */
|
|
1318
|
-
if (
|
|
1341
|
+
if (sourceTransferEdges.length > 0 && sourceSequenceEdges.length > 0) {
|
|
1319
1342
|
continue;
|
|
1320
1343
|
}
|
|
1321
1344
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|