melchizedek-agents 0.18.0 → 0.20.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 (293) hide show
  1. package/.env.example +12 -0
  2. package/CHANGELOG.md +1001 -0
  3. package/DOCUMENTATION.md +252 -65
  4. package/QUICKSTART.md +9 -6
  5. package/README.md +42 -4
  6. package/config/agents/examples/harness.yaml +3 -1
  7. package/config/agents/examples/model_zoo.yaml +10 -9
  8. package/config/agents/syndicateSchema.yaml +53 -16
  9. package/config/agents/templates/account_memory.yaml +2 -6
  10. package/config/agents/templates/case_desk.yaml +3 -9
  11. package/config/agents/templates/draft_review.yaml +3 -9
  12. package/config/agents/templates/intake_extractor.yaml +1 -3
  13. package/config/agents/templates/research_brief.yaml +3 -9
  14. package/config/agents/templates/research_desk.yaml +3 -9
  15. package/config/agents/templates/review_panel.yaml +4 -12
  16. package/config/agents/templates/support_triage.yaml +4 -12
  17. package/config/agents/templates/systems_operator.yaml +2 -6
  18. package/db/hardening.sql +7 -1
  19. package/db/migrations/0013_tool_credentials.sql +260 -0
  20. package/dist/lib/a2a/app.d.ts +70 -6
  21. package/dist/lib/a2a/app.js +107 -2
  22. package/dist/lib/a2a/executor.d.ts +20 -3
  23. package/dist/lib/a2a/executor.js +66 -7
  24. package/dist/lib/a2a/remoteAgent.d.ts +24 -9
  25. package/dist/lib/a2a/remoteAgent.js +31 -14
  26. package/dist/lib/adkPeer.d.ts +63 -0
  27. package/dist/lib/adkPeer.js +240 -0
  28. package/dist/lib/compile.d.ts +159 -30
  29. package/dist/lib/compile.js +215 -134
  30. package/dist/lib/compileAdk.d.ts +32 -0
  31. package/dist/lib/compileAdk.js +142 -0
  32. package/dist/lib/compileNative.d.ts +111 -0
  33. package/dist/lib/compileNative.js +278 -0
  34. package/dist/lib/config.d.ts +6 -5
  35. package/dist/lib/config.js +6 -5
  36. package/dist/lib/doctor.d.ts +28 -1
  37. package/dist/lib/doctor.js +64 -2
  38. package/dist/lib/index.d.ts +6 -1
  39. package/dist/lib/index.js +7 -1
  40. package/dist/lib/loadSyndicate.d.ts +18 -0
  41. package/dist/lib/memory/erase.d.ts +5 -3
  42. package/dist/lib/memory/erase.js +5 -2
  43. package/dist/lib/memory/namespace.d.ts +8 -1
  44. package/dist/lib/memory/namespace.js +12 -6
  45. package/dist/lib/memory/providers.js +2 -1
  46. package/dist/lib/memory/supabaseMemoryService.d.ts +37 -3
  47. package/dist/lib/memory/supabaseMemoryService.js +42 -2
  48. package/dist/lib/model.d.ts +47 -0
  49. package/dist/lib/model.js +37 -0
  50. package/dist/lib/models/adapterResolver.d.ts +115 -0
  51. package/dist/lib/models/adapterResolver.js +137 -0
  52. package/dist/lib/models/adkGeminiAdapter.d.ts +90 -0
  53. package/dist/lib/models/adkGeminiAdapter.js +369 -0
  54. package/dist/lib/models/adkShim.d.ts +111 -0
  55. package/dist/lib/models/adkShim.js +136 -0
  56. package/dist/lib/models/capabilities.d.ts +3 -0
  57. package/dist/lib/models/capabilities.js +33 -23
  58. package/dist/lib/models/chatCompletionsAdapter.d.ts +279 -0
  59. package/dist/lib/models/chatCompletionsAdapter.js +854 -0
  60. package/dist/lib/models/circuitBreaker.d.ts +42 -0
  61. package/dist/lib/models/circuitBreaker.js +70 -0
  62. package/dist/lib/models/claudeAdapter.d.ts +103 -0
  63. package/dist/lib/models/claudeAdapter.js +669 -0
  64. package/dist/lib/models/claudeLlm.d.ts +39 -62
  65. package/dist/lib/models/claudeLlm.js +45 -350
  66. package/dist/lib/models/claudeModels.d.ts +127 -0
  67. package/dist/lib/models/claudeModels.js +237 -0
  68. package/dist/lib/models/contract.d.ts +395 -0
  69. package/dist/lib/models/contract.js +39 -0
  70. package/dist/lib/models/endpoints.js +2 -1
  71. package/dist/lib/models/errorResponse.d.ts +50 -0
  72. package/dist/lib/models/errorResponse.js +48 -0
  73. package/dist/lib/models/fallback.d.ts +18 -11
  74. package/dist/lib/models/fallback.js +54 -43
  75. package/dist/lib/models/fallbackAdapter.d.ts +55 -0
  76. package/dist/lib/models/fallbackAdapter.js +62 -0
  77. package/dist/lib/models/gateway.js +2 -1
  78. package/dist/lib/models/gatewayAdapter.d.ts +51 -0
  79. package/dist/lib/models/gatewayAdapter.js +81 -0
  80. package/dist/lib/models/gatewayLlm.d.ts +12 -33
  81. package/dist/lib/models/gatewayLlm.js +16 -77
  82. package/dist/lib/models/geminiAdapter.d.ts +125 -0
  83. package/dist/lib/models/geminiAdapter.js +854 -0
  84. package/dist/lib/models/geminiState.d.ts +46 -0
  85. package/dist/lib/models/geminiState.js +51 -0
  86. package/dist/lib/models/genaiMapping.d.ts +283 -0
  87. package/dist/lib/models/genaiMapping.js +965 -0
  88. package/dist/lib/models/gptAdapter.d.ts +240 -0
  89. package/dist/lib/models/gptAdapter.js +804 -0
  90. package/dist/lib/models/gptLlm.d.ts +63 -123
  91. package/dist/lib/models/gptLlm.js +89 -603
  92. package/dist/lib/models/grokAdapter.d.ts +86 -0
  93. package/dist/lib/models/grokAdapter.js +142 -0
  94. package/dist/lib/models/grokLlm.d.ts +22 -45
  95. package/dist/lib/models/grokLlm.js +24 -69
  96. package/dist/lib/models/kimiAdapter.d.ts +157 -0
  97. package/dist/lib/models/kimiAdapter.js +203 -0
  98. package/dist/lib/models/kimiLlm.d.ts +15 -107
  99. package/dist/lib/models/kimiLlm.js +19 -155
  100. package/dist/lib/models/ollamaAdapter.d.ts +125 -0
  101. package/dist/lib/models/ollamaAdapter.js +155 -0
  102. package/dist/lib/models/ollamaLlm.d.ts +11 -107
  103. package/dist/lib/models/ollamaLlm.js +15 -153
  104. package/dist/lib/models/openAiCompatibleLlm.d.ts +73 -216
  105. package/dist/lib/models/openAiCompatibleLlm.js +109 -671
  106. package/dist/lib/models/providerState.d.ts +63 -0
  107. package/dist/lib/models/providerState.js +61 -0
  108. package/dist/lib/models/reasoning.d.ts +26 -0
  109. package/dist/lib/models/reasoning.js +59 -0
  110. package/dist/lib/models/registry.d.ts +62 -35
  111. package/dist/lib/models/registry.js +100 -124
  112. package/dist/lib/models/retry.d.ts +2 -1
  113. package/dist/lib/models/retry.js +3 -2
  114. package/dist/lib/models/schemaNormalize.d.ts +98 -0
  115. package/dist/lib/models/schemaNormalize.js +321 -1
  116. package/dist/lib/models/tracedGemini.d.ts +41 -0
  117. package/dist/lib/models/tracedGemini.js +98 -0
  118. package/dist/lib/models/urls.d.ts +11 -0
  119. package/dist/lib/models/urls.js +16 -0
  120. package/dist/lib/net/redirects.d.ts +21 -2
  121. package/dist/lib/net/redirects.js +11 -34
  122. package/dist/lib/observability/audit.d.ts +5 -1
  123. package/dist/lib/observability/lineage.d.ts +14 -0
  124. package/dist/lib/observability/lineage.js +33 -0
  125. package/dist/lib/observability/supabaseSpanExporter.d.ts +10 -4
  126. package/dist/lib/observability/supabaseSpanExporter.js +17 -9
  127. package/dist/lib/observability/tracer.d.ts +26 -8
  128. package/dist/lib/observability/tracer.js +43 -18
  129. package/dist/lib/runtime/adkMemoryBridge.d.ts +46 -0
  130. package/dist/lib/runtime/adkMemoryBridge.js +68 -0
  131. package/dist/lib/runtime/adkSessionBridge.d.ts +78 -0
  132. package/dist/lib/runtime/adkSessionBridge.js +176 -0
  133. package/dist/lib/runtime/approvals.d.ts +5 -0
  134. package/dist/lib/runtime/approvals.js +39 -8
  135. package/dist/lib/runtime/credentials.d.ts +61 -0
  136. package/dist/lib/runtime/credentials.js +67 -0
  137. package/dist/lib/runtime/events.d.ts +229 -0
  138. package/dist/lib/runtime/events.js +274 -0
  139. package/dist/lib/runtime/logging.d.ts +32 -0
  140. package/dist/lib/runtime/logging.js +55 -0
  141. package/dist/lib/runtime/memoryService.d.ts +72 -0
  142. package/dist/lib/runtime/memoryService.js +25 -0
  143. package/dist/lib/runtime/native/agentLoop.d.ts +209 -0
  144. package/dist/lib/runtime/native/agentLoop.js +705 -0
  145. package/dist/lib/runtime/native/compaction.d.ts +103 -0
  146. package/dist/lib/runtime/native/compaction.js +191 -0
  147. package/dist/lib/runtime/native/delegate.d.ts +151 -0
  148. package/dist/lib/runtime/native/delegate.js +256 -0
  149. package/dist/lib/runtime/native/history.d.ts +83 -0
  150. package/dist/lib/runtime/native/history.js +312 -0
  151. package/dist/lib/runtime/native/interrupts.d.ts +129 -0
  152. package/dist/lib/runtime/native/interrupts.js +310 -0
  153. package/dist/lib/runtime/native/request.d.ts +215 -0
  154. package/dist/lib/runtime/native/request.js +585 -0
  155. package/dist/lib/runtime/native/selfCorrection.d.ts +164 -0
  156. package/dist/lib/runtime/native/selfCorrection.js +327 -0
  157. package/dist/lib/runtime/native/step.d.ts +156 -0
  158. package/dist/lib/runtime/native/step.js +263 -0
  159. package/dist/lib/runtime/native/taskMode.d.ts +64 -0
  160. package/dist/lib/runtime/native/taskMode.js +126 -0
  161. package/dist/lib/runtime/native/telemetry.d.ts +75 -0
  162. package/dist/lib/runtime/native/telemetry.js +299 -0
  163. package/dist/lib/runtime/native/tempState.d.ts +31 -0
  164. package/dist/lib/runtime/native/tempState.js +56 -0
  165. package/dist/lib/runtime/nativeTurn.d.ts +86 -0
  166. package/dist/lib/runtime/nativeTurn.js +107 -0
  167. package/dist/lib/runtime/questions.d.ts +29 -7
  168. package/dist/lib/runtime/questions.js +47 -32
  169. package/dist/lib/runtime/runtimeFlag.d.ts +38 -0
  170. package/dist/lib/runtime/runtimeFlag.js +58 -0
  171. package/dist/lib/runtime/sessions.d.ts +166 -0
  172. package/dist/lib/runtime/sessions.js +201 -0
  173. package/dist/lib/runtime/syndicateTurn.d.ts +46 -5
  174. package/dist/lib/runtime/syndicateTurn.js +233 -36
  175. package/dist/lib/runtime/valueDepth.d.ts +23 -0
  176. package/dist/lib/runtime/valueDepth.js +41 -0
  177. package/dist/lib/session/supabaseSessionService.d.ts +65 -18
  178. package/dist/lib/session/supabaseSessionService.js +148 -105
  179. package/dist/lib/session/transcript.d.ts +69 -23
  180. package/dist/lib/session/transcript.js +105 -29
  181. package/dist/lib/skills.js +33 -7
  182. package/dist/lib/storage/postgres/credentialStore.d.ts +20 -0
  183. package/dist/lib/storage/postgres/credentialStore.js +82 -0
  184. package/dist/lib/storage/postgres/index.d.ts +12 -0
  185. package/dist/lib/storage/postgres/index.js +6 -1
  186. package/dist/lib/storage/postgres/sessionService.d.ts +44 -16
  187. package/dist/lib/storage/postgres/sessionService.js +128 -66
  188. package/dist/lib/syndicateSchema.d.ts +49 -0
  189. package/dist/lib/syndicateSchema.js +99 -9
  190. package/dist/lib/toolRegistry.d.ts +26 -2
  191. package/dist/lib/toolRegistry.js +73 -17
  192. package/dist/lib/tools/adkTool.d.ts +81 -0
  193. package/dist/lib/tools/adkTool.js +268 -0
  194. package/dist/lib/tools/auth.d.ts +118 -0
  195. package/dist/lib/tools/auth.js +89 -0
  196. package/dist/lib/tools/collectionsSearchTool.d.ts +8 -7
  197. package/dist/lib/tools/collectionsSearchTool.js +12 -27
  198. package/dist/lib/tools/credentialCipher.d.ts +58 -0
  199. package/dist/lib/tools/credentialCipher.js +116 -0
  200. package/dist/lib/tools/credentialStore.d.ts +77 -0
  201. package/dist/lib/tools/credentialStore.js +246 -0
  202. package/dist/lib/tools/examples.d.ts +43 -0
  203. package/dist/lib/tools/examples.js +62 -0
  204. package/dist/lib/tools/generateImageTool.d.ts +15 -6
  205. package/dist/lib/tools/generateImageTool.js +19 -29
  206. package/dist/lib/tools/inspectImageTool.d.ts +9 -6
  207. package/dist/lib/tools/inspectImageTool.js +13 -17
  208. package/dist/lib/tools/mcpToolFactory.d.ts +42 -1
  209. package/dist/lib/tools/mcpToolFactory.js +46 -47
  210. package/dist/lib/tools/memoryTools.d.ts +61 -0
  211. package/dist/lib/tools/memoryTools.js +119 -0
  212. package/dist/lib/tools/nativeTools.d.ts +38 -0
  213. package/dist/lib/tools/nativeTools.js +43 -0
  214. package/dist/lib/tools/oauthConsent.d.ts +181 -0
  215. package/dist/lib/tools/oauthConsent.js +330 -0
  216. package/dist/lib/tools/openapi/call.d.ts +108 -0
  217. package/dist/lib/tools/openapi/call.js +295 -0
  218. package/dist/lib/tools/openapi/parse.d.ts +147 -0
  219. package/dist/lib/tools/openapi/parse.js +570 -0
  220. package/dist/lib/tools/openapiTools.d.ts +25 -17
  221. package/dist/lib/tools/openapiTools.js +124 -121
  222. package/dist/lib/tools/scienceTools.d.ts +14 -14
  223. package/dist/lib/tools/skillToolset.d.ts +58 -87
  224. package/dist/lib/tools/skillToolset.js +61 -142
  225. package/dist/lib/tools/skills/env.d.ts +58 -0
  226. package/dist/lib/tools/skills/env.js +165 -0
  227. package/dist/lib/tools/skills/executor.d.ts +114 -0
  228. package/dist/lib/tools/skills/executor.js +331 -0
  229. package/dist/lib/tools/skills/frontmatter.d.ts +79 -0
  230. package/dist/lib/tools/skills/frontmatter.js +215 -0
  231. package/dist/lib/tools/skills/loader.d.ts +91 -0
  232. package/dist/lib/tools/skills/loader.js +257 -0
  233. package/dist/lib/tools/skills/tools.d.ts +187 -0
  234. package/dist/lib/tools/skills/tools.js +386 -0
  235. package/dist/lib/tools/taskTools.d.ts +15 -15
  236. package/dist/lib/tools/tool.d.ts +354 -0
  237. package/dist/lib/tools/tool.js +304 -0
  238. package/dist/lib/tools/toolContract.d.ts +89 -33
  239. package/dist/lib/tools/toolContract.js +104 -82
  240. package/dist/lib/tools/urlContextTool.d.ts +4 -1
  241. package/dist/lib/tools/urlContextTool.js +6 -2
  242. package/dist/lib/tools/webExtractTool.d.ts +3 -3
  243. package/dist/lib/tools/webExtractTool.js +2 -1
  244. package/dist/lib/tools/webSearchTool.d.ts +7 -10
  245. package/dist/lib/tools/webSearchTool.js +15 -60
  246. package/dist/lib/tools/wikiTools.d.ts +19 -19
  247. package/dist/lib/tools/wikiTools.js +13 -9
  248. package/dist/lib/tools/xApiSearchTool.d.ts +3 -3
  249. package/dist/lib/tools/xApiSearchTool.js +2 -1
  250. package/dist/lib/tools/xSearchTool.d.ts +8 -10
  251. package/dist/lib/tools/xSearchTool.js +12 -63
  252. package/dist/lib/tools/xaiSearchParams.d.ts +41 -0
  253. package/dist/lib/tools/xaiSearchParams.js +126 -0
  254. package/dist/lib/wiki/agentRun.d.ts +12 -0
  255. package/dist/lib/wiki/agentRun.js +74 -35
  256. package/dist/lib/wiki/entities.d.ts +12 -0
  257. package/dist/lib/wiki/entities.js +45 -3
  258. package/dist/lib/workflow/agentNode.d.ts +153 -0
  259. package/dist/lib/workflow/agentNode.js +365 -0
  260. package/dist/lib/workflow/graph.d.ts +178 -0
  261. package/dist/lib/workflow/graph.js +468 -0
  262. package/dist/lib/workflow/nodeEvents.d.ts +50 -0
  263. package/dist/lib/workflow/nodeEvents.js +101 -0
  264. package/dist/lib/workflow/pause.d.ts +122 -0
  265. package/dist/lib/workflow/pause.js +180 -0
  266. package/dist/lib/workflow/resume.d.ts +166 -0
  267. package/dist/lib/workflow/resume.js +441 -0
  268. package/dist/lib/workflow/route.d.ts +58 -0
  269. package/dist/lib/workflow/route.js +69 -0
  270. package/dist/lib/workflow/scheduler.d.ts +418 -0
  271. package/dist/lib/workflow/scheduler.js +756 -0
  272. package/dist/lib/workflow/toolNode.d.ts +127 -0
  273. package/dist/lib/workflow/toolNode.js +236 -0
  274. package/dist/lib/workflow/turn.d.ts +118 -0
  275. package/dist/lib/workflow/turn.js +216 -0
  276. package/dist/lib/workflow.d.ts +33 -11
  277. package/dist/lib/workflow.js +49 -27
  278. package/dist/lib/workflowConfig.d.ts +7 -2
  279. package/dist/lib/workflowConfig.js +7 -12
  280. package/dist/scripts/a2a_server.js +4 -3
  281. package/dist/scripts/assistant_worker.d.ts +2 -1
  282. package/dist/scripts/assistant_worker.js +9 -4
  283. package/dist/scripts/doctor.d.ts +3 -1
  284. package/dist/scripts/doctor.js +4 -2
  285. package/dist/scripts/syndicate_chat.js +19 -10
  286. package/package.json +9 -3
  287. package/skills/briefs/melchizedek-author.md +3 -2
  288. package/skills/briefs/melchizedek-models.md +13 -9
  289. package/skills/briefs/melchizedek.md +3 -3
  290. package/skills/melchizedek/SKILL.md +2 -1
  291. package/skills/melchizedek-author/SKILL.md +2 -1
  292. package/skills/melchizedek-author/assets/minimal.yaml +2 -0
  293. package/skills/melchizedek-models/SKILL.md +23 -6
