@oni.bot/core 1.0.3 → 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.
Files changed (146) hide show
  1. package/dist/checkpointers/postgres.d.ts.map +1 -1
  2. package/dist/checkpointers/postgres.js +2 -1
  3. package/dist/checkpointers/postgres.js.map +1 -1
  4. package/dist/cli/inspect.d.ts.map +1 -1
  5. package/dist/cli/inspect.js +4 -2
  6. package/dist/cli/inspect.js.map +1 -1
  7. package/dist/coordination/request-reply.d.ts +11 -2
  8. package/dist/coordination/request-reply.d.ts.map +1 -1
  9. package/dist/coordination/request-reply.js.map +1 -1
  10. package/dist/events/bus.d.ts.map +1 -1
  11. package/dist/events/bus.js +1 -0
  12. package/dist/events/bus.js.map +1 -1
  13. package/dist/graph.d.ts +11 -1
  14. package/dist/graph.d.ts.map +1 -1
  15. package/dist/graph.js +4 -2
  16. package/dist/graph.js.map +1 -1
  17. package/dist/harness/agent-loop.d.ts +1 -7
  18. package/dist/harness/agent-loop.d.ts.map +1 -1
  19. package/dist/harness/agent-loop.js +2 -642
  20. package/dist/harness/agent-loop.js.map +1 -1
  21. package/dist/harness/loop/hooks.d.ts +7 -0
  22. package/dist/harness/loop/hooks.d.ts.map +1 -0
  23. package/dist/harness/loop/hooks.js +46 -0
  24. package/dist/harness/loop/hooks.js.map +1 -0
  25. package/dist/harness/loop/index.d.ts +8 -0
  26. package/dist/harness/loop/index.d.ts.map +1 -0
  27. package/dist/harness/loop/index.js +257 -0
  28. package/dist/harness/loop/index.js.map +1 -0
  29. package/dist/harness/loop/inference.d.ts +19 -0
  30. package/dist/harness/loop/inference.d.ts.map +1 -0
  31. package/dist/harness/loop/inference.js +121 -0
  32. package/dist/harness/loop/inference.js.map +1 -0
  33. package/dist/harness/loop/memory.d.ts +22 -0
  34. package/dist/harness/loop/memory.d.ts.map +1 -0
  35. package/dist/harness/loop/memory.js +73 -0
  36. package/dist/harness/loop/memory.js.map +1 -0
  37. package/dist/harness/loop/safety.d.ts +8 -0
  38. package/dist/harness/loop/safety.d.ts.map +1 -0
  39. package/dist/harness/loop/safety.js +21 -0
  40. package/dist/harness/loop/safety.js.map +1 -0
  41. package/dist/harness/loop/tools.d.ts +24 -0
  42. package/dist/harness/loop/tools.d.ts.map +1 -0
  43. package/dist/harness/loop/tools.js +184 -0
  44. package/dist/harness/loop/tools.js.map +1 -0
  45. package/dist/harness/loop/types.d.ts +7 -0
  46. package/dist/harness/loop/types.d.ts.map +1 -0
  47. package/dist/harness/loop/types.js +9 -0
  48. package/dist/harness/loop/types.js.map +1 -0
  49. package/dist/harness/memory/fs-compat.d.ts +3 -0
  50. package/dist/harness/memory/fs-compat.d.ts.map +1 -0
  51. package/dist/harness/memory/fs-compat.js +26 -0
  52. package/dist/harness/memory/fs-compat.js.map +1 -0
  53. package/dist/harness/memory/index.d.ts +105 -0
  54. package/dist/harness/memory/index.d.ts.map +1 -0
  55. package/dist/harness/memory/index.js +491 -0
  56. package/dist/harness/memory/index.js.map +1 -0
  57. package/dist/harness/memory/prompter.d.ts +7 -0
  58. package/dist/harness/memory/prompter.d.ts.map +1 -0
  59. package/dist/harness/memory/prompter.js +24 -0
  60. package/dist/harness/memory/prompter.js.map +1 -0
  61. package/dist/harness/memory/ranker.d.ts +15 -0
  62. package/dist/harness/memory/ranker.d.ts.map +1 -0
  63. package/dist/harness/memory/ranker.js +72 -0
  64. package/dist/harness/memory/ranker.js.map +1 -0
  65. package/dist/harness/memory/scanner.d.ts +26 -0
  66. package/dist/harness/memory/scanner.d.ts.map +1 -0
  67. package/dist/harness/memory/scanner.js +187 -0
  68. package/dist/harness/memory/scanner.js.map +1 -0
  69. package/dist/harness/memory/types.d.ts +50 -0
  70. package/dist/harness/memory/types.d.ts.map +1 -0
  71. package/dist/harness/memory/types.js +7 -0
  72. package/dist/harness/memory/types.js.map +1 -0
  73. package/dist/harness/memory-loader.d.ts +2 -149
  74. package/dist/harness/memory-loader.d.ts.map +1 -1
  75. package/dist/harness/memory-loader.js +1 -713
  76. package/dist/harness/memory-loader.js.map +1 -1
  77. package/dist/hitl/interrupt.d.ts.map +1 -1
  78. package/dist/hitl/interrupt.js +2 -1
  79. package/dist/hitl/interrupt.js.map +1 -1
  80. package/dist/prebuilt/react-agent.d.ts.map +1 -1
  81. package/dist/prebuilt/react-agent.js +6 -2
  82. package/dist/prebuilt/react-agent.js.map +1 -1
  83. package/dist/pregel/checkpointing.d.ts +12 -0
  84. package/dist/pregel/checkpointing.d.ts.map +1 -0
  85. package/dist/pregel/checkpointing.js +60 -0
  86. package/dist/pregel/checkpointing.js.map +1 -0
  87. package/dist/pregel/execution.d.ts +7 -0
  88. package/dist/pregel/execution.d.ts.map +1 -0
  89. package/dist/pregel/execution.js +178 -0
  90. package/dist/pregel/execution.js.map +1 -0
  91. package/dist/pregel/index.d.ts +61 -0
  92. package/dist/pregel/index.d.ts.map +1 -0
  93. package/dist/pregel/index.js +154 -0
  94. package/dist/pregel/index.js.map +1 -0
  95. package/dist/pregel/interrupts.d.ts +3 -0
  96. package/dist/pregel/interrupts.d.ts.map +1 -0
  97. package/dist/pregel/interrupts.js +7 -0
  98. package/dist/pregel/interrupts.js.map +1 -0
  99. package/dist/pregel/state-helpers.d.ts +12 -0
  100. package/dist/pregel/state-helpers.d.ts.map +1 -0
  101. package/dist/pregel/state-helpers.js +71 -0
  102. package/dist/pregel/state-helpers.js.map +1 -0
  103. package/dist/pregel/streaming.d.ts +5 -0
  104. package/dist/pregel/streaming.d.ts.map +1 -0
  105. package/dist/pregel/streaming.js +462 -0
  106. package/dist/pregel/streaming.js.map +1 -0
  107. package/dist/pregel/types.d.ts +48 -0
  108. package/dist/pregel/types.d.ts.map +1 -0
  109. package/dist/pregel/types.js +5 -0
  110. package/dist/pregel/types.js.map +1 -0
  111. package/dist/pregel.d.ts +1 -66
  112. package/dist/pregel.d.ts.map +1 -1
  113. package/dist/pregel.js +2 -854
  114. package/dist/pregel.js.map +1 -1
  115. package/dist/swarm/agent-node.d.ts +11 -0
  116. package/dist/swarm/agent-node.d.ts.map +1 -0
  117. package/dist/swarm/agent-node.js +156 -0
  118. package/dist/swarm/agent-node.js.map +1 -0
  119. package/dist/swarm/compile-ext.d.ts +5 -0
  120. package/dist/swarm/compile-ext.d.ts.map +1 -0
  121. package/dist/swarm/compile-ext.js +126 -0
  122. package/dist/swarm/compile-ext.js.map +1 -0
  123. package/dist/swarm/config.d.ts +147 -0
  124. package/dist/swarm/config.d.ts.map +1 -0
  125. package/dist/swarm/config.js +17 -0
  126. package/dist/swarm/config.js.map +1 -0
  127. package/dist/swarm/factories.d.ts +37 -0
  128. package/dist/swarm/factories.d.ts.map +1 -0
  129. package/dist/swarm/factories.js +703 -0
  130. package/dist/swarm/factories.js.map +1 -0
  131. package/dist/swarm/graph.d.ts +14 -147
  132. package/dist/swarm/graph.d.ts.map +1 -1
  133. package/dist/swarm/graph.js +30 -917
  134. package/dist/swarm/graph.js.map +1 -1
  135. package/dist/swarm/pool.js.map +1 -1
  136. package/dist/swarm/supervisor.js.map +1 -1
  137. package/dist/testing/index.d.ts +2 -2
  138. package/dist/testing/index.d.ts.map +1 -1
  139. package/dist/testing/index.js +3 -2
  140. package/dist/testing/index.js.map +1 -1
  141. package/dist/tools/define.d.ts +2 -1
  142. package/dist/tools/define.d.ts.map +1 -1
  143. package/dist/tools/define.js +3 -1
  144. package/dist/tools/define.js.map +1 -1
  145. package/dist/tools/types.d.ts.map +1 -1
  146. package/package.json +1 -1
