@desplega.ai/agent-swarm 1.150.0 → 1.151.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/README.md +11 -0
  2. package/dist/{acp-adapter-d5mzggyk.js → acp-adapter-ex8hzjfc.js} +5 -5
  3. package/dist/{actions-wn6ew0p7.js → actions-2bgpyhww.js} +38 -11
  4. package/dist/{anthropic-messages-ec35ktvx.js → anthropic-messages-2y8nktar.js} +16 -16
  5. package/dist/{app-dty7p9hg.js → app-1706zhr1.js} +7 -7
  6. package/dist/{assistant-q4vhgjtx.js → assistant-bavke5p7.js} +41 -16
  7. package/dist/{azure-openai-responses-g85jv7m8.js → azure-openai-responses-f38x2avq.js} +13 -13
  8. package/dist/{boot-reembed-92292pgf.js → boot-reembed-3cmrrmpa.js} +8 -8
  9. package/dist/{boot-reembed-n15q6s9x.js → boot-reembed-nafcmmmc.js} +13 -15
  10. package/dist/{boot-scrub-logs-44pc955m.js → boot-scrub-logs-cd6a2sq0.js} +5 -5
  11. package/dist/{claude-adapter-cjegjbje.js → claude-adapter-wfp2gzvz.js} +12 -13
  12. package/dist/{claude-managed-adapter-p3ts5m9m.js → claude-managed-adapter-dsmkcrjy.js} +3 -3
  13. package/dist/{claude-sdk-session-7ywbn12v.js → claude-sdk-session-bht9jjak.js} +12 -13
  14. package/dist/{cli-ceb5y160.js → cli-0qb1z6s2.js} +1 -1
  15. package/dist/{cli-ryz8q8p1.js → cli-2tbvf1z1.js} +40 -40
  16. package/dist/{cli-zthatyrx.js → cli-3v9xhjcn.js} +5 -5
  17. package/dist/{cli-mzb0n642.js → cli-4wppfkcm.js} +350 -20
  18. package/dist/{cli-3ckzxdt1.js → cli-5jd2z7av.js} +90 -81
  19. package/dist/{cli-3mh119qv.js → cli-5tpgq60f.js} +9726 -693
  20. package/dist/{cli-ck1vqzdj.js → cli-7tgcstn2.js} +9 -4
  21. package/dist/{cli-ttje5a4z.js → cli-9004vfdf.js} +105 -56
  22. package/dist/{cli-asvrh7kj.js → cli-9xvdm3bx.js} +1 -1
  23. package/dist/{cli-9gqddmxs.js → cli-a1z3h107.js} +8 -8
  24. package/dist/{cli-72xdszyx.js → cli-czjtgfys.js} +2 -2
  25. package/dist/{cli-5my3bjsd.js → cli-d3p82erb.js} +7 -7
  26. package/dist/{cli-kyysm7m3.js → cli-f1wky6zp.js} +1 -1
  27. package/dist/{cli-a45d5v9b.js → cli-fm4ytsdk.js} +2 -2
  28. package/dist/{cli-111ey3ra.js → cli-frcta2w4.js} +5 -5
  29. package/dist/{cli-h505yhex.js → cli-g69fdc9g.js} +2 -2
  30. package/dist/{cli-cmp8g3sw.js → cli-gbfwcd3k.js} +44 -9
  31. package/dist/{cli-hbagregr.js → cli-gjyvhmmh.js} +88 -13
  32. package/dist/{cli-hmyynq9b.js → cli-ht5rs63n.js} +1 -1
  33. package/dist/{cli-dyx0237s.js → cli-j4nv2wqy.js} +2 -4
  34. package/dist/{cli-vr6qj104.js → cli-j9xczhaq.js} +1 -1
  35. package/dist/cli-pf9q3696.js +172 -0
  36. package/dist/{cli-7fesa1ky.js → cli-pgj4a5fb.js} +1 -1
  37. package/dist/{cli-7kwmt3r5.js → cli-ppm4975n.js} +9 -3
  38. package/dist/{cli-8chp8xab.js → cli-q88st9aj.js} +2 -2
  39. package/dist/{cli-kt7wevqh.js → cli-r2q6pevn.js} +132 -46
  40. package/dist/{cli-07jd68a1.js → cli-re8z5p8p.js} +1 -1
  41. package/dist/{cli-qp69dd5d.js → cli-s14agm5s.js} +105 -12
  42. package/dist/{cli-zd21jrrc.js → cli-sjaae6ff.js} +9872 -424
  43. package/dist/{cli-taym3dmg.js → cli-vwm416m6.js} +13 -13
  44. package/dist/{cli-1mx65de9.js → cli-wbyk465z.js} +1 -1
  45. package/dist/{cli-n75jt793.js → cli-z61ykvam.js} +6 -5
  46. package/dist/{cli-gm0fhz13.js → cli-zkxymas5.js} +2 -1
  47. package/dist/cli.js +22 -21
  48. package/dist/{codex-adapter-rn3aq7jm.js → codex-adapter-jjjk8w4m.js} +8 -9
  49. package/dist/{codex-hook-gthcmm0g.js → codex-hook-8jexc34x.js} +2 -2
  50. package/dist/{codex-session-runner-bge0cbsw.js → codex-session-runner-nd6ayxkx.js} +8 -9
  51. package/dist/{commands-26phw0pc.js → commands-9v4psyq1.js} +5 -5
  52. package/dist/{db-jxjx6s1b.js → db-wnt58tpe.js} +5 -5
  53. package/dist/dispatcher-05jh1aqk.js +74 -0
  54. package/dist/{google-generative-ai-tsskx7te.js → google-generative-ai-ndjmj5fa.js} +7 -7
  55. package/dist/{google-vertex-pkhpkbq9.js → google-vertex-fr665tqs.js} +10 -10
  56. package/dist/handlers-tw90pkh5.js +65 -0
  57. package/dist/{hook-t6fqsggw.js → hook-123e2scb.js} +9 -10
  58. package/dist/{hook-jw0ztn96.js → hook-sxe3r349.js} +9 -10
  59. package/dist/{http-hyc7s57s.js → http-ecsar8ez.js} +1431 -579
  60. package/dist/{index-sfhaq59d.js → index-16js521z.js} +119 -101
  61. package/dist/{index-wtwpthe1.js → index-hw26vj40.js} +25 -19
  62. package/dist/{index-bcyx5djw.js → index-nnrbpcjp.js} +25 -17
  63. package/dist/index-ymn7em0p.js +25 -0
  64. package/dist/{index-1d9r6a74.js → index-zj518gf9.js} +26 -20
  65. package/dist/{keepalive-fbsak4ya.js → keepalive-asqtcm4y.js} +7 -7
  66. package/dist/{lead-rdzdm627.js → lead-xbfr7fvt.js} +41 -49
  67. package/dist/{maintenance-25z1c1vf.js → maintenance-qma55qvw.js} +8 -8
  68. package/dist/{node-209sxmg3.js → node-x1g4v0pq.js} +9 -9
  69. package/dist/{oauth-refresh-sweep-j3d7gtse.js → oauth-refresh-sweep-3s04c6qw.js} +6 -6
  70. package/dist/{onboard-dkcwrc75.js → onboard-drskd386.js} +13 -4
  71. package/dist/{openai-codex-responses-pm5gnyfa.js → openai-codex-responses-wy6a43wd.js} +11 -11
  72. package/dist/{openai-responses-vwvk1taj.js → openai-responses-6xwf7bzg.js} +13 -13
  73. package/dist/{opencode-adapter-dce4wskd.js → opencode-adapter-28axqa09.js} +4 -4
  74. package/dist/{otel-impl-8rgyy3j8.js → otel-impl-943c96er.js} +1 -1
  75. package/dist/{pi-mono-adapter-4s2bbkgq.js → pi-mono-adapter-jcqt71cg.js} +14 -16
  76. package/dist/{pricing-refresh-9cek71nq.js → pricing-refresh-mx51az05.js} +7 -7
  77. package/dist/{rbac-roles-z98s2ypy.js → rbac-roles-78baecqd.js} +13 -9
  78. package/dist/{rbac-roles-vk8mqp9v.js → rbac-roles-wcmrg6sv.js} +7 -7
  79. package/dist/{render-v2-3j26g0qh.js → render-v2-8zjntgf5.js} +7 -7
  80. package/dist/{seed-pricing-7rxhcnzm.js → seed-pricing-jqjs6cws.js} +6 -6
  81. package/dist/{setup-e2t7vz0c.js → setup-4pks0vfk.js} +2 -2
  82. package/dist/{work-objects-8d3nd3x2.js → work-objects-wangt792.js} +38 -14
  83. package/dist/{worker-0x9fn7nq.js → worker-p2mr422x.js} +41 -49
  84. package/openapi.json +1351 -265
  85. package/package.json +2 -1
  86. package/src/agentmail/handlers.ts +95 -71
  87. package/src/be/db-client.ts +5 -0
  88. package/src/be/db.ts +11 -3
  89. package/src/be/extensions/db.ts +461 -0
  90. package/src/be/extensions/validate.ts +107 -0
  91. package/src/be/memory/boot-reembed.ts +17 -12
  92. package/src/be/memory/index.ts +12 -0
  93. package/src/be/memory/providers/openai-embedding.ts +4 -0
  94. package/src/be/memory/types.ts +2 -0
  95. package/src/be/migrations/155_extensions.sql +66 -0
  96. package/src/be/migrations/156_inbox_item_state_read_at.sql +7 -0
  97. package/src/be/rbac-roles.ts +3 -2
  98. package/src/be/scripts/typecheck.ts +52 -38
  99. package/src/be/seed-skills/index.ts +7 -0
  100. package/src/be/swarm-config-guard.ts +3 -1
  101. package/src/commands/onboard/compose-generator.ts +14 -2
  102. package/src/commands/onboard/env-generator.ts +7 -0
  103. package/src/commands/runner.ts +1 -1
  104. package/src/extensions/apply-task-create.ts +135 -0
  105. package/src/extensions/bundle-path.ts +10 -0
  106. package/src/extensions/contract-runtime.ts +7 -0
  107. package/src/extensions/contract-types.generated.ts +4 -0
  108. package/src/extensions/contract.ts +270 -0
  109. package/src/extensions/ctx.ts +186 -0
  110. package/src/extensions/dispatcher.ts +479 -0
  111. package/src/extensions/identity.ts +44 -0
  112. package/src/extensions/lifecycle.ts +281 -0
  113. package/src/extensions/loader.ts +207 -0
  114. package/src/extensions/post-bridge.ts +161 -0
  115. package/src/github/handlers.ts +185 -149
  116. package/src/gitlab/handlers.ts +97 -81
  117. package/src/heartbeat/heartbeat.ts +152 -83
  118. package/src/http/agents.ts +1 -1
  119. package/src/http/all-routes.ts +1 -0
  120. package/src/http/apps.ts +14 -10
  121. package/src/http/core.ts +34 -0
  122. package/src/http/extensions.ts +547 -0
  123. package/src/http/inbox-state.ts +2 -0
  124. package/src/http/index.ts +25 -0
  125. package/src/http/mcp-bridge.ts +96 -62
  126. package/src/http/mcp.ts +1 -1
  127. package/src/http/status.ts +6 -4
  128. package/src/http/tasks.ts +51 -35
  129. package/src/integrations/kapso/inbound.ts +15 -11
  130. package/src/jira/sync.ts +26 -18
  131. package/src/linear/sync.ts +26 -18
  132. package/src/prompts/session-templates.ts +5 -4
  133. package/src/rbac/admission.ts +1 -1
  134. package/src/rbac/elevated-agents.ts +24 -0
  135. package/src/rbac/legacy-policy.ts +35 -6
  136. package/src/rbac/permissions.ts +8 -0
  137. package/src/scheduler/deferred-task-waits.ts +133 -51
  138. package/src/scheduler/schedule-task.ts +50 -24
  139. package/src/scheduler/scheduler.ts +4 -2
  140. package/src/scripts-runtime/executors/native.ts +5 -1
  141. package/src/scripts-runtime/executors/types.ts +5 -0
  142. package/src/scripts-runtime/import-allowlist.ts +79 -19
  143. package/src/scripts-runtime/sdk-allowlist.ts +4 -0
  144. package/src/scripts-runtime/swarm-config.ts +8 -0
  145. package/src/scripts-runtime/swarm-sdk.ts +3 -0
  146. package/src/scripts-runtime/types/stdlib.d.ts +9 -0
  147. package/src/scripts-runtime/types/swarm-extension.d.ts +562 -0
  148. package/src/scripts-runtime/types/swarm-sdk.d.ts +9 -0
  149. package/src/server.ts +6 -0
  150. package/src/slack/HEURISTICS.md +1 -1
  151. package/src/slack/actions.ts +21 -17
  152. package/src/slack/blocks.ts +1 -1
  153. package/src/slack/handlers.ts +98 -5
  154. package/src/slack/inbound-files.ts +6 -2
  155. package/src/slack/render-v2.ts +171 -35
  156. package/src/slack/router.ts +13 -3
  157. package/src/slack/steering.ts +3 -3
  158. package/src/tasks/errors.ts +9 -0
  159. package/src/tasks/sibling-awareness.ts +35 -1
  160. package/src/tasks/worker-follow-up.ts +64 -12
  161. package/src/tests/base-prompt.test.ts +1 -0
  162. package/src/tests/deferred-task-wake.test.ts +161 -9
  163. package/src/tests/docs-index-footprint.test.js +3 -3
  164. package/src/tests/env-flag.test.ts +1 -0
  165. package/src/tests/extensions-db.test.ts +216 -0
  166. package/src/tests/extensions-dispatcher.test.ts +275 -0
  167. package/src/tests/extensions-example-heartbeat-remediate.test.ts +69 -0
  168. package/src/tests/extensions-example-mcp-install.test.ts +63 -0
  169. package/src/tests/extensions-example-no-exclamation-marks.test.ts +72 -0
  170. package/src/tests/extensions-example-rewrite-and-suppress.test.ts +70 -0
  171. package/src/tests/extensions-example-route-slack-channel.test.ts +217 -0
  172. package/src/tests/extensions-heartbeat.test.ts +291 -0
  173. package/src/tests/extensions-http.test.ts +431 -0
  174. package/src/tests/extensions-lead-equivalence.test.ts +49 -0
  175. package/src/tests/extensions-lifecycle.test.ts +209 -0
  176. package/src/tests/extensions-loader.test.ts +95 -0
  177. package/src/tests/extensions-mcp-tools.test.ts +245 -0
  178. package/src/tests/extensions-pre-task.test.ts +487 -0
  179. package/src/tests/extensions-run-context.test.ts +41 -0
  180. package/src/tests/extensions-tool-call.test.ts +288 -0
  181. package/src/tests/extensions-validate.test.ts +196 -0
  182. package/src/tests/fixtures/extensions/bad-import/hooks.ts +8 -0
  183. package/src/tests/fixtures/extensions/bad-import/manifest.json +9 -0
  184. package/src/tests/fixtures/extensions/bad-return-shape/hooks.ts +10 -0
  185. package/src/tests/fixtures/extensions/bad-return-shape/manifest.json +9 -0
  186. package/src/tests/fixtures/extensions/block-tasks-from-source/hooks.ts +22 -0
  187. package/src/tests/fixtures/extensions/block-tasks-from-source/manifest.json +9 -0
  188. package/src/tests/fixtures/extensions/ignore-channel/hooks.ts +22 -0
  189. package/src/tests/fixtures/extensions/ignore-channel/manifest.json +9 -0
  190. package/src/tests/fixtures/extensions/load.ts +10 -0
  191. package/src/tests/fixtures/extensions/minimal/hooks.ts +7 -0
  192. package/src/tests/fixtures/extensions/minimal/manifest.json +9 -0
  193. package/src/tests/fixtures/extensions/never-fail-long-tasks/hooks.ts +11 -0
  194. package/src/tests/fixtures/extensions/never-fail-long-tasks/manifest.json +9 -0
  195. package/src/tests/fixtures/extensions/no-exclamation-marks/hooks.ts +23 -0
  196. package/src/tests/fixtures/extensions/no-exclamation-marks/manifest.json +9 -0
  197. package/src/tests/fixtures/extensions/post-logger/hooks.ts +9 -0
  198. package/src/tests/fixtures/extensions/post-logger/manifest.json +9 -0
  199. package/src/tests/fixtures/extensions/priority-a/hooks.ts +7 -0
  200. package/src/tests/fixtures/extensions/priority-a/manifest.json +9 -0
  201. package/src/tests/fixtures/extensions/priority-b/hooks.ts +7 -0
  202. package/src/tests/fixtures/extensions/priority-b/manifest.json +9 -0
  203. package/src/tests/fixtures/extensions/record-only-on-tag/hooks.ts +16 -0
  204. package/src/tests/fixtures/extensions/record-only-on-tag/manifest.json +9 -0
  205. package/src/tests/fixtures/extensions/reserved-assets/hooks.ts +5 -0
  206. package/src/tests/fixtures/extensions/reserved-assets/manifest.json +10 -0
  207. package/src/tests/fixtures/extensions/rewrite-progress-prefix/hooks.ts +22 -0
  208. package/src/tests/fixtures/extensions/rewrite-progress-prefix/manifest.json +9 -0
  209. package/src/tests/fixtures/extensions/rewrite-task-priority/hooks.ts +7 -0
  210. package/src/tests/fixtures/extensions/rewrite-task-priority/manifest.json +9 -0
  211. package/src/tests/fixtures/extensions/route-channel-to-agent/hooks.ts +26 -0
  212. package/src/tests/fixtures/extensions/route-channel-to-agent/manifest.json +9 -0
  213. package/src/tests/fixtures/extensions/slow/hooks.ts +10 -0
  214. package/src/tests/fixtures/extensions/slow/manifest.json +9 -0
  215. package/src/tests/fixtures/extensions/suppress-lead-follow-up/hooks.ts +12 -0
  216. package/src/tests/fixtures/extensions/suppress-lead-follow-up/manifest.json +9 -0
  217. package/src/tests/fixtures/extensions/throws/hooks.ts +12 -0
  218. package/src/tests/fixtures/extensions/throws/manifest.json +9 -0
  219. package/src/tests/fixtures/extensions/worker-runtime/hooks.ts +5 -0
  220. package/src/tests/fixtures/extensions/worker-runtime/manifest.json +9 -0
  221. package/src/tests/memory-edit-authorization.test.ts +32 -0
  222. package/src/tests/multi-runtime-enabled-stats.test.ts +2 -2
  223. package/src/tests/multi-runtime-registration.test.ts +14 -14
  224. package/src/tests/onboard-compose.test.ts +44 -0
  225. package/src/tests/preload.ts +5 -0
  226. package/src/tests/rbac-admission.test.ts +18 -3
  227. package/src/tests/rbac-engine.test.ts +39 -0
  228. package/src/tests/realtime-room-auth.test.ts +1 -1
  229. package/src/tests/reload-config.test.ts +44 -0
  230. package/src/tests/routing-affinity-inventory.test.ts +1 -1
  231. package/src/tests/script-connections.test.ts +2 -0
  232. package/src/tests/scripts-boot-reembed.test.ts +4 -0
  233. package/src/tests/scripts-embeddings.test.ts +4 -0
  234. package/src/tests/scripts-import-allowlist.test.ts +44 -1
  235. package/src/tests/scripts-mcp-e2e.test.ts +80 -0
  236. package/src/tests/scripts-only-gating.test.ts +2 -0
  237. package/src/tests/scripts-runtime-identity.test.ts +1 -1
  238. package/src/tests/slack-blocks.test.ts +12 -1
  239. package/src/tests/slack-render-v2.test.ts +76 -3
  240. package/src/tests/slack-router.test.ts +17 -2
  241. package/src/tests/slack-steering.test.ts +19 -0
  242. package/src/tests/status.test.ts +12 -0
  243. package/src/tests/steering-enabled.test.ts +6 -8
  244. package/src/tests/task-tool-preload-mcp.test.ts +4 -1
  245. package/src/tests/tool-annotations.test.ts +1 -1
  246. package/src/tests/tool-registrar-no-input.test.ts +42 -1
  247. package/src/tools/defer-task.ts +1 -1
  248. package/src/tools/extension-common.ts +84 -0
  249. package/src/tools/extension-install.ts +65 -0
  250. package/src/tools/extension-list.ts +66 -0
  251. package/src/tools/script-common.ts +43 -18
  252. package/src/tools/send-task.ts +69 -107
  253. package/src/tools/task-action.ts +43 -22
  254. package/src/tools/task-tool-ctx.ts +2 -0
  255. package/src/tools/tool-config.ts +3 -1
  256. package/src/tools/utils.ts +140 -16
  257. package/src/types.ts +133 -0
  258. package/src/utils/multi-runtime.ts +2 -2
  259. package/src/utils/steering-enabled.ts +4 -4
  260. package/src/workflows/executors/agent-task.ts +13 -3
  261. package/templates/extensions/README.md +17 -0
  262. package/templates/extensions/notify-on-complete/README.md +11 -0
  263. package/templates/extensions/notify-on-complete/hooks.ts +61 -0
  264. package/templates/extensions/notify-on-complete/manifest.json +7 -0
  265. package/templates/extensions/require-ticket-ref/README.md +11 -0
  266. package/templates/extensions/require-ticket-ref/hooks.ts +30 -0
  267. package/templates/extensions/require-ticket-ref/manifest.json +7 -0
  268. package/templates/extensions/require-verification-note/README.md +11 -0
  269. package/templates/extensions/require-verification-note/hooks.ts +32 -0
  270. package/templates/extensions/require-verification-note/manifest.json +7 -0
  271. package/templates/skills/scheduling/content.md +2 -0
  272. package/templates/skills/swarm-extensions/config.json +14 -0
  273. package/templates/skills/swarm-extensions/content.md +120 -0
  274. package/dist/cli-b8x19vbh.js +0 -823
  275. package/dist/cli-n1yj5fd5.js +0 -164
  276. package/dist/cli-q04q68be.js +0 -3066
  277. package/dist/cli-qgm2pfpq.js +0 -6377
  278. package/dist/cli-rnsr8asm.js +0 -4323
  279. package/dist/cli-rwp3ksm7.js +0 -545
  280. package/dist/cli-xjb9wghj.js +0 -668
  281. package/dist/cli-xvkmedgn.js +0 -332
  282. package/dist/cli-ydk0wjj8.js +0 -338
  283. package/dist/handlers-vr1djg4n.js +0 -41
  284. package/dist/{claude-managed-setup-f5pmsyrp.js → claude-managed-setup-48dwg9bv.js} +3 -3
  285. package/dist/{cli-81r683bv.js → cli-ch38q5z4.js} +0 -0
  286. package/dist/{cli-wk0gjrn3.js → cli-kj76p1ez.js} +6 -6
  287. package/dist/{cli-c71mg51n.js → cli-r2fpe7cx.js} +5 -5
  288. package/dist/{cli-8qx7jnrk.js → cli-y6x0gqtg.js} +16 -16
  289. package/dist/{index-aa3sa6s8.js → index-2wsdsnmm.js} +1 -1
  290. package/dist/{mistral-conversations-4ryk7h83.js → mistral-conversations-qhr84ph3.js} +9 -9
  291. package/dist/{openai-completions-qdcmkfnr.js → openai-completions-32rdwmze.js} +17 -17
  292. package/dist/{openrouter-images-qx18fyp8.js → openrouter-images-9f9ek4rw.js} +3 -3
  293. package/dist/{pi-messages-79jvzz1d.js → pi-messages-b1mbr6gp.js} +4 -4