@@ -0,0 +1,756 @@
1
+ /**
2
+ * lib/workflow/scheduler.ts — the engine's own walk of a workflow graph.
3
+ *
4
+ * `runWorkflowGraph(graph, options)` runs the `WorkflowGraph` that
5
+ * `buildWorkflowGraph` (lib/workflow/graph.ts) builds: a node runs when a
6
+ * predecessor's completion triggers it, a node with several successors fans
7
+ * out, a `join` waits until every predecessor has completed, a `map` runs its
8
+ * agent once per item under `maxParallel`, and each node's output is the
9
+ * input its successors receive. It imports nothing from ADK.
10
+ *
11
+ * What a node DOES is not this module's business. Agent, tool and ask_user
12
+ * nodes, and each item of a map, are run by the `runNode` function the caller
13
+ * passes in (WS4-3, WS4-5 and WS4-4a supply them). The scheduler runs the
14
+ * kinds that are pure graph mechanics itself: the start node seeds the walk,
15
+ * a `join` outputs `{ <predecessor>: <output> }`, a `route` step re-emits its
16
+ * input with the route `routeOf` reads from it, and a `map` is the worker
17
+ * pool around its items.
18
+ *
19
+ * ── The same walk as ADK's Workflow ──────────────────────────────────────
20
+ * The loop is ADK 2.2's (`Workflow.runLoop`, workflow/workflow.js), step for
21
+ * step, so a graph completes in the order ADK records for it
22
+ * (tests/workflowScheduler.test.ts runs both and compares):
23
+ *
24
+ * - Triggers are buffered per node, in the order they were pushed. Each
25
+ * pass starts every buffered node that is not already running, in the
26
+ * buffer's order, until `maxConcurrency` nodes are pending.
27
+ * - The loop then waits for the first pending node to settle, by
28
+ * `Promise.race` over the pending runs in the order they started, so of
29
+ * two runs already settled the one started first is handled first.
30
+ * - A completed node pushes one trigger to each successor its route
31
+ * selects (`always` edges, the edges keyed to its route compared as
32
+ * strings in ADK's spelling of the key, else the `default` edge). A join
33
+ * is triggered only once every predecessor has COMPLETED, with their
34
+ * outputs keyed in edge order. A node triggered while it runs runs again
35
+ * afterwards, once per trigger, as ADK does: only a join waits for all.
36
+ * - Branches and node paths are ADK's (`Planner@1`, `Six.Fan.Agent@0`), so
37
+ * the events a node runner writes can carry the values ADK writes.
38
+ * - The workflow's output is the one terminal node's output; two terminal
39
+ * outputs fail the run with ADK's message.
40
+ *
41
+ * ── The controls (ADK's node_runner.js, ADR 0089) ─────────────────────────
42
+ * Every node run goes through `executeNode`, and every attempt of it through
43
+ * `withControls`, which is ADK's `runChildNode` loop:
44
+ *
45
+ * - **Timeout.** A node with `timeout` races each attempt against a timer;
46
+ * the runner's signal aborts when it fires and the attempt fails with
47
+ * `NodeTimeoutError`. Without a timeout the attempt is awaited, as ADK
48
+ * awaits it: the runner gets the signal and must settle when it aborts.
49
+ * - **Retry.** A failed attempt is retried while the node's `retry` allows
50
+ * (`max_attempts`, default 5; an `exceptions` list of error names when
51
+ * one is given), after ADK's backoff with its jitter. An abort and a map
52
+ * item's failure are never retried; an abort cuts a backoff short.
53
+ * - **Node errors.** A runner reports an error by returning `error`: the
54
+ * walk emits it (`node_error`, source `node`) and, when the result has no
55
+ * output and no route, the attempt fails with `NodeReportedError`. A node
56
+ * that gives up fails the walk; the walk emits that error once (source
57
+ * `workflow`) unless the node already reported it or the walk was
58
+ * aborted, then aborts the other runs, lets them settle and rethrows the
59
+ * error unchanged. Every `node_error` is collected in `nodeErrors`, in
60
+ * the shape runSyndicateTurn's drain collects ADK's error events.
61
+ * - **Abort and deadline.** The walk's signal is the caller's, else the
62
+ * current turn's (lib/runtime/turnControl.ts), so a cancel or a deadline
63
+ * reaches every run. Once it fires no node starts, and the walk rejects
64
+ * with `InvocationAbortedError` when nothing is left running.
65
+ * - **max_concurrency** counts every pending run, a retrying one included.
66
+ *
67
+ * A map item runs under its agent's own modifiers (`nodes.<agent>`), and an
68
+ * item that gives up fails the map with `DynamicNodeFailError`, as ADK's
69
+ * ParallelWorker does; the map entry's own `retry` and `timeout` are not
70
+ * applied, because ADK's compile does not apply them.
71
+ *
72
+ * ── Interrupts (ADK's handleCompletion and finalize, ADR 0092) ───────────
73
+ * A runner that pauses its node on a person returns `interruptIds` (the ids
74
+ * of the input requests its events carry; lib/workflow/pause.ts raises them
75
+ * for an ask_user node). The node then WAITS, as ADK's does: it is not
76
+ * completed, records no output, triggers no successor, and is not started
77
+ * again by a later trigger in the same walk. The rest of the graph runs on.
78
+ * A walk that ends with a node waiting resolves with every open id in
79
+ * `interruptIds`, in the order the nodes paused, and no output (ADK skips
80
+ * the terminal check). A reported error with an interrupt is not the node's
81
+ * failure. A map item's interrupts are not carried (an agent node's own
82
+ * pause does not run here yet).
83
+ *
84
+ * ── Resume (ADK's startNodeTask on rehydrated state, ADR 0094) ───────────
85
+ * A walk given `resume` (lib/workflow/resume.ts builds it from the stored
86
+ * events) starts from START as ADK's does, and each node's first activation
87
+ * takes that node's next prior run:
88
+ *
89
+ * - a prior run with an output or a route and no open interrupt is done:
90
+ * the node completes at once with that output, route and branch, emits
91
+ * `node_resumed` (not `node_start` or `node_end`; ADK writes no event
92
+ * for it) and triggers its successors as a completion does;
93
+ * - a paused prior run of a node that does not rerun on resume
94
+ * (`rerunsOnResume`) completes with its answers, the same way;
95
+ * - any other node runs, a paused one on the input it recorded, with the
96
+ * answers in `NodeRun.resumeInputs` (none on a repeat activation in the
97
+ * same walk). Neither shortcut counts as a run of the node.
98
+ *
99
+ * A task-mode node that waits for its output (WS4-3) is a later ticket.
100
+ *
101
+ * ── Tracing (ADR 0095) ───────────────────────────────────────────────────
102
+ * The scheduler opens no span. A caller that passes `traceNode` has each
103
+ * node run (every attempt and backoff inside it) and each map item's run
104
+ * wrapped, as ADK's node runner opens `execute_node` around them; the
105
+ * native turn (lib/workflow/turn.ts) passes the engine's `node.execute`
106
+ * span, so an agent node's own spans nest under it.
107
+ */
108
+ import { routeOf } from '../workflowConfig.js';
109
+ import { currentTurnSignal } from '../runtime/turnControl.js';
110
+ import { createTurnEvent } from '../runtime/events.js';
111
+ import { START_NODE, adkRouteString } from './graph.js';
112
+ import { isFastForwardable, rerunsOnResume } from './resume.js';
113
+ /** ADK's ParallelWorker pool size when `max_parallel` is not set. */
114
+ export const DEFAULT_MAX_PARALLEL = 8;
115
+ // ── Errors (ADK's workflow/errors.js; the names are ADK's, so errorType matches) ─
116
+ /** A node ran past its `timeout`. */
117
+ export class NodeTimeoutError extends Error {
118
+ nodeName;
119
+ timeout;
120
+ constructor(options) {
121
+ super(`Node '${options.nodeName}' timed out after ${options.timeout} seconds.`);
122
+ this.name = 'NodeTimeoutError';
123
+ this.nodeName = options.nodeName;
124
+ this.timeout = options.timeout;
125
+ }
126
+ }
127
+ /** A node reported an error and produced no output and no route. */
128
+ export class NodeReportedError extends Error {
129
+ code;
130
+ nodeName;
131
+ constructor(options) {
132
+ const code = options.errorCode ?? 'UNKNOWN_ERROR';
133
+ const detail = options.errorMessage ?? code;
134
+ super(`Node '${options.nodeName}' failed: ${code === 'UNKNOWN_ERROR' ? detail : `${code}: ${detail}`}`);
135
+ this.name = 'NodeReportedError';
136
+ this.code = code;
137
+ this.nodeName = options.nodeName;
138
+ }
139
+ }
140
+ /** The walk, or one node's run, stopped because its signal fired. */
141
+ export class InvocationAbortedError extends Error {
142
+ constructor(message = 'Invocation aborted.', options) {
143
+ super(message, options);
144
+ this.name = 'InvocationAbortedError';
145
+ }
146
+ }
147
+ /** A map item gave up; the map fails with this. */
148
+ export class DynamicNodeFailError extends Error {
149
+ error;
150
+ errorNodePath;
151
+ constructor(options) {
152
+ super(options.message);
153
+ this.name = 'DynamicNodeFailError';
154
+ this.error = options.error;
155
+ this.errorNodePath = options.errorNodePath;
156
+ }
157
+ }
158
+ const isNamed = (error, name) => error instanceof Error && error.name === name;
159
+ const isWaiting = (state) => state?.status === 'waiting' && state.interrupts.length > 0;
160
+ // ── Retry (ADK's retry_utils.js) ─────────────────────────────────────────────
161
+ /** ADK's defaults when a `retry` block leaves a field out. */
162
+ export const RETRY_DEFAULTS = { maxAttempts: 5, initialDelay: 1, maxDelay: 60, backoffFactor: 2, jitter: 1 };
163
+ /** The names an error answers to: its class's and its `name`, as ADK matches `exceptions`. */
164
+ export function errorNames(error) {
165
+ if (error instanceof Error)
166
+ return [...new Set([error.constructor?.name, error.name].filter((n) => !!n))];
167
+ if (typeof error === 'object' && error !== null)
168
+ return [error.constructor.name];
169
+ return [typeof error];
170
+ }
171
+ /** ADK's errorType: the class name, or the assigned name of a plain Error. */
172
+ export function errorName(error) {
173
+ const [className, assigned] = errorNames(error);
174
+ return className === 'Error' && assigned ? assigned : className;
175
+ }
176
+ /** Whether a node that has made `attempts` attempts and failed with `error` runs again. */
177
+ export function shouldRetry(error, retry, attempts) {
178
+ if (attempts >= (retry.max_attempts ?? RETRY_DEFAULTS.maxAttempts))
179
+ return false;
180
+ const exceptions = retry.exceptions;
181
+ return exceptions === undefined || errorNames(error).some((name) => exceptions.includes(name));
182
+ }
183
+ /** Seconds to wait before the attempt after `attempts`: exponential backoff, capped, with ADK's jitter. */
184
+ export function retryDelaySeconds(retry, attempts, random = Math.random) {
185
+ const initialDelay = retry.initial_delay ?? RETRY_DEFAULTS.initialDelay;
186
+ const maxDelay = retry.max_delay ?? RETRY_DEFAULTS.maxDelay;
187
+ const backoffFactor = retry.backoff_factor ?? RETRY_DEFAULTS.backoffFactor;
188
+ const jitter = retry.jitter ?? RETRY_DEFAULTS.jitter;
189
+ let delay = initialDelay * Math.pow(backoffFactor, Math.max(0, (attempts || 1) - 1));
190
+ if (jitter > 0) {
191
+ delay = Math.min(delay, maxDelay / (1 + jitter));
192
+ const span = jitter * delay;
193
+ delay = Math.max(0, delay - span + random() * (2 * span));
194
+ }
195
+ return Math.min(delay, maxDelay);
196
+ }
197
+ // ── Branches (ADK's branch_path.js) ──────────────────────────────────────────
198
+ const segmentsOf = (branch) => (branch ? branch.split('.') : []);
199
+ function subBranch(base, name, runId) {
200
+ return [...segmentsOf(base), `${name}@${runId}`].join('.');
201
+ }
202
+ function commonPrefix(branches) {
203
+ if (branches.length === 0)
204
+ return '';
205
+ const all = branches.map(segmentsOf);
206
+ const common = [];
207
+ const min = Math.min(...all.map((s) => s.length));
208
+ for (let i = 0; i < min; i++) {
209
+ const seg = all[0][i];
210
+ if (all.every((s) => s[i] === seg))
211
+ common.push(seg);
212
+ else
213
+ break;
214
+ }
215
+ return common.join('.');
216
+ }
217
+ // ── The walk ─────────────────────────────────────────────────────────────────
218
+ /**
219
+ * Run a workflow graph to its end. Resolves with the workflow's output, every
220
+ * node's output and the completion order; rejects with the first node error,
221
+ * after every run still pending has settled.
222
+ */
223
+ export async function runWorkflowGraph(graph, options) {
224
+ const workflowPath = options.nodePath ?? graph.name;
225
+ const parentBranch = options.branch;
226
+ const onEvent = options.onEvent ?? (() => { });
227
+ const nodeErrors = [];
228
+ const emit = (event) => {
229
+ if (event.type === 'node_error')
230
+ nodeErrors.push({ node: event.node, code: event.code, message: event.message });
231
+ onEvent(event);
232
+ };
233
+ const walk = { runNode: options.runNode, traceNode: options.traceNode ?? ((_node, run) => run()), emit, claimed: new WeakSet() };
234
+ // The caller's signal, else the turn's: a cancel or the turn's deadline stops the walk.
235
+ const parentSignal = options.signal ?? currentTurnSignal();
236
+ const controller = new AbortController();
237
+ /** True once the walk was stopped from outside (not by a node's failure). */
238
+ let stopped = false;
239
+ const onParentAbort = () => {
240
+ stopped = true;
241
+ controller.abort(parentSignal?.reason);
242
+ };
243
+ if (parentSignal?.aborted)
244
+ onParentAbort();
245
+ else
246
+ parentSignal?.addEventListener('abort', onParentAbort, { once: true });
247
+ const nodes = new Map();
248
+ const outputs = new Map();
249
+ const branches = new Map();
250
+ const triggers = new Map();
251
+ const pending = new Map();
252
+ const order = [];
253
+ const nodeOf = (name) => {
254
+ const node = graph.nodes.get(name);
255
+ if (!node)
256
+ throw new Error(`Node ${name} not found in graph.`);
257
+ return node;
258
+ };
259
+ const pushTrigger = (name, trigger) => {
260
+ const buffer = triggers.get(name);
261
+ if (buffer)
262
+ buffer.push(trigger);
263
+ else
264
+ triggers.set(name, [trigger]);
265
+ };
266
+ const popTrigger = (name) => {
267
+ const buffer = triggers.get(name);
268
+ if (!buffer || buffer.length === 0)
269
+ return undefined;
270
+ const trigger = buffer.shift();
271
+ if (buffer.length === 0)
272
+ triggers.delete(name);
273
+ return trigger;
274
+ };
275
+ const atConcurrencyLimit = () => graph.maxConcurrency !== undefined && pending.size >= graph.maxConcurrency;
276
+ // ADK's startNodeTask on a resumed walk: each node's first activation takes its next prior run.
277
+ const activated = new Set();
278
+ const resumeStart = (name, node) => {
279
+ const resume = options.resume;
280
+ if (!resume)
281
+ return {};
282
+ const repeat = activated.has(name);
283
+ activated.add(name);
284
+ const prior = resume.priorRuns.get(name)?.shift();
285
+ if (prior && isFastForwardable(prior)) {
286
+ return { shortcut: { name, resumed: 'stored', result: { output: prior.output, route: prior.route, branch: prior.branch ?? parentBranch } } };
287
+ }
288
+ if (prior && !rerunsOnResume(node) && prior.output === undefined && prior.interruptIds.size > 0) {
289
+ const values = [...prior.interruptIds].map((id) => resume.resumeInputs[id]);
290
+ if (values.every((v) => v !== undefined)) {
291
+ return { shortcut: { name, resumed: 'answers', result: { output: values.length === 1 ? values[0] : values, branch: prior.branch ?? parentBranch } } };
292
+ }
293
+ }
294
+ const resuming = prior !== undefined && prior.interruptIds.size > 0 && prior.input !== undefined;
295
+ const waited = prior !== undefined && prior.interruptIds.size > 0 && !repeat;
296
+ return {
297
+ ...(resuming ? { input: prior.input } : {}),
298
+ resumeInputs: repeat ? {} : resume.resumeInputs,
299
+ ...(waited ? { resumedInterruptIds: [...prior.interruptIds] } : {}),
300
+ };
301
+ };
302
+ // Seed: one trigger per START edge, each on its own branch when there are several.
303
+ const startEdges = graph.edges.filter((e) => e.from === START_NODE);
304
+ for (const edge of startEdges)
305
+ pushTrigger(edge.to, { input: options.input, useSubBranch: startEdges.length > 1 });
306
+ const scheduleReadyNodes = () => {
307
+ // A stopped walk starts nothing; the runs in flight have their signal.
308
+ if (controller.signal.aborted)
309
+ return;
310
+ for (const name of [...triggers.keys()]) {
311
+ if (pending.has(name))
312
+ continue;
313
+ if (nodes.get(name)?.status === 'running')
314
+ continue;
315
+ // ADK: a node waiting on a person is not started again until it is answered.
316
+ if (isWaiting(nodes.get(name)))
317
+ continue;
318
+ if (atConcurrencyLimit())
319
+ break;
320
+ const trigger = popTrigger(name);
321
+ if (!trigger)
322
+ continue;
323
+ const state = { status: 'running', runCounter: nodes.get(name)?.runCounter ?? 0, attempts: { count: 1 }, interrupts: [] };
324
+ nodes.set(name, state);
325
+ const node = nodeOf(name);
326
+ const start = resumeStart(name, node);
327
+ if (start.shortcut) {
328
+ pending.set(name, Promise.resolve(start.shortcut));
329
+ continue;
330
+ }
331
+ state.runCounter += 1;
332
+ const runId = String(state.runCounter);
333
+ const branch = trigger.branch !== undefined ? trigger.branch : trigger.useSubBranch ? subBranch(parentBranch, name, runId) : parentBranch;
334
+ const path = `${workflowPath}.${name}`;
335
+ const input = start.input !== undefined ? start.input : trigger.input;
336
+ emit({ type: 'node_start', node: name, kind: node.kind, runId, path, branch, input });
337
+ const ctx = {
338
+ input,
339
+ runId,
340
+ path,
341
+ branch,
342
+ signal: controller.signal,
343
+ ...(start.resumeInputs ? { resumeInputs: start.resumeInputs } : {}),
344
+ ...(start.resumedInterruptIds ? { resumedInterruptIds: start.resumedInterruptIds } : {}),
345
+ };
346
+ const traced = { name, kind: node.kind, path, runId, attempts: state.attempts };
347
+ const run = walk.traceNode(traced, () => executeNode(node, ctx, walk, state.attempts)).then((result) => ({ name, result: { ...result, branch } }), (error) => ({ name, error }));
348
+ pending.set(name, run);
349
+ }
350
+ };
351
+ const bufferDownstreamTriggers = (name, result, branch) => {
352
+ const next = nextNodes(graph, name, result.route);
353
+ const useSubBranch = next.length > 1;
354
+ for (const target of next) {
355
+ if (nodeOf(target).kind !== 'join') {
356
+ pushTrigger(target, { input: result.output, useSubBranch, ...(branch !== undefined ? { branch } : {}) });
357
+ continue;
358
+ }
359
+ const predecessors = [...new Set(graph.edges.filter((e) => e.to === target).map((e) => e.from))];
360
+ if (!predecessors.every((p) => nodes.get(p)?.status === 'completed'))
361
+ continue;
362
+ const joined = {};
363
+ for (const p of predecessors)
364
+ joined[p] = outputs.get(p);
365
+ const common = commonPrefix(predecessors.map((p) => branches.get(p) ?? ''));
366
+ pushTrigger(target, { input: joined, useSubBranch: false, ...(common ? { branch: common } : {}) });
367
+ }
368
+ };
369
+ // ADK's reportNodeError: the error a node gave up with, once, unless the
370
+ // node already reported it or the walk was stopped from outside.
371
+ const reportNodeError = (name, error, attempts) => {
372
+ if (isNamed(error, 'InvocationAbortedError') || controller.signal.aborted)
373
+ return;
374
+ if (typeof error === 'object' && error !== null) {
375
+ if (walk.claimed.has(error))
376
+ return;
377
+ walk.claimed.add(error);
378
+ }
379
+ emit({
380
+ type: 'node_error',
381
+ source: 'workflow',
382
+ node: name,
383
+ path: `${workflowPath}.${name}`,
384
+ branch: parentBranch,
385
+ code: errorCodeOf(error),
386
+ message: errorMessageOf(error),
387
+ errorType: errorName(error),
388
+ attempt: attempts,
389
+ });
390
+ };
391
+ /** Record a completed run and emit its end; returns what its successors need. */
392
+ const complete = (name, settledResult, resumed) => {
393
+ const state = nodes.get(name);
394
+ const { branch, ...result } = settledResult;
395
+ state.status = 'completed';
396
+ if (result.output !== undefined)
397
+ outputs.set(name, result.output);
398
+ branches.set(name, branch ?? '');
399
+ order.push(name);
400
+ if (resumed) {
401
+ const route = result.route !== undefined ? { route: result.route } : {};
402
+ emit({ type: 'node_resumed', node: name, kind: nodeOf(name).kind, path: `${workflowPath}.${name}`, branch, output: result.output, ...route, from: resumed });
403
+ return [result, branch];
404
+ }
405
+ emit({
406
+ type: 'node_end',
407
+ node: name,
408
+ kind: nodeOf(name).kind,
409
+ runId: String(state.runCounter),
410
+ path: `${workflowPath}.${name}`,
411
+ branch,
412
+ output: result.output,
413
+ ...(result.route !== undefined ? { route: result.route } : {}),
414
+ });
415
+ return [result, branch];
416
+ };
417
+ /** Every open interrupt, in the order the nodes paused (ADK's loop.interruptIds). */
418
+ const interrupted = new Set();
419
+ /** ADK's handleCompletion for a run that paused: the node waits, nothing downstream runs. */
420
+ const wait = (name, settledResult) => {
421
+ const state = nodes.get(name);
422
+ state.status = 'waiting';
423
+ state.interrupts = [...settledResult.interruptIds];
424
+ for (const id of state.interrupts)
425
+ interrupted.add(id);
426
+ emit({
427
+ type: 'node_waiting',
428
+ node: name,
429
+ kind: nodeOf(name).kind,
430
+ runId: String(state.runCounter),
431
+ path: `${workflowPath}.${name}`,
432
+ branch: settledResult.branch,
433
+ interruptIds: [...state.interrupts],
434
+ });
435
+ };
436
+ const paused = (result) => (result.interruptIds?.length ?? 0) > 0;
437
+ try {
438
+ for (;;) {
439
+ scheduleReadyNodes();
440
+ if (pending.size === 0)
441
+ break;
442
+ const settled = await Promise.race(pending.values());
443
+ pending.delete(settled.name);
444
+ const state = nodes.get(settled.name);
445
+ if (!('result' in settled)) {
446
+ state.status = 'failed';
447
+ reportNodeError(settled.name, settled.error, state.attempts.count);
448
+ controller.abort(settled.error);
449
+ const outstanding = [...pending.values()];
450
+ pending.clear();
451
+ // A run that still finishes during the shutdown has written its output
452
+ // on ADK (its node emits it), so it ends here too, in settle order,
453
+ // and triggers nothing.
454
+ await Promise.all(outstanding.map((run) => run.then((late) => ('result' in late ? (paused(late.result) ? wait(late.name, late.result) : complete(late.name, late.result, late.resumed)) : undefined))));
455
+ throw settled.error;
456
+ }
457
+ if (paused(settled.result))
458
+ wait(settled.name, settled.result);
459
+ else
460
+ bufferDownstreamTriggers(settled.name, ...complete(settled.name, settled.result, settled.resumed));
461
+ }
462
+ }
463
+ finally {
464
+ parentSignal?.removeEventListener('abort', onParentAbort);
465
+ }
466
+ // Stopped from outside: the nodes in flight finished, but the walk did not.
467
+ if (stopped)
468
+ throw new InvocationAbortedError(`Workflow ${graph.name} aborted.`, { cause: controller.signal.reason });
469
+ // ADK's collectRemainingInterrupts and finalize: a paused walk has no output.
470
+ const interruptIds = [...interrupted];
471
+ if (interruptIds.length > 0)
472
+ return { output: undefined, outputs, order, nodeErrors, interruptIds };
473
+ const terminalOutputs = graph.terminals.filter((n) => outputs.has(n)).map((n) => outputs.get(n));
474
+ if (terminalOutputs.length > 1) {
475
+ throw new Error(`Workflow ${graph.name}: multiple terminal nodes produced output (${terminalOutputs.length}). A workflow must have at most one terminal output.`);
476
+ }
477
+ return { output: terminalOutputs[0], outputs, order, nodeErrors, interruptIds: [] };
478
+ }
479
+ /**
480
+ * The event ADK stores for a `node_error` the walk reported (source
481
+ * `workflow`): ADK's `createNodeErrorEvent`, field for field and in its key
482
+ * order, so a turn runner can write it where ADK writes it. An error a node
483
+ * reported itself (source `node`) is already on the event its runner wrote.
484
+ */
485
+ export function nodeErrorEvent(event, invocationId) {
486
+ return {
487
+ ...createTurnEvent({ author: event.node, invocationId, nodeInfo: { path: event.path }, branch: event.branch }),
488
+ isNodeError: true,
489
+ errorType: event.errorType ?? 'Error',
490
+ errorCode: event.code,
491
+ errorMessage: event.message,
492
+ attemptCount: event.attempt,
493
+ };
494
+ }
495
+ /** ADK's errorCodeOf: an error's `code` when it is a string or a number, else UNKNOWN_ERROR. */
496
+ function errorCodeOf(error) {
497
+ const code = error?.code;
498
+ return typeof code === 'string' || typeof code === 'number' ? String(code) : 'UNKNOWN_ERROR';
499
+ }
500
+ function errorMessageOf(error) {
501
+ return error instanceof Error ? error.message : String(error);
502
+ }
503
+ /**
504
+ * The walk as a stream: every event as it happens, then the run as the
505
+ * generator's return value. The walk does not wait for the consumer, so a
506
+ * slow reader never changes the order.
507
+ */
508
+ export async function* streamWorkflowGraph(graph, options) {
509
+ const queue = [];
510
+ let wake;
511
+ let finished = false;
512
+ const done = runWorkflowGraph(graph, {
513
+ ...options,
514
+ onEvent: (event) => {
515
+ queue.push(event);
516
+ wake?.();
517
+ },
518
+ }).finally(() => {
519
+ finished = true;
520
+ wake?.();
521
+ });
522
+ done.catch(() => { }); // surfaced below, after the queued events
523
+ for (;;) {
524
+ while (queue.length > 0)
525
+ yield queue.shift();
526
+ if (finished)
527
+ return await done;
528
+ await new Promise((resolve) => (wake = resolve));
529
+ wake = undefined;
530
+ }
531
+ }
532
+ /**
533
+ * The successors a completed node triggers, as ADK's `getNextPendingNodes`
534
+ * selects them: every `always` edge; every keyed edge whose key (in ADK's
535
+ * spelling) equals the emitted route, or one of them, compared as strings;
536
+ * and the `default` edge when no keyed edge matched.
537
+ */
538
+ export function nextNodes(graph, name, route) {
539
+ const next = [];
540
+ const emitted = route === undefined || route === null ? [] : (Array.isArray(route) ? route : [route]).map(String);
541
+ let matched = false;
542
+ let fallback;
543
+ for (const edge of graph.edges) {
544
+ if (edge.from !== name)
545
+ continue;
546
+ if (edge.route.kind === 'always') {
547
+ next.push(edge.to);
548
+ }
549
+ else if (edge.route.kind === 'default') {
550
+ fallback = edge.to;
551
+ }
552
+ else if (emitted.includes(adkRouteString(edge.route))) {
553
+ next.push(edge.to);
554
+ matched = true;
555
+ }
556
+ }
557
+ if (!matched && fallback !== undefined)
558
+ next.push(fallback);
559
+ return next;
560
+ }
561
+ /**
562
+ * The modifiers a node runs under. A map's own are not applied (ADK's compile
563
+ * does not hand them to its ParallelWorker); its items run under the agent's.
564
+ */
565
+ function settingsFor(node) {
566
+ switch (node.kind) {
567
+ case 'agent':
568
+ case 'tool':
569
+ case 'ask_user':
570
+ case 'join':
571
+ return node.settings;
572
+ default:
573
+ return {};
574
+ }
575
+ }
576
+ /**
577
+ * Runs one node: the single place a node executes, under its retry and
578
+ * timeout. Always asynchronous, so a node the scheduler runs itself settles a
579
+ * tick later as one the runner runs does.
580
+ */
581
+ async function executeNode(node, ctx, walk, attempts) {
582
+ return withControls(node.name, settingsFor(node), ctx, walk, attempts, (signal, attempt) => runOnce(node, { ...ctx, signal }, attempt, walk));
583
+ }
584
+ /** One attempt of a node, without its controls. */
585
+ async function runOnce(node, ctx, attempt, walk) {
586
+ switch (node.kind) {
587
+ case 'agent':
588
+ case 'tool':
589
+ case 'ask_user':
590
+ return await walk.runNode({ target: node, ...ctx, attempt });
591
+ case 'join':
592
+ return { output: ctx.input };
593
+ case 'route':
594
+ return { output: ctx.input, route: routeOf(ctx.input, node.routeKey) };
595
+ case 'map':
596
+ return { output: await runMap(node, ctx, walk) };
597
+ case 'start':
598
+ throw new Error(`Node ${node.name} is the start node and never runs.`);
599
+ }
600
+ }
601
+ /**
602
+ * ADK's runChildNode loop around one run: each attempt under the timeout; a
603
+ * reported error emitted, and fatal when the attempt produced nothing; a
604
+ * failure retried while the retry allows, after the backoff. An abort and a
605
+ * map item's failure are thrown at once. `attempts` is the run's counter,
606
+ * which the walk reads when it reports the error the node gave up with.
607
+ */
608
+ async function withControls(name, settings, ctx, walk, attempts, attemptOnce) {
609
+ for (;;) {
610
+ try {
611
+ const { error, ...result } = await underTimeout(name, settings.timeout, ctx.signal, (signal) => attemptOnce(signal, attempts.count));
612
+ // ADK carries no output for a node that yields null: no output event,
613
+ // nothing recorded, and its successors run on undefined.
614
+ if (result.output === null)
615
+ delete result.output;
616
+ if (error) {
617
+ walk.emit({ type: 'node_error', source: 'node', node: name, path: ctx.path, branch: ctx.branch, code: String(error.code), message: String(error.message), attempt: attempts.count });
618
+ // ADK's failIfNodeReportedError: an output, a route or an interrupt keeps the node alive.
619
+ if (result.output === undefined && result.route === undefined && !(result.interruptIds?.length)) {
620
+ const failure = new NodeReportedError({ nodeName: name, errorCode: error.code, errorMessage: error.message });
621
+ walk.claimed.add(failure);
622
+ throw failure;
623
+ }
624
+ }
625
+ return result;
626
+ }
627
+ catch (error) {
628
+ if (isNamed(error, 'InvocationAbortedError') || isNamed(error, 'DynamicNodeFailError'))
629
+ throw error;
630
+ const retry = settings.retry;
631
+ if (!retry || !shouldRetry(error, retry, attempts.count))
632
+ throw error;
633
+ const seconds = retryDelaySeconds(retry, attempts.count);
634
+ attempts.count += 1;
635
+ await abortableDelay(seconds * 1000, ctx.signal);
636
+ }
637
+ }
638
+ }
639
+ /**
640
+ * One attempt under a timeout, as ADK's runOnce: the attempt's own signal
641
+ * follows the walk's and aborts when the timer fires, and the attempt fails
642
+ * at once (`NodeTimeoutError`, or `InvocationAbortedError` on the walk's
643
+ * abort) without waiting for the runner. Without a timeout the attempt is
644
+ * awaited with the walk's signal.
645
+ */
646
+ async function underTimeout(name, timeout, signal, attempt) {
647
+ if (!(typeof timeout === 'number' && timeout > 0))
648
+ return await attempt(signal);
649
+ const controller = new AbortController();
650
+ let deadlineFired = false;
651
+ const onParentAbort = () => controller.abort(signal.reason);
652
+ if (signal.aborted)
653
+ controller.abort(signal.reason);
654
+ else
655
+ signal.addEventListener('abort', onParentAbort, { once: true });
656
+ const timer = setTimeout(() => {
657
+ deadlineFired = true;
658
+ controller.abort(new NodeTimeoutError({ nodeName: name, timeout }));
659
+ }, timeout * 1000);
660
+ // Registered before the runner starts, so it settles the race first.
661
+ const aborted = new Promise((_, reject) => {
662
+ const fail = () => reject(deadlineFired ? new NodeTimeoutError({ nodeName: name, timeout }) : new InvocationAbortedError(`Invocation aborted while running node '${name}'.`, { cause: signal.reason }));
663
+ if (controller.signal.aborted)
664
+ fail();
665
+ else
666
+ controller.signal.addEventListener('abort', fail, { once: true });
667
+ });
668
+ aborted.catch(() => { });
669
+ const run = Promise.resolve().then(() => attempt(controller.signal));
670
+ run.catch(() => { }); // an abandoned attempt's late failure is nobody's
671
+ try {
672
+ return await Promise.race([run, aborted]);
673
+ }
674
+ finally {
675
+ clearTimeout(timer);
676
+ signal.removeEventListener('abort', onParentAbort);
677
+ }
678
+ }
679
+ /** A retry's backoff; the walk's abort cuts it short. */
680
+ function abortableDelay(ms, signal) {
681
+ return new Promise((resolve, reject) => {
682
+ if (signal.aborted) {
683
+ reject(new InvocationAbortedError('Invocation aborted during retry.', { cause: signal.reason }));
684
+ return;
685
+ }
686
+ const onAbort = () => {
687
+ clearTimeout(timer);
688
+ reject(new InvocationAbortedError('Invocation aborted during retry.', { cause: signal.reason }));
689
+ };
690
+ const timer = setTimeout(() => {
691
+ signal.removeEventListener('abort', onAbort);
692
+ resolve();
693
+ }, ms);
694
+ signal.addEventListener('abort', onAbort, { once: true });
695
+ });
696
+ }
697
+ /**
698
+ * A map: ADK's ParallelWorker. A list input runs the agent once per item (a
699
+ * non-list input is one item), on a pool of `min(maxParallel ?? 8, items)`
700
+ * workers that each take the next index; the output is the list of results
701
+ * by index. An empty list outputs `[]`. Each item runs under the agent's own
702
+ * retry and timeout. The first item that gives up stops the pool taking new
703
+ * items, and once every worker has stopped the map fails with
704
+ * `DynamicNodeFailError` naming the agent, as ADK's dynamic-node scheduler
705
+ * wraps it. A map whose signal aborted outputs nothing, as ADK's yields
706
+ * nothing then.
707
+ */
708
+ async function runMap(node, ctx, walk) {
709
+ const items = Array.isArray(ctx.input) ? ctx.input : [ctx.input];
710
+ if (items.length === 0)
711
+ return [];
712
+ const results = new Array(items.length);
713
+ const poolSize = Math.min(node.maxParallel ?? DEFAULT_MAX_PARALLEL, items.length);
714
+ let nextIndex = 0;
715
+ let failed = false;
716
+ let firstError;
717
+ const worker = async () => {
718
+ while (!failed && !ctx.signal.aborted) {
719
+ const index = nextIndex++;
720
+ if (index >= items.length)
721
+ break;
722
+ const path = `${ctx.path}.${node.agent}@${index}`;
723
+ const branch = subBranch(ctx.branch, node.agent, String(index));
724
+ const base = { node: node.name, agent: node.agent, index, path, branch };
725
+ try {
726
+ walk.emit({ type: 'item_start', ...base, input: items[index] });
727
+ const itemCtx = { input: items[index], runId: String(index), path, branch, signal: ctx.signal };
728
+ const target = { kind: 'map_item', agent: node.agent, map: node, index };
729
+ const attempts = { count: 1 };
730
+ const traced = { name: node.agent, kind: 'map_item', path, runId: String(index), attempts };
731
+ const result = await walk.traceNode(traced, () => withControls(node.agent, node.agentSettings, itemCtx, walk, attempts, async (signal, attempt) => walk.runNode({ target, ...itemCtx, signal, attempt }))).catch((error) => {
732
+ if (isNamed(error, 'DynamicNodeFailError'))
733
+ throw error;
734
+ const cause = error instanceof Error ? error : new Error(String(error));
735
+ throw new DynamicNodeFailError({ message: `Dynamic node ${node.agent} failed: ${cause.message}`, error: cause, errorNodePath: path });
736
+ });
737
+ results[index] = result.output;
738
+ walk.emit({ type: 'item_end', ...base, output: result.output });
739
+ }
740
+ catch (error) {
741
+ if (!failed) {
742
+ failed = true;
743
+ firstError = error;
744
+ }
745
+ break;
746
+ }
747
+ }
748
+ };
749
+ await Promise.all(Array.from({ length: poolSize }, () => worker()));
750
+ if (failed)
751
+ throw firstError;
752
+ // ADK's ParallelWorker yields nothing once aborted: no output, so no map event.
753
+ if (ctx.signal.aborted)
754
+ return undefined;
755
+ return results;
756
+ }