@oni.bot/core 1.0.2 → 1.1.0
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/CHANGELOG.md +146 -146
- package/dist/agents/define-agent.d.ts.map +1 -1
- package/dist/agents/define-agent.js +7 -2
- package/dist/agents/define-agent.js.map +1 -1
- package/dist/checkpoint.d.ts.map +1 -1
- package/dist/checkpoint.js +7 -2
- package/dist/checkpoint.js.map +1 -1
- package/dist/checkpointers/postgres.d.ts.map +1 -1
- package/dist/checkpointers/postgres.js +45 -28
- package/dist/checkpointers/postgres.js.map +1 -1
- package/dist/circuit-breaker.d.ts +1 -0
- package/dist/circuit-breaker.d.ts.map +1 -1
- package/dist/circuit-breaker.js +13 -0
- package/dist/circuit-breaker.js.map +1 -1
- package/dist/cli/dev.d.ts.map +1 -1
- package/dist/cli/dev.js +0 -1
- package/dist/cli/dev.js.map +1 -1
- package/dist/cli/inspect.d.ts.map +1 -1
- package/dist/cli/inspect.js +4 -2
- package/dist/cli/inspect.js.map +1 -1
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +0 -1
- package/dist/cli/run.js.map +1 -1
- package/dist/config/loader.d.ts +1 -1
- package/dist/config/loader.js +12 -4
- package/dist/config/loader.js.map +1 -1
- package/dist/coordination/pubsub.d.ts +1 -0
- package/dist/coordination/pubsub.d.ts.map +1 -1
- package/dist/coordination/pubsub.js +31 -16
- package/dist/coordination/pubsub.js.map +1 -1
- package/dist/coordination/request-reply.d.ts +17 -2
- package/dist/coordination/request-reply.d.ts.map +1 -1
- package/dist/coordination/request-reply.js +56 -14
- package/dist/coordination/request-reply.js.map +1 -1
- package/dist/events/bus.d.ts +1 -0
- package/dist/events/bus.d.ts.map +1 -1
- package/dist/events/bus.js +17 -10
- package/dist/events/bus.js.map +1 -1
- package/dist/functional.d.ts.map +1 -1
- package/dist/functional.js +3 -0
- package/dist/functional.js.map +1 -1
- package/dist/graph.d.ts +11 -1
- package/dist/graph.d.ts.map +1 -1
- package/dist/graph.js +9 -4
- package/dist/graph.js.map +1 -1
- package/dist/guardrails/audit.d.ts +4 -1
- package/dist/guardrails/audit.d.ts.map +1 -1
- package/dist/guardrails/audit.js +18 -1
- package/dist/guardrails/audit.js.map +1 -1
- package/dist/harness/agent-loop.d.ts +1 -7
- package/dist/harness/agent-loop.d.ts.map +1 -1
- package/dist/harness/agent-loop.js +2 -523
- package/dist/harness/agent-loop.js.map +1 -1
- package/dist/harness/context-compactor.d.ts +1 -0
- package/dist/harness/context-compactor.d.ts.map +1 -1
- package/dist/harness/context-compactor.js +43 -1
- package/dist/harness/context-compactor.js.map +1 -1
- package/dist/harness/harness.d.ts +6 -0
- package/dist/harness/harness.d.ts.map +1 -1
- package/dist/harness/harness.js +32 -5
- package/dist/harness/harness.js.map +1 -1
- package/dist/harness/hooks-engine.d.ts.map +1 -1
- package/dist/harness/hooks-engine.js +12 -10
- package/dist/harness/hooks-engine.js.map +1 -1
- package/dist/harness/index.d.ts +3 -1
- package/dist/harness/index.d.ts.map +1 -1
- package/dist/harness/index.js +2 -0
- package/dist/harness/index.js.map +1 -1
- package/dist/harness/loop/hooks.d.ts +7 -0
- package/dist/harness/loop/hooks.d.ts.map +1 -0
- package/dist/harness/loop/hooks.js +46 -0
- package/dist/harness/loop/hooks.js.map +1 -0
- package/dist/harness/loop/index.d.ts +8 -0
- package/dist/harness/loop/index.d.ts.map +1 -0
- package/dist/harness/loop/index.js +257 -0
- package/dist/harness/loop/index.js.map +1 -0
- package/dist/harness/loop/inference.d.ts +19 -0
- package/dist/harness/loop/inference.d.ts.map +1 -0
- package/dist/harness/loop/inference.js +121 -0
- package/dist/harness/loop/inference.js.map +1 -0
- package/dist/harness/loop/memory.d.ts +22 -0
- package/dist/harness/loop/memory.d.ts.map +1 -0
- package/dist/harness/loop/memory.js +73 -0
- package/dist/harness/loop/memory.js.map +1 -0
- package/dist/harness/loop/safety.d.ts +8 -0
- package/dist/harness/loop/safety.d.ts.map +1 -0
- package/dist/harness/loop/safety.js +21 -0
- package/dist/harness/loop/safety.js.map +1 -0
- package/dist/harness/loop/tools.d.ts +24 -0
- package/dist/harness/loop/tools.d.ts.map +1 -0
- package/dist/harness/loop/tools.js +184 -0
- package/dist/harness/loop/tools.js.map +1 -0
- package/dist/harness/loop/types.d.ts +7 -0
- package/dist/harness/loop/types.d.ts.map +1 -0
- package/dist/harness/loop/types.js +9 -0
- package/dist/harness/loop/types.js.map +1 -0
- package/dist/harness/memory/fs-compat.d.ts +3 -0
- package/dist/harness/memory/fs-compat.d.ts.map +1 -0
- package/dist/harness/memory/fs-compat.js +26 -0
- package/dist/harness/memory/fs-compat.js.map +1 -0
- package/dist/harness/memory/index.d.ts +105 -0
- package/dist/harness/memory/index.d.ts.map +1 -0
- package/dist/harness/memory/index.js +491 -0
- package/dist/harness/memory/index.js.map +1 -0
- package/dist/harness/memory/prompter.d.ts +7 -0
- package/dist/harness/memory/prompter.d.ts.map +1 -0
- package/dist/harness/memory/prompter.js +24 -0
- package/dist/harness/memory/prompter.js.map +1 -0
- package/dist/harness/memory/ranker.d.ts +15 -0
- package/dist/harness/memory/ranker.d.ts.map +1 -0
- package/dist/harness/memory/ranker.js +72 -0
- package/dist/harness/memory/ranker.js.map +1 -0
- package/dist/harness/memory/scanner.d.ts +26 -0
- package/dist/harness/memory/scanner.d.ts.map +1 -0
- package/dist/harness/memory/scanner.js +187 -0
- package/dist/harness/memory/scanner.js.map +1 -0
- package/dist/harness/memory/types.d.ts +50 -0
- package/dist/harness/memory/types.d.ts.map +1 -0
- package/dist/harness/memory/types.js +7 -0
- package/dist/harness/memory/types.js.map +1 -0
- package/dist/harness/memory-loader.d.ts +3 -0
- package/dist/harness/memory-loader.d.ts.map +1 -0
- package/dist/harness/memory-loader.js +2 -0
- package/dist/harness/memory-loader.js.map +1 -0
- package/dist/harness/safety-gate.d.ts.map +1 -1
- package/dist/harness/safety-gate.js +47 -26
- package/dist/harness/safety-gate.js.map +1 -1
- package/dist/harness/skill-loader.d.ts +7 -0
- package/dist/harness/skill-loader.d.ts.map +1 -1
- package/dist/harness/skill-loader.js +24 -8
- package/dist/harness/skill-loader.js.map +1 -1
- package/dist/harness/todo-module.d.ts.map +1 -1
- package/dist/harness/todo-module.js +13 -6
- package/dist/harness/todo-module.js.map +1 -1
- package/dist/harness/types.d.ts +7 -0
- package/dist/harness/types.d.ts.map +1 -1
- package/dist/harness/types.js.map +1 -1
- package/dist/harness/validate-args.js +18 -3
- package/dist/harness/validate-args.js.map +1 -1
- package/dist/hitl/interrupt.d.ts +2 -2
- package/dist/hitl/interrupt.d.ts.map +1 -1
- package/dist/hitl/interrupt.js +8 -5
- package/dist/hitl/interrupt.js.map +1 -1
- package/dist/hitl/resume.d.ts +10 -0
- package/dist/hitl/resume.d.ts.map +1 -1
- package/dist/hitl/resume.js +31 -0
- package/dist/hitl/resume.js.map +1 -1
- package/dist/injected.d.ts.map +1 -1
- package/dist/injected.js.map +1 -1
- package/dist/inspect.d.ts.map +1 -1
- package/dist/inspect.js +28 -8
- package/dist/inspect.js.map +1 -1
- package/dist/lsp/client.d.ts +2 -0
- package/dist/lsp/client.d.ts.map +1 -1
- package/dist/lsp/client.js +62 -17
- package/dist/lsp/client.js.map +1 -1
- package/dist/lsp/index.d.ts.map +1 -1
- package/dist/lsp/index.js.map +1 -1
- package/dist/mcp/client.d.ts +2 -0
- package/dist/mcp/client.d.ts.map +1 -1
- package/dist/mcp/client.js +44 -13
- package/dist/mcp/client.js.map +1 -1
- package/dist/mcp/convert.js +1 -1
- package/dist/mcp/convert.js.map +1 -1
- package/dist/mcp/transport.d.ts +2 -0
- package/dist/mcp/transport.d.ts.map +1 -1
- package/dist/mcp/transport.js +33 -8
- package/dist/mcp/transport.js.map +1 -1
- package/dist/messages/index.d.ts.map +1 -1
- package/dist/messages/index.js +7 -1
- package/dist/messages/index.js.map +1 -1
- package/dist/models/anthropic.d.ts.map +1 -1
- package/dist/models/anthropic.js +25 -15
- package/dist/models/anthropic.js.map +1 -1
- package/dist/models/google.d.ts.map +1 -1
- package/dist/models/google.js +23 -7
- package/dist/models/google.js.map +1 -1
- package/dist/models/ollama.d.ts.map +1 -1
- package/dist/models/ollama.js +11 -1
- package/dist/models/ollama.js.map +1 -1
- package/dist/models/openai.d.ts.map +1 -1
- package/dist/models/openai.js +15 -3
- package/dist/models/openai.js.map +1 -1
- package/dist/models/openrouter.d.ts.map +1 -1
- package/dist/models/openrouter.js +14 -3
- package/dist/models/openrouter.js.map +1 -1
- package/dist/prebuilt/react-agent.d.ts.map +1 -1
- package/dist/prebuilt/react-agent.js +7 -2
- package/dist/prebuilt/react-agent.js.map +1 -1
- package/dist/pregel/checkpointing.d.ts +12 -0
- package/dist/pregel/checkpointing.d.ts.map +1 -0
- package/dist/pregel/checkpointing.js +60 -0
- package/dist/pregel/checkpointing.js.map +1 -0
- package/dist/pregel/execution.d.ts +7 -0
- package/dist/pregel/execution.d.ts.map +1 -0
- package/dist/pregel/execution.js +178 -0
- package/dist/pregel/execution.js.map +1 -0
- package/dist/pregel/index.d.ts +61 -0
- package/dist/pregel/index.d.ts.map +1 -0
- package/dist/pregel/index.js +154 -0
- package/dist/pregel/index.js.map +1 -0
- package/dist/pregel/interrupts.d.ts +3 -0
- package/dist/pregel/interrupts.d.ts.map +1 -0
- package/dist/pregel/interrupts.js +7 -0
- package/dist/pregel/interrupts.js.map +1 -0
- package/dist/pregel/state-helpers.d.ts +12 -0
- package/dist/pregel/state-helpers.d.ts.map +1 -0
- package/dist/pregel/state-helpers.js +71 -0
- package/dist/pregel/state-helpers.js.map +1 -0
- package/dist/pregel/streaming.d.ts +5 -0
- package/dist/pregel/streaming.d.ts.map +1 -0
- package/dist/pregel/streaming.js +462 -0
- package/dist/pregel/streaming.js.map +1 -0
- package/dist/pregel/types.d.ts +48 -0
- package/dist/pregel/types.d.ts.map +1 -0
- package/dist/pregel/types.js +5 -0
- package/dist/pregel/types.js.map +1 -0
- package/dist/pregel.d.ts +1 -63
- package/dist/pregel.d.ts.map +1 -1
- package/dist/pregel.js +2 -804
- package/dist/pregel.js.map +1 -1
- package/dist/retry.d.ts.map +1 -1
- package/dist/retry.js +7 -6
- package/dist/retry.js.map +1 -1
- package/dist/store/index.d.ts.map +1 -1
- package/dist/store/index.js +36 -9
- package/dist/store/index.js.map +1 -1
- package/dist/stream-events.d.ts.map +1 -1
- package/dist/stream-events.js +3 -9
- package/dist/stream-events.js.map +1 -1
- package/dist/swarm/agent-node.d.ts +11 -0
- package/dist/swarm/agent-node.d.ts.map +1 -0
- package/dist/swarm/agent-node.js +156 -0
- package/dist/swarm/agent-node.js.map +1 -0
- package/dist/swarm/compile-ext.d.ts +5 -0
- package/dist/swarm/compile-ext.d.ts.map +1 -0
- package/dist/swarm/compile-ext.js +126 -0
- package/dist/swarm/compile-ext.js.map +1 -0
- package/dist/swarm/config.d.ts +147 -0
- package/dist/swarm/config.d.ts.map +1 -0
- package/dist/swarm/config.js +17 -0
- package/dist/swarm/config.js.map +1 -0
- package/dist/swarm/factories.d.ts +37 -0
- package/dist/swarm/factories.d.ts.map +1 -0
- package/dist/swarm/factories.js +703 -0
- package/dist/swarm/factories.js.map +1 -0
- package/dist/swarm/graph.d.ts +16 -136
- package/dist/swarm/graph.d.ts.map +1 -1
- package/dist/swarm/graph.js +39 -897
- package/dist/swarm/graph.js.map +1 -1
- package/dist/swarm/index.d.ts +2 -1
- package/dist/swarm/index.d.ts.map +1 -1
- package/dist/swarm/index.js.map +1 -1
- package/dist/swarm/mailbox.d.ts.map +1 -1
- package/dist/swarm/mailbox.js +3 -1
- package/dist/swarm/mailbox.js.map +1 -1
- package/dist/swarm/mermaid.d.ts +2 -1
- package/dist/swarm/mermaid.d.ts.map +1 -1
- package/dist/swarm/mermaid.js +6 -3
- package/dist/swarm/mermaid.js.map +1 -1
- package/dist/swarm/pool.d.ts.map +1 -1
- package/dist/swarm/pool.js +18 -1
- package/dist/swarm/pool.js.map +1 -1
- package/dist/swarm/registry.d.ts.map +1 -1
- package/dist/swarm/registry.js +7 -0
- package/dist/swarm/registry.js.map +1 -1
- package/dist/swarm/scaling.d.ts +10 -1
- package/dist/swarm/scaling.d.ts.map +1 -1
- package/dist/swarm/scaling.js +85 -14
- package/dist/swarm/scaling.js.map +1 -1
- package/dist/swarm/snapshot.d.ts.map +1 -1
- package/dist/swarm/snapshot.js +10 -1
- package/dist/swarm/snapshot.js.map +1 -1
- package/dist/swarm/supervisor.js +20 -12
- package/dist/swarm/supervisor.js.map +1 -1
- package/dist/swarm/tracer.d.ts +3 -1
- package/dist/swarm/tracer.d.ts.map +1 -1
- package/dist/swarm/tracer.js +66 -15
- package/dist/swarm/tracer.js.map +1 -1
- package/dist/swarm/types.d.ts +1 -6
- package/dist/swarm/types.d.ts.map +1 -1
- package/dist/testing/index.d.ts +4 -4
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +3 -2
- package/dist/testing/index.js.map +1 -1
- package/dist/tools/define.d.ts +2 -1
- package/dist/tools/define.d.ts.map +1 -1
- package/dist/tools/define.js +3 -1
- package/dist/tools/define.js.map +1 -1
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/types.d.ts +3 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/swarm/graph.js
CHANGED
|
@@ -8,37 +8,26 @@
|
|
|
8
8
|
// - compile() returns a full ONISkeleton
|
|
9
9
|
// ============================================================
|
|
10
10
|
import { StateGraph } from "../graph.js";
|
|
11
|
-
import { START, END
|
|
12
|
-
import { Command, Send } from "../types.js";
|
|
13
|
-
import { Handoff } from "./types.js";
|
|
11
|
+
import { START, END } from "../types.js";
|
|
14
12
|
import { AgentRegistry } from "./registry.js";
|
|
13
|
+
import { baseSwarmChannels } from "./config.js";
|
|
15
14
|
import { createSupervisorNode } from "./supervisor.js";
|
|
16
|
-
import { getInbox } from "./mailbox.js";
|
|
17
15
|
import { RequestReplyBroker } from "../coordination/request-reply.js";
|
|
18
16
|
import { PubSub } from "../coordination/pubsub.js";
|
|
19
|
-
import {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
context: mergeObject(() => ({})),
|
|
23
|
-
agentResults: mergeObject(() => ({})),
|
|
24
|
-
messages: appendList(() => []),
|
|
25
|
-
swarmMessages: appendList(() => []),
|
|
26
|
-
supervisorRound: lastValue(() => 0),
|
|
27
|
-
currentAgent: lastValue(() => null),
|
|
28
|
-
done: lastValue(() => false),
|
|
29
|
-
handoffHistory: appendList(() => []),
|
|
30
|
-
};
|
|
17
|
+
import { createAgentNode } from "./agent-node.js";
|
|
18
|
+
import { buildSwarmExtensions } from "./compile-ext.js";
|
|
19
|
+
import { buildHierarchical, buildFanOut, buildPipeline, buildPeerNetwork, buildMapReduce, buildDebate, buildHierarchicalMesh, buildRace, buildDag, buildPool, buildCompose, } from "./factories.js";
|
|
31
20
|
// ----------------------------------------------------------------
|
|
32
21
|
// SwarmGraph
|
|
33
22
|
// ----------------------------------------------------------------
|
|
34
23
|
export class SwarmGraph {
|
|
35
|
-
inner;
|
|
36
|
-
registry;
|
|
37
|
-
channels;
|
|
38
|
-
agentIds = new Set();
|
|
39
|
-
supervisorNodeName = "__supervisor__";
|
|
40
|
-
hasSupervisor = false;
|
|
41
|
-
onErrorPolicy = "fallback";
|
|
24
|
+
/** @internal */ inner;
|
|
25
|
+
/** @internal */ registry;
|
|
26
|
+
/** @internal */ channels;
|
|
27
|
+
/** @internal */ agentIds = new Set();
|
|
28
|
+
/** @internal */ supervisorNodeName = "__supervisor__";
|
|
29
|
+
/** @internal */ hasSupervisor = false;
|
|
30
|
+
/** @internal */ onErrorPolicy = "fallback";
|
|
42
31
|
_broker;
|
|
43
32
|
_pubsub;
|
|
44
33
|
get broker() {
|
|
@@ -62,23 +51,7 @@ export class SwarmGraph {
|
|
|
62
51
|
* ready-to-compile SwarmGraph.
|
|
63
52
|
*/
|
|
64
53
|
static hierarchical(config) {
|
|
65
|
-
|
|
66
|
-
for (const agentDef of config.agents) {
|
|
67
|
-
swarm.addAgent(agentDef);
|
|
68
|
-
}
|
|
69
|
-
swarm.addSupervisor({
|
|
70
|
-
model: config.supervisor.model,
|
|
71
|
-
strategy: config.supervisor.strategy,
|
|
72
|
-
taskField: "task",
|
|
73
|
-
contextField: "context",
|
|
74
|
-
rules: config.supervisor.rules,
|
|
75
|
-
systemPrompt: config.supervisor.systemPrompt,
|
|
76
|
-
maxRounds: config.supervisor.maxRounds,
|
|
77
|
-
deadlineMs: config.supervisor.deadlineMs,
|
|
78
|
-
autoRecover: config.supervisor.autoRecover,
|
|
79
|
-
});
|
|
80
|
-
swarm.onErrorPolicy = config.onError ?? "fallback";
|
|
81
|
-
return swarm;
|
|
54
|
+
return buildHierarchical(config, new SwarmGraph(config.channels));
|
|
82
55
|
}
|
|
83
56
|
// ---- Static factory: fan-out template ----
|
|
84
57
|
/**
|
|
@@ -86,68 +59,7 @@ export class SwarmGraph {
|
|
|
86
59
|
* and collects results through a reducer node.
|
|
87
60
|
*/
|
|
88
61
|
static fanOut(config) {
|
|
89
|
-
|
|
90
|
-
const agentIds = config.agents.map((a) => a.id);
|
|
91
|
-
const maxConcurrency = config.maxConcurrency;
|
|
92
|
-
const timeoutMs = config.timeoutMs;
|
|
93
|
-
const weights = config.weights;
|
|
94
|
-
// Register agents in registry (but don't use addAgent — we wire manually)
|
|
95
|
-
for (const agentDef of config.agents) {
|
|
96
|
-
swarm.registry.register(agentDef);
|
|
97
|
-
swarm.agentIds.add(agentDef.id);
|
|
98
|
-
}
|
|
99
|
-
// Single orchestrator node that runs agents with concurrency/timeout control
|
|
100
|
-
swarm.inner.addNode("__fanout_runner__", async (state, cfg) => {
|
|
101
|
-
const agentMap = new Map(config.agents.map((a) => [a.id, a]));
|
|
102
|
-
async function runAgent(id) {
|
|
103
|
-
const agent = agentMap.get(id);
|
|
104
|
-
try {
|
|
105
|
-
await agent.hooks?.onStart?.(id, state);
|
|
106
|
-
const result = await runWithTimeout(() => agent.skeleton.invoke({ ...state }, { ...cfg, agentId: id }), timeoutMs, () => new Error(`Agent "${id}" timed out after ${timeoutMs}ms`));
|
|
107
|
-
await agent.hooks?.onComplete?.(id, result);
|
|
108
|
-
return { id, result, error: null };
|
|
109
|
-
}
|
|
110
|
-
catch (err) {
|
|
111
|
-
await agent.hooks?.onError?.(id, err);
|
|
112
|
-
return { id, result: null, error: err };
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
let allResults;
|
|
116
|
-
if (maxConcurrency != null && maxConcurrency > 0) {
|
|
117
|
-
// Batched execution with concurrency limit
|
|
118
|
-
allResults = [];
|
|
119
|
-
const remaining = [...agentIds];
|
|
120
|
-
while (remaining.length > 0) {
|
|
121
|
-
const batch = remaining.splice(0, maxConcurrency);
|
|
122
|
-
const batchResults = await Promise.all(batch.map(runAgent));
|
|
123
|
-
allResults.push(...batchResults);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
else {
|
|
127
|
-
// All in parallel
|
|
128
|
-
allResults = await Promise.all(agentIds.map(runAgent));
|
|
129
|
-
}
|
|
130
|
-
// Collect results
|
|
131
|
-
const agentResults = { ...(state.agentResults ?? {}) };
|
|
132
|
-
for (const { id, result, error } of allResults) {
|
|
133
|
-
if (error) {
|
|
134
|
-
agentResults[id] = {
|
|
135
|
-
_error: true,
|
|
136
|
-
agent: id,
|
|
137
|
-
error: String(error instanceof Error ? error.message : error),
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
else {
|
|
141
|
-
agentResults[id] = result;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
// Run reducer with weights
|
|
145
|
-
const reduced = config.reducer(agentResults, weights);
|
|
146
|
-
return { ...reduced, agentResults };
|
|
147
|
-
});
|
|
148
|
-
swarm.inner.addEdge(START, "__fanout_runner__");
|
|
149
|
-
swarm.inner.addEdge("__fanout_runner__", END);
|
|
150
|
-
return swarm;
|
|
62
|
+
return buildFanOut(config, new SwarmGraph(config.channels));
|
|
151
63
|
}
|
|
152
64
|
// ---- Static factory: pipeline template ----
|
|
153
65
|
/**
|
|
@@ -156,35 +68,7 @@ export class SwarmGraph {
|
|
|
156
68
|
* transition (e.g. loop back for review cycles).
|
|
157
69
|
*/
|
|
158
70
|
static pipeline(config) {
|
|
159
|
-
|
|
160
|
-
throw new Error("SwarmGraph.pipeline: stages must contain at least one agent.");
|
|
161
|
-
}
|
|
162
|
-
const swarm = new SwarmGraph(config.channels);
|
|
163
|
-
const ids = config.stages.map((a) => a.id);
|
|
164
|
-
for (const agentDef of config.stages) {
|
|
165
|
-
swarm.addAgent(agentDef);
|
|
166
|
-
}
|
|
167
|
-
// Wire: START → first stage
|
|
168
|
-
swarm.inner.addEdge(START, ids[0]);
|
|
169
|
-
// Wire each stage to the next (or conditional)
|
|
170
|
-
for (let i = 0; i < ids.length - 1; i++) {
|
|
171
|
-
const id = ids[i];
|
|
172
|
-
if (config.transitions?.[id]) {
|
|
173
|
-
swarm.addConditionalHandoff(id, config.transitions[id]);
|
|
174
|
-
}
|
|
175
|
-
else {
|
|
176
|
-
swarm.inner.addEdge(id, ids[i + 1]);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
// Wire last stage
|
|
180
|
-
const lastId = ids[ids.length - 1];
|
|
181
|
-
if (config.transitions?.[lastId]) {
|
|
182
|
-
swarm.addConditionalHandoff(lastId, config.transitions[lastId]);
|
|
183
|
-
}
|
|
184
|
-
else {
|
|
185
|
-
swarm.inner.addEdge(lastId, END);
|
|
186
|
-
}
|
|
187
|
-
return swarm;
|
|
71
|
+
return buildPipeline(config, new SwarmGraph(config.channels));
|
|
188
72
|
}
|
|
189
73
|
// ---- Static factory: peer network template ----
|
|
190
74
|
/**
|
|
@@ -192,17 +76,7 @@ export class SwarmGraph {
|
|
|
192
76
|
* via conditional handoffs, with no supervisor.
|
|
193
77
|
*/
|
|
194
78
|
static peerNetwork(config) {
|
|
195
|
-
|
|
196
|
-
for (const agentDef of config.agents) {
|
|
197
|
-
swarm.addAgent(agentDef);
|
|
198
|
-
}
|
|
199
|
-
// Wire: START → entrypoint
|
|
200
|
-
swarm.inner.addEdge(START, config.entrypoint);
|
|
201
|
-
// Wire each agent's conditional handoff
|
|
202
|
-
for (const [agentId, handoffFn] of Object.entries(config.handoffs)) {
|
|
203
|
-
swarm.addConditionalHandoff(agentId, handoffFn);
|
|
204
|
-
}
|
|
205
|
-
return swarm;
|
|
79
|
+
return buildPeerNetwork(config, new SwarmGraph(config.channels));
|
|
206
80
|
}
|
|
207
81
|
// ---- Static factory: map-reduce template ----
|
|
208
82
|
/**
|
|
@@ -210,74 +84,7 @@ export class SwarmGraph {
|
|
|
210
84
|
* fan-out via Send, and collect results through a reducer node.
|
|
211
85
|
*/
|
|
212
86
|
static mapReduce(config) {
|
|
213
|
-
|
|
214
|
-
if (poolSize < 1) {
|
|
215
|
-
throw new Error("SwarmGraph.mapReduce: poolSize must be at least 1.");
|
|
216
|
-
}
|
|
217
|
-
const swarm = new SwarmGraph(config.channels);
|
|
218
|
-
// Register poolSize copies of the mapper agent
|
|
219
|
-
const mapperIds = [];
|
|
220
|
-
for (let i = 0; i < poolSize; i++) {
|
|
221
|
-
const id = poolSize === 1 ? config.mapper.id : `${config.mapper.id}_${i}`;
|
|
222
|
-
mapperIds.push(id);
|
|
223
|
-
swarm.addAgent({
|
|
224
|
-
...config.mapper,
|
|
225
|
-
id,
|
|
226
|
-
});
|
|
227
|
-
}
|
|
228
|
-
// Splitter node: passthrough — the conditional edge handles fan-out via Send
|
|
229
|
-
swarm.inner.addNode("__splitter__", (_state) => {
|
|
230
|
-
return {};
|
|
231
|
-
});
|
|
232
|
-
// Reducer node: collects agentResults and applies user reducer
|
|
233
|
-
swarm.inner.addNode("__reducer__", (state) => {
|
|
234
|
-
return config.reducer(state.agentResults ?? {});
|
|
235
|
-
});
|
|
236
|
-
// Wiring: START → __splitter__
|
|
237
|
-
swarm.inner.addEdge(START, "__splitter__");
|
|
238
|
-
// __splitter__ → Send to mapper agents using the configured poolStrategy
|
|
239
|
-
const inputField = config.inputField;
|
|
240
|
-
const strategy = config.poolStrategy ?? "round-robin";
|
|
241
|
-
swarm.inner.addConditionalEdges("__splitter__", ((state) => {
|
|
242
|
-
const items = state[inputField];
|
|
243
|
-
if (!Array.isArray(items) || items.length === 0) {
|
|
244
|
-
return "__reducer__";
|
|
245
|
-
}
|
|
246
|
-
// Per-batch assignment counts — used by least-busy strategy
|
|
247
|
-
const assignCounts = new Map(mapperIds.map((id) => [id, 0]));
|
|
248
|
-
return items.map((item, idx) => {
|
|
249
|
-
let targetId;
|
|
250
|
-
if (strategy === "random") {
|
|
251
|
-
targetId = mapperIds[Math.floor(Math.random() * mapperIds.length)];
|
|
252
|
-
}
|
|
253
|
-
else if (strategy === "least-busy") {
|
|
254
|
-
// Pick the mapper with fewest assignments in this batch so far
|
|
255
|
-
let minCount = Infinity;
|
|
256
|
-
let minId = mapperIds[0];
|
|
257
|
-
for (const id of mapperIds) {
|
|
258
|
-
const c = assignCounts.get(id) ?? 0;
|
|
259
|
-
if (c < minCount) {
|
|
260
|
-
minCount = c;
|
|
261
|
-
minId = id;
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
targetId = minId;
|
|
265
|
-
}
|
|
266
|
-
else {
|
|
267
|
-
// round-robin (default)
|
|
268
|
-
targetId = mapperIds[idx % mapperIds.length];
|
|
269
|
-
}
|
|
270
|
-
assignCounts.set(targetId, (assignCounts.get(targetId) ?? 0) + 1);
|
|
271
|
-
return new Send(targetId, { ...state, task: String(item) });
|
|
272
|
-
});
|
|
273
|
-
}));
|
|
274
|
-
// Each mapper → __reducer__
|
|
275
|
-
for (const id of mapperIds) {
|
|
276
|
-
swarm.inner.addEdge(id, "__reducer__");
|
|
277
|
-
}
|
|
278
|
-
// __reducer__ → END
|
|
279
|
-
swarm.inner.addEdge("__reducer__", END);
|
|
280
|
-
return swarm;
|
|
87
|
+
return buildMapReduce(config, new SwarmGraph(config.channels));
|
|
281
88
|
}
|
|
282
89
|
// ---- Static factory: debate template ----
|
|
283
90
|
/**
|
|
@@ -286,108 +93,7 @@ export class SwarmGraph {
|
|
|
286
93
|
* and decides whether consensus has been reached.
|
|
287
94
|
*/
|
|
288
95
|
static debate(config) {
|
|
289
|
-
|
|
290
|
-
throw new Error("SwarmGraph.debate: debaters must contain at least one agent.");
|
|
291
|
-
}
|
|
292
|
-
const swarm = new SwarmGraph(config.channels);
|
|
293
|
-
const debaterIds = config.debaters.map((d) => d.id);
|
|
294
|
-
const consensusKeyword = config.judge.consensusKeyword ?? "CONSENSUS";
|
|
295
|
-
for (const debater of config.debaters) {
|
|
296
|
-
swarm.addAgent(debater);
|
|
297
|
-
}
|
|
298
|
-
const scoreDebaters = config.judge.scoreDebaters ?? false;
|
|
299
|
-
const consensusThreshold = config.judge.consensusThreshold;
|
|
300
|
-
// Judge node: evaluates arguments, decides continue or consensus
|
|
301
|
-
swarm.inner.addNode("__judge__", async (state) => {
|
|
302
|
-
const round = state.supervisorRound ?? 0;
|
|
303
|
-
// If no agent results yet (first round), just kick off debaters
|
|
304
|
-
const results = state.agentResults ?? {};
|
|
305
|
-
if (round === 0 && Object.keys(results).length === 0) {
|
|
306
|
-
return {
|
|
307
|
-
supervisorRound: round + 1,
|
|
308
|
-
};
|
|
309
|
-
}
|
|
310
|
-
// Evaluate arguments via judge model
|
|
311
|
-
const argsText = Object.entries(results)
|
|
312
|
-
.map(([id, r]) => `${id}: ${JSON.stringify(r)}`)
|
|
313
|
-
.join("\n\n");
|
|
314
|
-
const scoreInstruction = scoreDebaters
|
|
315
|
-
? `\nAlso provide a JSON object with scores for each debater and a verdict. Format: {"scores": {"debater_id": score}, "verdict": "CONTINUE" or "${consensusKeyword}"}`
|
|
316
|
-
: "";
|
|
317
|
-
const response = await config.judge.model.chat({
|
|
318
|
-
messages: [{
|
|
319
|
-
role: "user",
|
|
320
|
-
content: `Round ${round}. Evaluate these arguments:\n\n${argsText}\n\nRespond "${consensusKeyword}" if consensus reached, otherwise "CONTINUE".${scoreInstruction}`,
|
|
321
|
-
}],
|
|
322
|
-
systemPrompt: config.judge.systemPrompt ?? "You are a debate judge.",
|
|
323
|
-
});
|
|
324
|
-
let isConsensus = false;
|
|
325
|
-
let roundScores;
|
|
326
|
-
const existingScores = (state.context.debateScores ?? []);
|
|
327
|
-
// Try to parse structured response (JSON with scores + verdict)
|
|
328
|
-
if (scoreDebaters) {
|
|
329
|
-
try {
|
|
330
|
-
const parsed = JSON.parse(response.content);
|
|
331
|
-
if (parsed.scores) {
|
|
332
|
-
roundScores = parsed.scores;
|
|
333
|
-
}
|
|
334
|
-
if (parsed.verdict) {
|
|
335
|
-
isConsensus = parsed.verdict.includes(consensusKeyword);
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
catch {
|
|
339
|
-
// Fallback to keyword detection
|
|
340
|
-
isConsensus = response.content.includes(consensusKeyword);
|
|
341
|
-
}
|
|
342
|
-
// Check consensus threshold if scores available
|
|
343
|
-
if (!isConsensus && roundScores && consensusThreshold != null) {
|
|
344
|
-
const scoreValues = Object.values(roundScores);
|
|
345
|
-
if (scoreValues.length >= 2) {
|
|
346
|
-
const spread = Math.max(...scoreValues) - Math.min(...scoreValues);
|
|
347
|
-
if (spread <= consensusThreshold) {
|
|
348
|
-
isConsensus = true;
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
else {
|
|
354
|
-
isConsensus = response.content.includes(consensusKeyword);
|
|
355
|
-
}
|
|
356
|
-
const nextRound = round + 1;
|
|
357
|
-
// Force done if consensus or max rounds exhausted
|
|
358
|
-
const isDone = isConsensus || nextRound > config.judge.maxRounds;
|
|
359
|
-
const updatedScores = roundScores
|
|
360
|
-
? [...existingScores, { round, scores: roundScores }]
|
|
361
|
-
: existingScores;
|
|
362
|
-
return {
|
|
363
|
-
done: isDone,
|
|
364
|
-
supervisorRound: nextRound,
|
|
365
|
-
context: {
|
|
366
|
-
...(state.context ?? {}),
|
|
367
|
-
...(scoreDebaters ? { debateScores: updatedScores } : {}),
|
|
368
|
-
},
|
|
369
|
-
messages: [{ role: "system", content: `Judge round ${round}: ${isDone ? "Consensus" : "Continue"}` }],
|
|
370
|
-
};
|
|
371
|
-
});
|
|
372
|
-
// Fan-out node: passthrough — conditional edges handle the Send dispatch
|
|
373
|
-
swarm.inner.addNode("__fanout__", (_state) => {
|
|
374
|
-
return {};
|
|
375
|
-
});
|
|
376
|
-
// Wiring: START → __judge__
|
|
377
|
-
swarm.inner.addEdge(START, "__judge__");
|
|
378
|
-
// __judge__ → conditional (done → END, else → __fanout__)
|
|
379
|
-
swarm.inner.addConditionalEdges("__judge__", (state) => {
|
|
380
|
-
if (state.done)
|
|
381
|
-
return END;
|
|
382
|
-
return "__fanout__";
|
|
383
|
-
});
|
|
384
|
-
// __fanout__ → Send to all debaters in parallel
|
|
385
|
-
swarm.inner.addConditionalEdges("__fanout__", ((state) => debaterIds.map((id) => new Send(id, { ...state }))));
|
|
386
|
-
// Each debater → __judge__
|
|
387
|
-
for (const id of debaterIds) {
|
|
388
|
-
swarm.inner.addEdge(id, "__judge__");
|
|
389
|
-
}
|
|
390
|
-
return swarm;
|
|
96
|
+
return buildDebate(config, new SwarmGraph(config.channels));
|
|
391
97
|
}
|
|
392
98
|
// ---- Static factory: hierarchical mesh template ----
|
|
393
99
|
/**
|
|
@@ -396,108 +102,7 @@ export class SwarmGraph {
|
|
|
396
102
|
* and compiled into a single node in the outer graph.
|
|
397
103
|
*/
|
|
398
104
|
static hierarchicalMesh(config) {
|
|
399
|
-
|
|
400
|
-
const teamIds = Object.keys(config.teams);
|
|
401
|
-
const maxRounds = config.coordinator.maxRounds ?? 10;
|
|
402
|
-
// Build each team as a compiled sub-skeleton and mount as a node
|
|
403
|
-
for (const [teamId, teamConfig] of Object.entries(config.teams)) {
|
|
404
|
-
let teamSwarm;
|
|
405
|
-
if (teamConfig.topology === "pipeline") {
|
|
406
|
-
teamSwarm = SwarmGraph.pipeline({ stages: teamConfig.agents });
|
|
407
|
-
}
|
|
408
|
-
else {
|
|
409
|
-
teamSwarm = SwarmGraph.peerNetwork({
|
|
410
|
-
agents: teamConfig.agents,
|
|
411
|
-
entrypoint: teamConfig.agents[0].id,
|
|
412
|
-
handoffs: teamConfig.handoffs ?? {},
|
|
413
|
-
});
|
|
414
|
-
}
|
|
415
|
-
const teamSkeleton = teamSwarm.compile();
|
|
416
|
-
// Mount team as a single node in the outer graph.
|
|
417
|
-
// Spread all top-level fields from teamResult (including done, context)
|
|
418
|
-
// so they propagate to the outer coordinator state.
|
|
419
|
-
swarm.inner.addNode(teamId, async (state, cfg) => {
|
|
420
|
-
const teamResult = await teamSkeleton.invoke(state, cfg);
|
|
421
|
-
return {
|
|
422
|
-
...teamResult,
|
|
423
|
-
agentResults: {
|
|
424
|
-
...(state.agentResults ?? {}),
|
|
425
|
-
[teamId]: teamResult,
|
|
426
|
-
},
|
|
427
|
-
};
|
|
428
|
-
});
|
|
429
|
-
}
|
|
430
|
-
// Coordinator node: routes to teams
|
|
431
|
-
swarm.inner.addNode("__coordinator__", async (state) => {
|
|
432
|
-
const round = state.supervisorRound ?? 0;
|
|
433
|
-
if (round >= maxRounds || state.done) {
|
|
434
|
-
return { done: true };
|
|
435
|
-
}
|
|
436
|
-
if (config.coordinator.strategy === "llm" && config.coordinator.model) {
|
|
437
|
-
const teamList = teamIds.map((id) => `- ${id}`).join("\n");
|
|
438
|
-
const response = await config.coordinator.model.chat({
|
|
439
|
-
messages: [{
|
|
440
|
-
role: "user",
|
|
441
|
-
content: `Task: ${state.task}\n\nAvailable teams:\n${teamList}\n\nRespond with the team name to route to, or "DONE" if complete.`,
|
|
442
|
-
}],
|
|
443
|
-
systemPrompt: config.coordinator.systemPrompt ?? "You coordinate teams.",
|
|
444
|
-
});
|
|
445
|
-
const picked = response.content.trim();
|
|
446
|
-
if (picked === "DONE" || !teamIds.includes(picked)) {
|
|
447
|
-
return { done: true, supervisorRound: round + 1 };
|
|
448
|
-
}
|
|
449
|
-
return new Command({
|
|
450
|
-
update: { supervisorRound: round + 1, currentAgent: picked },
|
|
451
|
-
goto: picked,
|
|
452
|
-
});
|
|
453
|
-
}
|
|
454
|
-
// Round-robin strategy: route to teams in order, mark done after all visited
|
|
455
|
-
if (config.coordinator.strategy === "round-robin") {
|
|
456
|
-
const target = teamIds[round % teamIds.length];
|
|
457
|
-
if (round >= teamIds.length) {
|
|
458
|
-
return { done: true };
|
|
459
|
-
}
|
|
460
|
-
return new Command({
|
|
461
|
-
update: { supervisorRound: round + 1 },
|
|
462
|
-
goto: target,
|
|
463
|
-
});
|
|
464
|
-
}
|
|
465
|
-
// Rule-based strategy: evaluate rules in order, route to the first match
|
|
466
|
-
if (config.coordinator.strategy === "rule") {
|
|
467
|
-
const rules = config.coordinator.rules ?? [];
|
|
468
|
-
const task = String(state.task ?? "");
|
|
469
|
-
const ctx = (state.context ?? {});
|
|
470
|
-
for (const rule of rules) {
|
|
471
|
-
if (rule.condition(task, ctx)) {
|
|
472
|
-
return new Command({
|
|
473
|
-
update: { supervisorRound: round + 1, currentAgent: rule.agentId },
|
|
474
|
-
goto: rule.agentId,
|
|
475
|
-
});
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
// No matching rule — done
|
|
479
|
-
return { done: true, supervisorRound: round + 1 };
|
|
480
|
-
}
|
|
481
|
-
return { done: true };
|
|
482
|
-
});
|
|
483
|
-
// Wiring: START → coordinator
|
|
484
|
-
swarm.inner.addEdge(START, "__coordinator__");
|
|
485
|
-
// Each team → coordinator (loop back unless done)
|
|
486
|
-
for (const teamId of teamIds) {
|
|
487
|
-
swarm.inner.addConditionalEdges(teamId, (state) => {
|
|
488
|
-
if (state.done)
|
|
489
|
-
return END;
|
|
490
|
-
return "__coordinator__";
|
|
491
|
-
});
|
|
492
|
-
}
|
|
493
|
-
// Coordinator → conditional (done → END, else handled by Command.goto)
|
|
494
|
-
swarm.inner.addConditionalEdges("__coordinator__", (state) => {
|
|
495
|
-
if (state.done)
|
|
496
|
-
return END;
|
|
497
|
-
// Command.goto handles routing — this is the fallback
|
|
498
|
-
return END;
|
|
499
|
-
});
|
|
500
|
-
return swarm;
|
|
105
|
+
return buildHierarchicalMesh(config, new SwarmGraph(config.channels), (ch) => new SwarmGraph(ch));
|
|
501
106
|
}
|
|
502
107
|
// ---- Static factory: race template ----
|
|
503
108
|
/**
|
|
@@ -505,67 +110,7 @@ export class SwarmGraph {
|
|
|
505
110
|
* Optionally filter results with an `accept` predicate.
|
|
506
111
|
*/
|
|
507
112
|
static race(config) {
|
|
508
|
-
|
|
509
|
-
const agentIds = config.agents.map((a) => a.id);
|
|
510
|
-
const accept = config.accept ?? (() => true);
|
|
511
|
-
const timeoutMs = config.timeoutMs;
|
|
512
|
-
// Register agents so they appear in the registry
|
|
513
|
-
for (const agentDef of config.agents) {
|
|
514
|
-
swarm.registry.register(agentDef);
|
|
515
|
-
swarm.agentIds.add(agentDef.id);
|
|
516
|
-
}
|
|
517
|
-
// Single node that races all agents — resolves as soon as one produces
|
|
518
|
-
// an acceptable result (true Promise.race semantics, not Promise.all).
|
|
519
|
-
swarm.inner.addNode("__race__", async (state, cfg) => {
|
|
520
|
-
const agentPromises = config.agents.map((agent) => {
|
|
521
|
-
const p = agent.skeleton
|
|
522
|
-
.invoke({ ...state }, { ...cfg, agentId: agent.id })
|
|
523
|
-
.then((result) => ({ id: agent.id, result, error: null }), (err) => ({ id: agent.id, result: null, error: err }));
|
|
524
|
-
if (timeoutMs != null) {
|
|
525
|
-
return Promise.race([
|
|
526
|
-
p,
|
|
527
|
-
new Promise((resolve) => setTimeout(() => resolve({ id: agent.id, result: null, error: new Error("timeout") }), timeoutMs)),
|
|
528
|
-
]);
|
|
529
|
-
}
|
|
530
|
-
return p;
|
|
531
|
-
});
|
|
532
|
-
// Resolve as soon as the first acceptable result arrives.
|
|
533
|
-
const winner = await new Promise((resolve) => {
|
|
534
|
-
let remaining = agentPromises.length;
|
|
535
|
-
if (remaining === 0) {
|
|
536
|
-
resolve(null);
|
|
537
|
-
return;
|
|
538
|
-
}
|
|
539
|
-
for (const p of agentPromises) {
|
|
540
|
-
p.then((r) => {
|
|
541
|
-
if (!r.error && accept(r.result)) {
|
|
542
|
-
resolve(r);
|
|
543
|
-
}
|
|
544
|
-
else {
|
|
545
|
-
remaining--;
|
|
546
|
-
if (remaining === 0)
|
|
547
|
-
resolve(null);
|
|
548
|
-
}
|
|
549
|
-
});
|
|
550
|
-
}
|
|
551
|
-
});
|
|
552
|
-
if (winner) {
|
|
553
|
-
return {
|
|
554
|
-
agentResults: { [winner.id]: winner.result },
|
|
555
|
-
context: { ...(state.context ?? {}), raceWinner: winner.id },
|
|
556
|
-
};
|
|
557
|
-
}
|
|
558
|
-
// No acceptable result
|
|
559
|
-
return {
|
|
560
|
-
context: {
|
|
561
|
-
...(state.context ?? {}),
|
|
562
|
-
raceError: "No agent produced an acceptable result",
|
|
563
|
-
},
|
|
564
|
-
};
|
|
565
|
-
});
|
|
566
|
-
swarm.inner.addEdge(START, "__race__");
|
|
567
|
-
swarm.inner.addEdge("__race__", END);
|
|
568
|
-
return swarm;
|
|
113
|
+
return buildRace(config, new SwarmGraph(config.channels));
|
|
569
114
|
}
|
|
570
115
|
// ---- Static factory: dag template ----
|
|
571
116
|
/**
|
|
@@ -573,82 +118,7 @@ export class SwarmGraph {
|
|
|
573
118
|
* Agents with no dependencies run in parallel.
|
|
574
119
|
*/
|
|
575
120
|
static dag(config) {
|
|
576
|
-
|
|
577
|
-
// Validate dependencies
|
|
578
|
-
for (const [node, deps] of Object.entries(config.dependencies)) {
|
|
579
|
-
for (const dep of deps) {
|
|
580
|
-
if (!agentMap.has(dep)) {
|
|
581
|
-
throw new Error(`Dependency "${dep}" not found in agents list.`);
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
// Cycle detection via topological sort
|
|
586
|
-
const visited = new Set();
|
|
587
|
-
const visiting = new Set();
|
|
588
|
-
const sorted = [];
|
|
589
|
-
function visit(id) {
|
|
590
|
-
if (visited.has(id))
|
|
591
|
-
return;
|
|
592
|
-
if (visiting.has(id))
|
|
593
|
-
throw new Error(`Cycle detected involving "${id}".`);
|
|
594
|
-
visiting.add(id);
|
|
595
|
-
for (const dep of config.dependencies[id] ?? []) {
|
|
596
|
-
visit(dep);
|
|
597
|
-
}
|
|
598
|
-
visiting.delete(id);
|
|
599
|
-
visited.add(id);
|
|
600
|
-
sorted.push(id);
|
|
601
|
-
}
|
|
602
|
-
for (const agent of config.agents) {
|
|
603
|
-
visit(agent.id);
|
|
604
|
-
}
|
|
605
|
-
// Build a standard swarm with wiring based on topological sort
|
|
606
|
-
const swarm = new SwarmGraph(config.channels);
|
|
607
|
-
for (const agent of config.agents) {
|
|
608
|
-
swarm.addAgent(agent);
|
|
609
|
-
}
|
|
610
|
-
// Group agents into layers based on dependencies
|
|
611
|
-
const deps = config.dependencies;
|
|
612
|
-
const rootIds = config.agents.filter((a) => !deps[a.id]?.length).map((a) => a.id);
|
|
613
|
-
const nonRootIds = config.agents.filter((a) => deps[a.id]?.length).map((a) => a.id);
|
|
614
|
-
// For DAG execution: use a single orchestrator node
|
|
615
|
-
swarm.inner.addNode("__dag_runner__", async (state, cfg) => {
|
|
616
|
-
const results = {};
|
|
617
|
-
const completed = new Set();
|
|
618
|
-
// Process in topological order
|
|
619
|
-
// Group into parallel batches
|
|
620
|
-
const remaining = new Set(sorted);
|
|
621
|
-
while (remaining.size > 0) {
|
|
622
|
-
// Find all nodes whose deps are satisfied
|
|
623
|
-
const ready = [];
|
|
624
|
-
for (const id of remaining) {
|
|
625
|
-
const idDeps = deps[id] ?? [];
|
|
626
|
-
if (idDeps.every((d) => completed.has(d))) {
|
|
627
|
-
ready.push(id);
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
// Execute ready nodes in parallel
|
|
631
|
-
const batchResults = await Promise.all(ready.map(async (id) => {
|
|
632
|
-
const agent = agentMap.get(id);
|
|
633
|
-
const result = await agent.skeleton.invoke({ ...state, agentResults: { ...(state.agentResults ?? {}), ...results } }, { ...cfg, agentId: id });
|
|
634
|
-
return { id, result };
|
|
635
|
-
}));
|
|
636
|
-
for (const { id, result } of batchResults) {
|
|
637
|
-
results[id] = result;
|
|
638
|
-
completed.add(id);
|
|
639
|
-
remaining.delete(id);
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
return {
|
|
643
|
-
agentResults: { ...(state.agentResults ?? {}), ...results },
|
|
644
|
-
};
|
|
645
|
-
});
|
|
646
|
-
// Wire: START → __dag_runner__ → END
|
|
647
|
-
// Remove any edges added by addAgent
|
|
648
|
-
swarm.inner.edges = [];
|
|
649
|
-
swarm.inner.addEdge(START, "__dag_runner__");
|
|
650
|
-
swarm.inner.addEdge("__dag_runner__", END);
|
|
651
|
-
return swarm;
|
|
121
|
+
return buildDag(config, new SwarmGraph(config.channels));
|
|
652
122
|
}
|
|
653
123
|
// ---- Static factory: pool template ----
|
|
654
124
|
/**
|
|
@@ -656,90 +126,7 @@ export class SwarmGraph {
|
|
|
656
126
|
* then reduce all results.
|
|
657
127
|
*/
|
|
658
128
|
static pool(config) {
|
|
659
|
-
|
|
660
|
-
const poolSize = config.poolSize;
|
|
661
|
-
// Create pool copies
|
|
662
|
-
const poolIds = [];
|
|
663
|
-
for (let i = 0; i < poolSize; i++) {
|
|
664
|
-
const id = poolSize === 1 ? config.agent.id : `${config.agent.id}_${i}`;
|
|
665
|
-
poolIds.push(id);
|
|
666
|
-
swarm.addAgent({ ...config.agent, id });
|
|
667
|
-
}
|
|
668
|
-
// Orchestrator node: dispatches items to pool agents and reduces
|
|
669
|
-
swarm.inner.addNode("__pool_runner__", async (state, cfg) => {
|
|
670
|
-
const items = state[config.inputField];
|
|
671
|
-
if (!Array.isArray(items) || items.length === 0) {
|
|
672
|
-
return config.reducer({});
|
|
673
|
-
}
|
|
674
|
-
// Semaphore for concurrency control
|
|
675
|
-
let running = 0;
|
|
676
|
-
const results = {};
|
|
677
|
-
const queue = items.map((item, idx) => ({
|
|
678
|
-
item,
|
|
679
|
-
idx,
|
|
680
|
-
targetId: poolIds[idx % poolIds.length],
|
|
681
|
-
}));
|
|
682
|
-
await new Promise((resolve, reject) => {
|
|
683
|
-
let completed = 0;
|
|
684
|
-
const total = queue.length;
|
|
685
|
-
function processNext() {
|
|
686
|
-
while (running < poolSize && queue.length > 0) {
|
|
687
|
-
const work = queue.shift();
|
|
688
|
-
// Respect removeAgent() — if the assigned slot was removed, redirect
|
|
689
|
-
// to an active pool slot; if none remain, mark the item as failed.
|
|
690
|
-
let agentDef = swarm.registry.getDef(work.targetId);
|
|
691
|
-
if (!agentDef) {
|
|
692
|
-
const activeIds = poolIds.filter((id) => !!swarm.registry.getDef(id));
|
|
693
|
-
if (activeIds.length > 0) {
|
|
694
|
-
work.targetId = activeIds[work.idx % activeIds.length];
|
|
695
|
-
agentDef = swarm.registry.getDef(work.targetId);
|
|
696
|
-
}
|
|
697
|
-
else {
|
|
698
|
-
results[`item_${work.idx}`] = { _error: `Pool slot removed; no active agents remain` };
|
|
699
|
-
completed++;
|
|
700
|
-
if (completed === total)
|
|
701
|
-
resolve();
|
|
702
|
-
continue;
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
running++;
|
|
706
|
-
// Use the full wrapped agentNode (hooks, retries, timeout) stored
|
|
707
|
-
// by addAgent() in swarm.inner.nodes rather than raw skeleton.invoke.
|
|
708
|
-
const wrappedFn = swarm.inner.nodes.get(work.targetId)?.fn;
|
|
709
|
-
const invocation = wrappedFn
|
|
710
|
-
? wrappedFn({ ...state, task: String(work.item) }, { ...cfg, agentId: work.targetId })
|
|
711
|
-
: agentDef.skeleton.invoke({ ...state, task: String(work.item) }, { ...cfg, agentId: work.targetId });
|
|
712
|
-
invocation.then((result) => {
|
|
713
|
-
results[`item_${work.idx}`] = result;
|
|
714
|
-
running--;
|
|
715
|
-
completed++;
|
|
716
|
-
if (completed === total)
|
|
717
|
-
resolve();
|
|
718
|
-
else
|
|
719
|
-
processNext();
|
|
720
|
-
}, (err) => {
|
|
721
|
-
results[`item_${work.idx}`] = { _error: String(err) };
|
|
722
|
-
running--;
|
|
723
|
-
completed++;
|
|
724
|
-
if (completed === total)
|
|
725
|
-
resolve();
|
|
726
|
-
else
|
|
727
|
-
processNext();
|
|
728
|
-
});
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
if (total === 0)
|
|
732
|
-
resolve();
|
|
733
|
-
else
|
|
734
|
-
processNext();
|
|
735
|
-
});
|
|
736
|
-
return config.reducer(results);
|
|
737
|
-
});
|
|
738
|
-
// Wire: START → __pool_runner__ → END (bypass agent edges)
|
|
739
|
-
swarm.inner.edges = [];
|
|
740
|
-
swarm.inner.addEdge(START, "__pool_runner__");
|
|
741
|
-
swarm.inner.addEdge("__pool_runner__", END);
|
|
742
|
-
return swarm;
|
|
129
|
+
return buildPool(config, new SwarmGraph(config.channels));
|
|
743
130
|
}
|
|
744
131
|
// ---- Static factory: compose template ----
|
|
745
132
|
/**
|
|
@@ -747,179 +134,13 @@ export class SwarmGraph {
|
|
|
747
134
|
* Each stage runs a compiled sub-swarm, passing state through.
|
|
748
135
|
*/
|
|
749
136
|
static compose(config) {
|
|
750
|
-
|
|
751
|
-
throw new Error("SwarmGraph.compose: stages must contain at least one sub-swarm.");
|
|
752
|
-
}
|
|
753
|
-
const swarm = new SwarmGraph(config.channels);
|
|
754
|
-
const stageIds = config.stages.map((s) => s.id);
|
|
755
|
-
for (const stage of config.stages) {
|
|
756
|
-
const compiled = stage.swarm.compile();
|
|
757
|
-
swarm.inner.addNode(stage.id, async (state, cfg) => {
|
|
758
|
-
const stageResult = await compiled.invoke(state, cfg);
|
|
759
|
-
return {
|
|
760
|
-
...stageResult,
|
|
761
|
-
agentResults: {
|
|
762
|
-
...(state.agentResults ?? {}),
|
|
763
|
-
[stage.id]: stageResult,
|
|
764
|
-
},
|
|
765
|
-
};
|
|
766
|
-
});
|
|
767
|
-
}
|
|
768
|
-
// Wire pipeline: START → stage1 → stage2 → ... → END
|
|
769
|
-
swarm.inner.addEdge(START, stageIds[0]);
|
|
770
|
-
for (let i = 0; i < stageIds.length - 1; i++) {
|
|
771
|
-
swarm.inner.addEdge(stageIds[i], stageIds[i + 1]);
|
|
772
|
-
}
|
|
773
|
-
swarm.inner.addEdge(stageIds[stageIds.length - 1], END);
|
|
774
|
-
return swarm;
|
|
137
|
+
return buildCompose(config, new SwarmGraph(config.channels));
|
|
775
138
|
}
|
|
776
139
|
// ---- Agent registration ----
|
|
777
140
|
addAgent(def) {
|
|
778
141
|
this.registry.register(def);
|
|
779
142
|
this.agentIds.add(def.id);
|
|
780
|
-
|
|
781
|
-
// The node executes the agent's skeleton and handles Handoff returns
|
|
782
|
-
const agentNode = async (state, config) => {
|
|
783
|
-
this.registry.markBusy(def.id);
|
|
784
|
-
// Fire onStart hook
|
|
785
|
-
await def.hooks?.onStart?.(def.id, state);
|
|
786
|
-
const maxRetries = def.maxRetries ?? 2;
|
|
787
|
-
const retryDelayMs = def.retryDelayMs ?? 0;
|
|
788
|
-
let lastError;
|
|
789
|
-
let lastAttempt = 0;
|
|
790
|
-
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
791
|
-
lastAttempt = attempt;
|
|
792
|
-
try {
|
|
793
|
-
// Get inbox and filter out already-consumed messages
|
|
794
|
-
const consumedIds = (state.context?.__consumedMsgIds ?? []);
|
|
795
|
-
const consumedSet = new Set(consumedIds);
|
|
796
|
-
const allInbox = getInbox(state.swarmMessages ?? [], def.id);
|
|
797
|
-
const inbox = allInbox.filter((m) => !consumedSet.has(m.id));
|
|
798
|
-
// Track consumed message IDs
|
|
799
|
-
const newConsumedIds = [...consumedIds, ...inbox.map((m) => m.id)];
|
|
800
|
-
const agentInput = {
|
|
801
|
-
...state,
|
|
802
|
-
context: {
|
|
803
|
-
...(state.context ?? {}),
|
|
804
|
-
inbox,
|
|
805
|
-
__consumedMsgIds: newConsumedIds,
|
|
806
|
-
},
|
|
807
|
-
};
|
|
808
|
-
let effectiveTimeout = def.timeout;
|
|
809
|
-
// Clamp agent timeout to remaining deadline (if set)
|
|
810
|
-
const deadlineAbs = state.context?.__deadlineAbsolute;
|
|
811
|
-
if (deadlineAbs != null) {
|
|
812
|
-
const remaining = deadlineAbs - Date.now();
|
|
813
|
-
if (remaining <= 0) {
|
|
814
|
-
throw new Error(`Agent "${def.id}" deadline expired`);
|
|
815
|
-
}
|
|
816
|
-
if (effectiveTimeout == null || remaining < effectiveTimeout) {
|
|
817
|
-
effectiveTimeout = remaining;
|
|
818
|
-
}
|
|
819
|
-
}
|
|
820
|
-
const result = await runWithTimeout(() => def.skeleton.invoke(agentInput, {
|
|
821
|
-
...config,
|
|
822
|
-
agentId: def.id,
|
|
823
|
-
}), effectiveTimeout, () => new Error(`Agent "${def.id}" timed out after ${effectiveTimeout}ms`));
|
|
824
|
-
this.registry.markIdle(def.id);
|
|
825
|
-
// ---- Handoff detection ----
|
|
826
|
-
if (result instanceof Handoff || (result && result.isHandoff)) {
|
|
827
|
-
const handoff = result instanceof Handoff
|
|
828
|
-
? result
|
|
829
|
-
: new Handoff(result.opts);
|
|
830
|
-
// Fire onComplete for handoffs too
|
|
831
|
-
await def.hooks?.onComplete?.(def.id, result);
|
|
832
|
-
return new Command({
|
|
833
|
-
update: {
|
|
834
|
-
context: { ...(state.context ?? {}), ...(handoff.context ?? {}) },
|
|
835
|
-
handoffHistory: [{
|
|
836
|
-
from: def.id,
|
|
837
|
-
to: handoff.to,
|
|
838
|
-
message: handoff.message,
|
|
839
|
-
step: state.supervisorRound ?? 0,
|
|
840
|
-
timestamp: Date.now(),
|
|
841
|
-
resume: handoff.resume,
|
|
842
|
-
}],
|
|
843
|
-
currentAgent: handoff.to,
|
|
844
|
-
},
|
|
845
|
-
goto: handoff.to,
|
|
846
|
-
});
|
|
847
|
-
}
|
|
848
|
-
// Fire onComplete hook
|
|
849
|
-
await def.hooks?.onComplete?.(def.id, result);
|
|
850
|
-
// ---- Normal result ----
|
|
851
|
-
return {
|
|
852
|
-
...result,
|
|
853
|
-
context: {
|
|
854
|
-
...(result.context ?? {}),
|
|
855
|
-
__consumedMsgIds: newConsumedIds,
|
|
856
|
-
},
|
|
857
|
-
agentResults: {
|
|
858
|
-
...(state.agentResults ?? {}),
|
|
859
|
-
[def.id]: result,
|
|
860
|
-
},
|
|
861
|
-
handoffHistory: [
|
|
862
|
-
...(state.handoffHistory ?? []),
|
|
863
|
-
{
|
|
864
|
-
from: def.id,
|
|
865
|
-
to: "__completed__",
|
|
866
|
-
message: "Agent completed",
|
|
867
|
-
step: state.supervisorRound ?? 0,
|
|
868
|
-
timestamp: Date.now(),
|
|
869
|
-
},
|
|
870
|
-
],
|
|
871
|
-
};
|
|
872
|
-
}
|
|
873
|
-
catch (err) {
|
|
874
|
-
lastError = err;
|
|
875
|
-
this.registry.markError(def.id);
|
|
876
|
-
if (attempt < maxRetries) {
|
|
877
|
-
// Backoff delay between retries — clamped to remaining deadline
|
|
878
|
-
if (retryDelayMs > 0) {
|
|
879
|
-
let delay = retryDelayMs;
|
|
880
|
-
const dl = state.context?.__deadlineAbsolute;
|
|
881
|
-
if (dl != null) {
|
|
882
|
-
const remaining = dl - Date.now();
|
|
883
|
-
if (remaining <= 0)
|
|
884
|
-
break; // deadline expired, stop retrying
|
|
885
|
-
delay = Math.min(delay, remaining);
|
|
886
|
-
}
|
|
887
|
-
await new Promise((r) => setTimeout(r, delay));
|
|
888
|
-
}
|
|
889
|
-
continue; // retry
|
|
890
|
-
}
|
|
891
|
-
}
|
|
892
|
-
}
|
|
893
|
-
// All retries exhausted — fire onError hook
|
|
894
|
-
await def.hooks?.onError?.(def.id, lastError);
|
|
895
|
-
// Keep agent in error status (don't reset to idle)
|
|
896
|
-
// Build structured error context
|
|
897
|
-
const errStr = String(lastError instanceof Error ? lastError.message : lastError);
|
|
898
|
-
const errType = errStr.includes("timeout") ? "timeout" :
|
|
899
|
-
errStr.includes("model") ? "model_error" :
|
|
900
|
-
errStr.includes("tool") ? "tool_error" :
|
|
901
|
-
"unknown";
|
|
902
|
-
if (this.hasSupervisor && this.onErrorPolicy !== "throw") {
|
|
903
|
-
return new Command({
|
|
904
|
-
update: {
|
|
905
|
-
context: {
|
|
906
|
-
...(state.context ?? {}),
|
|
907
|
-
lastAgentError: {
|
|
908
|
-
agent: def.id,
|
|
909
|
-
error: errStr,
|
|
910
|
-
type: errType,
|
|
911
|
-
attempt: lastAttempt,
|
|
912
|
-
maxRetries,
|
|
913
|
-
},
|
|
914
|
-
},
|
|
915
|
-
},
|
|
916
|
-
goto: this.supervisorNodeName,
|
|
917
|
-
});
|
|
918
|
-
}
|
|
919
|
-
// No supervisor, or onError: "throw" — rethrow
|
|
920
|
-
throw lastError;
|
|
921
|
-
};
|
|
922
|
-
this.inner.addNode(def.id, agentNode);
|
|
143
|
+
this.inner.addNode(def.id, createAgentNode(def, this.registry, this));
|
|
923
144
|
return this;
|
|
924
145
|
}
|
|
925
146
|
// ---- Supervisor ----
|
|
@@ -976,7 +197,8 @@ export class SwarmGraph {
|
|
|
976
197
|
return issues;
|
|
977
198
|
// For non-supervised swarms, check that every agent has at least one incoming edge
|
|
978
199
|
const edgeTargets = new Set();
|
|
979
|
-
|
|
200
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
201
|
+
for (const edge of this.inner.edges) { // SAFE: external boundary — accessing private StateGraph.edges for topology validation
|
|
980
202
|
if (edge.type === "static") {
|
|
981
203
|
edgeTargets.add(edge.to);
|
|
982
204
|
}
|
|
@@ -1011,99 +233,18 @@ export class SwarmGraph {
|
|
|
1011
233
|
// ---- Compile ----
|
|
1012
234
|
compile(opts = {}) {
|
|
1013
235
|
const skeleton = this.inner.compile({
|
|
1014
|
-
...(opts.checkpointer ? { checkpointer: opts.checkpointer } : {}),
|
|
1015
|
-
...(opts.interruptBefore ? { interruptBefore: opts.interruptBefore } : {}),
|
|
1016
|
-
...(opts.interruptAfter ? { interruptAfter: opts.interruptAfter } : {}),
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
agentStats: () => registry.stats(),
|
|
1027
|
-
toMermaid: () => inner.toMermaid(),
|
|
1028
|
-
spawnAgent(def) {
|
|
1029
|
-
// spawnAgent requires a supervisor to route the spawned agent back into the graph.
|
|
1030
|
-
// In unsupervised topologies there is no return path, so the agent would never execute.
|
|
1031
|
-
if (!hasSupervisor) {
|
|
1032
|
-
throw new Error(`spawnAgent() requires a supervisor node. ` +
|
|
1033
|
-
`Use SwarmGraph.hierarchical() to add a supervisor, or call addAgent() before compile() for static topologies.`);
|
|
1034
|
-
}
|
|
1035
|
-
// Register in the registry
|
|
1036
|
-
registry.register(def);
|
|
1037
|
-
// Create the agent node function (same logic as addAgent)
|
|
1038
|
-
const agentNode = async (state, config) => {
|
|
1039
|
-
registry.markBusy(def.id);
|
|
1040
|
-
await def.hooks?.onStart?.(def.id, state);
|
|
1041
|
-
const maxRetries = def.maxRetries ?? 2;
|
|
1042
|
-
let lastError;
|
|
1043
|
-
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
1044
|
-
try {
|
|
1045
|
-
const result = await def.skeleton.invoke({ ...state, context: { ...(state.context ?? {}), inbox: getInbox(state.swarmMessages ?? [], def.id) } }, { ...config, agentId: def.id });
|
|
1046
|
-
registry.markIdle(def.id);
|
|
1047
|
-
await def.hooks?.onComplete?.(def.id, result);
|
|
1048
|
-
return {
|
|
1049
|
-
...result,
|
|
1050
|
-
agentResults: { ...(state.agentResults ?? {}), [def.id]: result },
|
|
1051
|
-
};
|
|
1052
|
-
}
|
|
1053
|
-
catch (err) {
|
|
1054
|
-
lastError = err;
|
|
1055
|
-
registry.markError(def.id);
|
|
1056
|
-
if (attempt < maxRetries)
|
|
1057
|
-
continue;
|
|
1058
|
-
}
|
|
1059
|
-
}
|
|
1060
|
-
await def.hooks?.onError?.(def.id, lastError);
|
|
1061
|
-
// Keep agent in error status (don't reset to idle)
|
|
1062
|
-
if (hasSupervisor) {
|
|
1063
|
-
return new Command({
|
|
1064
|
-
update: { context: { ...(state.context ?? {}), lastAgentError: { agent: def.id, error: String(lastError) } } },
|
|
1065
|
-
goto: supervisorNodeName,
|
|
1066
|
-
});
|
|
1067
|
-
}
|
|
1068
|
-
throw lastError;
|
|
1069
|
-
};
|
|
1070
|
-
// Add node to the compiled runner's nodes map
|
|
1071
|
-
const runner = skeleton._runner;
|
|
1072
|
-
if (runner?.nodes) {
|
|
1073
|
-
runner.nodes.set(def.id, { name: def.id, fn: agentNode });
|
|
1074
|
-
}
|
|
1075
|
-
// In supervised swarms, add a conditional return edge so the runner
|
|
1076
|
-
// knows to send this agent's output back to the supervisor (or END).
|
|
1077
|
-
if (hasSupervisor && runner) {
|
|
1078
|
-
const returnEdge = {
|
|
1079
|
-
type: "conditional",
|
|
1080
|
-
from: def.id,
|
|
1081
|
-
condition: (st) => st.done ? END : supervisorNodeName,
|
|
1082
|
-
};
|
|
1083
|
-
const edgesBySource = runner._edgesBySource;
|
|
1084
|
-
const list = edgesBySource.get(def.id) ?? [];
|
|
1085
|
-
list.push(returnEdge);
|
|
1086
|
-
edgesBySource.set(def.id, list);
|
|
1087
|
-
}
|
|
1088
|
-
},
|
|
1089
|
-
removeAgent(agentId) {
|
|
1090
|
-
registry.deregister(agentId);
|
|
1091
|
-
const runner = skeleton._runner;
|
|
1092
|
-
if (runner?.nodes)
|
|
1093
|
-
runner.nodes.delete(agentId);
|
|
1094
|
-
// Remove stale edges pointing TO the removed agent so Pregel doesn't try to route to it.
|
|
1095
|
-
// Also remove edges FROM the agent (it won't execute, but keeps _edgesBySource clean).
|
|
1096
|
-
if (runner?._edgesBySource) {
|
|
1097
|
-
const edgesBySource = runner._edgesBySource;
|
|
1098
|
-
for (const [from, edges] of edgesBySource) {
|
|
1099
|
-
const filtered = edges.filter((e) => !(e.type === "static" && e.to === agentId));
|
|
1100
|
-
if (filtered.length !== edges.length)
|
|
1101
|
-
edgesBySource.set(from, filtered);
|
|
1102
|
-
}
|
|
1103
|
-
edgesBySource.delete(agentId);
|
|
1104
|
-
}
|
|
1105
|
-
},
|
|
1106
|
-
};
|
|
236
|
+
...(opts.checkpointer !== undefined ? { checkpointer: opts.checkpointer } : {}),
|
|
237
|
+
...(opts.interruptBefore !== undefined ? { interruptBefore: opts.interruptBefore } : {}),
|
|
238
|
+
...(opts.interruptAfter !== undefined ? { interruptAfter: opts.interruptAfter } : {}),
|
|
239
|
+
...(opts.store !== undefined ? { store: opts.store } : {}),
|
|
240
|
+
...(opts.guardrails !== undefined ? { guardrails: opts.guardrails } : {}),
|
|
241
|
+
...(opts.listeners !== undefined ? { listeners: opts.listeners } : {}),
|
|
242
|
+
...(opts.defaults !== undefined ? { defaults: opts.defaults } : {}),
|
|
243
|
+
...(opts.deadLetterQueue !== undefined ? { deadLetterQueue: opts.deadLetterQueue } : {}),
|
|
244
|
+
...(opts.tracer !== undefined ? { tracer: opts.tracer } : {}),
|
|
245
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
246
|
+
}); // SAFE: CompileOptions conditional spread — TypeScript can't narrow the intersection type
|
|
247
|
+
const extensions = buildSwarmExtensions(skeleton, this.registry, this.inner, this.hasSupervisor, this.supervisorNodeName, this.onErrorPolicy);
|
|
1107
248
|
return Object.assign(skeleton, extensions);
|
|
1108
249
|
}
|
|
1109
250
|
}
|
|
@@ -1134,4 +275,5 @@ export function quickAgent(id, fn, opts) {
|
|
|
1134
275
|
retryDelayMs: opts?.retryDelayMs,
|
|
1135
276
|
};
|
|
1136
277
|
}
|
|
278
|
+
export { baseSwarmChannels } from "./config.js";
|
|
1137
279
|
//# sourceMappingURL=graph.js.map
|