@@ -1,4323 +0,0 @@
1
- import {
2
- DEFAULT_MODEL,
3
- resolveCredential
4
- } from "./cli-mzb0n642.js";
5
- import {
6
- getOpenRouterBaseUrl
7
- } from "./cli-3dpj7atn.js";
8
- import {
9
- DEFAULT_SCRIPT_RESOURCES,
10
- MAX_SCRIPT_WALL_CLOCK_MS,
11
- MIN_SCRIPT_WALL_CLOCK_MS,
12
- buildSandboxedCommand,
13
- buildScriptCredentialBindingsWithFailures,
14
- createCappedStreamState,
15
- findEntryNodes,
16
- getNextTargets,
17
- getScript,
18
- getScriptApiConnectionDescriptors,
19
- getScriptMcpConnectionDescriptors,
20
- getScriptVersion,
21
- getSuccessors,
22
- readStreamCapped,
23
- restoreScratchScriptLastUsedIfUnchanged,
24
- runScript,
25
- sandboxSpawnEnv,
26
- snapshotCapped,
27
- touchScratchScriptLastUsed
28
- } from "./cli-zd21jrrc.js";
29
- import {
30
- detachedProcessGroup,
31
- registerProcessGroup,
32
- terminateProcessGroup
33
- } from "./cli-y89t4nca.js";
34
- import {
35
- getOAuthApp,
36
- getOAuthTokens
37
- } from "./cli-vr6qj104.js";
38
- import {
39
- withSiblingAwareness
40
- } from "./cli-n1yj5fd5.js";
41
- import {
42
- getSlackApp
43
- } from "./cli-kt7wevqh.js";
44
- import {
45
- FollowUpConfigSchema,
46
- ModelTierSchema,
47
- ReasoningEffortSchema,
48
- RoutingReasonSchema,
49
- cancelPendingApprovalRequestsForRun,
50
- cancelTask,
51
- claimApprovalCancellationNotification,
52
- completeApprovalCancellationNotificationClaim,
53
- createWorkflowRun,
54
- createWorkflowRunStep,
55
- createWorkflowVersion,
56
- exports_db,
57
- getCompletedStepNodeIds,
58
- getDbClient,
59
- getDueWaitStates,
60
- getLastRunStart,
61
- getLastSuccessfulRun,
62
- getLatestStepForNode,
63
- getPendingEventWaitNames,
64
- getPendingWaitsByEvent,
65
- getRetryableSteps,
66
- getStepByIdempotencyKey,
67
- getStepCountForNode,
68
- getStuckApprovalRuns,
69
- getStuckWaitRuns,
70
- getStuckWorkflowRuns,
71
- getSwarmConfigs,
72
- getTaskByWorkflowRunStepId,
73
- getWaitStateById,
74
- getWorkflow,
75
- getWorkflowRun,
76
- getWorkflowRunStep,
77
- getWorkflowRunStepsByRunId,
78
- getWorkflowVersions,
79
- getWorkflowsByScheduleId,
80
- init_context_key,
81
- init_db,
82
- init_telemetry,
83
- init_types,
84
- listCancelledApprovalRequestsForRun,
85
- listWorkflows,
86
- releaseApprovalCancellationNotificationClaim,
87
- resolveApprovalRequest,
88
- resolveWaitState,
89
- splitLegacyModelAlias,
90
- telemetry,
91
- updateWorkflow,
92
- updateWorkflowRun,
93
- updateWorkflowRunStep,
94
- workflowContextKey
95
- } from "./cli-qp69dd5d.js";
96
- import {
97
- deepInterpolate,
98
- findInterpolationTokens,
99
- init_template,
100
- interpolate
101
- } from "./cli-n75jt793.js";
102
- import {
103
- getAppUrl,
104
- getMcpBaseUrl,
105
- init_constants
106
- } from "./cli-dv6cdjs2.js";
107
- import {
108
- init_zod
109
- } from "./cli-sd0mxa3c.js";
110
- import {
111
- exports_external
112
- } from "./cli-w5e1d1vv.js";
113
- import {
114
- init_secret_scrubber,
115
- isSensitiveKey,
116
- scrubSecrets
117
- } from "./cli-r2ap2czm.js";
118
- import {
119
- init_event_bus,
120
- workflowEventBus
121
- } from "./cli-f7kvq46p.js";
122
- import {
123
- __require
124
- } from "./cli-bc05qbss.js";
125
- // src/automation-preflight-alert.ts
126
- init_constants();
127
- init_secret_scrubber();
128
- async function notifyAutomationPreflightFailure(preflight) {
129
- const channel = process.env.SLACK_ALERTS_CHANNEL?.trim();
130
- if (!channel) {
131
- console.warn("[Automation Preflight] SLACK_ALERTS_CHANNEL not set; skipping alert");
132
- return;
133
- }
134
- try {
135
- const { getSlackApp: getSlackApp2 } = await import("./app-dty7p9hg.js");
136
- const app = getSlackApp2();
137
- if (!app) {
138
- console.warn("[Automation Preflight] Slack not available, cannot send notification");
139
- return;
140
- }
141
- await app.client.chat.postMessage({
142
- channel,
143
- text: scrubSecrets(`⚠️ Automation needs setup
144
- ` + `Automation: ${preflight.kind} "${preflight.name}"
145
- Missing params: ${preflight.missing.params.join(", ") || "none"}
146
- Missing integrations: ${preflight.missing.integrations.join(", ") || "none"}
147
- Fire SKIPPED. It was not retried or queued.
148
- Fix setup: ${getAppUrl()}${preflight.fixUrl}`),
149
- unfurl_links: false,
150
- unfurl_media: false
151
- });
152
- } catch (error) {
153
- console.error("[Automation Preflight] Failed to send Slack notification:", scrubSecrets(error instanceof Error ? error.message : String(error)));
154
- }
155
- }
156
-
157
- // src/be/automation-preflight.ts
158
- init_db();
159
- var NEEDS_SETUP_PREFIX = "needs_setup:";
160
- function hasValue(value) {
161
- if (value == null)
162
- return false;
163
- if (typeof value === "string")
164
- return value.trim().length > 0;
165
- if (Array.isArray(value))
166
- return value.length > 0;
167
- return true;
168
- }
169
- var GITHUB_REPOSITORY_SLUG = /^[A-Za-z0-9](?:[A-Za-z0-9-]{0,38})\/[A-Za-z0-9][A-Za-z0-9._-]{0,99}(?:\.git)?$/;
170
- var SAFE_SHELL_DATA = /^[A-Za-z0-9._~%/:@ -]+$/;
171
- var DNS_NAME = /^(?=.{1,253}$)(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\.)+[A-Za-z]{2,63}$/;
172
- var SAFE_EXTERNAL_ID = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/;
173
- var SAFE_GIT_REF = /^(?!-)(?!.*(?:\.\.|@\{|\/\/))[A-Za-z0-9._/-]+$/;
174
- var SAFE_TAG_PATTERN = /^(?!-)(?!.*(?:\.\.|\/\/))[A-Za-z0-9._*?/-]+$/;
175
- var SAFE_RELATIVE_PATH = /^(?![-/])(?!.*(?:^|\/)\.\.(?:\/|$))[A-Za-z0-9._/-]+$/;
176
- var SAFE_REPORT_NAME = /^(?!\.{1,2}$)[A-Za-z0-9][A-Za-z0-9._ -]{0,127}$/;
177
- function isSafeRepository(value) {
178
- if (!SAFE_SHELL_DATA.test(value) || value.trim() !== value || value.includes(" "))
179
- return false;
180
- const slug = value.replace(/^https:\/\/github\.com\//, "").replace(/^git@github\.com:/, "").replace(/\/$/, "");
181
- return GITHUB_REPOSITORY_SLUG.test(slug);
182
- }
183
- function isSafeGscProperty(value) {
184
- if (!SAFE_SHELL_DATA.test(value) || value.trim() !== value)
185
- return false;
186
- return value.split(" ").every((property) => {
187
- if (!property)
188
- return false;
189
- if (property.startsWith("sc-domain:"))
190
- return DNS_NAME.test(property.slice(10));
191
- if (DNS_NAME.test(property))
192
- return true;
193
- try {
194
- const url = new URL(property);
195
- return url.protocol === "https:" && !url.username && !url.password && !url.port && !url.search && !url.hash && DNS_NAME.test(url.hostname);
196
- } catch {
197
- return false;
198
- }
199
- });
200
- }
201
- var PARAM_VALIDATORS = {
202
- REPO_URL: (value) => typeof value === "string" && isSafeRepository(value),
203
- GSC_PROPERTY: (value) => typeof value === "string" && isSafeGscProperty(value),
204
- BRANCH: (value) => typeof value === "string" && SAFE_GIT_REF.test(value),
205
- SCOPE_PATH: (value) => typeof value === "string" && SAFE_RELATIVE_PATH.test(value),
206
- REPORT_NAME: (value) => typeof value === "string" && value.trim() === value && SAFE_REPORT_NAME.test(value),
207
- TAG_PATTERN: (value) => typeof value === "string" && SAFE_TAG_PATTERN.test(value),
208
- AGENT_FS_ORG_ID: (value) => typeof value === "string" && SAFE_EXTERNAL_ID.test(value),
209
- ORG_ID: (value) => typeof value === "string" && SAFE_EXTERNAL_ID.test(value)
210
- };
211
- function isUsableParam(key, value) {
212
- if (!hasValue(value))
213
- return false;
214
- return PARAM_VALIDATORS[key]?.(value) ?? true;
215
- }
216
- function uniqueSorted(values) {
217
- return [...new Set(values)].sort((a, b) => a.localeCompare(b));
218
- }
219
- function automationUrl(input) {
220
- return input.kind === "schedule" ? `/schedules/${input.id}` : `/workflows/${input.id}`;
221
- }
222
- var INTEGRATION_FIX_URL = {
223
- slack: "/settings/integrations/slack",
224
- github: "/settings/integrations/github",
225
- linear: "/settings/integrations/linear",
226
- jira: "/settings/integrations/jira",
227
- agentmail: "/settings/integrations/agentmail",
228
- gsc: "/settings/integrations/gsc",
229
- agentfs: "/settings/integrations/agentfs"
230
- };
231
- var INTEGRATION_FIRST_REQUIREMENTS = new Set([
232
- "gsc",
233
- "linear",
234
- "agentmail"
235
- ]);
236
- function automationIntegrationFixUrl(id) {
237
- return INTEGRATION_FIX_URL[id];
238
- }
239
- function isNeedsSetupFailure(reason) {
240
- return reason?.startsWith(NEEDS_SETUP_PREFIX) ?? false;
241
- }
242
- function preflightAutomation(input, setup) {
243
- const params = input.params ?? {};
244
- const guardedParams = Object.keys(params).filter((key) => (key in PARAM_VALIDATORS));
245
- const missingParams = uniqueSorted([...input.requiredParams ?? [], ...guardedParams].filter((key) => !isUsableParam(key, params[key])));
246
- const missingIntegrations = uniqueSorted((input.requires ?? []).filter((id) => setup[id] === "unverified"));
247
- const missing = { params: missingParams, integrations: missingIntegrations };
248
- const baseUrl = automationUrl(input);
249
- const fixes = [
250
- ...missingParams.map((key) => ({
251
- type: "param",
252
- key,
253
- url: `${baseUrl}?param=${encodeURIComponent(key)}`
254
- })),
255
- ...missingIntegrations.map((key) => ({
256
- type: "integration",
257
- key,
258
- url: automationIntegrationFixUrl(key)
259
- }))
260
- ];
261
- if (missingParams.length === 0 && missingIntegrations.length === 0) {
262
- return {
263
- id: input.id,
264
- name: input.name,
265
- kind: input.kind,
266
- state: "running",
267
- missing,
268
- fixes,
269
- fixUrl: baseUrl
270
- };
271
- }
272
- const firstParam = missingParams[0];
273
- const firstIntegration = (input.requires ?? []).find((id) => missingIntegrations.includes(id));
274
- const integrationFirst = (input.requires ?? []).some((id) => INTEGRATION_FIRST_REQUIREMENTS.has(id));
275
- const fixUrl = integrationFirst && firstIntegration ? automationIntegrationFixUrl(firstIntegration) : firstParam ? `${baseUrl}?param=${encodeURIComponent(firstParam)}` : firstIntegration ? automationIntegrationFixUrl(firstIntegration) : baseUrl;
276
- const failureReason = `${NEEDS_SETUP_PREFIX} params=[${missingParams.join(",")}] integrations=[${missingIntegrations.join(",")}]`;
277
- return {
278
- id: input.id,
279
- name: input.name,
280
- kind: input.kind,
281
- state: "needs_setup",
282
- missing,
283
- fixes,
284
- fixUrl,
285
- failureReason
286
- };
287
- }
288
- var EXACT_TOKEN = /^\{\{\s*([^}]+?)\s*\}\}$/;
289
- var TOKEN = /\{\{\s*([^}]+?)\s*\}\}/g;
290
- function renderAutomationTokens(value, params) {
291
- function render(current) {
292
- if (typeof current === "string") {
293
- const exact = EXACT_TOKEN.exec(current);
294
- if (exact?.[1] && Object.hasOwn(params, exact[1]))
295
- return params[exact[1]];
296
- return current.replace(TOKEN, (token, key) => {
297
- if (!Object.hasOwn(params, key))
298
- return token;
299
- const replacement = params[key];
300
- return typeof replacement === "object" ? JSON.stringify(replacement) : String(replacement);
301
- });
302
- }
303
- if (Array.isArray(current))
304
- return current.map(render);
305
- if (current != null && typeof current === "object") {
306
- return Object.fromEntries(Object.entries(current).map(([key, nested]) => [key, render(nested)]));
307
- }
308
- return current;
309
- }
310
- return render(value);
311
- }
312
- function enabled(flag) {
313
- return flag !== "true" && flag !== "1";
314
- }
315
- function present(value) {
316
- return !!value?.trim();
317
- }
318
- async function getAutomationSetupStates() {
319
- const [linearTokens, jiraTokens, jiraApp] = await Promise.all([
320
- getOAuthTokens("linear"),
321
- getOAuthTokens("jira"),
322
- getOAuthApp("jira")
323
- ]);
324
- let jiraCloudId = false;
325
- try {
326
- const metadata = jiraApp?.metadata ? JSON.parse(jiraApp.metadata) : null;
327
- jiraCloudId = !!(metadata && typeof metadata === "object" && metadata.cloudId);
328
- } catch {
329
- jiraCloudId = false;
330
- }
331
- const agentFsKey = process.env.API_AGENT_FS_API_KEY ?? process.env.AGENT_FS_API_KEY;
332
- const agentFsOrg = process.env.AGENT_FS_DEFAULT_ORG_ID ?? process.env.AGENT_FS_SHARED_ORG_ID;
333
- const gscConfigured = present(process.env.GSC_SERVICE_ACCOUNT_BASE64) || present(process.env.GSC_SERVICE_ACCOUNT_JSON);
334
- return {
335
- slack: enabled(process.env.SLACK_DISABLE) && present(process.env.SLACK_BOT_TOKEN) && present(process.env.SLACK_APP_TOKEN) ? "verified" : "unverified",
336
- github: present(process.env.GITHUB_WEBHOOK_SECRET) && present(process.env.GITHUB_APP_ID) && present(process.env.GITHUB_APP_PRIVATE_KEY) ? "verified" : "unverified",
337
- linear: linearTokens ? "verified" : "unverified",
338
- jira: jiraTokens && jiraCloudId ? "verified" : "unverified",
339
- gsc: gscConfigured ? "verified" : "unverified",
340
- agentmail: enabled(process.env.AGENTMAIL_DISABLE) && present(process.env.AGENTMAIL_API_KEY) ? "verified" : "unverified",
341
- agentfs: present(process.env.AGENT_FS_API_URL) && present(agentFsKey) && present(agentFsOrg) && present(process.env.AGENT_FS_DEFAULT_DRIVE_ID) ? "verified" : "unverified"
342
- };
343
- }
344
- function schedulePreflightInput(schedule) {
345
- return { ...schedule, kind: "schedule" };
346
- }
347
- function workflowPreflightInput(workflow) {
348
- return { ...workflow, kind: "workflow" };
349
- }
350
- async function listEnabledAutomationPreflightInputs() {
351
- const rows = await getDbClient().query(`SELECT id, name, 'schedule' AS kind, params, requiredParams, requires
352
- FROM scheduled_tasks
353
- WHERE enabled = 1
354
- UNION ALL
355
- SELECT id, name, 'workflow' AS kind, params, requiredParams, requires
356
- FROM workflows
357
- WHERE enabled = 1`);
358
- return rows.map((row) => ({
359
- id: row.id,
360
- name: row.name,
361
- kind: row.kind,
362
- params: JSON.parse(row.params),
363
- requiredParams: JSON.parse(row.requiredParams),
364
- requires: JSON.parse(row.requires)
365
- }));
366
- }
367
- function utcDayBounds(now) {
368
- const start = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));
369
- return { start: start.toISOString(), end: new Date(start.getTime() + 86400000).toISOString() };
370
- }
371
- async function recordSchedulePreflightFailure(scheduleId, failureReason, now = new Date) {
372
- return await getDbClient().transaction(async (tx) => {
373
- const row = await tx.get("SELECT lastErrorAt, lastErrorMessage FROM scheduled_tasks WHERE id = ?", [scheduleId]);
374
- if (!row)
375
- return false;
376
- if (row.lastErrorMessage === failureReason && row.lastErrorAt?.slice(0, 10) === now.toISOString().slice(0, 10)) {
377
- return false;
378
- }
379
- await tx.run(`UPDATE scheduled_tasks
380
- SET lastErrorAt = ?, lastErrorMessage = ?, lastUpdatedAt = ?
381
- WHERE id = ?`, [now.toISOString(), failureReason, now.toISOString(), scheduleId]);
382
- return true;
383
- });
384
- }
385
- async function recordWorkflowPreflightFailure(input) {
386
- const now = input.now ?? new Date;
387
- const bounds = utcDayBounds(now);
388
- return await getDbClient().transaction(async (tx) => {
389
- const existing = await tx.get(`SELECT id FROM workflow_runs
390
- WHERE workflowId = ? AND status = 'failed'
391
- AND error LIKE 'needs_setup:%'
392
- AND startedAt >= ? AND startedAt < ?
393
- ORDER BY startedAt ASC LIMIT 1`, [input.workflowId, bounds.start, bounds.end]);
394
- if (existing)
395
- return { runId: existing.id, recorded: false };
396
- const runId = crypto.randomUUID();
397
- const timestamp = now.toISOString();
398
- await tx.run(`INSERT INTO workflow_runs
399
- (id, workflowId, status, triggerType, triggerData, error, created_by,
400
- startedAt, lastUpdatedAt, finishedAt)
401
- VALUES (?, ?, 'failed', ?, ?, ?, ?, ?, ?, ?)`, [
402
- runId,
403
- input.workflowId,
404
- input.triggerType,
405
- input.triggerData === undefined ? null : JSON.stringify(input.triggerData),
406
- input.failureReason,
407
- input.createdBy ?? null,
408
- timestamp,
409
- timestamp,
410
- timestamp
411
- ]);
412
- return { runId, recorded: true };
413
- });
414
- }
415
-
416
- // src/workflows/engine.ts
417
- init_db();
418
- init_telemetry();
419
-
420
- // src/workflows/checkpoint.ts
421
- init_db();
422
- async function checkpointStep(runId, stepId, nodeId, result, ctx) {
423
- await getDbClient().transaction(async () => {
424
- await updateWorkflowRunStep(stepId, {
425
- status: "completed",
426
- output: result.output,
427
- nextPort: result.nextPort || undefined,
428
- finishedAt: new Date().toISOString()
429
- });
430
- ctx[nodeId] = result.output;
431
- await updateWorkflowRun(runId, {
432
- context: ctx
433
- });
434
- });
435
- }
436
- async function checkpointStepFailure(runId, stepId, error, retryCount, retryPolicy, options) {
437
- const now = new Date().toISOString();
438
- if (retryPolicy && retryCount < retryPolicy.maxRetries) {
439
- const delay = calculateBackoff(retryPolicy, retryCount);
440
- const nextRetryAt = new Date(Date.now() + delay).toISOString();
441
- await updateWorkflowRunStep(stepId, {
442
- status: "failed",
443
- error,
444
- retryCount: retryCount + 1,
445
- maxRetries: retryPolicy.maxRetries,
446
- nextRetryAt
447
- });
448
- return { shouldRetry: true };
449
- }
450
- await updateWorkflowRunStep(stepId, {
451
- status: "failed",
452
- error,
453
- finishedAt: now,
454
- nextRetryAt: null
455
- });
456
- const markRunFailed = options?.markRunFailed ?? true;
457
- if (markRunFailed) {
458
- await updateWorkflowRun(runId, {
459
- status: "failed",
460
- error: `Step failed: ${error}`,
461
- finishedAt: now
462
- });
463
- }
464
- return { shouldRetry: false };
465
- }
466
- async function checkpointStepWaiting(runId, stepId, ctx) {
467
- return getDbClient().transaction(async () => {
468
- const step = await getDbClient().get(`UPDATE workflow_run_steps
469
- SET status = 'waiting'
470
- WHERE id = ? AND runId = ? AND status = 'running'
471
- AND EXISTS (
472
- SELECT 1 FROM workflow_runs
473
- WHERE id = ? AND status IN ('running', 'waiting')
474
- )
475
- RETURNING id`, [stepId, runId, runId]);
476
- if (!step)
477
- return false;
478
- await updateWorkflowRun(runId, { status: "waiting", context: ctx });
479
- return true;
480
- });
481
- }
482
- function calculateBackoff(policy, attempt) {
483
- let delay;
484
- switch (policy.strategy) {
485
- case "exponential": {
486
- const base = policy.baseDelayMs * 2 ** attempt;
487
- delay = Math.random() * Math.min(base, policy.maxDelayMs);
488
- break;
489
- }
490
- case "linear":
491
- delay = policy.baseDelayMs * (attempt + 1);
492
- break;
493
- case "static":
494
- delay = policy.baseDelayMs;
495
- break;
496
- default:
497
- delay = policy.baseDelayMs;
498
- }
499
- return Math.min(delay, policy.maxDelayMs);
500
- }
501
-
502
- // src/workflows/completed-step-routing.ts
503
- init_db();
504
-
505
- // src/workflows/foreach-join.ts
506
- init_db();
507
- var FOREACH_TERMINAL_STEP_STATUSES = new Set([
508
- "completed",
509
- "failed",
510
- "cancelled",
511
- "skipped"
512
- ]);
513
- function parseSyntheticNodeId(nodeId) {
514
- const separatorIndex = nodeId.indexOf("#");
515
- if (separatorIndex <= 0 || separatorIndex === nodeId.length - 1)
516
- return null;
517
- return {
518
- parentNodeId: nodeId.slice(0, separatorIndex),
519
- itemKey: nodeId.slice(separatorIndex + 1)
520
- };
521
- }
522
- function resolveForeachParent(def, nodeId) {
523
- const parsed = parseSyntheticNodeId(nodeId);
524
- if (!parsed)
525
- return null;
526
- const parent = def.nodes.find((node) => node.id === parsed.parentNodeId);
527
- return parent?.type === "foreach" ? parent : null;
528
- }
529
- async function joinForeach(def, runId, childStep, ctx) {
530
- const parent = resolveForeachParent(def, childStep.nodeId);
531
- if (!parent) {
532
- throw new Error(`Step "${childStep.nodeId}" is not a foreach child`);
533
- }
534
- const children = (await getWorkflowRunStepsByRunId(runId)).filter((step) => resolveForeachParent(def, step.nodeId)?.id === parent.id).sort((a, b) => childIndex(a) - childIndex(b));
535
- if (children.some((step) => !FOREACH_TERMINAL_STEP_STATUSES.has(step.status))) {
536
- return { joined: false, parentNodeId: parent.id, successors: [] };
537
- }
538
- const parentStep = await getLatestStepForNode(runId, parent.id);
539
- if (!parentStep) {
540
- throw new Error(`Waiting foreach parent step "${parent.id}" was not found`);
541
- }
542
- if (parentStep.status === "completed") {
543
- return { joined: false, parentNodeId: parent.id, successors: [] };
544
- }
545
- const aggregate = buildForeachAggregate(children);
546
- await checkpointStep(runId, parentStep.id, parent.id, { output: aggregate }, ctx);
547
- return {
548
- joined: true,
549
- parentNodeId: parent.id,
550
- successors: getSuccessors(def, parent.id)
551
- };
552
- }
553
- function buildForeachAggregate(children) {
554
- const orderedChildren = [...children].sort((a, b) => childIndex(a) - childIndex(b));
555
- const results = orderedChildren.map((step) => {
556
- const parsed = parseSyntheticNodeId(step.nodeId);
557
- const status = resultStatus(step);
558
- return {
559
- itemKey: parsed.itemKey,
560
- status,
561
- output: step.output
562
- };
563
- });
564
- const failedCount = results.filter((result) => result.status !== "completed").length;
565
- const aggregate = {
566
- results,
567
- okCount: results.length - failedCount,
568
- failedCount
569
- };
570
- return aggregate;
571
- }
572
- function childIndex(step) {
573
- if (typeof step.input !== "object" || step.input === null)
574
- return Number.MAX_SAFE_INTEGER;
575
- const index = step.input.index;
576
- return typeof index === "number" ? index : Number.MAX_SAFE_INTEGER;
577
- }
578
- function resultStatus(step) {
579
- if (step.status !== "completed")
580
- return step.status;
581
- return step.error ? "failed" : "completed";
582
- }
583
-
584
- // src/workflows/completed-step-routing.ts
585
- async function loadCompletedStepRouting(def, runId, completedNodeIds) {
586
- const activeEdges = new Set;
587
- const stepsByNodeId = new Map;
588
- for (const nodeId of completedNodeIds) {
589
- if (resolveForeachParent(def, nodeId))
590
- continue;
591
- const step = await getLatestStepForNode(runId, nodeId);
592
- if (step)
593
- stepsByNodeId.set(nodeId, step);
594
- const successors = step?.nextPort ? getSuccessors(def, nodeId, step.nextPort) : getSuccessors(def, nodeId);
595
- for (const successor of successors) {
596
- activeEdges.add(`${nodeId}→${successor.id}`);
597
- }
598
- }
599
- return { activeEdges, stepsByNodeId };
600
- }
601
-
602
- // src/workflows/cooldown.ts
603
- init_db();
604
- function cooldownToMs(cooldown) {
605
- let ms = 0;
606
- if (cooldown.hours)
607
- ms += cooldown.hours * 60 * 60 * 1000;
608
- if (cooldown.minutes)
609
- ms += cooldown.minutes * 60 * 1000;
610
- if (cooldown.seconds)
611
- ms += cooldown.seconds * 1000;
612
- return ms;
613
- }
614
- async function shouldSkipCooldown(workflowId, cooldown) {
615
- const cooldownMs = cooldownToMs(cooldown);
616
- const now = Date.now();
617
- const lastSuccess = await getLastSuccessfulRun(workflowId);
618
- if (lastSuccess?.finishedAt) {
619
- const lastFinished = new Date(lastSuccess.finishedAt).getTime();
620
- if (now - lastFinished < cooldownMs)
621
- return true;
622
- }
623
- const lastAttempt = await getLastRunStart(workflowId);
624
- if (lastAttempt?.startedAt) {
625
- const lastStarted = new Date(lastAttempt.startedAt).getTime();
626
- if (now - lastStarted < cooldownMs)
627
- return true;
628
- }
629
- return false;
630
- }
631
-
632
- // src/workflows/input.ts
633
- init_db();
634
- init_secret_scrubber();
635
- async function resolveInputs(input) {
636
- const resolved = {};
637
- for (const [key, value] of Object.entries(input)) {
638
- resolved[key] = await resolveInputValue(value);
639
- }
640
- return resolved;
641
- }
642
- async function resolveInputValue(value) {
643
- const envMatch = /^\$\{(.+)\}$/.exec(value);
644
- if (envMatch?.[1]) {
645
- const envName = envMatch[1];
646
- const envValue = process.env[envName];
647
- if (envValue === undefined) {
648
- throw new Error(`Environment variable "${envName}" is not set`);
649
- }
650
- return envValue;
651
- }
652
- if (value.startsWith("secret.")) {
653
- const secretName = value.slice("secret.".length);
654
- const configs = await getSwarmConfigs({ scope: "global", key: secretName });
655
- const secretConfig = configs.find((c) => c.isSecret);
656
- if (!secretConfig) {
657
- throw new Error(`Secret "${secretName}" not found in config store`);
658
- }
659
- return secretConfig.value;
660
- }
661
- return value;
662
- }
663
- var REDACTED_SECRET_VALUE = "***REDACTED***";
664
- function getSecretInputKeys(input) {
665
- const keys = new Set;
666
- if (!input)
667
- return keys;
668
- for (const [key, value] of Object.entries(input)) {
669
- if (typeof value !== "string")
670
- continue;
671
- if (value.startsWith("secret.")) {
672
- keys.add(key);
673
- continue;
674
- }
675
- const envMatch = /^\$\{(.+)\}$/.exec(value);
676
- if (envMatch?.[1] && isSensitiveKey(envMatch[1])) {
677
- keys.add(key);
678
- }
679
- }
680
- return keys;
681
- }
682
- function redactSecretsForStorage(ctx, secretKeys) {
683
- if (secretKeys.size === 0)
684
- return ctx;
685
- const inputBlock = ctx.input;
686
- if (!inputBlock || typeof inputBlock !== "object")
687
- return ctx;
688
- const redactedInput = { ...inputBlock };
689
- let touched = false;
690
- for (const key of secretKeys) {
691
- if (key in redactedInput) {
692
- redactedInput[key] = REDACTED_SECRET_VALUE;
693
- touched = true;
694
- }
695
- }
696
- if (!touched)
697
- return ctx;
698
- return { ...ctx, input: redactedInput };
699
- }
700
-
701
- // src/workflows/json-schema-validator.ts
702
- function validateJsonSchema(schema, data) {
703
- const errors = [];
704
- validate(schema, data, "", errors);
705
- return errors;
706
- }
707
- function validate(schema, data, path, errors) {
708
- const prefix = path || "root";
709
- if (schema.type !== undefined) {
710
- const expected = schema.type;
711
- if (!matchesType(expected, data)) {
712
- errors.push(`${prefix}: expected type "${expected}", got ${typeOf(data)}`);
713
- return;
714
- }
715
- }
716
- if (Array.isArray(schema.required) && typeof data === "object" && data !== null) {
717
- const obj = data;
718
- for (const key of schema.required) {
719
- if (!(key in obj)) {
720
- errors.push(`${prefix}: missing required property "${key}"`);
721
- }
722
- }
723
- }
724
- if (Array.isArray(schema.enum)) {
725
- const allowed = schema.enum;
726
- if (!allowed.some((v) => JSON.stringify(v) === JSON.stringify(data))) {
727
- errors.push(`${prefix}: value ${JSON.stringify(data)} not in enum [${allowed.map((v) => JSON.stringify(v)).join(", ")}]`);
728
- return;
729
- }
730
- }
731
- if ("const" in schema) {
732
- if (JSON.stringify(data) !== JSON.stringify(schema.const)) {
733
- errors.push(`${prefix}: value ${JSON.stringify(data)} does not match const ${JSON.stringify(schema.const)}`);
734
- return;
735
- }
736
- }
737
- if (schema.properties !== undefined && typeof schema.properties === "object" && schema.properties !== null && typeof data === "object" && data !== null) {
738
- const props = schema.properties;
739
- const obj = data;
740
- for (const [key, subSchema] of Object.entries(props)) {
741
- if (key in obj) {
742
- validate(subSchema, obj[key], path ? `${path}.${key}` : key, errors);
743
- }
744
- }
745
- }
746
- if (schema.items !== undefined && typeof schema.items === "object" && schema.items !== null && Array.isArray(data)) {
747
- const itemSchema = schema.items;
748
- for (let i = 0;i < data.length; i++) {
749
- validate(itemSchema, data[i], `${prefix}[${i}]`, errors);
750
- }
751
- }
752
- }
753
- function matchesType(expected, data) {
754
- switch (expected) {
755
- case "string":
756
- return typeof data === "string";
757
- case "number":
758
- return typeof data === "number";
759
- case "boolean":
760
- return typeof data === "boolean";
761
- case "array":
762
- return Array.isArray(data);
763
- case "object":
764
- return typeof data === "object" && data !== null && !Array.isArray(data);
765
- default:
766
- return true;
767
- }
768
- }
769
- function typeOf(data) {
770
- if (data === null)
771
- return "null";
772
- if (data === undefined)
773
- return "undefined";
774
- if (Array.isArray(data))
775
- return "array";
776
- return typeof data;
777
- }
778
- function isPlainObject(value) {
779
- return typeof value === "object" && value !== null && !Array.isArray(value);
780
- }
781
- function findJsonSchemaShapeErrors(schema, path = "outputSchema") {
782
- const errors = [];
783
- if (!isPlainObject(schema)) {
784
- errors.push(`${path}: must be a JSON Schema object, got ${typeOf(schema)}`);
785
- return errors;
786
- }
787
- if (schema.type !== undefined && typeof schema.type !== "string") {
788
- errors.push(`${path}.type: must be a string, got ${typeOf(schema.type)}`);
789
- }
790
- if (schema.required !== undefined && (!Array.isArray(schema.required) || !schema.required.every((v) => typeof v === "string"))) {
791
- errors.push(`${path}.required: must be an array of strings`);
792
- }
793
- if (schema.enum !== undefined && !Array.isArray(schema.enum)) {
794
- errors.push(`${path}.enum: must be an array`);
795
- }
796
- if (schema.properties !== undefined) {
797
- if (!isPlainObject(schema.properties)) {
798
- errors.push(`${path}.properties: must be an object mapping property names to schemas`);
799
- } else {
800
- for (const [key, value] of Object.entries(schema.properties)) {
801
- errors.push(...findJsonSchemaShapeErrors(value, `${path}.properties.${key}`));
802
- }
803
- }
804
- }
805
- if (schema.items !== undefined) {
806
- errors.push(...findJsonSchemaShapeErrors(schema.items, `${path}.items`));
807
- }
808
- return errors;
809
- }
810
-
811
- // src/workflows/limits.ts
812
- var DEFAULT_MAX_STEPS_PER_RUN = 500;
813
- function getMaxWorkflowStepsPerRun() {
814
- return Number(process.env.WORKFLOW_MAX_STEPS_PER_RUN) || DEFAULT_MAX_STEPS_PER_RUN;
815
- }
816
-
817
- // src/workflows/template.ts
818
- init_template();
819
-
820
- // src/workflows/validation.ts
821
- function extractPassResult(executorType, output) {
822
- if (!output || typeof output !== "object")
823
- return false;
824
- const o = output;
825
- switch (executorType) {
826
- case "validate":
827
- return o.pass === true;
828
- case "script":
829
- return o.exitCode === 0;
830
- case "property-match":
831
- return o.passed === true;
832
- case "raw-llm":
833
- if (typeof o.result === "object" && o.result !== null) {
834
- return o.result.pass === true;
835
- }
836
- return false;
837
- default:
838
- return o.pass === true || o.passed === true || o.exitCode === 0;
839
- }
840
- }
841
- async function runStepValidation(registry, stepNode, stepOutput, context, meta) {
842
- if (!stepNode.validation) {
843
- return { outcome: "pass" };
844
- }
845
- const validation = stepNode.validation;
846
- const executorType = validation.executor || "validate";
847
- const executor = registry.get(executorType);
848
- const validationConfig = {
849
- targetNodeId: meta.nodeId,
850
- ...validation.config
851
- };
852
- const validationContext = {
853
- ...context,
854
- [meta.nodeId]: stepOutput
855
- };
856
- const result = await executor.run({
857
- config: validationConfig,
858
- context: validationContext,
859
- meta: {
860
- ...meta,
861
- stepId: crypto.randomUUID()
862
- }
863
- });
864
- const passed = result.status === "success" && extractPassResult(executorType, result.output);
865
- if (passed) {
866
- return { outcome: "pass", passed: true };
867
- }
868
- if (validation.mustPass) {
869
- if (validation.retry) {
870
- return {
871
- outcome: "retry",
872
- passed: false,
873
- retryContext: {
874
- previousOutput: stepOutput,
875
- validationResult: result.output
876
- }
877
- };
878
- }
879
- return { outcome: "halt", passed: false };
880
- }
881
- return { outcome: "pass", passed: false };
882
- }
883
-
884
- // src/workflows/engine.ts
885
- var DEFAULT_TIMEOUT_MS = 30000;
886
- function maxIterations() {
887
- return Number(process.env.WORKFLOW_MAX_ITERATIONS) || 100;
888
- }
889
-
890
- class TriggerSchemaError extends Error {
891
- validationErrors;
892
- constructor(validationErrors) {
893
- super(`Trigger schema validation failed: ${validationErrors.join("; ")}`);
894
- this.validationErrors = validationErrors;
895
- this.name = "TriggerSchemaError";
896
- }
897
- }
898
- async function resolveRenderedWorkflowInputs(input) {
899
- const stringInputs = {};
900
- const resolved = {};
901
- for (const [key, value] of Object.entries(input)) {
902
- if (typeof value === "string")
903
- stringInputs[key] = value;
904
- else
905
- resolved[key] = value;
906
- }
907
- return { ...resolved, ...await resolveInputs(stringInputs) };
908
- }
909
- async function startWorkflowExecution(workflow, triggerData, registry, options = {}) {
910
- const preflight = preflightAutomation(workflowPreflightInput(workflow), await getAutomationSetupStates());
911
- if (preflight.state === "needs_setup") {
912
- const { runId: runId2, recorded } = await recordWorkflowPreflightFailure({
913
- workflowId: workflow.id,
914
- triggerType: options.triggerType ?? "manual",
915
- triggerData,
916
- failureReason: preflight.failureReason,
917
- createdBy: options.requestedByUserId
918
- });
919
- if (recorded)
920
- await notifyAutomationPreflightFailure(preflight);
921
- return runId2;
922
- }
923
- workflow = renderAutomationTokens(workflow, workflow.params ?? {});
924
- if (workflow.triggerSchema) {
925
- const validationErrors = validateJsonSchema(workflow.triggerSchema, triggerData);
926
- if (validationErrors.length > 0) {
927
- throw new TriggerSchemaError(validationErrors);
928
- }
929
- }
930
- if (workflow.cooldown && await shouldSkipCooldown(workflow.id, workflow.cooldown)) {
931
- const runId2 = crypto.randomUUID();
932
- await createWorkflowRun({
933
- id: runId2,
934
- workflowId: workflow.id,
935
- triggerType: options.triggerType ?? "manual",
936
- triggerData,
937
- createdBy: options.requestedByUserId
938
- });
939
- await updateWorkflowRun(runId2, {
940
- status: "skipped",
941
- error: "cooldown",
942
- finishedAt: new Date().toISOString()
943
- });
944
- return runId2;
945
- }
946
- const runId = crypto.randomUUID();
947
- await createWorkflowRun({
948
- id: runId,
949
- workflowId: workflow.id,
950
- triggerType: options.triggerType ?? "manual",
951
- triggerData,
952
- createdBy: options.requestedByUserId
953
- });
954
- telemetry.workflow("started", {
955
- workflowId: workflow.id,
956
- nodeCount: workflow.definition.nodes.length,
957
- triggerType: options.triggerType ?? "manual"
958
- });
959
- const ctx = { trigger: triggerData };
960
- if (options.requestedByUserId) {
961
- ctx.swarm = { requestedByUserId: options.requestedByUserId };
962
- }
963
- if (workflow.dir || workflow.vcsRepo) {
964
- ctx.workflow = { dir: workflow.dir, vcsRepo: workflow.vcsRepo };
965
- }
966
- if (workflow.input) {
967
- try {
968
- const resolved = await resolveRenderedWorkflowInputs(workflow.input);
969
- Object.assign(ctx, { input: resolved });
970
- } catch (err) {
971
- await updateWorkflowRun(runId, {
972
- status: "failed",
973
- error: `Input resolution failed: ${err}`,
974
- finishedAt: new Date().toISOString()
975
- });
976
- return runId;
977
- }
978
- }
979
- const entryNodes = findEntryNodes(workflow.definition);
980
- const secretKeys = getSecretInputKeys(workflow.input);
981
- await walkGraph(workflow.definition, runId, ctx, entryNodes, registry, workflow.id, secretKeys, options);
982
- return runId;
983
- }
984
- async function walkGraph(def, runId, ctx, startNodes, registry, workflowId, secretKeys = new Set, options = {}) {
985
- if (!options.requestedByUserId) {
986
- options = {
987
- ...options,
988
- requestedByUserId: (await getWorkflowRun(runId))?.createdBy
989
- };
990
- }
991
- let nodeExecutionCount = 0;
992
- if (!("run" in ctx)) {
993
- ctx.run = { id: runId };
994
- }
995
- const { completedNodeIds } = await rehydrateCompletedStepOutputs(def, runId, ctx, registry);
996
- const { activeEdges } = await loadCompletedStepRouting(def, runId, completedNodeIds);
997
- const allSteps = await getWorkflowRunStepsByRunId(runId);
998
- if (allSteps.length >= getMaxWorkflowStepsPerRun()) {
999
- await updateWorkflowRun(runId, {
1000
- status: "failed",
1001
- error: `Circuit breaker: run exceeded ${getMaxWorkflowStepsPerRun()} total steps (WORKFLOW_MAX_STEPS_PER_RUN)`,
1002
- finishedAt: new Date().toISOString()
1003
- });
1004
- return;
1005
- }
1006
- for (const step of allSteps) {
1007
- if (step.status !== "waiting")
1008
- continue;
1009
- const successors = getSuccessors(def, step.nodeId);
1010
- for (const succ of successors) {
1011
- activeEdges.add(`${step.nodeId}→${succ.id}`);
1012
- }
1013
- }
1014
- let pendingNodes = startNodes.filter((n) => {
1015
- const preds = getAllPredecessors(def, n.id);
1016
- if (preds.length === 0) {
1017
- return !completedNodeIds.has(n.id);
1018
- }
1019
- const activePreds = preds.filter((predId) => activeEdges.has(`${predId}→${n.id}`));
1020
- const predsToCheck = activePreds.length > 0 ? activePreds : preds;
1021
- return predsToCheck.every((p) => completedNodeIds.has(p));
1022
- });
1023
- const executedInThisWalk = new Set;
1024
- while (pendingNodes.length > 0) {
1025
- nodeExecutionCount += pendingNodes.length;
1026
- if (nodeExecutionCount > maxIterations()) {
1027
- await updateWorkflowRun(runId, {
1028
- status: "failed",
1029
- error: `Max node executions (${maxIterations()}) exceeded — possible infinite loop`,
1030
- finishedAt: new Date().toISOString()
1031
- });
1032
- return;
1033
- }
1034
- const results = await Promise.all(pendingNodes.map((node) => executeStep(def, runId, ctx, node, registry, workflowId, secretKeys, options).catch((_err) => ({
1035
- outcome: "failed",
1036
- successors: []
1037
- }))));
1038
- const nextBatch = new Map;
1039
- let hasWaiting = false;
1040
- for (let i = 0;i < results.length; i++) {
1041
- const result = results[i];
1042
- if (result.outcome === "failed") {
1043
- const currentRun = await getWorkflowRun(runId);
1044
- if (currentRun?.status === "failed")
1045
- return;
1046
- continue;
1047
- }
1048
- if (result.outcome === "waiting") {
1049
- hasWaiting = true;
1050
- continue;
1051
- }
1052
- const sourceNodeId = pendingNodes[i].id;
1053
- completedNodeIds.add(sourceNodeId);
1054
- executedInThisWalk.add(sourceNodeId);
1055
- for (const succ of result.successors) {
1056
- activeEdges.add(`${sourceNodeId}→${succ.id}`);
1057
- nextBatch.set(succ.id, succ);
1058
- }
1059
- }
1060
- if (hasWaiting)
1061
- return;
1062
- const readyNext = [];
1063
- for (const [nodeId, node] of nextBatch) {
1064
- if (executedInThisWalk.has(nodeId))
1065
- continue;
1066
- const allPreds = getAllPredecessors(def, nodeId);
1067
- const activePreds = allPreds.filter((predId) => activeEdges.has(`${predId}→${nodeId}`));
1068
- const allActivePredsCompleted = activePreds.every((p) => completedNodeIds.has(p));
1069
- if (allActivePredsCompleted) {
1070
- readyNext.push(node);
1071
- }
1072
- }
1073
- pendingNodes = readyNext;
1074
- }
1075
- await getDbClient().transaction(async () => {
1076
- const run = await getWorkflowRun(runId);
1077
- if (!run || run.status !== "running")
1078
- return;
1079
- const finalSteps = await getWorkflowRunStepsByRunId(runId);
1080
- const hasWaitingSteps = finalSteps.some((s) => s.status === "waiting");
1081
- const hasPendingRetries = finalSteps.some((s) => s.status === "failed" && s.nextRetryAt != null);
1082
- const failedSteps = finalSteps.filter((s) => s.status === "failed" && s.nextRetryAt == null);
1083
- const entryNodeIds = new Set(findEntryNodes(def).map((n) => n.id));
1084
- const hasCompletedSteps = finalSteps.some((s) => s.status === "completed" && !entryNodeIds.has(s.nodeId));
1085
- if (hasWaitingSteps) {
1086
- await updateWorkflowRun(runId, { status: "waiting" });
1087
- } else if (!hasPendingRetries) {
1088
- if (failedSteps.length > 0 && !hasCompletedSteps) {
1089
- const failedNodeIds = failedSteps.map((s) => s.nodeId).join(", ");
1090
- await updateWorkflowRun(runId, {
1091
- status: "failed",
1092
- error: `All branches failed. Failed nodes: ${failedNodeIds}`,
1093
- context: ctx,
1094
- finishedAt: new Date().toISOString()
1095
- });
1096
- } else if (failedSteps.length > 0) {
1097
- const failedNodeIds = failedSteps.map((s) => s.nodeId).join(", ");
1098
- await updateWorkflowRun(runId, {
1099
- status: "completed",
1100
- error: `Partial failure: nodes [${failedNodeIds}] failed (mustPass validation), but other branches completed successfully`,
1101
- context: ctx,
1102
- finishedAt: new Date().toISOString()
1103
- });
1104
- } else {
1105
- await updateWorkflowRun(runId, {
1106
- status: "completed",
1107
- context: ctx,
1108
- finishedAt: new Date().toISOString()
1109
- });
1110
- }
1111
- }
1112
- });
1113
- }
1114
- async function rehydrateCompletedStepOutputs(def, runId, ctx, registry) {
1115
- const completedNodeIds = new Set(await getCompletedStepNodeIds(runId));
1116
- const latestSteps = new Map;
1117
- for (const nodeId of completedNodeIds) {
1118
- if (resolveForeachParent(def, nodeId))
1119
- continue;
1120
- const step = await getLatestStepForNode(runId, nodeId);
1121
- if (!step)
1122
- continue;
1123
- if (step.output !== undefined) {
1124
- const node = def.nodes.find((candidate) => candidate.id === nodeId);
1125
- if (node && registry.has(node.type)) {
1126
- const executor = registry.get(node.type);
1127
- const parseResult = executor.outputSchema.safeParse(step.output);
1128
- if (!parseResult.success) {
1129
- console.warn(`[workflow] Recovery: step ${nodeId} output failed validation: ${parseResult.error.message}`);
1130
- continue;
1131
- }
1132
- }
1133
- ctx[nodeId] = step.output;
1134
- }
1135
- latestSteps.set(nodeId, step);
1136
- }
1137
- return { completedNodeIds, latestSteps };
1138
- }
1139
- function getAllPredecessors(def, nodeId) {
1140
- const preds = [];
1141
- for (const node of def.nodes) {
1142
- if (!node.next)
1143
- continue;
1144
- if (getNextTargets(node.next).includes(nodeId)) {
1145
- preds.push(node.id);
1146
- }
1147
- }
1148
- return preds;
1149
- }
1150
- async function executeStep(def, runId, ctx, node, registry, workflowId, secretKeys = new Set, options = {}) {
1151
- const dedup = await getDbClient().transaction(async () => {
1152
- const run = await getWorkflowRun(runId);
1153
- if (!run || run.status !== "running" && run.status !== "waiting") {
1154
- return { halted: true };
1155
- }
1156
- const iteration = await getStepCountForNode(runId, node.id);
1157
- const idempotencyKey = `${runId}:${node.id}:${iteration}`;
1158
- const existingStep2 = await getStepByIdempotencyKey(idempotencyKey);
1159
- if (existingStep2 && (existingStep2.status === "completed" || existingStep2.status === "waiting")) {
1160
- return { existingStep: existingStep2, stepId: existingStep2.id, deduped: true };
1161
- }
1162
- const latestForeachStep = node.type === "foreach" ? await getLatestStepForNode(runId, node.id) : undefined;
1163
- const reusableForeachStep = latestForeachStep && !FOREACH_TERMINAL_STEP_STATUSES.has(latestForeachStep.status) ? latestForeachStep : undefined;
1164
- const stepId2 = reusableForeachStep?.id ?? crypto.randomUUID();
1165
- if (!reusableForeachStep) {
1166
- await createWorkflowRunStep({
1167
- id: stepId2,
1168
- runId,
1169
- nodeId: node.id,
1170
- nodeType: node.type,
1171
- input: redactSecretsForStorage(ctx, secretKeys),
1172
- idempotencyKey
1173
- });
1174
- }
1175
- return { existingStep: existingStep2, stepId: stepId2, deduped: false };
1176
- });
1177
- if ("halted" in dedup)
1178
- return { outcome: "completed", successors: [] };
1179
- if (dedup.deduped && dedup.existingStep) {
1180
- if (dedup.existingStep.status === "completed") {
1181
- ctx[node.id] = dedup.existingStep.output;
1182
- const successors2 = getSuccessors(def, node.id);
1183
- return { outcome: "completed", successors: successors2 };
1184
- }
1185
- return { outcome: "waiting", successors: [] };
1186
- }
1187
- const existingStep = dedup.existingStep;
1188
- const stepId = dedup.stepId;
1189
- const executor = registry.get(node.type);
1190
- const interpolationCtx = buildNodeInterpolationCtx(node, ctx);
1191
- if (node.inputSchema) {
1192
- const inputErrors = validateJsonSchema(node.inputSchema, interpolationCtx);
1193
- if (inputErrors.length > 0) {
1194
- const errorMsg = `Input schema validation failed: ${inputErrors.join("; ")}`;
1195
- await checkpointStepFailure(runId, stepId, errorMsg, 0);
1196
- throw new Error(errorMsg);
1197
- }
1198
- }
1199
- const {
1200
- value: interpolatedValue,
1201
- unresolved,
1202
- scriptBodyUnresolved
1203
- } = interpolateNodeConfig(node, interpolationCtx);
1204
- const interpolatedConfig = interpolatedValue;
1205
- const executionCtx = { ...ctx, ...interpolationCtx };
1206
- if (scriptBodyUnresolved && scriptBodyUnresolved.length > 0) {
1207
- const errorMsg = scriptBodyInterpolationError(node.id, scriptBodyUnresolved);
1208
- await checkpointStepFailure(runId, stepId, errorMsg, 0);
1209
- return { outcome: "failed", successors: [] };
1210
- }
1211
- if (unresolved.length > 0) {
1212
- console.warn(`[workflow] Step ${node.id}: unresolved interpolation tokens: ${unresolved.join(", ")}`);
1213
- await updateWorkflowRunStep(stepId, {
1214
- diagnostics: JSON.stringify({ unresolvedTokens: unresolved })
1215
- });
1216
- }
1217
- const meta = {
1218
- runId,
1219
- stepId,
1220
- nodeId: node.id,
1221
- workflowId: workflowId || "",
1222
- dryRun: false,
1223
- requestedByUserId: options.requestedByUserId,
1224
- inputCtx: interpolationCtx
1225
- };
1226
- const configuredTimeoutMs = typeof node.config?.timeoutMs === "number" ? node.config.timeoutMs : node.type === "script" && typeof node.config?.timeout === "number" ? node.config.timeout : undefined;
1227
- const timeoutMs = configuredTimeoutMs ?? DEFAULT_TIMEOUT_MS;
1228
- let result;
1229
- try {
1230
- result = await Promise.race([
1231
- executor.run({
1232
- config: interpolatedConfig,
1233
- context: executionCtx,
1234
- meta
1235
- }),
1236
- timeoutPromise(timeoutMs)
1237
- ]);
1238
- } catch (err) {
1239
- const errorMsg = err instanceof Error ? err.message : String(err);
1240
- const retryPolicy = node.retry || executor.retryPolicy;
1241
- const currentRetryCount = existingStep?.retryCount || 0;
1242
- const { shouldRetry } = await checkpointStepFailure(runId, stepId, errorMsg, currentRetryCount, retryPolicy);
1243
- if (!shouldRetry) {
1244
- throw err;
1245
- }
1246
- return { outcome: "completed", successors: [] };
1247
- }
1248
- if (result.status === "failed") {
1249
- const retryPolicy = node.retry || executor.retryPolicy;
1250
- const currentRetryCount = existingStep?.retryCount || 0;
1251
- const { shouldRetry } = await checkpointStepFailure(runId, stepId, result.error || "Executor returned failed status", currentRetryCount, retryPolicy);
1252
- if (result.output !== undefined) {
1253
- await updateWorkflowRunStep(stepId, { output: result.output });
1254
- }
1255
- if (!shouldRetry) {
1256
- throw new Error(result.error || "Step execution failed");
1257
- }
1258
- return { outcome: "completed", successors: [] };
1259
- }
1260
- if ("async" in result && result.async) {
1261
- const waiting = await checkpointStepWaiting(runId, stepId, ctx);
1262
- return { outcome: waiting ? "waiting" : "completed", successors: [] };
1263
- }
1264
- if (node.outputSchema && result.status === "success") {
1265
- const outputErrors = validateJsonSchema(node.outputSchema, result.output);
1266
- if (outputErrors.length > 0) {
1267
- const errorMsg = `Output schema validation failed: ${outputErrors.join("; ")}`;
1268
- await checkpointStepFailure(runId, stepId, errorMsg, 0);
1269
- throw new Error(errorMsg);
1270
- }
1271
- }
1272
- let validationResult;
1273
- if (node.validation) {
1274
- validationResult = await runStepValidation(registry, node, result.output, executionCtx, meta);
1275
- if (validationResult.outcome === "halt") {
1276
- const errorMsg = "Validation failed (mustPass)";
1277
- await checkpointStepFailure(runId, stepId, errorMsg, 0, undefined, { markRunFailed: false });
1278
- return { outcome: "failed", successors: [] };
1279
- }
1280
- if (validationResult.outcome === "retry") {
1281
- if (validationResult.retryContext) {
1282
- const historyKey = `${node.id}_validations`;
1283
- const existing = ctx[historyKey] || [];
1284
- ctx[historyKey] = [...existing, validationResult.retryContext];
1285
- }
1286
- const retryPolicy = node.validation.retry || node.retry;
1287
- const currentRetryCount = existingStep?.retryCount || 0;
1288
- await checkpointStepFailure(runId, stepId, "Validation failed, retrying", currentRetryCount, retryPolicy);
1289
- return { outcome: "completed", successors: [] };
1290
- }
1291
- }
1292
- if (validationResult?.passed !== undefined && !result.nextPort && node.next && typeof node.next === "object" && !Array.isArray(node.next)) {
1293
- result.nextPort = validationResult.passed ? "pass" : "fail";
1294
- }
1295
- await checkpointStep(runId, stepId, node.id, result, ctx);
1296
- const successors = result.nextPort ? getSuccessors(def, node.id, result.nextPort) : getSuccessors(def, node.id);
1297
- return { outcome: "completed", successors };
1298
- }
1299
- function findReadyNodes(def, completedNodeIds, activeEdges) {
1300
- const predecessors = new Map;
1301
- for (const node of def.nodes) {
1302
- if (!predecessors.has(node.id)) {
1303
- predecessors.set(node.id, new Set);
1304
- }
1305
- }
1306
- for (const node of def.nodes) {
1307
- if (!node.next)
1308
- continue;
1309
- for (const target of getNextTargets(node.next)) {
1310
- if (!predecessors.has(target)) {
1311
- predecessors.set(target, new Set);
1312
- }
1313
- predecessors.get(target).add(node.id);
1314
- }
1315
- }
1316
- return def.nodes.filter((node) => {
1317
- if (completedNodeIds.has(node.id))
1318
- return false;
1319
- const preds = predecessors.get(node.id);
1320
- if (!preds || preds.size === 0)
1321
- return true;
1322
- if (activeEdges) {
1323
- const activePreds = [...preds].filter((predId) => activeEdges.has(`${predId}→${node.id}`));
1324
- if (activePreds.length === 0)
1325
- return false;
1326
- return activePreds.every((pred) => completedNodeIds.has(pred));
1327
- }
1328
- for (const pred of preds) {
1329
- if (!completedNodeIds.has(pred))
1330
- return false;
1331
- }
1332
- return true;
1333
- });
1334
- }
1335
- function buildNodeInterpolationCtx(node, ctx) {
1336
- const interpolationCtx = {};
1337
- if (ctx.trigger !== undefined)
1338
- interpolationCtx.trigger = ctx.trigger;
1339
- if (ctx.input !== undefined)
1340
- interpolationCtx.input = ctx.input;
1341
- if (ctx.workflow !== undefined)
1342
- interpolationCtx.workflow = ctx.workflow;
1343
- if (ctx.swarm !== undefined)
1344
- interpolationCtx.swarm = ctx.swarm;
1345
- if (ctx.run !== undefined)
1346
- interpolationCtx.run = ctx.run;
1347
- if (!node.inputs)
1348
- return interpolationCtx;
1349
- for (const [localName, sourcePath] of Object.entries(node.inputs)) {
1350
- const keys = sourcePath.split(".");
1351
- let value = ctx;
1352
- for (const key of keys) {
1353
- if (value == null || typeof value !== "object") {
1354
- value = undefined;
1355
- break;
1356
- }
1357
- value = value[key];
1358
- }
1359
- interpolationCtx[localName] = value;
1360
- }
1361
- return interpolationCtx;
1362
- }
1363
- var SCRIPT_BODY_ALLOWED_SOURCES = new Set(["input", "workflow", "swarm", "run"]);
1364
- function buildScriptBodyCtx(node, interpolationCtx) {
1365
- const aliases = node.inputs ?? {};
1366
- const safe = {};
1367
- for (const [key, value] of Object.entries(interpolationCtx)) {
1368
- const sourcePath = aliases[key];
1369
- const root = sourcePath === undefined ? key : sourcePath.split(".")[0] ?? "";
1370
- if (SCRIPT_BODY_ALLOWED_SOURCES.has(root))
1371
- safe[key] = value;
1372
- }
1373
- return safe;
1374
- }
1375
- function interpolateNodeConfig(node, interpolationCtx) {
1376
- if (node.type === "foreach" && Object.hasOwn(node.config, "over")) {
1377
- const { over, body, ...configWithoutOverAndBody } = node.config;
1378
- const configResult2 = deepInterpolate(configWithoutOverAndBody, interpolationCtx);
1379
- const overResult = deepInterpolate(over, interpolationCtx, { preserveRawTokens: true });
1380
- return {
1381
- value: {
1382
- ...configResult2.value,
1383
- over: overResult.value,
1384
- body
1385
- },
1386
- unresolved: [...configResult2.unresolved, ...overResult.unresolved]
1387
- };
1388
- }
1389
- if (node.type === "script" && typeof node.config.script === "string") {
1390
- const { script, ...configWithoutScript } = node.config;
1391
- const restResult = deepInterpolate(configWithoutScript, interpolationCtx);
1392
- const scriptResult = deepInterpolate(script, buildScriptBodyCtx(node, interpolationCtx));
1393
- return {
1394
- value: {
1395
- ...restResult.value,
1396
- script: scriptResult.unresolved.length > 0 ? script : scriptResult.value
1397
- },
1398
- unresolved: [...restResult.unresolved, ...scriptResult.unresolved],
1399
- scriptBodyUnresolved: scriptResult.unresolved
1400
- };
1401
- }
1402
- if (node.type !== "swarm-script" || !Object.hasOwn(node.config, "args")) {
1403
- return deepInterpolate(node.config, interpolationCtx);
1404
- }
1405
- const { args, ...configWithoutArgs } = node.config;
1406
- const configResult = deepInterpolate(configWithoutArgs, interpolationCtx);
1407
- const argsResult = deepInterpolate(args, interpolationCtx, { preserveRawTokens: true });
1408
- return {
1409
- value: {
1410
- ...configResult.value,
1411
- args: argsResult.value
1412
- },
1413
- unresolved: [...configResult.unresolved, ...argsResult.unresolved]
1414
- };
1415
- }
1416
- function scriptBodyInterpolationError(nodeId, tokens) {
1417
- const renderedTokens = [...new Set(tokens)].map((token) => `{{${token}}}`).join(", ");
1418
- return `Script body interpolation failed for node "${nodeId}": unresolved or disallowed token(s): ${renderedTokens}. ` + "Executable source may interpolate only input, workflow, swarm, or run values. " + "Pass dynamic trigger or upstream values through config.args and read them from argv.";
1419
- }
1420
- function timeoutPromise(ms) {
1421
- return new Promise((_, reject) => {
1422
- setTimeout(() => reject(new Error(`Step timed out after ${ms}ms`)), ms);
1423
- });
1424
- }
1425
-
1426
- // src/workflows/index.ts
1427
- init_event_bus();
1428
-
1429
- // src/workflows/recovery.ts
1430
- init_db();
1431
-
1432
- // src/workflows/constants.ts
1433
- var FAILED_TASK_OUTPUT_PREFIX = "[FAILED:";
1434
-
1435
- // src/workflows/resume.ts
1436
- init_db();
1437
- init_secret_scrubber();
1438
-
1439
- // src/workflows/approval-notifications.ts
1440
- init_db();
1441
- init_secret_scrubber();
1442
- async function postApprovalCancellationUpdates(approvals, reason, client = getSlackApp()?.client) {
1443
- if (!client)
1444
- return;
1445
- const text = `This approval is no longer actionable: ${reason}`;
1446
- for (const approval of approvals) {
1447
- const channels = approval.notificationChannels;
1448
- const slackThreads = (channels ?? []).filter((notification) => notification.channel === "slack" && notification.messageTs);
1449
- if (slackThreads.length === 0)
1450
- continue;
1451
- for (const notification of slackThreads) {
1452
- if (notification.channel !== "slack" || !notification.messageTs)
1453
- continue;
1454
- const notificationKey = `${notification.target}:${notification.messageTs}`;
1455
- const claim = await claimApprovalCancellationNotification(approval.id, notificationKey);
1456
- if (!claim)
1457
- continue;
1458
- try {
1459
- await client.chat.postMessage({
1460
- channel: notification.target,
1461
- thread_ts: notification.messageTs,
1462
- text,
1463
- unfurl_links: false,
1464
- unfurl_media: false
1465
- });
1466
- await completeApprovalCancellationNotificationClaim(approval.id, notificationKey, claim.leaseToken);
1467
- } catch (error) {
1468
- await releaseApprovalCancellationNotificationClaim(approval.id, notificationKey, claim.leaseToken);
1469
- console.error(`[HITL] Failed to post cancellation update for approval ${approval.id}:`, scrubSecrets(error instanceof Error ? error.message : String(error)));
1470
- }
1471
- }
1472
- }
1473
- }
1474
-
1475
- // src/workflows/resume.ts
1476
- init_event_bus();
1477
-
1478
- // src/workflows/executors/wait.ts
1479
- init_zod();
1480
-
1481
- // src/workflows/wait-filter.ts
1482
- var PARAM_SHADOW_KEYS = [
1483
- "require",
1484
- "process",
1485
- "Bun",
1486
- "globalThis",
1487
- "global",
1488
- "fetch",
1489
- "setTimeout",
1490
- "setInterval"
1491
- ];
1492
- var PARAM_SHADOW_VALUES = PARAM_SHADOW_KEYS.map(() => {
1493
- return;
1494
- });
1495
- var BODY_SHADOW_KEYS = ["eval", "Function", "AsyncFunction"];
1496
- var BODY_SHADOW_PREAMBLE = BODY_SHADOW_KEYS.map((k) => `var ${k} = undefined;`).join(" ");
1497
- var FILTER_TIMEOUT_MS = 50;
1498
- function getByDotPath(obj, path) {
1499
- if (obj === null || typeof obj !== "object") {
1500
- return { found: false, value: undefined };
1501
- }
1502
- const segments = path.split(".");
1503
- let cursor = obj;
1504
- for (const seg of segments) {
1505
- if (cursor === null || typeof cursor !== "object") {
1506
- return { found: false, value: undefined };
1507
- }
1508
- const rec = cursor;
1509
- if (!(seg in rec)) {
1510
- return { found: false, value: undefined };
1511
- }
1512
- cursor = rec[seg];
1513
- }
1514
- return { found: true, value: cursor };
1515
- }
1516
- function deepEqual(a, b) {
1517
- if (a === b)
1518
- return true;
1519
- if (typeof a !== typeof b)
1520
- return false;
1521
- if (a === null || b === null)
1522
- return false;
1523
- if (Array.isArray(a)) {
1524
- if (!Array.isArray(b))
1525
- return false;
1526
- if (a.length !== b.length)
1527
- return false;
1528
- for (let i = 0;i < a.length; i++) {
1529
- if (!deepEqual(a[i], b[i]))
1530
- return false;
1531
- }
1532
- return true;
1533
- }
1534
- if (typeof a === "object" && typeof b === "object") {
1535
- const aKeys = Object.keys(a);
1536
- const bKeys = Object.keys(b);
1537
- if (aKeys.length !== bKeys.length)
1538
- return false;
1539
- for (const k of aKeys) {
1540
- if (!deepEqual(a[k], b[k])) {
1541
- return false;
1542
- }
1543
- }
1544
- return true;
1545
- }
1546
- return false;
1547
- }
1548
- function matchObjectFilter(payload, filter) {
1549
- for (const [key, expected] of Object.entries(filter)) {
1550
- const { found, value } = getByDotPath(payload, key);
1551
- if (!found)
1552
- return false;
1553
- if (!deepEqual(value, expected))
1554
- return false;
1555
- }
1556
- return true;
1557
- }
1558
- async function runWithTimeout(fn) {
1559
- return new Promise((resolve) => {
1560
- let settled = false;
1561
- const timer = setTimeout(() => {
1562
- if (settled)
1563
- return;
1564
- settled = true;
1565
- resolve(null);
1566
- }, FILTER_TIMEOUT_MS);
1567
- queueMicrotask(() => {
1568
- if (settled)
1569
- return;
1570
- try {
1571
- const v = fn();
1572
- if (v !== null && typeof v === "object" && typeof v.then === "function") {
1573
- v.catch(() => {});
1574
- settled = true;
1575
- clearTimeout(timer);
1576
- resolve({ __asyncRejected: true });
1577
- return;
1578
- }
1579
- if (settled)
1580
- return;
1581
- settled = true;
1582
- clearTimeout(timer);
1583
- resolve(v);
1584
- } catch {
1585
- if (settled)
1586
- return;
1587
- settled = true;
1588
- clearTimeout(timer);
1589
- resolve(null);
1590
- }
1591
- });
1592
- });
1593
- }
1594
- var ASYNC_SENTINEL = "__asyncRejected";
1595
- function isAsyncSentinel(value) {
1596
- return value !== null && typeof value === "object" && value[ASYNC_SENTINEL] === true;
1597
- }
1598
- function compileStringFilter(filter) {
1599
- if (filter.length > 2048) {
1600
- throw new Error(`wait filter source exceeds 2KB cap (${filter.length} bytes)`);
1601
- }
1602
- let fn;
1603
- try {
1604
- fn = new Function(...PARAM_SHADOW_KEYS, "payload", `${BODY_SHADOW_PREAMBLE} return (${filter})(payload);`);
1605
- } catch (err) {
1606
- const msg = err instanceof Error ? err.message : String(err);
1607
- throw new Error(`wait filter compile error: ${msg}`);
1608
- }
1609
- return (payload) => fn(...PARAM_SHADOW_VALUES, payload);
1610
- }
1611
- function nullifyPrototypes(value) {
1612
- if (value === null || typeof value !== "object")
1613
- return value;
1614
- if (Array.isArray(value)) {
1615
- return value.map(nullifyPrototypes);
1616
- }
1617
- const out = Object.create(null);
1618
- for (const [k, v] of Object.entries(value)) {
1619
- out[k] = nullifyPrototypes(v);
1620
- }
1621
- return out;
1622
- }
1623
- async function matchesFilter(payload, filter) {
1624
- if (filter === undefined || filter === null)
1625
- return true;
1626
- if (typeof filter === "object" && !Array.isArray(filter)) {
1627
- return matchObjectFilter(payload, filter);
1628
- }
1629
- if (typeof filter === "string") {
1630
- let userFn;
1631
- try {
1632
- userFn = compileStringFilter(filter);
1633
- } catch {
1634
- return false;
1635
- }
1636
- let cloned;
1637
- try {
1638
- const cloneOnce = structuredClone(payload);
1639
- cloned = nullifyPrototypes(cloneOnce);
1640
- } catch {
1641
- try {
1642
- cloned = nullifyPrototypes(payload);
1643
- } catch {
1644
- cloned = payload;
1645
- }
1646
- }
1647
- const result = await runWithTimeout(() => userFn(cloned));
1648
- if (result === null)
1649
- return false;
1650
- if (isAsyncSentinel(result))
1651
- return false;
1652
- return !!result;
1653
- }
1654
- return false;
1655
- }
1656
-
1657
- // src/workflows/executors/base.ts
1658
- class BaseExecutor {
1659
- deps;
1660
- retryPolicy;
1661
- constructor(deps) {
1662
- this.deps = deps;
1663
- }
1664
- async run(input) {
1665
- const configResult = this.configSchema.safeParse(input.config);
1666
- if (!configResult.success) {
1667
- return {
1668
- status: "failed",
1669
- error: `Input validation failed: ${configResult.error.message}`
1670
- };
1671
- }
1672
- let result;
1673
- try {
1674
- result = await this.execute(configResult.data, input.context, input.meta);
1675
- } catch (err) {
1676
- return {
1677
- status: "failed",
1678
- error: `Executor threw: ${err instanceof Error ? err.message : String(err)}`
1679
- };
1680
- }
1681
- if (result.status === "success" && result.output !== undefined) {
1682
- const outputResult = this.outputSchema.safeParse(result.output);
1683
- if (!outputResult.success) {
1684
- return {
1685
- status: "failed",
1686
- error: `Output validation failed: ${outputResult.error.message}`
1687
- };
1688
- }
1689
- }
1690
- return result;
1691
- }
1692
- }
1693
-
1694
- // src/workflows/executors/wait.ts
1695
- var WaitConfigSchema = exports_external.discriminatedUnion("mode", [
1696
- exports_external.object({
1697
- mode: exports_external.literal("time"),
1698
- durationMs: exports_external.number().int().min(1).max(31536000000).describe("Wait duration in milliseconds (effective resolution ~5s)")
1699
- }),
1700
- exports_external.object({
1701
- mode: exports_external.literal("event"),
1702
- eventName: exports_external.string().min(1),
1703
- filter: exports_external.union([exports_external.record(exports_external.string(), exports_external.unknown()), exports_external.string().max(2048)]).optional(),
1704
- scope: exports_external.enum(["run", "global"]).default("run"),
1705
- timeoutMs: exports_external.number().int().min(1).max(31536000000).optional().describe("Timeout in milliseconds (effective resolution ~5s) — when reached, routes via 'timeout' port")
1706
- })
1707
- ]);
1708
- var WaitOutputSchema = exports_external.object({
1709
- waitId: exports_external.string().uuid(),
1710
- mode: exports_external.enum(["time", "event"]),
1711
- firedAt: exports_external.string().nullable(),
1712
- payload: exports_external.unknown().optional()
1713
- });
1714
-
1715
- class WaitExecutor extends BaseExecutor {
1716
- type = "wait";
1717
- mode = "async";
1718
- configSchema = WaitConfigSchema;
1719
- outputSchema = WaitOutputSchema;
1720
- async execute(config, _context, meta) {
1721
- const { db } = this.deps;
1722
- const existing = await db.getWaitStateByStepId(meta.stepId);
1723
- if (existing) {
1724
- if (existing.status !== "pending") {
1725
- const nextPort = computeNextPort(existing.mode, existing.status);
1726
- return {
1727
- status: "success",
1728
- output: {
1729
- waitId: existing.id,
1730
- mode: existing.mode,
1731
- firedAt: existing.resolvedAt,
1732
- payload: existing.firedPayload ?? undefined
1733
- },
1734
- nextPort
1735
- };
1736
- }
1737
- return {
1738
- status: "success",
1739
- async: true,
1740
- waitFor: "wait.fired",
1741
- correlationId: existing.id
1742
- };
1743
- }
1744
- if (config.mode === "time") {
1745
- const waitId2 = crypto.randomUUID();
1746
- const wakeUpAt = new Date(Date.now() + config.durationMs).toISOString();
1747
- await db.createWaitState({
1748
- id: waitId2,
1749
- workflowRunId: meta.runId,
1750
- workflowRunStepId: meta.stepId,
1751
- mode: "time",
1752
- wakeUpAt
1753
- });
1754
- return {
1755
- status: "success",
1756
- async: true,
1757
- waitFor: "wait.fired",
1758
- correlationId: waitId2
1759
- };
1760
- }
1761
- if (typeof config.filter === "string") {
1762
- compileStringFilter(config.filter);
1763
- }
1764
- const waitId = crypto.randomUUID();
1765
- const expiresAt = config.timeoutMs ? new Date(Date.now() + config.timeoutMs).toISOString() : null;
1766
- await db.createWaitState({
1767
- id: waitId,
1768
- workflowRunId: meta.runId,
1769
- workflowRunStepId: meta.stepId,
1770
- mode: "event",
1771
- eventName: config.eventName,
1772
- eventFilter: config.filter ?? null,
1773
- expiresAt,
1774
- scope: config.scope
1775
- });
1776
- subscribeWaitToBus(waitId, config.eventName);
1777
- return {
1778
- status: "success",
1779
- async: true,
1780
- waitFor: "wait.fired",
1781
- correlationId: waitId
1782
- };
1783
- }
1784
- }
1785
- function computeNextPort(mode, status) {
1786
- if (mode === "time")
1787
- return "default";
1788
- return status === "timeout" ? "timeout" : "event";
1789
- }
1790
-
1791
- // src/workflows/task-step-routing.ts
1792
- init_db();
1793
- async function failStepAndRunIfWaiting(stepId, runId, reason) {
1794
- const now = new Date().toISOString();
1795
- return await getDbClient().transaction(async () => {
1796
- const current = await getWorkflowRunStep(stepId);
1797
- if (!current || current.status !== "waiting")
1798
- return false;
1799
- await updateWorkflowRunStep(stepId, {
1800
- status: "failed",
1801
- error: reason,
1802
- finishedAt: now
1803
- });
1804
- await updateWorkflowRun(runId, {
1805
- status: "failed",
1806
- error: reason,
1807
- finishedAt: now
1808
- });
1809
- return true;
1810
- });
1811
- }
1812
- async function checkpointPortStepAndResolveSuccessors(def, runId, stepId, nodeId, output, nextPort, ctx) {
1813
- return await getDbClient().transaction(async () => {
1814
- const current = await getWorkflowRunStep(stepId);
1815
- if (!current || current.status !== "waiting")
1816
- return { claimed: false, successors: [] };
1817
- await checkpointStep(runId, stepId, nodeId, { output, nextPort }, ctx);
1818
- await updateWorkflowRun(runId, { status: "running" });
1819
- return { claimed: true, successors: getSuccessors(def, nodeId, nextPort) };
1820
- });
1821
- }
1822
- var UNCLAIMED = {
1823
- claimed: false,
1824
- foreachChild: false,
1825
- joined: false,
1826
- successors: []
1827
- };
1828
- async function completeTaskStepAndResolveSuccessors(def, runId, step, output, ctx, failureReason) {
1829
- return await getDbClient().transaction(async () => {
1830
- const current = await getWorkflowRunStep(step.id);
1831
- if (!current || current.status !== "waiting")
1832
- return UNCLAIMED;
1833
- const foreachParent = resolveForeachParent(def, step.nodeId);
1834
- if (foreachParent) {
1835
- await updateWorkflowRunStep(step.id, {
1836
- status: "completed",
1837
- output,
1838
- ...failureReason !== undefined ? { error: failureReason } : {},
1839
- finishedAt: new Date().toISOString()
1840
- });
1841
- const join = await joinForeach(def, runId, step, ctx);
1842
- await updateWorkflowRun(runId, { status: "running" });
1843
- return {
1844
- claimed: true,
1845
- foreachChild: true,
1846
- joined: join.joined,
1847
- successors: join.successors
1848
- };
1849
- }
1850
- await checkpointStep(runId, step.id, step.nodeId, { output }, ctx);
1851
- await updateWorkflowRun(runId, { status: "running" });
1852
- return {
1853
- claimed: true,
1854
- foreachChild: false,
1855
- joined: true,
1856
- successors: getSuccessors(def, step.nodeId)
1857
- };
1858
- });
1859
- }
1860
-
1861
- // src/workflows/resume.ts
1862
- function setupWorkflowResumeListener(eventBus, registry) {
1863
- const onTaskCompleted = async (data) => {
1864
- try {
1865
- const event = data;
1866
- if (!event.workflowRunId || !event.workflowRunStepId)
1867
- return;
1868
- await resumeFromTaskCompletion(event, registry);
1869
- } catch (err) {
1870
- console.error("[workflows] Resume from task completion failed:", err);
1871
- }
1872
- };
1873
- eventBus.on("task.completed", onTaskCompleted);
1874
- const onTaskFailed = async (data) => {
1875
- try {
1876
- const event = data;
1877
- if (!event.workflowRunId || !event.workflowRunStepId)
1878
- return;
1879
- await handleTaskFailure(event, event.failureReason ?? "Task failed", registry);
1880
- } catch (err) {
1881
- console.error("[workflows] Handle task failure error:", err);
1882
- }
1883
- };
1884
- eventBus.on("task.failed", onTaskFailed);
1885
- const onTaskCancelled = async (data) => {
1886
- try {
1887
- const event = data;
1888
- if (!event.workflowRunId || !event.workflowRunStepId)
1889
- return;
1890
- await handleTaskFailure(event, "Task was cancelled", registry);
1891
- } catch (err) {
1892
- console.error("[workflows] Handle task cancellation error:", err);
1893
- }
1894
- };
1895
- eventBus.on("task.cancelled", onTaskCancelled);
1896
- const onApprovalResolved = async (data) => {
1897
- try {
1898
- const event = data;
1899
- if (!event.workflowRunId || !event.workflowRunStepId)
1900
- return;
1901
- await resumeFromApprovalResolution(event, registry);
1902
- } catch (err) {
1903
- console.error("[workflows] Resume from approval resolution failed:", err);
1904
- }
1905
- };
1906
- eventBus.on("approval.resolved", onApprovalResolved);
1907
- return () => {
1908
- eventBus.off("task.completed", onTaskCompleted);
1909
- eventBus.off("task.failed", onTaskFailed);
1910
- eventBus.off("task.cancelled", onTaskCancelled);
1911
- eventBus.off("approval.resolved", onApprovalResolved);
1912
- };
1913
- }
1914
- async function resumeFromTaskCompletion(event, registry) {
1915
- const run = await getWorkflowRun(event.workflowRunId);
1916
- if (!run || run.status !== "waiting" && run.status !== "running")
1917
- return;
1918
- const step = await getWorkflowRunStep(event.workflowRunStepId);
1919
- if (!step || step.status !== "waiting")
1920
- return;
1921
- if (await isStaleTaskEvent(step.id, event))
1922
- return;
1923
- const workflow = await getWorkflow(run.workflowId);
1924
- if (!workflow)
1925
- return;
1926
- const ctx = run.context ?? {};
1927
- let taskOutput = event.output;
1928
- if (event.output) {
1929
- try {
1930
- const parsed = JSON.parse(event.output);
1931
- if (typeof parsed === "object" && parsed !== null) {
1932
- taskOutput = parsed;
1933
- }
1934
- } catch {}
1935
- }
1936
- const stepOutput = { taskId: event.taskId, taskOutput };
1937
- const routing = await completeTaskStepAndResolveSuccessors(workflow.definition, run.id, step, stepOutput, ctx);
1938
- if (!routing.claimed)
1939
- return;
1940
- const successors = routing.successors;
1941
- if (successors.length > 0) {
1942
- const secretKeys = getSecretInputKeys(workflow.input);
1943
- await walkGraph(workflow.definition, run.id, ctx, successors, registry, workflow.id, secretKeys);
1944
- } else {
1945
- await finalizeOrWait(run.id);
1946
- }
1947
- }
1948
- async function finalizeOrWait(runId) {
1949
- await getDbClient().transaction(async () => {
1950
- const steps = await getWorkflowRunStepsByRunId(runId);
1951
- const hasWaiting = steps.some((s) => s.status === "waiting");
1952
- if (hasWaiting) {
1953
- await updateWorkflowRun(runId, { status: "waiting" });
1954
- } else {
1955
- await updateWorkflowRun(runId, {
1956
- status: "completed",
1957
- finishedAt: new Date().toISOString()
1958
- });
1959
- }
1960
- });
1961
- }
1962
- async function handleTaskFailure(event, reason, registry) {
1963
- const run = await getWorkflowRun(event.workflowRunId);
1964
- if (!run || run.status !== "waiting" && run.status !== "running")
1965
- return;
1966
- const step = await getWorkflowRunStep(event.workflowRunStepId);
1967
- if (!step || step.status !== "waiting")
1968
- return;
1969
- if (await isStaleTaskEvent(step.id, event))
1970
- return;
1971
- const workflow = await getWorkflow(run.workflowId);
1972
- if (!workflow)
1973
- return;
1974
- const onFailure = workflow.definition.onNodeFailure ?? "fail";
1975
- if (onFailure === "fail") {
1976
- await markRunFailed(event, reason);
1977
- return;
1978
- }
1979
- const ctx = run.context ?? {};
1980
- const stepOutput = {
1981
- taskId: event.taskId,
1982
- taskOutput: `${FAILED_TASK_OUTPUT_PREFIX} ${reason}] This node failed or was cancelled.`
1983
- };
1984
- const routing = await completeTaskStepAndResolveSuccessors(workflow.definition, run.id, step, stepOutput, ctx, reason);
1985
- if (!routing.claimed)
1986
- return;
1987
- const successors = routing.successors;
1988
- if (successors.length > 0) {
1989
- const secretKeys = getSecretInputKeys(workflow.input);
1990
- await walkGraph(workflow.definition, run.id, ctx, successors, registry, workflow.id, secretKeys);
1991
- } else {
1992
- await finalizeOrWait(run.id);
1993
- }
1994
- }
1995
- async function isStaleTaskEvent(stepId, event) {
1996
- if (!event.taskId)
1997
- return false;
1998
- const boundTask = await getTaskByWorkflowRunStepId(stepId);
1999
- return boundTask != null && boundTask.id !== event.taskId;
2000
- }
2001
- async function markRunFailed(event, reason) {
2002
- await failStepAndRunIfWaiting(event.workflowRunStepId, event.workflowRunId, reason);
2003
- }
2004
- async function retryFailedRun(runId, registry) {
2005
- const run = await getWorkflowRun(runId);
2006
- if (!run || run.status !== "failed")
2007
- throw new Error("Run is not in failed state");
2008
- const workflow = await getWorkflow(run.workflowId);
2009
- if (!workflow)
2010
- throw new Error("Workflow not found");
2011
- const steps = await getWorkflowRunStepsByRunId(runId);
2012
- const failedStep = steps.find((s) => s.status === "failed");
2013
- if (!failedStep)
2014
- throw new Error("No failed step found");
2015
- const completedNodeIds = new Set(await getCompletedStepNodeIds(runId));
2016
- const { activeEdges } = await loadCompletedStepRouting(workflow.definition, runId, completedNodeIds);
2017
- const foreachParent = resolveForeachParent(workflow.definition, failedStep.nodeId);
2018
- const failedNode = foreachParent ?? workflow.definition.nodes.find((n) => n.id === failedStep.nodeId);
2019
- if (!failedNode)
2020
- throw new Error(`Node ${failedStep.nodeId} not found in workflow definition`);
2021
- const hasStructuralPredecessor = workflow.definition.nodes.some((node) => node.next && getNextTargets(node.next).includes(failedNode.id));
2022
- const hasActivePredecessor = [...activeEdges].some((edge) => edge.endsWith(`→${failedNode.id}`));
2023
- const shouldRetryFailedNode = foreachParent != null || !hasStructuralPredecessor || hasActivePredecessor;
2024
- const ctx = run.context ?? {};
2025
- const claimed = await getDbClient().transaction(async () => {
2026
- const current = await getWorkflowRun(runId);
2027
- if (!current || current.status !== "failed")
2028
- return false;
2029
- await updateWorkflowRunStep(failedStep.id, {
2030
- status: shouldRetryFailedNode ? "pending" : "cancelled",
2031
- error: shouldRetryFailedNode ? null : "Skipped on retry because its branch was not active"
2032
- });
2033
- await updateWorkflowRun(runId, { status: "running", error: null, context: ctx });
2034
- return true;
2035
- });
2036
- if (!claimed)
2037
- throw new Error("Run is not in failed state");
2038
- const readyNodes = findReadyNodes(workflow.definition, completedNodeIds, activeEdges);
2039
- const nodesToRun = !shouldRetryFailedNode || readyNodes.some((n) => n.id === failedNode.id) ? readyNodes : [failedNode, ...readyNodes];
2040
- const secretKeys = getSecretInputKeys(workflow.input);
2041
- await walkGraph(workflow.definition, runId, ctx, nodesToRun, registry, workflow.id, secretKeys);
2042
- }
2043
- async function cancelWorkflowRun(runId, reason) {
2044
- const run = await getWorkflowRun(runId);
2045
- if (!run)
2046
- throw new Error("Workflow run not found");
2047
- const terminalStatuses = ["completed", "failed", "cancelled", "skipped"];
2048
- if (terminalStatuses.includes(run.status) && run.status !== "cancelled") {
2049
- throw new Error(`Cannot cancel run in '${run.status}' state`);
2050
- }
2051
- const now = new Date().toISOString();
2052
- const cancelReason = reason ?? "Cancelled by user";
2053
- let applied = false;
2054
- await getDbClient().transaction(async () => {
2055
- const current = await getWorkflowRun(runId);
2056
- if (!current || terminalStatuses.includes(current.status))
2057
- return;
2058
- applied = true;
2059
- const steps = await getWorkflowRunStepsByRunId(runId);
2060
- for (const step of steps) {
2061
- if (terminalStatuses.includes(step.status))
2062
- continue;
2063
- const task = await getTaskByWorkflowRunStepId(step.id);
2064
- if (task) {
2065
- await cancelTask(task.id, cancelReason);
2066
- }
2067
- await updateWorkflowRunStep(step.id, {
2068
- status: "cancelled",
2069
- error: cancelReason,
2070
- finishedAt: now
2071
- });
2072
- }
2073
- await cancelPendingApprovalRequestsForRun(runId, cancelReason);
2074
- await updateWorkflowRun(runId, {
2075
- status: "cancelled",
2076
- error: cancelReason,
2077
- finishedAt: now
2078
- });
2079
- });
2080
- const cancelledApprovals = await listCancelledApprovalRequestsForRun(runId);
2081
- const latestRun = await getWorkflowRun(runId);
2082
- if (!applied && latestRun?.status !== "cancelled") {
2083
- throw new Error(`Cannot cancel run in '${latestRun?.status ?? "missing"}' state`);
2084
- }
2085
- await postApprovalCancellationUpdates(cancelledApprovals, applied ? cancelReason : latestRun?.error ?? cancelReason);
2086
- }
2087
- async function resumeFromApprovalResolution(event, registry) {
2088
- const run = await getWorkflowRun(event.workflowRunId);
2089
- if (!run || run.status !== "waiting" && run.status !== "running")
2090
- return;
2091
- const step = await getWorkflowRunStep(event.workflowRunStepId);
2092
- if (!step || step.status !== "waiting")
2093
- return;
2094
- const workflow = await getWorkflow(run.workflowId);
2095
- if (!workflow)
2096
- return;
2097
- const ctx = run.context ?? {};
2098
- const nextPort = event.status === "timeout" ? "timeout" : event.status === "rejected" ? "rejected" : "approved";
2099
- const stepOutput = {
2100
- requestId: event.requestId,
2101
- status: event.status,
2102
- responses: event.responses
2103
- };
2104
- const routing = await checkpointPortStepAndResolveSuccessors(workflow.definition, run.id, step.id, step.nodeId, stepOutput, nextPort, ctx);
2105
- if (!routing.claimed)
2106
- return;
2107
- const successors = routing.successors;
2108
- if (successors.length > 0) {
2109
- const secretKeys = getSecretInputKeys(workflow.input);
2110
- await walkGraph(workflow.definition, run.id, ctx, successors, registry, workflow.id, secretKeys);
2111
- } else {
2112
- await finalizeOrWait(run.id);
2113
- }
2114
- }
2115
- async function resumeWaitState(waitId, status, payload, registry) {
2116
- const cappedPayload = capPayload(payload);
2117
- const result = await resolveWaitState(waitId, { status, firedPayload: cappedPayload });
2118
- if (!result.updated || !result.row)
2119
- return;
2120
- const waitRow = result.row;
2121
- const run = await getWorkflowRun(waitRow.workflowRunId);
2122
- if (!run || run.status !== "waiting" && run.status !== "running")
2123
- return;
2124
- const step = await getWorkflowRunStep(waitRow.workflowRunStepId);
2125
- if (!step || step.status !== "waiting")
2126
- return;
2127
- const workflow = await getWorkflow(run.workflowId);
2128
- if (!workflow)
2129
- return;
2130
- const nextPort = computeNextPort(waitRow.mode, status);
2131
- const ctx = run.context ?? {};
2132
- const stepOutput = {
2133
- waitId: waitRow.id,
2134
- mode: waitRow.mode,
2135
- firedAt: waitRow.resolvedAt,
2136
- payload: cappedPayload === undefined ? undefined : cappedPayload
2137
- };
2138
- const routing = await checkpointPortStepAndResolveSuccessors(workflow.definition, run.id, step.id, step.nodeId, stepOutput, nextPort, ctx);
2139
- if (!routing.claimed)
2140
- return;
2141
- if (waitRow.mode === "event" && waitRow.eventName) {
2142
- pruneWaitFromBus(waitRow.id, waitRow.eventName);
2143
- }
2144
- const successors = routing.successors;
2145
- if (successors.length > 0) {
2146
- const secretKeys = getSecretInputKeys(workflow.input);
2147
- await walkGraph(workflow.definition, run.id, ctx, successors, registry, workflow.id, secretKeys);
2148
- } else {
2149
- await finalizeOrWait(run.id);
2150
- }
2151
- }
2152
- var FIRED_PAYLOAD_BYTE_CAP = 64 * 1024;
2153
- function capPayload(payload) {
2154
- if (payload === undefined || payload === null)
2155
- return payload;
2156
- let encoded;
2157
- try {
2158
- encoded = JSON.stringify(payload);
2159
- } catch {
2160
- return { truncated: true, reason: "non-serializable" };
2161
- }
2162
- if (encoded.length <= FIRED_PAYLOAD_BYTE_CAP) {
2163
- return payload;
2164
- }
2165
- const summary = encoded.slice(0, 1024);
2166
- return {
2167
- truncated: true,
2168
- originalSize: encoded.length,
2169
- summary
2170
- };
2171
- }
2172
- var waitsByEvent = new Map;
2173
- var listenersByEvent = new Map;
2174
- var busRegistry = null;
2175
- async function initWaitBusSubscriptions(registry) {
2176
- busRegistry = registry;
2177
- const pendingNames = await collectPendingEventNames();
2178
- for (const name of pendingNames) {
2179
- const pending = await getPendingWaitsByEvent(name);
2180
- for (const w of pending) {
2181
- registerWait(w.id, name);
2182
- }
2183
- }
2184
- }
2185
- async function collectPendingEventNames() {
2186
- return new Set(await getPendingEventWaitNames());
2187
- }
2188
- function subscribeWaitToBus(waitId, eventName) {
2189
- registerWait(waitId, eventName);
2190
- }
2191
- function registerWait(waitId, eventName) {
2192
- let set = waitsByEvent.get(eventName);
2193
- if (!set) {
2194
- set = new Set;
2195
- waitsByEvent.set(eventName, set);
2196
- }
2197
- set.add(waitId);
2198
- if (!listenersByEvent.has(eventName)) {
2199
- const listener = (data) => {
2200
- processBusEvent(eventName, data).catch((err) => {
2201
- console.error(`[workflows] Wait bus listener failed for event=${eventName}:`, scrubSecrets(err instanceof Error ? err.message : String(err)));
2202
- });
2203
- };
2204
- listenersByEvent.set(eventName, listener);
2205
- workflowEventBus.on(eventName, listener);
2206
- }
2207
- }
2208
- function pruneWaitFromBus(waitId, eventName) {
2209
- const set = waitsByEvent.get(eventName);
2210
- if (!set)
2211
- return;
2212
- set.delete(waitId);
2213
- if (set.size === 0) {
2214
- waitsByEvent.delete(eventName);
2215
- const listener = listenersByEvent.get(eventName);
2216
- if (listener) {
2217
- workflowEventBus.off(eventName, listener);
2218
- listenersByEvent.delete(eventName);
2219
- }
2220
- }
2221
- }
2222
- async function processBusEvent(eventName, payload) {
2223
- const set = waitsByEvent.get(eventName);
2224
- if (!set || set.size === 0)
2225
- return;
2226
- if (!busRegistry)
2227
- return;
2228
- const waitIds = [...set];
2229
- for (const waitId of waitIds) {
2230
- try {
2231
- const row = await getWaitStateById(waitId);
2232
- if (!row || row.status !== "pending") {
2233
- set.delete(waitId);
2234
- continue;
2235
- }
2236
- if (row.eventScope === "run") {
2237
- if (!isPayloadInRun(payload, row.workflowRunId))
2238
- continue;
2239
- }
2240
- const ok = await matchesFilter(payload, row.eventFilter ?? undefined);
2241
- if (!ok)
2242
- continue;
2243
- await resumeWaitState(waitId, "fired", payload, busRegistry);
2244
- } catch (err) {
2245
- console.error(`[workflows] Wait bus listener failed for wait=${waitId} event=${eventName}:`, err);
2246
- }
2247
- }
2248
- if (set.size === 0) {
2249
- waitsByEvent.delete(eventName);
2250
- const listener = listenersByEvent.get(eventName);
2251
- if (listener) {
2252
- workflowEventBus.off(eventName, listener);
2253
- listenersByEvent.delete(eventName);
2254
- }
2255
- }
2256
- }
2257
- function isPayloadInRun(payload, runId) {
2258
- if (typeof payload !== "object" || payload === null)
2259
- return false;
2260
- const rec = payload;
2261
- return rec._runId === runId || rec.workflowRunId === runId;
2262
- }
2263
-
2264
- // src/workflows/recovery.ts
2265
- async function recoverIncompleteRuns(registry) {
2266
- let recovered = 0;
2267
- recovered += await recoverRunningRuns(registry);
2268
- recovered += await recoverWaitingRuns(registry);
2269
- recovered += await recoverApprovalWaitingRuns(registry);
2270
- recovered += await recoverWaitStates(registry);
2271
- if (recovered > 0) {
2272
- console.log(`[workflows] Recovered ${recovered} incomplete run(s) on startup`);
2273
- }
2274
- return recovered;
2275
- }
2276
- async function recoverRunningRuns(registry) {
2277
- const runningRunIds = await getRunIdsByStatus("running");
2278
- let recovered = 0;
2279
- for (const runId of runningRunIds) {
2280
- try {
2281
- const run = await getWorkflowRun(runId);
2282
- if (!run || run.status !== "running")
2283
- continue;
2284
- const workflow = await getWorkflow(run.workflowId);
2285
- if (!workflow)
2286
- continue;
2287
- const completedNodeIds = new Set(await getCompletedStepNodeIds(runId));
2288
- const ctx = run.context ?? {};
2289
- const readyNodes = findReadyNodes(workflow.definition, completedNodeIds);
2290
- if (readyNodes.length === 0) {
2291
- await updateWorkflowRun(runId, {
2292
- status: "completed",
2293
- context: ctx,
2294
- finishedAt: new Date().toISOString()
2295
- });
2296
- } else {
2297
- const secretKeys = getSecretInputKeys(workflow.input);
2298
- await walkGraph(workflow.definition, runId, ctx, readyNodes, registry, workflow.id, secretKeys);
2299
- }
2300
- recovered++;
2301
- } catch (err) {
2302
- console.error(`[workflows] Failed to recover running run ${runId}:`, err);
2303
- }
2304
- }
2305
- return recovered;
2306
- }
2307
- async function recoverWaitingRuns(registry) {
2308
- const stuckRuns = await getStuckWorkflowRuns();
2309
- let recovered = 0;
2310
- for (const stuck of stuckRuns) {
2311
- try {
2312
- const run = await getWorkflowRun(stuck.runId);
2313
- const workflow = await getWorkflow(stuck.workflowId);
2314
- if (!run || run.status !== "waiting" || !workflow)
2315
- continue;
2316
- const taskCompleted = stuck.taskStatus === "completed";
2317
- if (!taskCompleted && (workflow.definition.onNodeFailure ?? "fail") === "fail") {
2318
- const reason2 = stuck.taskStatus === "failed" ? "Task failed (recovered)" : "Task cancelled (recovered)";
2319
- const claimed = await failStepAndRunIfWaiting(stuck.stepId, stuck.runId, reason2);
2320
- if (!claimed)
2321
- continue;
2322
- recovered++;
2323
- continue;
2324
- }
2325
- const ctx = run.context ?? {};
2326
- const reason = stuck.taskStatus === "failed" ? "Task failed (recovered)" : "Task cancelled (recovered)";
2327
- const stepOutput = {
2328
- taskId: stuck.taskId,
2329
- taskOutput: taskCompleted ? parseRecoveredTaskOutput(stuck.taskOutput) : `${FAILED_TASK_OUTPUT_PREFIX} ${reason}] This node failed or was cancelled.`
2330
- };
2331
- const step = await getWorkflowRunStep(stuck.stepId);
2332
- if (!step)
2333
- continue;
2334
- const routing = await completeTaskStepAndResolveSuccessors(workflow.definition, stuck.runId, step, stepOutput, ctx, taskCompleted ? undefined : reason);
2335
- if (!routing.claimed)
2336
- continue;
2337
- if (routing.foreachChild && !routing.joined) {
2338
- await finalizeOrWait(stuck.runId);
2339
- } else {
2340
- const secretKeys = getSecretInputKeys(workflow.input);
2341
- await walkGraph(workflow.definition, stuck.runId, ctx, routing.successors, registry, workflow.id, secretKeys);
2342
- }
2343
- recovered++;
2344
- } catch (err) {
2345
- console.error(`[workflows] Failed to recover waiting run ${stuck.runId}:`, err);
2346
- }
2347
- }
2348
- return recovered;
2349
- }
2350
- function parseRecoveredTaskOutput(output) {
2351
- if (output === null)
2352
- return null;
2353
- try {
2354
- const parsed = JSON.parse(output);
2355
- return typeof parsed === "object" && parsed !== null ? parsed : output;
2356
- } catch {
2357
- return output;
2358
- }
2359
- }
2360
- async function recoverApprovalWaitingRuns(registry) {
2361
- const stuckRuns = await getStuckApprovalRuns();
2362
- let recovered = 0;
2363
- for (const stuck of stuckRuns) {
2364
- try {
2365
- const run = await getWorkflowRun(stuck.runId);
2366
- const workflow = await getWorkflow(stuck.workflowId);
2367
- if (!run || !workflow)
2368
- continue;
2369
- let approvalStatus = stuck.approvalStatus;
2370
- let responses = stuck.approvalResponses ? JSON.parse(stuck.approvalResponses) : null;
2371
- if (approvalStatus === "pending" && stuck.expiresAt) {
2372
- await resolveApprovalRequest(stuck.approvalId, {
2373
- status: "timeout"
2374
- });
2375
- approvalStatus = "timeout";
2376
- responses = null;
2377
- }
2378
- const nextPort = approvalStatus === "timeout" ? "timeout" : approvalStatus === "rejected" ? "rejected" : "approved";
2379
- const ctx = run.context ?? {};
2380
- const stepOutput = {
2381
- requestId: stuck.approvalId,
2382
- status: approvalStatus,
2383
- responses
2384
- };
2385
- const routing = await checkpointPortStepAndResolveSuccessors(workflow.definition, stuck.runId, stuck.stepId, stuck.nodeId, stepOutput, nextPort, ctx);
2386
- if (!routing.claimed)
2387
- continue;
2388
- if (routing.successors.length > 0) {
2389
- const secretKeys = getSecretInputKeys(workflow.input);
2390
- await walkGraph(workflow.definition, stuck.runId, ctx, routing.successors, registry, workflow.id, secretKeys);
2391
- } else {
2392
- await finalizeOrWait(stuck.runId);
2393
- }
2394
- recovered++;
2395
- } catch (err) {
2396
- console.error(`[workflows] Failed to recover approval-waiting run ${stuck.runId}:`, err);
2397
- }
2398
- }
2399
- return recovered;
2400
- }
2401
- async function recoverWaitStates(registry) {
2402
- const stuckRuns = await getStuckWaitRuns();
2403
- let recovered = 0;
2404
- for (const stuck of stuckRuns) {
2405
- try {
2406
- let resumeStatus;
2407
- let payload;
2408
- if (stuck.waitStatus === "fired") {
2409
- resumeStatus = "fired";
2410
- payload = stuck.firedPayload != null ? safeJsonParse(stuck.firedPayload) : undefined;
2411
- } else if (stuck.waitStatus === "timeout") {
2412
- resumeStatus = "timeout";
2413
- } else {
2414
- resumeStatus = stuck.waitMode === "time" ? "fired" : "timeout";
2415
- }
2416
- await resumeWaitState(stuck.waitId, resumeStatus, payload, registry);
2417
- recovered++;
2418
- } catch (err) {
2419
- console.error(`[workflows] Failed to recover wait-state ${stuck.waitId}:`, err);
2420
- }
2421
- }
2422
- return recovered;
2423
- }
2424
- function safeJsonParse(s) {
2425
- try {
2426
- return JSON.parse(s);
2427
- } catch {
2428
- return s;
2429
- }
2430
- }
2431
- async function getRunIdsByStatus(status) {
2432
- const rows = await getDbClient().query("SELECT id FROM workflow_runs WHERE status = ?", [status]);
2433
- return rows.map((r) => r.id);
2434
- }
2435
- // src/workflows/retry-poller.ts
2436
- init_db();
2437
- var pollerTimeout = null;
2438
- function startRetryPoller(registry, intervalMs = 5000) {
2439
- if (pollerTimeout !== null)
2440
- return;
2441
- async function poll() {
2442
- try {
2443
- const retryableSteps = await getRetryableSteps();
2444
- for (const step of retryableSteps) {
2445
- try {
2446
- const run = await getWorkflowRun(step.runId);
2447
- if (!run)
2448
- continue;
2449
- const workflow = await getWorkflow(run.workflowId);
2450
- if (!workflow)
2451
- continue;
2452
- const node = workflow.definition.nodes.find((n) => n.id === step.nodeId);
2453
- if (!node)
2454
- continue;
2455
- console.log(`[workflows] Retrying step ${step.nodeId} (attempt ${step.retryCount}) for run ${step.runId}`);
2456
- if (run.status === "failed") {
2457
- await updateWorkflowRun(run.id, {
2458
- status: "running",
2459
- error: undefined
2460
- });
2461
- }
2462
- await updateWorkflowRunStep(step.id, {
2463
- status: "running",
2464
- error: undefined,
2465
- nextRetryAt: undefined
2466
- });
2467
- const ctx = run.context ?? {};
2468
- if (!("run" in ctx))
2469
- ctx.run = { id: run.id };
2470
- await rehydrateCompletedStepOutputs(workflow.definition, run.id, ctx, registry);
2471
- const inputCtx = buildNodeInterpolationCtx(node, ctx);
2472
- const { value: interpolatedValue, scriptBodyUnresolved } = interpolateNodeConfig(node, inputCtx);
2473
- if (scriptBodyUnresolved && scriptBodyUnresolved.length > 0) {
2474
- await checkpointStepFailure(run.id, step.id, scriptBodyInterpolationError(node.id, scriptBodyUnresolved), step.retryCount);
2475
- continue;
2476
- }
2477
- const interpolatedConfig = interpolatedValue;
2478
- const executor = registry.get(node.type);
2479
- const meta = {
2480
- runId: run.id,
2481
- stepId: step.id,
2482
- nodeId: step.nodeId,
2483
- workflowId: workflow.id,
2484
- dryRun: false,
2485
- requestedByUserId: run.createdBy,
2486
- inputCtx
2487
- };
2488
- try {
2489
- const result = await executor.run({
2490
- config: interpolatedConfig,
2491
- context: ctx,
2492
- meta
2493
- });
2494
- if (result.status === "failed") {
2495
- const retryPolicy = node.retry || executor.retryPolicy;
2496
- await checkpointStepFailure(run.id, step.id, result.error || "Retry failed", step.retryCount, retryPolicy);
2497
- } else if ("async" in result && result.async) {
2498
- const waiting = await checkpointStepWaiting(run.id, step.id, ctx);
2499
- if (!waiting)
2500
- continue;
2501
- } else {
2502
- if (node.validation) {
2503
- const validationResult = await runStepValidation(registry, node, result.output, ctx, meta);
2504
- if (validationResult.outcome === "halt") {
2505
- await checkpointStepFailure(run.id, step.id, "Validation failed (mustPass)", step.retryCount);
2506
- continue;
2507
- }
2508
- if (validationResult.outcome === "retry") {
2509
- if (validationResult.retryContext) {
2510
- const historyKey = `${node.id}_validations`;
2511
- const existing = ctx[historyKey] || [];
2512
- ctx[historyKey] = [...existing, validationResult.retryContext];
2513
- }
2514
- const retryPolicy = node.validation.retry || node.retry;
2515
- await checkpointStepFailure(run.id, step.id, "Validation failed, retrying", step.retryCount, retryPolicy);
2516
- continue;
2517
- }
2518
- }
2519
- await checkpointStep(run.id, step.id, step.nodeId, result, ctx);
2520
- const port = result.nextPort || "default";
2521
- const successors = getSuccessors(workflow.definition, step.nodeId, port);
2522
- if (successors.length > 0) {
2523
- await walkGraph(workflow.definition, run.id, ctx, successors, registry, workflow.id);
2524
- } else {
2525
- await updateWorkflowRun(run.id, {
2526
- status: "completed",
2527
- context: ctx,
2528
- finishedAt: new Date().toISOString()
2529
- });
2530
- }
2531
- }
2532
- } catch (err) {
2533
- const errorMsg = err instanceof Error ? err.message : String(err);
2534
- const retryPolicy = node.retry || executor.retryPolicy;
2535
- await checkpointStepFailure(run.id, step.id, errorMsg, step.retryCount, retryPolicy);
2536
- }
2537
- } catch (err) {
2538
- console.error(`[workflows] Retry failed for step ${step.id}:`, err);
2539
- }
2540
- }
2541
- } catch (err) {
2542
- console.error("[workflows] Retry poller error:", err);
2543
- }
2544
- pollerTimeout = setTimeout(poll, intervalMs);
2545
- }
2546
- pollerTimeout = setTimeout(poll, intervalMs);
2547
- }
2548
- function stopRetryPoller() {
2549
- if (pollerTimeout !== null) {
2550
- clearTimeout(pollerTimeout);
2551
- pollerTimeout = null;
2552
- }
2553
- }
2554
- // src/workflows/templates.ts
2555
- function validateTemplateVariables(template, provided) {
2556
- const missing = [];
2557
- for (const variable of template.variables) {
2558
- if (variable.required && !(variable.name in provided)) {
2559
- if (variable.default === undefined) {
2560
- missing.push(variable.name);
2561
- }
2562
- }
2563
- }
2564
- return missing;
2565
- }
2566
- function instantiateTemplate(template, variables) {
2567
- const missing = validateTemplateVariables(template, variables);
2568
- if (missing.length > 0) {
2569
- throw new Error(`Missing required template variables: ${missing.join(", ")}`);
2570
- }
2571
- const resolved = {};
2572
- for (const variable of template.variables) {
2573
- if (variable.name in variables) {
2574
- resolved[variable.name] = variables[variable.name];
2575
- } else if (variable.default !== undefined) {
2576
- resolved[variable.name] = variable.default;
2577
- }
2578
- }
2579
- const cloned = JSON.parse(JSON.stringify(template.definition));
2580
- return replaceVariables(cloned, resolved);
2581
- }
2582
- function replaceVariables(obj, variables) {
2583
- if (typeof obj === "string") {
2584
- return replaceStringPlaceholders(obj, variables);
2585
- }
2586
- if (Array.isArray(obj)) {
2587
- return obj.map((item) => replaceVariables(item, variables));
2588
- }
2589
- if (obj !== null && typeof obj === "object") {
2590
- const result = {};
2591
- for (const [key, value] of Object.entries(obj)) {
2592
- result[key] = replaceVariables(value, variables);
2593
- }
2594
- return result;
2595
- }
2596
- return obj;
2597
- }
2598
- function replaceStringPlaceholders(template, variables) {
2599
- return template.replace(/\{\{(\w+)\}\}/g, (_match, name) => {
2600
- const value = variables[name];
2601
- if (value === undefined)
2602
- return _match;
2603
- if (typeof value === "object" && value !== null)
2604
- return JSON.stringify(value);
2605
- return String(value);
2606
- });
2607
- }
2608
- // src/workflows/triggers.ts
2609
- init_db();
2610
- import crypto2 from "node:crypto";
2611
- var DEFAULT_HMAC_HEADER = "X-Hub-Signature-256";
2612
- var FALLBACK_HMAC_HEADERS = ["x-hub-signature-256", "x-signature", "x-webhook-signature"];
2613
- function getHeader(headers, name) {
2614
- const target = name.toLowerCase();
2615
- for (const [key, value] of Object.entries(headers)) {
2616
- if (key.toLowerCase() === target) {
2617
- return Array.isArray(value) ? value[0] : value;
2618
- }
2619
- }
2620
- return;
2621
- }
2622
- function resolveSignature(headers, hmacHeader) {
2623
- for (const name of [hmacHeader, ...FALLBACK_HMAC_HEADERS]) {
2624
- const value = getHeader(headers, name);
2625
- if (value)
2626
- return value;
2627
- }
2628
- return;
2629
- }
2630
- async function resolveHmacSecret(raw) {
2631
- if (/^secret\..+$/.test(raw) || /^\$\{.+\}$/.test(raw)) {
2632
- try {
2633
- return await resolveInputValue(raw);
2634
- } catch (err) {
2635
- throw new WebhookError(`Failed to resolve webhook HMAC secret: ${err instanceof Error ? err.message : String(err)}`, 500);
2636
- }
2637
- }
2638
- return raw;
2639
- }
2640
- async function verifyWebhookRequest(trigger, rawBody, headers) {
2641
- if (!trigger.hmacSecret) {
2642
- if (trigger.verification) {
2643
- throw new WebhookError("Webhook trigger has `verification` configured but no `hmacSecret`; refusing to accept unverified requests", 500);
2644
- }
2645
- return;
2646
- }
2647
- const secret = await resolveHmacSecret(trigger.hmacSecret);
2648
- const verification = trigger.verification;
2649
- if (!verification) {
2650
- const hmacHeader = trigger.hmacHeader || DEFAULT_HMAC_HEADER;
2651
- const signature2 = resolveSignature(headers, hmacHeader);
2652
- if (!signature2) {
2653
- throw new WebhookError("Missing signature", 401);
2654
- }
2655
- if (!verifyHmacSignature(secret, rawBody, signature2)) {
2656
- throw new WebhookError("Invalid signature", 401);
2657
- }
2658
- return;
2659
- }
2660
- const header = verification.header || DEFAULT_HMAC_HEADER;
2661
- const signature = getHeader(headers, header);
2662
- if (!signature) {
2663
- throw new WebhookError("Missing signature", 401);
2664
- }
2665
- let isValid = false;
2666
- switch (verification.format) {
2667
- case "hmac-sha256":
2668
- isValid = verifyHmacSignature(secret, rawBody, signature);
2669
- break;
2670
- case "timestamped-hmac-sha256":
2671
- isValid = verifyTimestampedHmacSignature(secret, rawBody, signature, {
2672
- timestampKey: verification.timestampKey,
2673
- signatureKey: verification.signatureKey,
2674
- toleranceSeconds: verification.toleranceSeconds
2675
- });
2676
- break;
2677
- case "token-equality":
2678
- isValid = verifyTokenEquality(secret, signature);
2679
- break;
2680
- }
2681
- if (!isValid) {
2682
- throw new WebhookError("Invalid signature", 401);
2683
- }
2684
- }
2685
- async function handleWebhookTrigger(workflowId, payload, headers, registry, options = {}) {
2686
- const workflow = await getWorkflow(workflowId);
2687
- if (!workflow) {
2688
- throw new WebhookError("Workflow not found", 404);
2689
- }
2690
- if (!workflow.enabled) {
2691
- throw new WebhookError("Workflow is disabled", 400);
2692
- }
2693
- const webhookTrigger = workflow.triggers.find((t) => t.type === "webhook");
2694
- if (!webhookTrigger) {
2695
- if (!options.alreadyAuthenticated) {
2696
- throw new WebhookError("Workflow does not declare a webhook trigger", 404);
2697
- }
2698
- } else {
2699
- await verifyWebhookRequest(webhookTrigger, typeof payload === "string" ? payload : JSON.stringify(payload), headers);
2700
- }
2701
- const triggerData = parseTriggerPayload(payload);
2702
- const runId = await startWorkflowExecution(workflow, triggerData, registry, {
2703
- triggerType: "event",
2704
- requestedByUserId: options.requestedByUserId
2705
- });
2706
- return { runId };
2707
- }
2708
- function parseTriggerPayload(payload) {
2709
- if (typeof payload !== "string" || payload.length === 0)
2710
- return payload;
2711
- try {
2712
- return JSON.parse(payload);
2713
- } catch {
2714
- return payload;
2715
- }
2716
- }
2717
- async function logOpenWebhookTriggers() {
2718
- try {
2719
- const open = (await listWorkflows({ enabled: true })).filter((workflow) => workflow.triggers.some((trigger) => trigger.type === "webhook" && !trigger.hmacSecret && !trigger.verification));
2720
- if (open.length === 0)
2721
- return;
2722
- console.log(`[workflows] ${open.length} enabled workflow(s) expose an open (unsigned) webhook trigger — ` + "anyone with the URL can start them, which is a supported opt-in. " + "Set `hmacSecret` on a trigger to require a signature: " + open.map((workflow) => `${workflow.name} (${workflow.id})`).join(", "));
2723
- } catch (err) {
2724
- console.error("[workflows] Failed to inventory open webhook triggers:", err);
2725
- }
2726
- }
2727
- async function handleEventTrigger(eventName, payload, registry) {
2728
- const workflows = (await listWorkflows({ enabled: true })).filter((workflow) => workflow.triggers.some((trigger) => trigger.type === "event" && trigger.eventName === eventName));
2729
- const runIds = [];
2730
- for (const workflow of workflows) {
2731
- const runId = await startWorkflowExecution(workflow, payload, registry, {
2732
- triggerType: "event"
2733
- });
2734
- runIds.push(runId);
2735
- }
2736
- return runIds;
2737
- }
2738
- async function handleScheduleTrigger(scheduleId, schedule, registry) {
2739
- const workflows = await getWorkflowsByScheduleId(scheduleId);
2740
- if (workflows.length === 0)
2741
- return [];
2742
- const runIds = [];
2743
- for (const workflow of workflows) {
2744
- const triggerData = {
2745
- scheduleId,
2746
- scheduleName: schedule.name,
2747
- firedAt: new Date().toISOString()
2748
- };
2749
- const runId = await startWorkflowExecution(workflow, triggerData, registry, {
2750
- triggerType: "schedule",
2751
- requestedByUserId: schedule.createdBy
2752
- });
2753
- runIds.push(runId);
2754
- console.log(`[Triggers] Schedule "${schedule.name}" triggered workflow "${workflow.name}" (run: ${runId})`);
2755
- }
2756
- return runIds;
2757
- }
2758
- function verifyHmacSignature(secret, body, providedSignature) {
2759
- const hmac = crypto2.createHmac("sha256", secret);
2760
- hmac.update(body);
2761
- const expectedHex = hmac.digest("hex");
2762
- const normalizedProvided = providedSignature.startsWith("sha256=") ? providedSignature.slice(7) : providedSignature;
2763
- try {
2764
- return crypto2.timingSafeEqual(Buffer.from(normalizedProvided, "hex"), Buffer.from(expectedHex, "hex"));
2765
- } catch {
2766
- return false;
2767
- }
2768
- }
2769
- function verifyTimestampedHmacSignature(secret, body, headerValue, opts = {}, nowMs = Date.now()) {
2770
- const timestampKey = opts.timestampKey ?? "t";
2771
- const signatureKey = opts.signatureKey ?? "v1";
2772
- const toleranceSeconds = opts.toleranceSeconds ?? 300;
2773
- const parsed = parseSignatureHeader(headerValue);
2774
- const timestampValue = parsed.get(timestampKey)?.[0];
2775
- const signatures = parsed.get(signatureKey) ?? [];
2776
- if (!timestampValue || signatures.length === 0 || !/^\d+$/.test(timestampValue)) {
2777
- return false;
2778
- }
2779
- const timestampSeconds = Number(timestampValue);
2780
- if (!Number.isSafeInteger(timestampSeconds)) {
2781
- return false;
2782
- }
2783
- const ageSeconds = Math.abs(nowMs / 1000 - timestampSeconds);
2784
- if (ageSeconds > toleranceSeconds) {
2785
- return false;
2786
- }
2787
- const hmac = crypto2.createHmac("sha256", secret);
2788
- hmac.update(`${timestampValue}.${body}`);
2789
- const expectedHex = hmac.digest("hex");
2790
- const expected = Buffer.from(expectedHex, "hex");
2791
- return signatures.some((signature) => timingSafeEqualHex(signature, expected));
2792
- }
2793
- function verifyTokenEquality(secret, providedToken) {
2794
- const secretDigest = crypto2.createHash("sha256").update(secret).digest();
2795
- const providedDigest = crypto2.createHash("sha256").update(providedToken).digest();
2796
- return crypto2.timingSafeEqual(providedDigest, secretDigest);
2797
- }
2798
- function parseSignatureHeader(headerValue) {
2799
- const parsed = new Map;
2800
- for (const part of headerValue.split(",")) {
2801
- const trimmed = part.trim();
2802
- const separatorIndex = trimmed.indexOf("=");
2803
- if (separatorIndex <= 0)
2804
- continue;
2805
- const key = trimmed.slice(0, separatorIndex).trim();
2806
- const value = trimmed.slice(separatorIndex + 1).trim();
2807
- const values = parsed.get(key) ?? [];
2808
- values.push(value);
2809
- parsed.set(key, values);
2810
- }
2811
- return parsed;
2812
- }
2813
- function timingSafeEqualHex(providedHex, expected) {
2814
- try {
2815
- return crypto2.timingSafeEqual(Buffer.from(providedHex, "hex"), expected);
2816
- } catch {
2817
- return false;
2818
- }
2819
- }
2820
-
2821
- class WebhookError extends Error {
2822
- statusCode;
2823
- constructor(message, statusCode) {
2824
- super(message);
2825
- this.statusCode = statusCode;
2826
- this.name = "WebhookError";
2827
- }
2828
- }
2829
- // src/workflows/version.ts
2830
- init_db();
2831
- async function snapshotWorkflow(workflowId, changedByAgentId) {
2832
- const workflow = await getWorkflow(workflowId);
2833
- if (!workflow) {
2834
- throw new Error(`Workflow ${workflowId} not found — cannot create snapshot`);
2835
- }
2836
- const existingVersions = await getWorkflowVersions(workflowId);
2837
- const maxVersion = existingVersions.length > 0 ? existingVersions[0].version : 0;
2838
- const nextVersion = maxVersion + 1;
2839
- const snapshot = {
2840
- name: workflow.name,
2841
- description: workflow.description,
2842
- definition: workflow.definition,
2843
- triggers: workflow.triggers,
2844
- cooldown: workflow.cooldown,
2845
- input: workflow.input,
2846
- triggerSchema: workflow.triggerSchema,
2847
- params: workflow.params,
2848
- requiredParams: workflow.requiredParams,
2849
- requires: workflow.requires,
2850
- dir: workflow.dir,
2851
- vcsRepo: workflow.vcsRepo,
2852
- enabled: workflow.enabled
2853
- };
2854
- return createWorkflowVersion({
2855
- workflowId,
2856
- version: nextVersion,
2857
- snapshot,
2858
- changedByAgentId
2859
- });
2860
- }
2861
- async function snapshotAndUpdateWorkflow(workflowId, updates, opts = {}) {
2862
- return await getDbClient().transaction(async () => {
2863
- let version = null;
2864
- if (opts.snapshotOptional) {
2865
- try {
2866
- version = await snapshotWorkflow(workflowId, opts.changedByAgentId);
2867
- } catch {}
2868
- } else {
2869
- version = await snapshotWorkflow(workflowId, opts.changedByAgentId);
2870
- }
2871
- const workflow = await updateWorkflow(workflowId, updates);
2872
- return { workflow, version };
2873
- });
2874
- }
2875
- // src/workflows/wait-poller.ts
2876
- init_db();
2877
- var pollerTimeout2 = null;
2878
- function startWaitPoller(registry, intervalMs = 5000) {
2879
- if (pollerTimeout2 !== null)
2880
- return;
2881
- async function poll() {
2882
- try {
2883
- const dueWaits = await getDueWaitStates();
2884
- for (const wait of dueWaits) {
2885
- try {
2886
- const kind = wait.mode === "time" ? "fired" : "timeout";
2887
- await resumeWaitState(wait.id, kind, undefined, registry);
2888
- } catch (err) {
2889
- console.error(`[workflows] Wait poller failed for wait ${wait.id}:`, err);
2890
- }
2891
- }
2892
- } catch (err) {
2893
- console.error("[workflows] Wait poller tick error:", err);
2894
- }
2895
- pollerTimeout2 = setTimeout(poll, intervalMs);
2896
- }
2897
- pollerTimeout2 = setTimeout(poll, intervalMs);
2898
- }
2899
- function stopWaitPoller() {
2900
- if (pollerTimeout2 !== null) {
2901
- clearTimeout(pollerTimeout2);
2902
- pollerTimeout2 = null;
2903
- }
2904
- }
2905
-
2906
- // src/workflows/index.ts
2907
- init_db();
2908
- init_secret_scrubber();
2909
- init_event_bus();
2910
-
2911
- // src/workflows/executors/registry.ts
2912
- init_zod();
2913
-
2914
- // src/workflows/executors/agent-task.ts
2915
- init_zod();
2916
- init_context_key();
2917
- init_types();
2918
- var AgentTaskConfigSchema = exports_external.object({
2919
- template: exports_external.string(),
2920
- agentId: exports_external.string().optional(),
2921
- routingReason: RoutingReasonSchema.optional(),
2922
- routingNote: exports_external.string().max(200).optional(),
2923
- tags: exports_external.array(exports_external.string()).optional(),
2924
- priority: exports_external.number().int().min(0).max(100).optional(),
2925
- offerMode: exports_external.boolean().optional(),
2926
- dir: exports_external.string().min(1).optional(),
2927
- vcsRepo: exports_external.string().min(1).optional(),
2928
- model: exports_external.string().min(1).optional(),
2929
- modelTier: ModelTierSchema.optional(),
2930
- effort: ReasoningEffortSchema.optional(),
2931
- parentTaskId: exports_external.string().uuid().optional(),
2932
- requestedByUserId: exports_external.string().optional(),
2933
- outputSchema: exports_external.record(exports_external.string(), exports_external.unknown()).optional(),
2934
- followUpConfig: FollowUpConfigSchema.optional()
2935
- });
2936
- var AgentTaskOutputSchema = exports_external.object({
2937
- taskId: exports_external.string().uuid(),
2938
- taskOutput: exports_external.unknown()
2939
- });
2940
-
2941
- class AgentTaskExecutor extends BaseExecutor {
2942
- type = "agent-task";
2943
- mode = "async";
2944
- configSchema = AgentTaskConfigSchema;
2945
- outputSchema = AgentTaskOutputSchema;
2946
- async execute(config, _context, meta) {
2947
- const { db } = this.deps;
2948
- const existingTask = await db.getTaskByWorkflowRunStepId(meta.stepId);
2949
- if (existingTask) {
2950
- if (existingTask.status === "completed") {
2951
- return {
2952
- status: "success",
2953
- output: {
2954
- taskId: existingTask.id,
2955
- taskOutput: existingTask.output
2956
- }
2957
- };
2958
- }
2959
- return {
2960
- status: "success",
2961
- async: true,
2962
- waitFor: "task.completed",
2963
- correlationId: existingTask.id
2964
- };
2965
- }
2966
- let effectiveDir = config.dir;
2967
- let effectiveVcsRepo = config.vcsRepo;
2968
- let effectiveKey;
2969
- if (meta.workflowId) {
2970
- const workflow = await db.getWorkflow(meta.workflowId);
2971
- if (workflow) {
2972
- if (!effectiveDir && workflow.dir)
2973
- effectiveDir = workflow.dir;
2974
- if (!effectiveVcsRepo && workflow.vcsRepo)
2975
- effectiveVcsRepo = workflow.vcsRepo;
2976
- effectiveKey = workflow.key;
2977
- }
2978
- }
2979
- const { description: taskDescription, options: taskOptions } = await withSiblingAwareness(config.template, {
2980
- key: effectiveKey,
2981
- agentId: config.agentId ?? null,
2982
- routingReason: config.routingReason ?? (config.agentId ? "human_pinned" : undefined),
2983
- routingNote: config.routingNote,
2984
- routingSource: config.routingReason ? "declared" : config.agentId ? "engine_default" : undefined,
2985
- source: "workflow",
2986
- tags: config.tags,
2987
- priority: config.priority,
2988
- offeredTo: config.offerMode ? config.agentId : undefined,
2989
- workflowRunId: meta.runId,
2990
- workflowRunStepId: meta.stepId,
2991
- dir: effectiveDir,
2992
- vcsRepo: effectiveVcsRepo,
2993
- ...splitLegacyModelAlias({ model: config.model, modelTier: config.modelTier }),
2994
- effort: config.effort,
2995
- parentTaskId: config.parentTaskId,
2996
- requestedByUserId: config.requestedByUserId ?? meta.requestedByUserId,
2997
- outputSchema: config.outputSchema,
2998
- followUpConfig: config.followUpConfig,
2999
- contextKey: workflowContextKey({ workflowRunId: meta.runId })
3000
- });
3001
- const task = await db.createTaskExtended(taskDescription, taskOptions);
3002
- return {
3003
- status: "success",
3004
- async: true,
3005
- waitFor: "task.completed",
3006
- correlationId: task.id
3007
- };
3008
- }
3009
- }
3010
-
3011
- // src/workflows/executors/code-match.ts
3012
- init_zod();
3013
- var CodeMatchConfigSchema = exports_external.object({
3014
- code: exports_external.string(),
3015
- outputPorts: exports_external.array(exports_external.string()).min(1)
3016
- });
3017
- var CodeMatchOutputSchema = exports_external.object({
3018
- port: exports_external.string(),
3019
- rawResult: exports_external.unknown()
3020
- });
3021
- var SANDBOX_KEYS = [
3022
- "require",
3023
- "process",
3024
- "Bun",
3025
- "globalThis",
3026
- "global",
3027
- "fetch",
3028
- "setTimeout",
3029
- "setInterval"
3030
- ];
3031
- var SANDBOX_VALUES = SANDBOX_KEYS.map(() => {
3032
- return;
3033
- });
3034
-
3035
- class CodeMatchExecutor extends BaseExecutor {
3036
- type = "code-match";
3037
- mode = "instant";
3038
- configSchema = CodeMatchConfigSchema;
3039
- outputSchema = CodeMatchOutputSchema;
3040
- async execute(config, context, _meta) {
3041
- try {
3042
- const fn = new Function(...SANDBOX_KEYS, "input", `"use strict"; return (${config.code})(input);`);
3043
- const rawResult = fn(...SANDBOX_VALUES, context);
3044
- let port;
3045
- if (typeof rawResult === "boolean") {
3046
- port = rawResult ? "true" : "false";
3047
- } else if (typeof rawResult === "string") {
3048
- port = rawResult;
3049
- } else {
3050
- port = String(rawResult);
3051
- }
3052
- if (!config.outputPorts.includes(port)) {
3053
- return {
3054
- status: "failed",
3055
- error: `code-match returned "${port}" which is not in outputPorts: [${config.outputPorts.join(", ")}]`
3056
- };
3057
- }
3058
- return {
3059
- status: "success",
3060
- output: { port, rawResult },
3061
- nextPort: port
3062
- };
3063
- } catch (err) {
3064
- return {
3065
- status: "failed",
3066
- error: `code-match execution error: ${err instanceof Error ? err.message : String(err)}`
3067
- };
3068
- }
3069
- }
3070
- }
3071
-
3072
- // src/workflows/executors/foreach.ts
3073
- init_zod();
3074
- init_template();
3075
- var ForeachBodySchema = exports_external.object({
3076
- type: exports_external.literal("agent-task"),
3077
- config: exports_external.record(exports_external.string(), exports_external.unknown())
3078
- });
3079
- var ForeachConfigSchema = exports_external.object({
3080
- over: exports_external.array(exports_external.record(exports_external.string(), exports_external.unknown())),
3081
- itemKey: exports_external.string().min(1),
3082
- body: ForeachBodySchema,
3083
- concurrency: exports_external.never().optional()
3084
- });
3085
- var ForeachResultSchema = exports_external.object({
3086
- itemKey: exports_external.string(),
3087
- status: exports_external.enum(["completed", "failed", "cancelled", "skipped"]),
3088
- output: exports_external.unknown().optional()
3089
- });
3090
- var ForeachOutputSchema = exports_external.object({
3091
- results: exports_external.array(ForeachResultSchema),
3092
- okCount: exports_external.number().int().nonnegative(),
3093
- failedCount: exports_external.number().int().nonnegative()
3094
- });
3095
-
3096
- class ForeachExecutor extends BaseExecutor {
3097
- type = "foreach";
3098
- mode = "async";
3099
- configSchema = ForeachConfigSchema;
3100
- outputSchema = ForeachOutputSchema;
3101
- async execute(config, context, meta) {
3102
- if (config.over.length === 0) {
3103
- return {
3104
- status: "success",
3105
- output: { results: [], okCount: 0, failedCount: 0 }
3106
- };
3107
- }
3108
- const items = config.over.map((item, index) => {
3109
- const rawItemKey = item[config.itemKey];
3110
- if (typeof rawItemKey !== "string" || rawItemKey.length === 0) {
3111
- throw new Error(`foreach item at index ${index} is missing non-empty string itemKey property "${config.itemKey}"`);
3112
- }
3113
- return { item, index, itemKey: rawItemKey };
3114
- });
3115
- const seenItemKeys = new Set;
3116
- for (const { itemKey } of items) {
3117
- if (seenItemKeys.has(itemKey)) {
3118
- throw new Error(`foreach itemKey "${itemKey}" is duplicated`);
3119
- }
3120
- seenItemKeys.add(itemKey);
3121
- }
3122
- const agentTaskExecutor = new AgentTaskExecutor(this.deps);
3123
- const runSteps = await this.deps.db.getWorkflowRunStepsByRunId(meta.runId);
3124
- const childStepsByNodeId = new Map(runSteps.filter((step) => parseSyntheticNodeId(step.nodeId)?.parentNodeId === meta.nodeId).map((step) => [step.nodeId, step]));
3125
- const childCountToCreate = items.filter(({ itemKey }) => !childStepsByNodeId.has(`${meta.nodeId}#${itemKey}`)).length;
3126
- const maxSteps = getMaxWorkflowStepsPerRun();
3127
- const workflow = await this.deps.db.getWorkflow(meta.workflowId);
3128
- const hasSuccessors = workflow ? getSuccessors(workflow.definition, meta.nodeId).length > 0 : true;
3129
- const totalSteps = runSteps.length + childCountToCreate;
3130
- const capBreached = hasSuccessors ? totalSteps >= maxSteps : totalSteps > maxSteps;
3131
- if (childCountToCreate > 0 && capBreached) {
3132
- return {
3133
- status: "failed",
3134
- error: `foreach would reach ${maxSteps} total steps (WORKFLOW_MAX_STEPS_PER_RUN): ${runSteps.length} existing + ${childCountToCreate} children${hasSuccessors ? " leaves no room for the post-join walk" : " exceeds the per-run cap"}`
3135
- };
3136
- }
3137
- for (const { index, itemKey } of items) {
3138
- const childNodeId = `${meta.nodeId}#${itemKey}`;
3139
- if (childStepsByNodeId.has(childNodeId))
3140
- continue;
3141
- const childStepId = crypto.randomUUID();
3142
- const childStep = await this.deps.db.createWorkflowRunStep({
3143
- id: childStepId,
3144
- runId: meta.runId,
3145
- nodeId: childNodeId,
3146
- nodeType: "agent-task",
3147
- input: { itemKey, index }
3148
- });
3149
- await this.deps.db.updateWorkflowRunStep(childStepId, {
3150
- idempotencyKey: `${meta.runId}:${childNodeId}:0`
3151
- });
3152
- childStepsByNodeId.set(childNodeId, childStep);
3153
- }
3154
- for (const { item, index, itemKey } of items) {
3155
- const childNodeId = `${meta.nodeId}#${itemKey}`;
3156
- const childStep = childStepsByNodeId.get(childNodeId);
3157
- if (FOREACH_TERMINAL_STEP_STATUSES.has(childStep.status))
3158
- continue;
3159
- const bodyCtx = { ...meta.inputCtx ?? context, item, index };
3160
- const childInterpolation = deepInterpolate(config.body.config, bodyCtx);
3161
- if (childInterpolation.unresolved.length > 0) {
3162
- console.warn(`[workflow] Step ${childNodeId}: unresolved interpolation tokens: ${childInterpolation.unresolved.join(", ")}`);
3163
- await this.deps.db.updateWorkflowRunStep(childStep.id, {
3164
- diagnostics: JSON.stringify({ unresolvedTokens: childInterpolation.unresolved })
3165
- });
3166
- }
3167
- const linkedTask = await this.deps.db.getTaskByWorkflowRunStepId(childStep.id);
3168
- if (linkedTask?.status === "failed" || linkedTask?.status === "cancelled") {
3169
- await this.deps.db.detachTaskFromWorkflowRunStep(linkedTask.id);
3170
- }
3171
- const childResult = await agentTaskExecutor.run({
3172
- config: childInterpolation.value,
3173
- context,
3174
- meta: {
3175
- ...meta,
3176
- stepId: childStep.id,
3177
- nodeId: childNodeId
3178
- }
3179
- });
3180
- if (childResult.status !== "success") {
3181
- throw new Error(childResult.error ?? `foreach child "${itemKey}" failed to start`);
3182
- }
3183
- if ("async" in childResult) {
3184
- await this.deps.db.updateWorkflowRunStep(childStep.id, { status: "waiting" });
3185
- } else {
3186
- await this.deps.db.updateWorkflowRunStep(childStep.id, {
3187
- status: "completed",
3188
- output: normalizeExistingTaskOutput(childResult.output),
3189
- finishedAt: new Date().toISOString()
3190
- });
3191
- }
3192
- }
3193
- const childSteps = (await this.deps.db.getWorkflowRunStepsByRunId(meta.runId)).filter((step) => parseSyntheticNodeId(step.nodeId)?.parentNodeId === meta.nodeId);
3194
- if (childSteps.every((step) => FOREACH_TERMINAL_STEP_STATUSES.has(step.status))) {
3195
- return { status: "success", output: buildForeachAggregate(childSteps) };
3196
- }
3197
- return {
3198
- status: "success",
3199
- async: true,
3200
- waitFor: "task.completed",
3201
- correlationId: meta.stepId
3202
- };
3203
- }
3204
- }
3205
- function normalizeExistingTaskOutput(output) {
3206
- if (typeof output !== "object" || output === null)
3207
- return output;
3208
- const record = output;
3209
- if (typeof record.taskOutput !== "string")
3210
- return output;
3211
- try {
3212
- const parsed = JSON.parse(record.taskOutput);
3213
- if (typeof parsed === "object" && parsed !== null) {
3214
- return { ...record, taskOutput: parsed };
3215
- }
3216
- } catch {}
3217
- return output;
3218
- }
3219
-
3220
- // src/workflows/executors/human-in-the-loop.ts
3221
- init_zod();
3222
- init_constants();
3223
- init_secret_scrubber();
3224
- var SelectOptionSchema = exports_external.object({
3225
- value: exports_external.string(),
3226
- label: exports_external.string(),
3227
- description: exports_external.string().optional()
3228
- });
3229
- var QuestionSchema = exports_external.discriminatedUnion("type", [
3230
- exports_external.object({
3231
- id: exports_external.string(),
3232
- type: exports_external.literal("approval"),
3233
- label: exports_external.string(),
3234
- required: exports_external.boolean().default(true),
3235
- description: exports_external.string().optional()
3236
- }),
3237
- exports_external.object({
3238
- id: exports_external.string(),
3239
- type: exports_external.literal("text"),
3240
- label: exports_external.string(),
3241
- required: exports_external.boolean().default(true),
3242
- description: exports_external.string().optional(),
3243
- placeholder: exports_external.string().optional(),
3244
- multiline: exports_external.boolean().optional()
3245
- }),
3246
- exports_external.object({
3247
- id: exports_external.string(),
3248
- type: exports_external.literal("single-select"),
3249
- label: exports_external.string(),
3250
- required: exports_external.boolean().default(true),
3251
- description: exports_external.string().optional(),
3252
- options: exports_external.array(SelectOptionSchema)
3253
- }),
3254
- exports_external.object({
3255
- id: exports_external.string(),
3256
- type: exports_external.literal("multi-select"),
3257
- label: exports_external.string(),
3258
- required: exports_external.boolean().default(true),
3259
- description: exports_external.string().optional(),
3260
- options: exports_external.array(SelectOptionSchema),
3261
- minSelections: exports_external.number().int().min(0).optional(),
3262
- maxSelections: exports_external.number().int().min(1).optional()
3263
- }),
3264
- exports_external.object({
3265
- id: exports_external.string(),
3266
- type: exports_external.literal("boolean"),
3267
- label: exports_external.string(),
3268
- required: exports_external.boolean().default(true),
3269
- description: exports_external.string().optional(),
3270
- defaultValue: exports_external.boolean().optional()
3271
- })
3272
- ]);
3273
- var ApproverConfigSchema = exports_external.object({
3274
- users: exports_external.array(exports_external.string()).optional(),
3275
- roles: exports_external.array(exports_external.string()).optional(),
3276
- policy: exports_external.union([exports_external.literal("any"), exports_external.literal("all"), exports_external.object({ min: exports_external.number().int().min(1) })])
3277
- });
3278
- var NotificationConfigSchema = exports_external.object({
3279
- channel: exports_external.enum(["slack", "email"]),
3280
- target: exports_external.string()
3281
- });
3282
- var HITLConfigSchema = exports_external.object({
3283
- title: exports_external.string(),
3284
- questions: exports_external.array(QuestionSchema).min(1),
3285
- approvers: ApproverConfigSchema,
3286
- timeout: exports_external.object({
3287
- seconds: exports_external.number().int().min(1),
3288
- action: exports_external.literal("reject")
3289
- }).optional(),
3290
- notifications: exports_external.array(NotificationConfigSchema).optional()
3291
- });
3292
- var HITLOutputSchema = exports_external.object({
3293
- requestId: exports_external.string().uuid(),
3294
- status: exports_external.string(),
3295
- responses: exports_external.record(exports_external.string(), exports_external.unknown()).nullable()
3296
- });
3297
-
3298
- class HumanInTheLoopExecutor extends BaseExecutor {
3299
- type = "human-in-the-loop";
3300
- mode = "async";
3301
- configSchema = HITLConfigSchema;
3302
- outputSchema = HITLOutputSchema;
3303
- async execute(config, _context, meta) {
3304
- const { db } = this.deps;
3305
- const existing = await db.getApprovalRequestByStepId(meta.stepId);
3306
- if (existing) {
3307
- if (existing.status !== "pending") {
3308
- if (existing.status === "cancelled") {
3309
- return {
3310
- status: "failed",
3311
- error: `Approval request was cancelled: ${existing.resolutionReason ?? "workflow run cancelled"}`
3312
- };
3313
- }
3314
- const nextPort = existing.status === "timeout" ? "timeout" : existing.status === "rejected" ? "rejected" : "approved";
3315
- return {
3316
- status: "success",
3317
- output: {
3318
- requestId: existing.id,
3319
- status: existing.status,
3320
- responses: existing.responses
3321
- },
3322
- nextPort
3323
- };
3324
- }
3325
- return {
3326
- status: "success",
3327
- async: true,
3328
- waitFor: "approval.resolved",
3329
- correlationId: existing.id
3330
- };
3331
- }
3332
- const requestId = crypto.randomUUID();
3333
- const created = await db.createApprovalRequest({
3334
- id: requestId,
3335
- title: config.title,
3336
- questions: config.questions,
3337
- approvers: config.approvers,
3338
- workflowRunId: meta.runId,
3339
- workflowRunStepId: meta.stepId,
3340
- timeoutSeconds: config.timeout?.seconds,
3341
- notificationChannels: config.notifications,
3342
- createdBy: meta.requestedByUserId,
3343
- requireActionableWorkflow: true
3344
- });
3345
- if (created.status === "cancelled") {
3346
- return {
3347
- status: "success",
3348
- async: true,
3349
- waitFor: "approval.resolved",
3350
- correlationId: requestId
3351
- };
3352
- }
3353
- if (config.notifications?.length) {
3354
- this.dispatchNotifications(requestId, config, db).catch((err) => {
3355
- console.error("[HITL] Unexpected error dispatching notifications:", scrubSecrets(err instanceof Error ? err.message : String(err)));
3356
- });
3357
- }
3358
- return {
3359
- status: "success",
3360
- async: true,
3361
- waitFor: "approval.resolved",
3362
- correlationId: requestId
3363
- };
3364
- }
3365
- async dispatchNotifications(requestId, config, db) {
3366
- if (!config.notifications?.length)
3367
- return;
3368
- const approvalUrl = `${getAppUrl()}/approval-requests/${requestId}`;
3369
- const updatedChannels = [...config.notifications];
3370
- let updated = false;
3371
- for (let i = 0;i < config.notifications.length; i++) {
3372
- const notification = config.notifications[i];
3373
- if (notification.channel === "email") {
3374
- console.warn(`[HITL] Email notifications not yet supported (target: ${notification.target})`);
3375
- continue;
3376
- }
3377
- if (notification.channel === "slack") {
3378
- try {
3379
- const { getSlackApp: getSlackApp2 } = await import("./app-dty7p9hg.js");
3380
- const slackApp = getSlackApp2();
3381
- if (!slackApp) {
3382
- console.warn("[HITL] Slack not initialized — cannot send notification");
3383
- continue;
3384
- }
3385
- const questionsSummary = config.questions.map((q) => `• ${q.label}`).join(`
3386
- `);
3387
- const timeoutText = config.timeout ? `
3388
- ⏱ _Timeout: ${formatTimeout(config.timeout.seconds)} — auto-rejects if not responded_` : "";
3389
- const blocks = [
3390
- {
3391
- type: "section",
3392
- text: {
3393
- type: "mrkdwn",
3394
- text: `\uD83D\uDD14 *Approval Required: ${config.title}*`
3395
- }
3396
- },
3397
- {
3398
- type: "section",
3399
- text: {
3400
- type: "mrkdwn",
3401
- text: `*Questions:*
3402
- ${questionsSummary}${timeoutText}`
3403
- }
3404
- },
3405
- {
3406
- type: "actions",
3407
- elements: [
3408
- {
3409
- type: "button",
3410
- text: { type: "plain_text", text: "Review & Respond" },
3411
- url: approvalUrl,
3412
- style: "primary"
3413
- }
3414
- ]
3415
- }
3416
- ];
3417
- const result = await slackApp.client.chat.postMessage({
3418
- channel: notification.target,
3419
- text: `Approval Required: ${config.title} — ${approvalUrl}`,
3420
- unfurl_links: false,
3421
- unfurl_media: false,
3422
- blocks
3423
- });
3424
- if (result.ts) {
3425
- updatedChannels[i] = { ...notification, messageTs: result.ts };
3426
- updated = true;
3427
- }
3428
- console.log(`[HITL] Slack notification sent to ${notification.target} for request ${requestId}`);
3429
- } catch (err) {
3430
- console.error(`[HITL] Failed to send Slack notification to ${notification.target}:`, scrubSecrets(err instanceof Error ? err.message : String(err)));
3431
- }
3432
- }
3433
- }
3434
- if (updated) {
3435
- try {
3436
- await db.updateApprovalRequestNotifications(requestId, updatedChannels);
3437
- const latest = await db.getApprovalRequestById(requestId);
3438
- if (latest?.status === "cancelled") {
3439
- await postApprovalCancellationUpdates([latest], latest.resolutionReason ?? "Workflow run cancelled");
3440
- }
3441
- } catch (err) {
3442
- console.error("[HITL] Failed to update notification channels in DB:", scrubSecrets(err instanceof Error ? err.message : String(err)));
3443
- }
3444
- }
3445
- }
3446
- }
3447
- function formatTimeout(seconds) {
3448
- if (seconds < 60)
3449
- return `${seconds}s`;
3450
- if (seconds < 3600)
3451
- return `${Math.round(seconds / 60)}m`;
3452
- const hours = Math.floor(seconds / 3600);
3453
- const mins = Math.round(seconds % 3600 / 60);
3454
- return mins > 0 ? `${hours}h ${mins}m` : `${hours}h`;
3455
- }
3456
-
3457
- // src/workflows/executors/notify.ts
3458
- init_zod();
3459
- var NotifyConfigSchema = exports_external.object({
3460
- channel: exports_external.enum(["swarm", "slack", "email"]),
3461
- target: exports_external.string().optional(),
3462
- template: exports_external.string()
3463
- });
3464
- var NotifyOutputSchema = exports_external.object({
3465
- sent: exports_external.boolean(),
3466
- messageId: exports_external.string().optional(),
3467
- message: exports_external.string()
3468
- });
3469
-
3470
- class NotifyExecutor extends BaseExecutor {
3471
- type = "notify";
3472
- mode = "instant";
3473
- configSchema = NotifyConfigSchema;
3474
- outputSchema = NotifyOutputSchema;
3475
- async execute(config, context, _meta) {
3476
- const message = this.deps.interpolate(config.template, context);
3477
- switch (config.channel) {
3478
- case "swarm": {
3479
- if (!config.target) {
3480
- return {
3481
- status: "success",
3482
- output: { sent: false, message }
3483
- };
3484
- }
3485
- try {
3486
- const result = await this.deps.db.postMessage(config.target, null, message);
3487
- return {
3488
- status: "success",
3489
- output: { sent: true, messageId: result.id, message }
3490
- };
3491
- } catch (err) {
3492
- return {
3493
- status: "failed",
3494
- error: `Failed to post swarm message: ${err instanceof Error ? err.message : String(err)}`
3495
- };
3496
- }
3497
- }
3498
- case "slack": {
3499
- const { getSlackApp: getSlackApp2 } = await import("./app-dty7p9hg.js");
3500
- const app = getSlackApp2();
3501
- if (!app) {
3502
- return {
3503
- status: "success",
3504
- output: { sent: false, message }
3505
- };
3506
- }
3507
- try {
3508
- const result = await app.client.chat.postMessage({
3509
- channel: config.target || "",
3510
- text: message,
3511
- unfurl_links: false,
3512
- unfurl_media: false
3513
- });
3514
- return {
3515
- status: "success",
3516
- output: { sent: true, messageId: result.ts || "", message }
3517
- };
3518
- } catch (err) {
3519
- return {
3520
- status: "failed",
3521
- error: `Failed to post Slack message: ${err instanceof Error ? err.message : String(err)}`
3522
- };
3523
- }
3524
- }
3525
- case "email": {
3526
- console.log(`[notify] Email stub — target: ${config.target}, message: ${message}`);
3527
- return {
3528
- status: "success",
3529
- output: { sent: false, message }
3530
- };
3531
- }
3532
- }
3533
- }
3534
- }
3535
-
3536
- // src/workflows/executors/property-match.ts
3537
- init_zod();
3538
- var ConditionSchema = exports_external.object({
3539
- field: exports_external.string(),
3540
- op: exports_external.enum(["eq", "neq", "contains", "not_contains", "gt", "lt", "exists"]),
3541
- value: exports_external.unknown().optional()
3542
- });
3543
- var PropertyMatchConfigSchema = exports_external.object({
3544
- conditions: exports_external.array(ConditionSchema).min(1),
3545
- mode: exports_external.enum(["all", "any"]).default("all")
3546
- });
3547
- var ConditionResultSchema = exports_external.object({
3548
- field: exports_external.string(),
3549
- op: exports_external.string(),
3550
- expected: exports_external.unknown().optional(),
3551
- actual: exports_external.unknown().optional(),
3552
- passed: exports_external.boolean()
3553
- });
3554
- var PropertyMatchOutputSchema = exports_external.object({
3555
- passed: exports_external.boolean(),
3556
- results: exports_external.array(ConditionResultSchema)
3557
- });
3558
-
3559
- class PropertyMatchExecutor extends BaseExecutor {
3560
- type = "property-match";
3561
- mode = "instant";
3562
- configSchema = PropertyMatchConfigSchema;
3563
- outputSchema = PropertyMatchOutputSchema;
3564
- async execute(config, context, _meta) {
3565
- const results = config.conditions.map((cond) => {
3566
- const actual = resolvePath(context, cond.field);
3567
- const passed2 = evaluateCondition(cond.op, actual, cond.value);
3568
- return { field: cond.field, op: cond.op, expected: cond.value, actual, passed: passed2 };
3569
- });
3570
- const passed = config.mode === "all" ? results.every((r) => r.passed) : results.some((r) => r.passed);
3571
- return {
3572
- status: "success",
3573
- output: { passed, results },
3574
- nextPort: passed ? "true" : "false"
3575
- };
3576
- }
3577
- }
3578
- function resolvePath(obj, path) {
3579
- const keys = path.split(".");
3580
- let current = obj;
3581
- for (const key of keys) {
3582
- if (current == null || typeof current !== "object")
3583
- return;
3584
- current = current[key];
3585
- }
3586
- return current;
3587
- }
3588
- function evaluateCondition(op, actual, expected) {
3589
- switch (op) {
3590
- case "eq":
3591
- return actual === expected;
3592
- case "neq":
3593
- return actual !== expected;
3594
- case "contains":
3595
- return Array.isArray(actual) ? actual.includes(expected) : String(actual ?? "").includes(String(expected));
3596
- case "not_contains":
3597
- return Array.isArray(actual) ? !actual.includes(expected) : !String(actual ?? "").includes(String(expected));
3598
- case "gt":
3599
- return Number(actual) > Number(expected);
3600
- case "lt":
3601
- return Number(actual) < Number(expected);
3602
- case "exists":
3603
- return actual != null;
3604
- default:
3605
- return false;
3606
- }
3607
- }
3608
-
3609
- // src/workflows/executors/raw-llm.ts
3610
- init_zod();
3611
-
3612
- // src/workflows/executors/workflow-llm.ts
3613
- async function resolveWorkflowLlmConfig(requestedModel, env = process.env) {
3614
- const credential = await resolveCredential({ env });
3615
- if (!credential) {
3616
- throw new Error("No workflow LLM credential found. Set OPENROUTER_API_KEY or OPENAI_API_KEY.");
3617
- }
3618
- if (credential.kind !== "openrouter" && credential.kind !== "openai") {
3619
- throw new Error(`Workflow LLM nodes do not support the resolved ${credential.kind} credential yet. Set OPENROUTER_API_KEY or OPENAI_API_KEY.`);
3620
- }
3621
- const providerPrefix = `${credential.kind}/`;
3622
- const model = requestedModel ?? DEFAULT_MODEL[credential.kind];
3623
- return {
3624
- apiKey: credential.apiKey,
3625
- baseURL: credential.kind === "openrouter" ? getOpenRouterBaseUrl(env) : undefined,
3626
- model: model.startsWith(providerPrefix) ? model.slice(providerPrefix.length) : model
3627
- };
3628
- }
3629
-
3630
- // src/workflows/executors/raw-llm.ts
3631
- var RawLlmConfigSchema = exports_external.object({
3632
- prompt: exports_external.string(),
3633
- model: exports_external.string().optional(),
3634
- schema: exports_external.record(exports_external.string(), exports_external.unknown()).optional(),
3635
- fallbackPort: exports_external.string().optional()
3636
- });
3637
- var RawLlmOutputSchema = exports_external.object({
3638
- result: exports_external.unknown(),
3639
- model: exports_external.string()
3640
- });
3641
- async function executeRawLlm(config) {
3642
- let modelName = config.model ?? "google/gemini-3-flash-preview";
3643
- try {
3644
- const { createOpenAI } = await import("./index-n5t1gzfb.js");
3645
- const llmConfig = await resolveWorkflowLlmConfig(config.model);
3646
- modelName = llmConfig.model;
3647
- const provider = createOpenAI({
3648
- baseURL: llmConfig.baseURL,
3649
- apiKey: llmConfig.apiKey
3650
- });
3651
- const model = provider(llmConfig.model);
3652
- if (config.schema) {
3653
- const { generateObject, jsonSchema } = await import("./index-aa3sa6s8.js");
3654
- const { object } = await generateObject({
3655
- model,
3656
- schema: jsonSchema(config.schema),
3657
- prompt: config.prompt,
3658
- providerOptions: {
3659
- openai: { strictJsonSchema: false }
3660
- }
3661
- });
3662
- return {
3663
- status: "success",
3664
- output: { result: object, model: modelName }
3665
- };
3666
- }
3667
- const { generateText } = await import("./index-aa3sa6s8.js");
3668
- const { text } = await generateText({
3669
- model,
3670
- prompt: config.prompt
3671
- });
3672
- return {
3673
- status: "success",
3674
- output: { result: text, model: modelName }
3675
- };
3676
- } catch (err) {
3677
- const httpStatus = err?.status ?? err?.statusCode;
3678
- const isRateLimited = httpStatus === 429 || httpStatus === 529 || err instanceof Error && /rate.?limit|too many requests|529/i.test(err.message);
3679
- if (isRateLimited) {
3680
- throw err;
3681
- }
3682
- if (config.fallbackPort) {
3683
- return {
3684
- status: "success",
3685
- output: { result: null, model: modelName },
3686
- error: `LLM call failed, using fallback port: ${err instanceof Error ? err.message : String(err)}`
3687
- };
3688
- }
3689
- return {
3690
- status: "failed",
3691
- error: `LLM call failed: ${err instanceof Error ? err.message : String(err)}`
3692
- };
3693
- }
3694
- }
3695
-
3696
- class RawLlmExecutor extends BaseExecutor {
3697
- type = "raw-llm";
3698
- mode = "instant";
3699
- configSchema = RawLlmConfigSchema;
3700
- outputSchema = RawLlmOutputSchema;
3701
- async execute(config, context, _meta) {
3702
- const prompt = this.deps.interpolate(config.prompt, context);
3703
- const result = await executeRawLlm({ ...config, prompt });
3704
- if (result.status === "success" && result.error) {
3705
- return {
3706
- status: "success",
3707
- output: result.output,
3708
- nextPort: config.fallbackPort,
3709
- error: result.error
3710
- };
3711
- }
3712
- return result;
3713
- }
3714
- }
3715
-
3716
- // src/workflows/executors/script.ts
3717
- init_zod();
3718
- var MAX_OUTPUT_BYTES = 1048576;
3719
- var STREAM_DRAIN_GRACE_MS = 5000;
3720
- function unrefTimer(id) {
3721
- if (typeof id === "object" && "unref" in id)
3722
- id.unref();
3723
- }
3724
- function withDeadline(promise, ms, onDeadline) {
3725
- return new Promise((resolve) => {
3726
- const id = globalThis.setTimeout(() => resolve(onDeadline()), ms);
3727
- unrefTimer(id);
3728
- promise.then((value) => {
3729
- globalThis.clearTimeout(id);
3730
- resolve(value);
3731
- }, () => {
3732
- globalThis.clearTimeout(id);
3733
- resolve(onDeadline());
3734
- });
3735
- });
3736
- }
3737
- var ScriptConfigSchema = exports_external.object({
3738
- runtime: exports_external.enum(["bash", "ts", "python"]),
3739
- script: exports_external.string(),
3740
- args: exports_external.array(exports_external.string()).optional(),
3741
- timeout: exports_external.number().int().min(1000).max(MAX_SCRIPT_WALL_CLOCK_MS).default(30000),
3742
- cwd: exports_external.string().optional()
3743
- });
3744
- var ScriptOutputSchema = exports_external.object({
3745
- exitCode: exports_external.number(),
3746
- stdout: exports_external.string(),
3747
- stderr: exports_external.string()
3748
- });
3749
- var DEFAULT_TIMEOUT = 30000;
3750
-
3751
- class ScriptExecutor extends BaseExecutor {
3752
- type = "script";
3753
- mode = "instant";
3754
- configSchema = ScriptConfigSchema;
3755
- outputSchema = ScriptOutputSchema;
3756
- async execute(config, _context, _meta) {
3757
- const timeoutMs = config.timeout ?? DEFAULT_TIMEOUT;
3758
- try {
3759
- const result = await this.runScript(config, timeoutMs);
3760
- if (result.exitCode !== 0) {
3761
- return {
3762
- status: "failed",
3763
- error: result.stderr || `Script exited with code ${result.exitCode}`,
3764
- output: result
3765
- };
3766
- }
3767
- let output = result;
3768
- if (result.stdout) {
3769
- try {
3770
- const parsed = JSON.parse(result.stdout);
3771
- if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
3772
- output = { ...result, ...parsed };
3773
- }
3774
- } catch {}
3775
- }
3776
- return {
3777
- status: "success",
3778
- output,
3779
- nextPort: "success"
3780
- };
3781
- } catch (err) {
3782
- const message = err instanceof Error ? err.message : String(err);
3783
- const isTimeout = message.startsWith("Script timed out after");
3784
- return {
3785
- status: "failed",
3786
- error: `Script execution error: ${message}`,
3787
- output: {
3788
- exitCode: -1,
3789
- stdout: "",
3790
- stderr: isTimeout ? message : `Script execution error: ${message}`
3791
- }
3792
- };
3793
- }
3794
- }
3795
- async runScript(config, timeoutMs) {
3796
- const { runtime, script, args = [], cwd } = config;
3797
- let cmd;
3798
- switch (runtime) {
3799
- case "bash":
3800
- cmd = ["bash", "-c", script, ...args];
3801
- break;
3802
- case "ts":
3803
- cmd = ["bun", "-e", script, "--", ...args];
3804
- break;
3805
- case "python":
3806
- cmd = ["python3", "-c", script, ...args];
3807
- break;
3808
- }
3809
- const scopedTmpdir = cwd ? undefined : `${process.env.TMPDIR ?? "/tmp"}/workflow-script-${crypto.randomUUID()}`;
3810
- if (scopedTmpdir)
3811
- await Bun.$`mkdir -p ${scopedTmpdir}`;
3812
- const workdir = cwd ?? scopedTmpdir;
3813
- const env = {
3814
- PATH: process.env.PATH ?? "/usr/bin:/bin",
3815
- HOME: process.env.HOME ?? "/tmp",
3816
- LANG: process.env.LANG ?? "C.UTF-8",
3817
- LC_ALL: process.env.LC_ALL ?? "C.UTF-8",
3818
- TMPDIR: workdir ?? process.env.TMPDIR ?? "/tmp"
3819
- };
3820
- try {
3821
- const proc = registerProcessGroup(Bun.spawn(buildSandboxedCommand(cmd, env), {
3822
- stdout: "pipe",
3823
- stderr: "pipe",
3824
- cwd: workdir,
3825
- detached: detachedProcessGroup,
3826
- env: sandboxSpawnEnv(env)
3827
- }));
3828
- const stdoutState = createCappedStreamState();
3829
- const stderrState = createCappedStreamState();
3830
- const stdoutPromise = readStreamCapped(proc.stdout, MAX_OUTPUT_BYTES, stdoutState);
3831
- const stderrPromise = readStreamCapped(proc.stderr, MAX_OUTPUT_BYTES, stderrState);
3832
- let timedOut = false;
3833
- const killTimer = globalThis.setTimeout(() => {
3834
- timedOut = true;
3835
- try {
3836
- terminateProcessGroup(proc.pid);
3837
- } catch {}
3838
- }, timeoutMs);
3839
- unrefTimer(killTimer);
3840
- let exitCode;
3841
- try {
3842
- exitCode = await proc.exited;
3843
- await terminateProcessGroup(proc.pid);
3844
- } finally {
3845
- globalThis.clearTimeout(killTimer);
3846
- }
3847
- if (timedOut) {
3848
- stdoutPromise.catch(() => {});
3849
- stderrPromise.catch(() => {});
3850
- throw new Error(`Script timed out after ${timeoutMs}ms`);
3851
- }
3852
- const [stdout, stderr] = await Promise.all([
3853
- withDeadline(stdoutPromise, STREAM_DRAIN_GRACE_MS, () => snapshotCapped(stdoutState)),
3854
- withDeadline(stderrPromise, STREAM_DRAIN_GRACE_MS, () => snapshotCapped(stderrState))
3855
- ]);
3856
- return {
3857
- exitCode,
3858
- stdout: (stdout.truncated ? `${stdout.text}
3859
- …[stdout truncated]` : stdout.text).trimEnd(),
3860
- stderr: (stderr.truncated ? `${stderr.text}
3861
- …[stderr truncated]` : stderr.text).trimEnd()
3862
- };
3863
- } finally {
3864
- if (scopedTmpdir)
3865
- await Bun.$`rm -rf ${scopedTmpdir}`.catch(() => {});
3866
- }
3867
- }
3868
- }
3869
-
3870
- // src/workflows/executors/swarm-script.ts
3871
- init_zod();
3872
- init_constants();
3873
- init_template();
3874
- var SWARM_SCRIPT_DEFAULT_TIMEOUT_MS = DEFAULT_SCRIPT_RESOURCES.wallClockMs;
3875
- var SWARM_SCRIPT_MIN_TIMEOUT_MS = MIN_SCRIPT_WALL_CLOCK_MS;
3876
- var SWARM_SCRIPT_MAX_TIMEOUT_MS = MAX_SCRIPT_WALL_CLOCK_MS;
3877
- var SwarmScriptConfigSchema = exports_external.object({
3878
- scriptName: exports_external.string().min(1),
3879
- scope: exports_external.enum(["global", "agent"]).optional(),
3880
- pinHash: exports_external.string().min(1).optional(),
3881
- args: exports_external.record(exports_external.string(), exports_external.unknown()).default({}),
3882
- fsMode: exports_external.enum(["none", "workspace-rw"]).default("none"),
3883
- timeoutMs: exports_external.number().int().min(SWARM_SCRIPT_MIN_TIMEOUT_MS).max(SWARM_SCRIPT_MAX_TIMEOUT_MS).default(SWARM_SCRIPT_DEFAULT_TIMEOUT_MS)
3884
- });
3885
- var SwarmScriptOutputSchema = exports_external.object({
3886
- result: exports_external.unknown().optional(),
3887
- stdout: exports_external.string(),
3888
- stderr: exports_external.string(),
3889
- truncated: exports_external.object({ stdout: exports_external.boolean(), stderr: exports_external.boolean() }),
3890
- durationMs: exports_external.number(),
3891
- exitCode: exports_external.number(),
3892
- scriptName: exports_external.string(),
3893
- contentHash: exports_external.string(),
3894
- version: exports_external.number()
3895
- });
3896
- var WORKFLOW_INTERPOLATION_ROOTS = new Set(["trigger", "input", "workflow", "swarm", "run"]);
3897
- function findWorkflowSourceTokens(source, inputCtx) {
3898
- const roots = new Set([...WORKFLOW_INTERPOLATION_ROOTS, ...Object.keys(inputCtx ?? {})]);
3899
- return findInterpolationTokens(source).filter((token) => {
3900
- const root = token.split(".")[0];
3901
- return root !== undefined && roots.has(root);
3902
- });
3903
- }
3904
-
3905
- class SwarmScriptExecutor extends BaseExecutor {
3906
- type = "swarm-script";
3907
- mode = "instant";
3908
- configSchema = SwarmScriptConfigSchema;
3909
- outputSchema = SwarmScriptOutputSchema;
3910
- async execute(config, context, meta) {
3911
- if (config.fsMode === "workspace-rw") {
3912
- return {
3913
- status: "failed",
3914
- error: "swarm-script: fsMode 'workspace-rw' is v2 only; use 'none' or omit"
3915
- };
3916
- }
3917
- const workflow = await this.deps.db.getWorkflow(meta.workflowId);
3918
- const agentId = workflow?.createdByAgentId ?? agentIdFromContext(context);
3919
- const resolved = await resolveScriptSource(config, agentId);
3920
- if (!resolved.ok) {
3921
- return { status: "failed", error: resolved.error };
3922
- }
3923
- const sourceTokens = findWorkflowSourceTokens(resolved.source, meta.inputCtx);
3924
- if (sourceTokens.length > 0) {
3925
- const renderedTokens = [...new Set(sourceTokens)].map((token) => `{{${token}}}`).join(", ");
3926
- return {
3927
- status: "failed",
3928
- error: `swarm-script body interpolation failed for node "${meta.nodeId}": unresolved token(s): ${renderedTokens}. ` + "Named script source is not workflow-interpolated; pass dynamic values through config.args and read them from args."
3929
- };
3930
- }
3931
- const runStartTouch = resolved.script.isScratch ? await touchScratchScriptLastUsed(resolved.script.id) : null;
3932
- const credentials = await buildScriptCredentialBindingsWithFailures({
3933
- agentId: agentId ?? undefined
3934
- });
3935
- const output = await runScript({
3936
- source: resolved.source,
3937
- args: config.args,
3938
- fsMode: "none",
3939
- agentId: agentId ?? "workflow",
3940
- mcpBaseUrl: getMcpBaseUrl(),
3941
- egressSecrets: credentials.egressSecrets,
3942
- failedBindings: credentials.failedBindings,
3943
- apiConnections: getScriptApiConnectionDescriptors({ agentId: agentId ?? undefined }),
3944
- mcpConnections: getScriptMcpConnectionDescriptors({ agentId: agentId ?? undefined }),
3945
- timeoutMs: config.timeoutMs
3946
- });
3947
- if (output.exitCode === 0 && !output.error && !output.runtimeError) {
3948
- await touchScratchScriptLastUsed(resolved.script.id);
3949
- } else if (resolved.script.isScratch && runStartTouch) {
3950
- await restoreScratchScriptLastUsedIfUnchanged(resolved.script.id, resolved.script.updatedAt, runStartTouch);
3951
- }
3952
- const workflowOutput = {
3953
- result: output.result,
3954
- stdout: output.stdout,
3955
- stderr: output.stderr,
3956
- truncated: output.truncated,
3957
- durationMs: output.durationMs,
3958
- exitCode: output.exitCode,
3959
- scriptName: resolved.script.name,
3960
- contentHash: resolved.contentHash,
3961
- version: resolved.version
3962
- };
3963
- if (output.exitCode !== 0 || output.error) {
3964
- return {
3965
- status: "failed",
3966
- error: output.stderr || `swarm-script: script exited with code ${output.exitCode}${output.error ? ` (${output.error})` : ""}`,
3967
- output: workflowOutput
3968
- };
3969
- }
3970
- return {
3971
- status: "success",
3972
- output: workflowOutput,
3973
- nextPort: "success"
3974
- };
3975
- }
3976
- }
3977
- function agentIdFromContext(context) {
3978
- const trigger = context.trigger;
3979
- if (trigger && typeof trigger === "object") {
3980
- const value = trigger.agentId;
3981
- if (typeof value === "string" && value.length > 0)
3982
- return value;
3983
- }
3984
- return;
3985
- }
3986
- async function resolveScriptSource(config, agentId) {
3987
- if (config.scope === "agent" && !agentId) {
3988
- return {
3989
- ok: false,
3990
- error: "swarm-script: agent-scoped scripts require the workflow to have createdByAgentId or trigger.agentId"
3991
- };
3992
- }
3993
- const script = config.scope === "global" ? await getScript({ name: config.scriptName, scope: "global" }) : config.scope === "agent" ? await getScript({ name: config.scriptName, scope: "agent", scopeId: agentId }) : agentId ? await getScript({ name: config.scriptName, scope: "agent", scopeId: agentId }) ?? await getScript({ name: config.scriptName, scope: "global" }) : await getScript({ name: config.scriptName, scope: "global" });
3994
- if (!script) {
3995
- const scopeHint = config.scope ? ` in ${config.scope} scope` : "";
3996
- return {
3997
- ok: false,
3998
- error: `swarm-script: script '${config.scriptName}' not found${scopeHint}`
3999
- };
4000
- }
4001
- if (!config.pinHash) {
4002
- return {
4003
- ok: true,
4004
- script,
4005
- source: script.source,
4006
- contentHash: script.contentHash,
4007
- version: script.version
4008
- };
4009
- }
4010
- const version = await getScriptVersion({ scriptId: script.id, contentHash: config.pinHash });
4011
- if (!version) {
4012
- return {
4013
- ok: false,
4014
- error: `swarm-script: pinHash '${config.pinHash}' not found for script '${config.scriptName}'`
4015
- };
4016
- }
4017
- return {
4018
- ok: true,
4019
- script,
4020
- source: version.source,
4021
- contentHash: version.contentHash,
4022
- version: version.version
4023
- };
4024
- }
4025
-
4026
- // src/workflows/executors/validate.ts
4027
- init_zod();
4028
- var ValidateConfigSchema = exports_external.object({
4029
- targetNodeId: exports_external.string(),
4030
- prompt: exports_external.string().optional(),
4031
- schema: exports_external.record(exports_external.string(), exports_external.unknown()).optional()
4032
- });
4033
- var ValidateOutputSchema = exports_external.object({
4034
- pass: exports_external.boolean(),
4035
- reasoning: exports_external.string(),
4036
- confidence: exports_external.number().min(0).max(1)
4037
- });
4038
-
4039
- class ValidateExecutor extends BaseExecutor {
4040
- type = "validate";
4041
- mode = "instant";
4042
- configSchema = ValidateConfigSchema;
4043
- outputSchema = ValidateOutputSchema;
4044
- async execute(config, context, _meta) {
4045
- const targetOutput = context[config.targetNodeId];
4046
- if (targetOutput === undefined) {
4047
- return {
4048
- status: "success",
4049
- output: {
4050
- pass: false,
4051
- reasoning: `Target node "${config.targetNodeId}" has no output in context`,
4052
- confidence: 1
4053
- },
4054
- nextPort: "fail"
4055
- };
4056
- }
4057
- if (config.schema) {
4058
- return this.validateWithSchema(targetOutput, config.schema);
4059
- }
4060
- if (config.prompt) {
4061
- return this.validateWithLlm(targetOutput, config.prompt, context);
4062
- }
4063
- return {
4064
- status: "success",
4065
- output: { pass: true, reasoning: "Target node output exists", confidence: 1 },
4066
- nextPort: "pass"
4067
- };
4068
- }
4069
- validateWithSchema(targetOutput, schema) {
4070
- try {
4071
- const errors = validateJsonSchemaBasic(targetOutput, schema);
4072
- if (errors.length === 0) {
4073
- return {
4074
- status: "success",
4075
- output: { pass: true, reasoning: "Output matches schema", confidence: 1 },
4076
- nextPort: "pass"
4077
- };
4078
- }
4079
- return {
4080
- status: "success",
4081
- output: {
4082
- pass: false,
4083
- reasoning: `Schema validation failed: ${errors.join("; ")}`,
4084
- confidence: 1
4085
- },
4086
- nextPort: "fail"
4087
- };
4088
- } catch (err) {
4089
- return {
4090
- status: "success",
4091
- output: {
4092
- pass: false,
4093
- reasoning: `Schema validation error: ${err instanceof Error ? err.message : String(err)}`,
4094
- confidence: 0.5
4095
- },
4096
- nextPort: "fail"
4097
- };
4098
- }
4099
- }
4100
- async validateWithLlm(targetOutput, prompt, context) {
4101
- const interpolatedPrompt = this.deps.interpolate(prompt, context);
4102
- const outputStr = typeof targetOutput === "string" ? targetOutput : JSON.stringify(targetOutput);
4103
- try {
4104
- const { createOpenAI } = await import("./index-n5t1gzfb.js");
4105
- const { generateObject, jsonSchema } = await import("./index-aa3sa6s8.js");
4106
- const llmConfig = await resolveWorkflowLlmConfig();
4107
- const provider = createOpenAI({
4108
- baseURL: llmConfig.baseURL,
4109
- apiKey: llmConfig.apiKey
4110
- });
4111
- const { object } = await generateObject({
4112
- model: provider(llmConfig.model),
4113
- schema: jsonSchema({
4114
- type: "object",
4115
- properties: {
4116
- pass: { type: "boolean" },
4117
- reasoning: { type: "string" },
4118
- confidence: { type: "number", minimum: 0, maximum: 1 }
4119
- },
4120
- required: ["pass", "reasoning", "confidence"]
4121
- }),
4122
- providerOptions: {
4123
- openai: { strictJsonSchema: false }
4124
- },
4125
- prompt: `Evaluate the following output against the validation criteria.
4126
-
4127
- Criteria: ${interpolatedPrompt}
4128
-
4129
- Output to validate:
4130
- ${outputStr}
4131
-
4132
- Respond with pass (boolean), reasoning (string), and confidence (0-1).`
4133
- });
4134
- const result = object;
4135
- return {
4136
- status: "success",
4137
- output: result,
4138
- nextPort: result.pass ? "pass" : "fail"
4139
- };
4140
- } catch (err) {
4141
- const httpStatus = err?.status ?? err?.statusCode;
4142
- const isRateLimited = httpStatus === 429 || httpStatus === 529 || err instanceof Error && /rate.?limit|too many requests|529/i.test(err.message);
4143
- if (isRateLimited) {
4144
- throw err;
4145
- }
4146
- return {
4147
- status: "success",
4148
- output: {
4149
- pass: false,
4150
- reasoning: `LLM validation failed: ${err instanceof Error ? err.message : String(err)}`,
4151
- confidence: 0
4152
- },
4153
- nextPort: "fail"
4154
- };
4155
- }
4156
- }
4157
- }
4158
- function validateJsonSchemaBasic(value, schema) {
4159
- const errors = [];
4160
- if (schema.type) {
4161
- const expectedType = schema.type;
4162
- if (expectedType === "object") {
4163
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
4164
- errors.push(`Expected object, got ${Array.isArray(value) ? "array" : typeof value}`);
4165
- return errors;
4166
- }
4167
- if (schema.properties && typeof schema.properties === "object") {
4168
- const props = schema.properties;
4169
- const obj = value;
4170
- for (const [key, propSchema] of Object.entries(props)) {
4171
- if ("const" in propSchema && obj[key] !== propSchema.const) {
4172
- errors.push(`Property "${key}": expected const ${JSON.stringify(propSchema.const)}, got ${JSON.stringify(obj[key])}`);
4173
- }
4174
- if ("enum" in propSchema && Array.isArray(propSchema.enum)) {
4175
- if (!propSchema.enum.includes(obj[key])) {
4176
- errors.push(`Property "${key}": expected one of ${JSON.stringify(propSchema.enum)}, got ${JSON.stringify(obj[key])}`);
4177
- }
4178
- }
4179
- }
4180
- }
4181
- if (Array.isArray(schema.required)) {
4182
- const obj = value;
4183
- for (const key of schema.required) {
4184
- if (!(key in obj)) {
4185
- errors.push(`Missing required property "${key}"`);
4186
- }
4187
- }
4188
- }
4189
- } else if (expectedType === "string" && typeof value !== "string") {
4190
- errors.push(`Expected string, got ${typeof value}`);
4191
- } else if (expectedType === "number" && typeof value !== "number") {
4192
- errors.push(`Expected number, got ${typeof value}`);
4193
- } else if (expectedType === "boolean" && typeof value !== "boolean") {
4194
- errors.push(`Expected boolean, got ${typeof value}`);
4195
- } else if (expectedType === "array" && !Array.isArray(value)) {
4196
- errors.push(`Expected array, got ${typeof value}`);
4197
- }
4198
- }
4199
- return errors;
4200
- }
4201
-
4202
- // src/workflows/executors/vcs.ts
4203
- init_zod();
4204
- var VcsConfigSchema = exports_external.object({
4205
- action: exports_external.enum(["create-issue", "create-pr", "comment"]),
4206
- provider: exports_external.enum(["github", "gitlab"]),
4207
- repo: exports_external.string(),
4208
- title: exports_external.string().optional(),
4209
- body: exports_external.string().optional(),
4210
- branch: exports_external.string().optional(),
4211
- baseBranch: exports_external.string().optional(),
4212
- issueNumber: exports_external.number().optional(),
4213
- prNumber: exports_external.number().optional()
4214
- });
4215
- var VcsOutputSchema = exports_external.object({
4216
- url: exports_external.string(),
4217
- id: exports_external.union([exports_external.string(), exports_external.number()])
4218
- });
4219
-
4220
- class VcsExecutor extends BaseExecutor {
4221
- type = "vcs";
4222
- mode = "instant";
4223
- configSchema = VcsConfigSchema;
4224
- outputSchema = VcsOutputSchema;
4225
- async execute(config, context, _meta) {
4226
- const title = config.title ? this.deps.interpolate(config.title, context) : "";
4227
- const body = config.body ? this.deps.interpolate(config.body, context) : "";
4228
- console.log(`[vcs] Stub — ${config.action} on ${config.provider}/${config.repo}: title="${title}" body="${body.slice(0, 80)}"`);
4229
- const mockId = `stub-${Date.now()}`;
4230
- const mockUrl = `https://${config.provider}.com/${config.repo}/${config.action}/${mockId}`;
4231
- return {
4232
- status: "success",
4233
- output: { url: mockUrl, id: mockId }
4234
- };
4235
- }
4236
- }
4237
-
4238
- // src/workflows/executors/registry.ts
4239
- class ExecutorRegistry {
4240
- executors = new Map;
4241
- register(executor) {
4242
- this.executors.set(executor.type, executor);
4243
- }
4244
- get(type) {
4245
- const executor = this.executors.get(type);
4246
- if (!executor)
4247
- throw new Error(`Unknown executor type: ${type}`);
4248
- return executor;
4249
- }
4250
- has(type) {
4251
- return this.executors.has(type);
4252
- }
4253
- types() {
4254
- return [...this.executors.keys()];
4255
- }
4256
- describe(type) {
4257
- const executor = this.get(type);
4258
- return {
4259
- type: executor.type,
4260
- mode: executor.mode,
4261
- configSchema: exports_external.toJSONSchema(executor.configSchema),
4262
- outputSchema: exports_external.toJSONSchema(executor.outputSchema)
4263
- };
4264
- }
4265
- describeAll() {
4266
- return this.types().map((t) => this.describe(t));
4267
- }
4268
- }
4269
- function createExecutorRegistry(deps) {
4270
- const registry = new ExecutorRegistry;
4271
- registry.register(new PropertyMatchExecutor(deps));
4272
- registry.register(new CodeMatchExecutor(deps));
4273
- registry.register(new NotifyExecutor(deps));
4274
- registry.register(new RawLlmExecutor(deps));
4275
- registry.register(new ScriptExecutor(deps));
4276
- registry.register(new SwarmScriptExecutor(deps));
4277
- registry.register(new VcsExecutor(deps));
4278
- registry.register(new ValidateExecutor(deps));
4279
- registry.register(new AgentTaskExecutor(deps));
4280
- registry.register(new ForeachExecutor(deps));
4281
- registry.register(new HumanInTheLoopExecutor(deps));
4282
- registry.register(new WaitExecutor(deps));
4283
- return registry;
4284
- }
4285
-
4286
- // src/workflows/index.ts
4287
- var _registry = null;
4288
- var slackMessageTriggerListenerInitialized = false;
4289
- function createBuiltInExecutorRegistry() {
4290
- return createExecutorRegistry({
4291
- db: exports_db,
4292
- eventBus: workflowEventBus,
4293
- interpolate: (template, ctx) => interpolate(template, ctx).result
4294
- });
4295
- }
4296
- function getExecutorRegistry() {
4297
- _registry ??= createBuiltInExecutorRegistry();
4298
- return _registry;
4299
- }
4300
- function setupSlackMessageTriggerListener() {
4301
- if (slackMessageTriggerListenerInitialized)
4302
- return;
4303
- workflowEventBus.on("slack.message", (payload) => {
4304
- handleEventTrigger("slack.message", payload, getExecutorRegistry()).catch((err) => {
4305
- console.error("[workflows] Failed to dispatch slack.message event trigger:", scrubSecrets(err instanceof Error ? err.message : String(err)));
4306
- });
4307
- });
4308
- slackMessageTriggerListenerInitialized = true;
4309
- }
4310
- async function initWorkflows() {
4311
- _registry = getExecutorRegistry();
4312
- setupWorkflowResumeListener(workflowEventBus, _registry);
4313
- setupSlackMessageTriggerListener();
4314
- recoverIncompleteRuns(_registry).catch((err) => {
4315
- console.error("[workflows] Failed to recover incomplete runs on startup:", err);
4316
- });
4317
- startRetryPoller(_registry);
4318
- startWaitPoller(_registry);
4319
- await initWaitBusSubscriptions(_registry);
4320
- logOpenWebhookTriggers();
4321
- }
4322
-
4323
- export { validateJsonSchema, findJsonSchemaShapeErrors, notifyAutomationPreflightFailure, automationIntegrationFixUrl, isNeedsSetupFailure, preflightAutomation, renderAutomationTokens, getAutomationSetupStates, schedulePreflightInput, listEnabledAutomationPreflightInputs, recordSchedulePreflightFailure, TriggerSchemaError, startWorkflowExecution, setupWorkflowResumeListener, retryFailedRun, cancelWorkflowRun, resumeWaitState, initWaitBusSubscriptions, subscribeWaitToBus, recoverIncompleteRuns, startRetryPoller, stopRetryPoller, validateTemplateVariables, instantiateTemplate, handleWebhookTrigger, logOpenWebhookTriggers, handleEventTrigger, handleScheduleTrigger, verifyHmacSignature, WebhookError, snapshotWorkflow, snapshotAndUpdateWorkflow, startWaitPoller, stopWaitPoller, RawLlmConfigSchema, RawLlmOutputSchema, executeRawLlm, getExecutorRegistry, initWorkflows };