@@ -8,37 +8,26 @@
8
8
  // - compile() returns a full ONISkeleton
9
9
  // ============================================================
10
10
  import { StateGraph } from "../graph.js";
11
- import { START, END, appendList, lastValue, mergeObject } from "../types.js";
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 { runWithTimeout } from "../internal/timeout.js";
20
- export const baseSwarmChannels = {
21
- task: lastValue(() => ""),
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
- const swarm = new SwarmGraph(config.channels);
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
- const swarm = new SwarmGraph(config.channels);
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
- if (config.stages.length === 0) {
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
- const swarm = new SwarmGraph(config.channels);
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
- const poolSize = config.poolSize ?? 1;
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
- if (config.debaters.length === 0) {
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
- const swarm = new SwarmGraph(config.channels);
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,73 +110,7 @@ export class SwarmGraph {
505
110
  * Optionally filter results with an `accept` predicate.
506
111
  */
507
112
  static race(config) {
508
- const swarm = new SwarmGraph(config.channels);
509
- const accept = config.accept ?? (() => true);
510
- const timeoutMs = config.timeoutMs;
511
- // Register agents so they appear in the registry
512
- for (const agentDef of config.agents) {
513
- swarm.registry.register(agentDef);
514
- swarm.agentIds.add(agentDef.id);
515
- }
516
- // Single node that races all agents — resolves as soon as one produces
517
- // an acceptable result (true Promise.race semantics, not Promise.all).
518
- swarm.inner.addNode("__race__", async (state, cfg) => {
519
- const agentPromises = config.agents.map((agent) => {
520
- const p = agent.skeleton
521
- .invoke({ ...state }, { ...cfg, agentId: agent.id })
522
- .then((result) => ({ id: agent.id, result, error: null }), (err) => ({ id: agent.id, result: null, error: err }));
523
- if (timeoutMs != null) {
524
- return Promise.race([
525
- p,
526
- new Promise((resolve) => setTimeout(() => resolve({ id: agent.id, result: null, error: new Error("timeout") }), timeoutMs)),
527
- ]);
528
- }
529
- return p;
530
- });
531
- // Resolve as soon as the first acceptable result arrives.
532
- const winner = await new Promise((resolve) => {
533
- let remaining = agentPromises.length;
534
- if (remaining === 0) {
535
- resolve(null);
536
- return;
537
- }
538
- for (const p of agentPromises) {
539
- p.then((r) => {
540
- let accepted = false;
541
- try {
542
- accepted = !r.error && accept(r.result);
543
- }
544
- catch {
545
- // accept() threw — treat as not accepted to keep remaining decrement working
546
- }
547
- if (accepted) {
548
- resolve(r);
549
- }
550
- else {
551
- remaining--;
552
- if (remaining === 0)
553
- resolve(null);
554
- }
555
- });
556
- }
557
- });
558
- if (winner) {
559
- return {
560
- agentResults: { [winner.id]: winner.result },
561
- context: { ...(state.context ?? {}), raceWinner: winner.id },
562
- };
563
- }
564
- // No acceptable result
565
- return {
566
- context: {
567
- ...(state.context ?? {}),
568
- raceError: "No agent produced an acceptable result",
569
- },
570
- };
571
- });
572
- swarm.inner.addEdge(START, "__race__");
573
- swarm.inner.addEdge("__race__", END);
574
- return swarm;
113
+ return buildRace(config, new SwarmGraph(config.channels));
575
114
  }
576
115
  // ---- Static factory: dag template ----
577
116
  /**
@@ -579,82 +118,7 @@ export class SwarmGraph {
579
118
  * Agents with no dependencies run in parallel.
580
119
  */
581
120
  static dag(config) {
582
- const agentMap = new Map(config.agents.map((a) => [a.id, a]));
583
- // Validate dependencies
584
- for (const [_node, deps] of Object.entries(config.dependencies)) {
585
- for (const dep of deps) {
586
- if (!agentMap.has(dep)) {
587
- throw new Error(`Dependency "${dep}" not found in agents list.`);
588
- }
589
- }
590
- }
591
- // Cycle detection via topological sort
592
- const visited = new Set();
593
- const visiting = new Set();
594
- const sorted = [];
595
- function visit(id) {
596
- if (visited.has(id))
597
- return;
598
- if (visiting.has(id))
599
- throw new Error(`Cycle detected involving "${id}".`);
600
- visiting.add(id);
601
- for (const dep of config.dependencies[id] ?? []) {
602
- visit(dep);
603
- }
604
- visiting.delete(id);
605
- visited.add(id);
606
- sorted.push(id);
607
- }
608
- for (const agent of config.agents) {
609
- visit(agent.id);
610
- }
611
- // Build a standard swarm with wiring based on topological sort
612
- const swarm = new SwarmGraph(config.channels);
613
- for (const agent of config.agents) {
614
- swarm.addAgent(agent);
615
- }
616
- // Group agents into layers based on dependencies
617
- const deps = config.dependencies;
618
- const _rootIds = config.agents.filter((a) => !deps[a.id]?.length).map((a) => a.id);
619
- const _nonRootIds = config.agents.filter((a) => deps[a.id]?.length).map((a) => a.id);
620
- // For DAG execution: use a single orchestrator node
621
- swarm.inner.addNode("__dag_runner__", async (state, cfg) => {
622
- const results = {};
623
- const completed = new Set();
624
- // Process in topological order
625
- // Group into parallel batches
626
- const remaining = new Set(sorted);
627
- while (remaining.size > 0) {
628
- // Find all nodes whose deps are satisfied
629
- const ready = [];
630
- for (const id of remaining) {
631
- const idDeps = deps[id] ?? [];
632
- if (idDeps.every((d) => completed.has(d))) {
633
- ready.push(id);
634
- }
635
- }
636
- // Execute ready nodes in parallel
637
- const batchResults = await Promise.all(ready.map(async (id) => {
638
- const agent = agentMap.get(id);
639
- const result = await agent.skeleton.invoke({ ...state, agentResults: { ...(state.agentResults ?? {}), ...results } }, { ...cfg, agentId: id });
640
- return { id, result };
641
- }));
642
- for (const { id, result } of batchResults) {
643
- results[id] = result;
644
- completed.add(id);
645
- remaining.delete(id);
646
- }
647
- }
648
- return {
649
- agentResults: { ...(state.agentResults ?? {}), ...results },
650
- };
651
- });
652
- // Wire: START → __dag_runner__ → END
653
- // Remove any edges added by addAgent
654
- swarm.inner.edges = [];
655
- swarm.inner.addEdge(START, "__dag_runner__");
656
- swarm.inner.addEdge("__dag_runner__", END);
657
- return swarm;
121
+ return buildDag(config, new SwarmGraph(config.channels));
658
122
  }
659
123
  // ---- Static factory: pool template ----
660
124
  /**
@@ -662,90 +126,7 @@ export class SwarmGraph {
662
126
  * then reduce all results.
663
127
  */
664
128
  static pool(config) {
665
- const swarm = new SwarmGraph(config.channels);
666
- const poolSize = config.poolSize;
667
- // Create pool copies
668
- const poolIds = [];
669
- for (let i = 0; i < poolSize; i++) {
670
- const id = poolSize === 1 ? config.agent.id : `${config.agent.id}_${i}`;
671
- poolIds.push(id);
672
- swarm.addAgent({ ...config.agent, id });
673
- }
674
- // Orchestrator node: dispatches items to pool agents and reduces
675
- swarm.inner.addNode("__pool_runner__", async (state, cfg) => {
676
- const items = state[config.inputField];
677
- if (!Array.isArray(items) || items.length === 0) {
678
- return config.reducer({});
679
- }
680
- // Semaphore for concurrency control
681
- let running = 0;
682
- const results = {};
683
- const queue = items.map((item, idx) => ({
684
- item,
685
- idx,
686
- targetId: poolIds[idx % poolIds.length],
687
- }));
688
- await new Promise((resolve, _reject) => {
689
- let completed = 0;
690
- const total = queue.length;
691
- function processNext() {
692
- while (running < poolSize && queue.length > 0) {
693
- const work = queue.shift();
694
- // Respect removeAgent() — if the assigned slot was removed, redirect
695
- // to an active pool slot; if none remain, mark the item as failed.
696
- let agentDef = swarm.registry.getDef(work.targetId);
697
- if (!agentDef) {
698
- const activeIds = poolIds.filter((id) => !!swarm.registry.getDef(id));
699
- if (activeIds.length > 0) {
700
- work.targetId = activeIds[work.idx % activeIds.length];
701
- agentDef = swarm.registry.getDef(work.targetId);
702
- }
703
- else {
704
- results[`item_${work.idx}`] = { _error: `Pool slot removed; no active agents remain` };
705
- completed++;
706
- if (completed === total)
707
- resolve();
708
- continue;
709
- }
710
- }
711
- running++;
712
- // Use the full wrapped agentNode (hooks, retries, timeout) stored
713
- // by addAgent() in swarm.inner.nodes rather than raw skeleton.invoke.
714
- const wrappedFn = swarm.inner.nodes.get(work.targetId)?.fn;
715
- const invocation = wrappedFn
716
- ? wrappedFn({ ...state, task: String(work.item) }, { ...cfg, agentId: work.targetId })
717
- : agentDef.skeleton.invoke({ ...state, task: String(work.item) }, { ...cfg, agentId: work.targetId });
718
- invocation.then((result) => {
719
- results[`item_${work.idx}`] = result;
720
- running--;
721
- completed++;
722
- if (completed === total)
723
- resolve();
724
- else
725
- processNext();
726
- }, (err) => {
727
- results[`item_${work.idx}`] = { _error: String(err) };
728
- running--;
729
- completed++;
730
- if (completed === total)
731
- resolve();
732
- else
733
- processNext();
734
- });
735
- }
736
- }
737
- if (total === 0)
738
- resolve();
739
- else
740
- processNext();
741
- });
742
- return config.reducer(results);
743
- });
744
- // Wire: START → __pool_runner__ → END (bypass agent edges)
745
- swarm.inner.edges = [];
746
- swarm.inner.addEdge(START, "__pool_runner__");
747
- swarm.inner.addEdge("__pool_runner__", END);
748
- return swarm;
129
+ return buildPool(config, new SwarmGraph(config.channels));
749
130
  }
750
131
  // ---- Static factory: compose template ----
751
132
  /**
@@ -753,178 +134,13 @@ export class SwarmGraph {
753
134
  * Each stage runs a compiled sub-swarm, passing state through.
754
135
  */
755
136
  static compose(config) {
756
- if (config.stages.length === 0) {
757
- throw new Error("SwarmGraph.compose: stages must contain at least one sub-swarm.");
758
- }
759
- const swarm = new SwarmGraph(config.channels);
760
- const stageIds = config.stages.map((s) => s.id);
761
- for (const stage of config.stages) {
762
- const compiled = stage.swarm.compile();
763
- swarm.inner.addNode(stage.id, async (state, cfg) => {
764
- const stageResult = await compiled.invoke(state, cfg);
765
- return {
766
- ...stageResult,
767
- agentResults: {
768
- ...(state.agentResults ?? {}),
769
- [stage.id]: stageResult,
770
- },
771
- };
772
- });
773
- }
774
- // Wire pipeline: START → stage1 → stage2 → ... → END
775
- swarm.inner.addEdge(START, stageIds[0]);
776
- for (let i = 0; i < stageIds.length - 1; i++) {
777
- swarm.inner.addEdge(stageIds[i], stageIds[i + 1]);
778
- }
779
- swarm.inner.addEdge(stageIds[stageIds.length - 1], END);
780
- return swarm;
137
+ return buildCompose(config, new SwarmGraph(config.channels));
781
138
  }
782
139
  // ---- Agent registration ----
783
140
  addAgent(def) {
784
141
  this.registry.register(def);
785
142
  this.agentIds.add(def.id);
786
- // Wrap the agent's skeleton as a node
787
- // The node executes the agent's skeleton and handles Handoff returns
788
- const agentNode = async (state, config) => {
789
- this.registry.markBusy(def.id);
790
- // Fire onStart hook
791
- await def.hooks?.onStart?.(def.id, state);
792
- const maxRetries = def.maxRetries ?? 2;
793
- const retryDelayMs = def.retryDelayMs ?? 0;
794
- let lastError;
795
- let lastAttempt = 0;
796
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
797
- lastAttempt = attempt;
798
- try {
799
- // Get inbox and filter out already-consumed messages
800
- const consumedIds = (state.context?.__consumedMsgIds ?? []);
801
- const consumedSet = new Set(consumedIds);
802
- const allInbox = getInbox(state.swarmMessages ?? [], def.id);
803
- const inbox = allInbox.filter((m) => !consumedSet.has(m.id));
804
- // Track consumed message IDs
805
- const newConsumedIds = [...consumedIds, ...inbox.map((m) => m.id)];
806
- const agentInput = {
807
- ...state,
808
- context: {
809
- ...(state.context ?? {}),
810
- inbox,
811
- __consumedMsgIds: newConsumedIds,
812
- },
813
- };
814
- let effectiveTimeout = def.timeout;
815
- // Clamp agent timeout to remaining deadline (if set)
816
- const deadlineAbs = state.context?.__deadlineAbsolute;
817
- if (deadlineAbs != null) {
818
- const remaining = deadlineAbs - Date.now();
819
- if (remaining <= 0) {
820
- throw new Error(`Agent "${def.id}" deadline expired`);
821
- }
822
- if (effectiveTimeout == null || remaining < effectiveTimeout) {
823
- effectiveTimeout = remaining;
824
- }
825
- }
826
- const result = await runWithTimeout(() => def.skeleton.invoke(agentInput, {
827
- ...config,
828
- agentId: def.id,
829
- }), effectiveTimeout, () => new Error(`Agent "${def.id}" timed out after ${effectiveTimeout}ms`));
830
- this.registry.markIdle(def.id);
831
- // ---- Handoff detection ----
832
- if (result instanceof Handoff || (result && result.isHandoff)) {
833
- const handoff = result instanceof Handoff
834
- ? result
835
- : new Handoff(result.opts);
836
- // Fire onComplete for handoffs too
837
- await def.hooks?.onComplete?.(def.id, result);
838
- return new Command({
839
- update: {
840
- context: { ...(state.context ?? {}), ...(handoff.context ?? {}) },
841
- handoffHistory: [{
842
- from: def.id,
843
- to: handoff.to,
844
- message: handoff.message,
845
- step: state.supervisorRound ?? 0,
846
- timestamp: Date.now(),
847
- resume: handoff.resume,
848
- }],
849
- currentAgent: handoff.to,
850
- },
851
- goto: handoff.to,
852
- });
853
- }
854
- // Fire onComplete hook
855
- await def.hooks?.onComplete?.(def.id, result);
856
- // ---- Normal result ----
857
- return {
858
- ...result,
859
- context: {
860
- ...(result.context ?? {}),
861
- __consumedMsgIds: newConsumedIds,
862
- },
863
- agentResults: {
864
- ...(state.agentResults ?? {}),
865
- [def.id]: result,
866
- },
867
- handoffHistory: [
868
- {
869
- from: def.id,
870
- to: "__completed__",
871
- message: "Agent completed",
872
- step: state.supervisorRound ?? 0,
873
- timestamp: Date.now(),
874
- },
875
- ],
876
- };
877
- }
878
- catch (err) {
879
- lastError = err;
880
- if (attempt < maxRetries) {
881
- // Backoff delay between retries — clamped to remaining deadline
882
- if (retryDelayMs > 0) {
883
- let delay = retryDelayMs;
884
- const dl = state.context?.__deadlineAbsolute;
885
- if (dl != null) {
886
- const remaining = dl - Date.now();
887
- if (remaining <= 0)
888
- break; // deadline expired, stop retrying
889
- delay = Math.min(delay, remaining);
890
- }
891
- await new Promise((r) => setTimeout(r, delay));
892
- }
893
- continue; // retry
894
- }
895
- }
896
- }
897
- // All retries exhausted — mark error once, then fire onError hook
898
- this.registry.markError(def.id);
899
- await def.hooks?.onError?.(def.id, lastError);
900
- // Keep agent in error status (don't reset to idle)
901
- // Build structured error context
902
- const errStr = String(lastError instanceof Error ? lastError.message : lastError);
903
- const errType = errStr.includes("timeout") ? "timeout" :
904
- errStr.includes("model") ? "model_error" :
905
- errStr.includes("tool") ? "tool_error" :
906
- "unknown";
907
- if (this.hasSupervisor && this.onErrorPolicy !== "throw") {
908
- return new Command({
909
- update: {
910
- context: {
911
- ...(state.context ?? {}),
912
- lastAgentError: {
913
- agent: def.id,
914
- error: errStr,
915
- type: errType,
916
- attempt: lastAttempt,
917
- maxRetries,
918
- },
919
- },
920
- },
921
- goto: this.supervisorNodeName,
922
- });
923
- }
924
- // No supervisor, or onError: "throw" — rethrow
925
- throw lastError;
926
- };
927
- this.inner.addNode(def.id, agentNode);
143
+ this.inner.addNode(def.id, createAgentNode(def, this.registry, this));
928
144
  return this;
929
145
  }
930
146
  // ---- Supervisor ----
@@ -981,7 +197,8 @@ export class SwarmGraph {
981
197
  return issues;
982
198
  // For non-supervised swarms, check that every agent has at least one incoming edge
983
199
  const edgeTargets = new Set();
984
- for (const edge of this.inner.edges) {
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
985
202
  if (edge.type === "static") {
986
203
  edgeTargets.add(edge.to);
987
204
  }
@@ -1025,114 +242,9 @@ export class SwarmGraph {
1025
242
  ...(opts.defaults !== undefined ? { defaults: opts.defaults } : {}),
1026
243
  ...(opts.deadLetterQueue !== undefined ? { deadLetterQueue: opts.deadLetterQueue } : {}),
1027
244
  ...(opts.tracer !== undefined ? { tracer: opts.tracer } : {}),
1028
- });
1029
- const registry = this.registry;
1030
- const inner = this.inner;
1031
- const hasSupervisor = this.hasSupervisor;
1032
- const supervisorNodeName = this.supervisorNodeName;
1033
- // Attach swarm-specific extensions
1034
- const extensions = {
1035
- registry,
1036
- agentStats: () => registry.stats(),
1037
- toMermaid: () => {
1038
- // Read from the live runner's edge map so the diagram reflects
1039
- // agents added/removed via spawnAgent()/removeAgent() after compile.
1040
- const runner = skeleton._runner;
1041
- const edgesBySource = runner?._edgesBySource;
1042
- if (!edgesBySource)
1043
- return inner.toMermaid(); // safe fallback
1044
- const lines = ["graph TD"];
1045
- for (const [from, edges] of edgesBySource) {
1046
- for (const edge of edges) {
1047
- if (edge.type === "static" && edge.to !== undefined) {
1048
- lines.push(` ${from} --> ${edge.to}`);
1049
- }
1050
- else {
1051
- lines.push(` ${from} -->|?| conditional_${from}`);
1052
- }
1053
- }
1054
- }
1055
- return lines.join("\n");
1056
- },
1057
- spawnAgent(def) {
1058
- // spawnAgent requires a supervisor to route the spawned agent back into the graph.
1059
- // In unsupervised topologies there is no return path, so the agent would never execute.
1060
- if (!hasSupervisor) {
1061
- throw new Error(`spawnAgent() requires a supervisor node. ` +
1062
- `Use SwarmGraph.hierarchical() to add a supervisor, or call addAgent() before compile() for static topologies.`);
1063
- }
1064
- // Register in the registry
1065
- registry.register(def);
1066
- // Create the agent node function (same logic as addAgent)
1067
- const agentNode = async (state, config) => {
1068
- registry.markBusy(def.id);
1069
- await def.hooks?.onStart?.(def.id, state);
1070
- const maxRetries = def.maxRetries ?? 2;
1071
- let lastError;
1072
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
1073
- try {
1074
- const result = await def.skeleton.invoke({ ...state, context: { ...(state.context ?? {}), inbox: getInbox(state.swarmMessages ?? [], def.id) } }, { ...config, agentId: def.id });
1075
- registry.markIdle(def.id);
1076
- await def.hooks?.onComplete?.(def.id, result);
1077
- return {
1078
- ...result,
1079
- agentResults: { ...(state.agentResults ?? {}), [def.id]: result },
1080
- };
1081
- }
1082
- catch (err) {
1083
- lastError = err;
1084
- registry.markError(def.id);
1085
- if (attempt < maxRetries)
1086
- continue;
1087
- }
1088
- }
1089
- await def.hooks?.onError?.(def.id, lastError);
1090
- // Keep agent in error status (don't reset to idle)
1091
- if (hasSupervisor) {
1092
- return new Command({
1093
- update: { context: { ...(state.context ?? {}), lastAgentError: { agent: def.id, error: String(lastError) } } },
1094
- goto: supervisorNodeName,
1095
- });
1096
- }
1097
- throw lastError;
1098
- };
1099
- // Add node to the compiled runner's nodes map
1100
- const runner = skeleton._runner;
1101
- if (runner?.nodes) {
1102
- runner.nodes.set(def.id, { name: def.id, fn: agentNode });
1103
- }
1104
- // In supervised swarms, add a conditional return edge so the runner
1105
- // knows to send this agent's output back to the supervisor (or END).
1106
- if (hasSupervisor && runner) {
1107
- const returnEdge = {
1108
- type: "conditional",
1109
- from: def.id,
1110
- condition: (st) => st.done ? END : supervisorNodeName,
1111
- };
1112
- const edgesBySource = runner._edgesBySource;
1113
- const list = edgesBySource.get(def.id) ?? [];
1114
- list.push(returnEdge);
1115
- edgesBySource.set(def.id, list);
1116
- }
1117
- },
1118
- removeAgent(agentId) {
1119
- registry.deregister(agentId);
1120
- const runner = skeleton._runner;
1121
- if (runner?.nodes)
1122
- runner.nodes.delete(agentId);
1123
- // Remove stale edges pointing TO the removed agent so Pregel doesn't try to route to it.
1124
- // Also remove edges FROM the agent (it won't execute, but keeps _edgesBySource clean).
1125
- if (runner?._edgesBySource) {
1126
- const edgesBySource = runner._edgesBySource;
1127
- for (const [from, edges] of edgesBySource) {
1128
- const filtered = edges.filter((e) => !(e.type === "static" && e.to === agentId));
1129
- if (filtered.length !== edges.length)
1130
- edgesBySource.set(from, filtered);
1131
- }
1132
- edgesBySource.delete(agentId);
1133
- }
1134
- },
1135
- };
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);
1136
248
  return Object.assign(skeleton, extensions);
1137
249
  }
1138
250
  }
@@ -1163,4 +275,5 @@ export function quickAgent(id, fn, opts) {
1163
275
  retryDelayMs: opts?.retryDelayMs,
1164
276
  };
1165
277
  }
278
+ export { baseSwarmChannels } from "./config.js";
1166
279
  //# sourceMappingURL=graph.js.map