@desplega.ai/agent-swarm 2.4.4 → 2.5.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 (382) hide show
  1. package/README.md +4 -2
  2. package/dist/{acp-adapter-thq04adh.js → acp-adapter-11gp67nr.js} +8 -6
  3. package/dist/{actions-j6ywh6x5.js → actions-8p2kze8j.js} +24 -22
  4. package/dist/{amp-adapter-k8b5xmj8.js → amp-adapter-z4qvhacj.js} +2 -2
  5. package/dist/{anthropic-messages-g6pqmqk4.js → anthropic-messages-txs190dd.js} +17 -20
  6. package/dist/{app-rb7vc02s.js → app-0qp0a2gk.js} +3 -3
  7. package/dist/{assistant-ftta1zjf.js → assistant-jedd3ktm.js} +27 -25
  8. package/dist/{azure-openai-responses-9dbmj346.js → azure-openai-responses-9kfwhmn7.js} +18 -13
  9. package/dist/{boot-reembed-xns7h5h9.js → boot-reembed-4xwq5tg0.js} +1 -1
  10. package/dist/{boot-reembed-srp6v6af.js → boot-reembed-n4wq5dd2.js} +1 -1
  11. package/dist/{boot-scrub-sweep-3qqrh2mz.js → boot-scrub-sweep-69h90xcq.js} +4 -4
  12. package/dist/{boot-seeds-9ks8hce7.js → boot-seeds-3vn0m9sg.js} +4 -4
  13. package/dist/{claude-adapter-k5ksyres.js → claude-adapter-221mvad5.js} +8 -6
  14. package/dist/{claude-managed-adapter-h2y889ge.js → claude-managed-adapter-gt6pa35d.js} +2 -2
  15. package/dist/{claude-managed-setup-n5a5eqks.js → claude-managed-setup-c3hp36ph.js} +4 -4
  16. package/dist/{claude-sdk-session-2j3j4kwn.js → claude-sdk-session-8thjsk5e.js} +9729 -9628
  17. package/dist/{cli-b9fcxxev.js → cli-007cnhm1.js} +50 -4
  18. package/dist/{cli-j4rqdf2n.js → cli-03vpq45j.js} +2 -2
  19. package/dist/{cli-amyhm3gj.js → cli-0y6f0ckq.js} +1 -1
  20. package/dist/{cli-4dg55wsk.js → cli-0z3vjrpm.js} +1 -1
  21. package/dist/{cli-pccfmtb5.js → cli-179xcyky.js} +11 -11
  22. package/dist/{cli-3r8samv3.js → cli-1f0gp9vz.js} +3 -2
  23. package/dist/{cli-hj3debgw.js → cli-1pr672hz.js} +3 -3
  24. package/dist/{cli-0bqteszy.js → cli-2nf3b08t.js} +328 -25
  25. package/dist/{cli-t7cyef5g.js → cli-36j4jnk3.js} +4 -4
  26. package/dist/{cli-er83cjbn.js → cli-3qv8naq8.js} +527 -231
  27. package/dist/cli-3zwye9sn.js +1193 -0
  28. package/dist/{cli-60cgh1rv.js → cli-459fk97x.js} +7 -7
  29. package/dist/{cli-xj0re5tm.js → cli-4w24f7z3.js} +1 -1
  30. package/dist/{cli-v3yr2gbg.js → cli-5kzk3472.js} +2198 -968
  31. package/dist/{cli-h7gc0b4r.js → cli-5m84zd2k.js} +55 -18
  32. package/dist/{cli-w6nadkh4.js → cli-5rvg1ddk.js} +5 -5
  33. package/dist/{cli-dvkmp9bt.js → cli-6ctwrf1z.js} +20 -10
  34. package/dist/{cli-9qtjfd60.js → cli-6rg2jn2y.js} +3 -2
  35. package/dist/{cli-2v045xyf.js → cli-7bwtc6yx.js} +3 -3
  36. package/dist/{cli-3cgngyd8.js → cli-7g40g8hq.js} +35 -17
  37. package/dist/{cli-q6r7t299.js → cli-92mqenkc.js} +2379 -154
  38. package/dist/{cli-cynvc9fj.js → cli-9ce2xw98.js} +8 -8
  39. package/dist/{cli-gqvh8d8a.js → cli-bgdtv62r.js} +36 -22
  40. package/dist/{cli-wxbn3yfx.js → cli-cz5nmdxt.js} +2 -2
  41. package/dist/{cli-wt645v7q.js → cli-dabehxhp.js} +1 -1
  42. package/dist/{cli-vfha21c9.js → cli-e5bxnjps.js} +2 -2
  43. package/dist/{cli-p20r8w42.js → cli-fcc5qe3f.js} +763 -268
  44. package/dist/{cli-1ephgwnq.js → cli-fes51ya2.js} +3 -3
  45. package/dist/{cli-1d70crsy.js → cli-ftmq4nvk.js} +2 -2
  46. package/dist/{cli-qkhc5aqy.js → cli-fy1jyyym.js} +13 -13
  47. package/dist/{cli-s95dknxh.js → cli-g1cw7xtt.js} +1 -1
  48. package/dist/{cli-qje9k2nh.js → cli-g9ypd5en.js} +12 -2
  49. package/dist/{cli-74c1wpm0.js → cli-gpv60278.js} +1 -1
  50. package/dist/{cli-pemywhfq.js → cli-ha9rmwgq.js} +2 -1
  51. package/dist/{cli-bnz0r3kc.js → cli-j1eghqfm.js} +5 -5
  52. package/dist/{cli-2zqpkvtt.js → cli-jpxsrxcb.js} +1 -1
  53. package/dist/{cli-ejs3m1sx.js → cli-kxpfx2xr.js} +1 -1
  54. package/dist/{cli-37yb7307.js → cli-mbfj11fq.js} +1 -1
  55. package/dist/{cli-pnp2j5zg.js → cli-mmhj8vd7.js} +2 -2
  56. package/dist/{cli-xf234ws8.js → cli-n4c8e3s5.js} +2 -0
  57. package/dist/{cli-a0kcty51.js → cli-n6kmqngw.js} +2 -2
  58. package/dist/{cli-te6b5wvg.js → cli-nnvjb17s.js} +3 -3
  59. package/dist/{cli-1dmcq2eh.js → cli-nzfnt2gs.js} +1 -1
  60. package/dist/{cli-625tb7aq.js → cli-p859hcc8.js} +2 -2
  61. package/dist/{cli-smvcw9z5.js → cli-pasyvjmm.js} +1 -1
  62. package/dist/cli-pkaqk57w.js +128 -0
  63. package/dist/{cli-8nyhf7za.js → cli-qqzmjxkf.js} +129 -105
  64. package/dist/{cli-zrwpctp4.js → cli-r25qpaxt.js} +161 -63
  65. package/dist/{cli-nc35pxb7.js → cli-rh5smt8t.js} +19 -4
  66. package/dist/{cli-qwkcwjdn.js → cli-rycdaj0a.js} +1 -1
  67. package/dist/{cli-esxerzg7.js → cli-s4sntwt4.js} +2 -2
  68. package/dist/{cli-b8k7epj1.js → cli-segs7hx9.js} +8 -8
  69. package/dist/{cli-36ckw0wx.js → cli-stxdrqs2.js} +2 -2
  70. package/dist/{cli-vdm20wej.js → cli-sy8f9705.js} +4 -4
  71. package/dist/{cli-1m17dc9d.js → cli-ta8bd7th.js} +2 -2
  72. package/dist/{cli-2y91wnbh.js → cli-we0ra057.js} +222 -10
  73. package/dist/{cli-55qmbp6z.js → cli-x21dqn18.js} +5 -5
  74. package/dist/{cli-dqb4n629.js → cli-x6vf4sad.js} +11 -11
  75. package/dist/{cli-d2mb5s8a.js → cli-x77gen2p.js} +680 -440
  76. package/dist/{cli-vq97g1jq.js → cli-xawhpgc9.js} +95 -195
  77. package/dist/{cli-82d3nkc3.js → cli-xd4eqtr2.js} +2 -2
  78. package/dist/{cli-9fhkkpj5.js → cli-xt768r5m.js} +2 -2
  79. package/dist/{cli-zrk6jxv8.js → cli-y4x8ww6s.js} +2 -2
  80. package/dist/{cli-xc27fwzg.js → cli-y9jvxfhk.js} +3 -3
  81. package/dist/{cli-44159k28.js → cli-z63a269p.js} +2 -2
  82. package/dist/{cli-sm8rses9.js → cli-zfdp01hn.js} +8 -5
  83. package/dist/cli.js +31 -29
  84. package/dist/{cloudflare-workers-ai-system-one-ch2wxfcp.js → cloudflare-workers-ai-system-one-43v9qkjk.js} +7 -2
  85. package/dist/{codex-adapter-968xwfy7.js → codex-adapter-tcg1q3jk.js} +6 -4
  86. package/dist/{codex-login-m819c41r.js → codex-login-rn7c5gxt.js} +1 -1
  87. package/dist/{codex-session-runner-ht4z99fa.js → codex-session-runner-xfx4wd9x.js} +7 -5
  88. package/dist/{commands-pcymaeg9.js → commands-ew83nj70.js} +2 -2
  89. package/dist/{controller-fpjyk46r.js → controller-gnq1k1mf.js} +4 -4
  90. package/dist/{controllers-connect-mwzx1fh6.js → controllers-connect-tw1dan9r.js} +26 -24
  91. package/dist/{cursor-adapter-cp4s2e2f.js → cursor-adapter-yq5xgfcg.js} +1 -1
  92. package/dist/{db-xsjg9jqq.js → db-7gbc53fd.js} +14 -2
  93. package/dist/{db-sgd12bt9.js → db-t86xrnes.js} +3 -3
  94. package/dist/{devin-adapter-zgqadvnm.js → devin-adapter-4ycy4vrf.js} +1 -1
  95. package/dist/{dispatcher-x0jfw2c9.js → dispatcher-64bdyhps.js} +23 -21
  96. package/dist/{docker-fkgtr75t.js → docker-nfy2gc8t.js} +2 -2
  97. package/dist/{dsh-adapter-jrq7ma7h.js → dsh-adapter-tvhc4k2d.js} +1 -1
  98. package/dist/{e2b-16d6ch39.js → e2b-9gkdhm3c.js} +2 -2
  99. package/dist/{e2b-zn0b4w5w.js → e2b-cjtf8k1z.js} +2 -2
  100. package/dist/{easter-egg-3d-5cgynaja.js → easter-egg-3d-e4qv8dxj.js} +1 -1
  101. package/dist/{event-bus-9vpk8gmq.js → event-bus-3z78ep5n.js} +1 -1
  102. package/dist/{execute-t4e6m3rw.js → execute-0xpz8950.js} +85 -23
  103. package/dist/{external-channel-8njj4h3b.js → external-channel-e9nw24zv.js} +2 -2
  104. package/dist/{google-generative-ai-bmq8gkf3.js → google-generative-ai-mk9vcpe1.js} +15 -15
  105. package/dist/{google-vertex-b1rvp52s.js → google-vertex-q580kz8k.js} +20 -20
  106. package/dist/{grok-adapter-kkr97v2f.js → grok-adapter-v82srhrx.js} +9 -7
  107. package/dist/handlers-c9ajzhbn.js +41 -0
  108. package/dist/{heartbeat-wd03zhn0.js → heartbeat-2vqs1319.js} +26 -24
  109. package/dist/{hook-xzj2ts2p.js → hook-01nk13wx.js} +7 -5
  110. package/dist/{hook-f0vghj42.js → hook-jzpheegg.js} +9 -7
  111. package/dist/{http-4hwxv62p.js → http-fc1k6ec8.js} +859 -252
  112. package/dist/index-07ef7nkm.js +42 -0
  113. package/dist/{index-g6tbb6ve.js → index-31ceyf42.js} +1 -1
  114. package/dist/index-7yts8n9s.js +43 -0
  115. package/dist/{index-c4ykd0f1.js → index-dr94zbnh.js} +13348 -11611
  116. package/dist/index-q3t40ryv.js +46 -0
  117. package/dist/{index-ww2vznkp.js → index-sepja052.js} +1 -1
  118. package/dist/{index-97q4xn03.js → index-zdycv3ja.js} +135 -79
  119. package/dist/{index-g216q6jr.js → index-zsyx51ap.js} +1 -4
  120. package/dist/{keepalive-hrh7xnr9.js → keepalive-5nh2rhmr.js} +4 -4
  121. package/dist/lead-aayq64jn.js +79 -0
  122. package/dist/{llama-cpp-classify-vv43gswy.js → llama-cpp-classify-ee7vmd7q.js} +4 -1
  123. package/dist/{maintenance-e8wnwnqx.js → maintenance-hjhzdt3k.js} +1 -1
  124. package/dist/{mistral-conversations-1medfwv7.js → mistral-conversations-sd0xf1tn.js} +13 -13
  125. package/dist/{node-3v3f08f7.js → node-jzjj7r9x.js} +10 -9
  126. package/dist/{oauth-refresh-sweep-zm0yj4sj.js → oauth-refresh-sweep-y3n1knmd.js} +3 -3
  127. package/dist/{onboard-dtwcq603.js → onboard-1zq3m7db.js} +2 -2
  128. package/dist/{openai-codex-responses-de9pvs4d.js → openai-codex-responses-79s8xnbh.js} +29 -23
  129. package/dist/{openai-completions-mtqge3nf.js → openai-completions-tdbyts0x.js} +21 -20
  130. package/dist/{openai-decisions-ygk4kkxd.js → openai-decisions-bqhnjyfz.js} +2 -1
  131. package/dist/{openai-responses-dkyng71g.js → openai-responses-dm3t5vqp.js} +26 -17
  132. package/dist/{opencode-adapter-x4tqzhkh.js → opencode-adapter-e56086m1.js} +7 -5
  133. package/dist/{openrouter-images-wvv7camj.js → openrouter-images-ssttr1y4.js} +6 -3
  134. package/dist/{otel-impl-w3fbhpn0.js → otel-impl-h9yyza47.js} +2 -2
  135. package/dist/{pi-messages-nhh1pkz2.js → pi-messages-3nf8bq1a.js} +6 -4
  136. package/dist/{pi-mono-adapter-mcfkn9vf.js → pi-mono-adapter-tzps8f38.js} +9 -37
  137. package/dist/{pricing-refresh-j38dehfs.js → pricing-refresh-nevhat8h.js} +3 -3
  138. package/dist/{rbac-roles-ae8yttq4.js → rbac-roles-4y6ngvzq.js} +1 -1
  139. package/dist/{rbac-roles-ejxx9b35.js → rbac-roles-exbg8xb6.js} +2 -2
  140. package/dist/{registry-rxw10xzg.js → registry-ajhexe44.js} +1 -1
  141. package/dist/{render-v2-n16n0phe.js → render-v2-cfsh13z3.js} +3 -3
  142. package/dist/{resume-z0fgjc38.js → resume-5s2t0dbs.js} +10 -8
  143. package/dist/{runtime-xe3aqy4x.js → runtime-bt5pkpm1.js} +1 -1
  144. package/dist/{session-access-cv3mq6mc.js → session-access-dnbyp3cn.js} +6 -4
  145. package/dist/{setup-mafhj3yt.js → setup-1wj1y77j.js} +1 -1
  146. package/dist/{typesafe-system-one-2hgpayzy.js → typesafe-system-one-bre38hvs.js} +4 -2
  147. package/dist/{virtual-modules-4r3zkc20.js → virtual-modules-mnfbh5mr.js} +8 -2
  148. package/dist/{work-objects-8b86mgjp.js → work-objects-7mg6216g.js} +26 -24
  149. package/dist/worker-nwb789mg.js +79 -0
  150. package/dist/{x-y45mthwn.js → x-pbpezr0s.js} +2 -2
  151. package/openapi.json +793 -6
  152. package/package.json +11 -11
  153. package/src/apps/catalog.generated.json +206 -4
  154. package/src/apps/page-validator.ts +13 -1
  155. package/src/assets/key.ts +17 -0
  156. package/src/azure-devops/handlers.ts +1 -1
  157. package/src/be/chunking.ts +42 -9
  158. package/src/be/db/approvals.ts +18 -0
  159. package/src/be/db/session-moves.ts +164 -0
  160. package/src/be/db/session-tree.ts +384 -0
  161. package/src/be/db/tasks/draft-text.ts +15 -0
  162. package/src/be/db/tasks/read.ts +54 -5
  163. package/src/be/db/workflow-runs.ts +96 -0
  164. package/src/be/db.ts +85 -237
  165. package/src/be/memory/index-content.ts +36 -17
  166. package/src/be/memory/providers/openai-embedding.ts +115 -61
  167. package/src/be/memory/providers/sqlite-store.ts +69 -23
  168. package/src/be/memory/reranker.ts +31 -1
  169. package/src/be/memory/search-candidates.ts +47 -0
  170. package/src/be/memory/types.ts +14 -0
  171. package/src/be/onboarding.ts +3 -0
  172. package/src/be/scripts/typecheck.ts +2 -0
  173. package/src/be/seed-scripts/catalog/heartbeat-jev-gate.ts +106 -54
  174. package/src/be/seed-scripts/index.ts +2 -2
  175. package/src/be/skill-sync.ts +43 -2
  176. package/src/be/swarm-config-guard.ts +25 -1
  177. package/src/be/task-attachment-store.ts +30 -0
  178. package/src/bitbucket/api.ts +81 -0
  179. package/src/bitbucket/auth.ts +64 -0
  180. package/src/bitbucket/handlers.ts +434 -0
  181. package/src/bitbucket/index.ts +28 -0
  182. package/src/bitbucket/templates.ts +134 -0
  183. package/src/bitbucket/types.ts +65 -0
  184. package/src/commands/runner.ts +55 -3
  185. package/src/e2b/env.ts +1 -0
  186. package/src/extensions/catalog.generated.json +4 -4
  187. package/src/extensions/contract-types.generated.ts +1 -1
  188. package/src/extensions/contract.ts +9 -2
  189. package/src/extensions/post-bridge.ts +1 -0
  190. package/src/fs/provider.ts +13 -1
  191. package/src/github/handlers.ts +6 -6
  192. package/src/gitlab/handlers.ts +5 -5
  193. package/src/heartbeat/heartbeat.ts +31 -11
  194. package/src/http/assets.ts +14 -2
  195. package/src/http/core.ts +12 -0
  196. package/src/http/fs.ts +54 -34
  197. package/src/http/index.ts +4 -0
  198. package/src/http/mcp-body.ts +37 -0
  199. package/src/http/mcp-user.ts +3 -5
  200. package/src/http/mcp.ts +6 -7
  201. package/src/http/memory.ts +26 -15
  202. package/src/http/onboarding.ts +1 -0
  203. package/src/http/page-proxy.ts +61 -5
  204. package/src/http/pages-public.ts +27 -44
  205. package/src/http/pages.ts +4 -4
  206. package/src/http/script-runs.ts +2 -0
  207. package/src/http/sessions.ts +366 -12
  208. package/src/http/skills.ts +48 -1
  209. package/src/http/tasks.ts +1 -1
  210. package/src/http/users.ts +13 -1
  211. package/src/http/webhooks.ts +141 -0
  212. package/src/integrations/chat-sdk/adapters.ts +24 -4
  213. package/src/integrations/chat-sdk/attachments.ts +90 -12
  214. package/src/integrations/chat-sdk/facts.ts +138 -0
  215. package/src/integrations/chat-sdk/inbound.ts +156 -8
  216. package/src/integrations/chat-sdk/limits.ts +24 -0
  217. package/src/integrations/chat-sdk/outbound-files.ts +186 -0
  218. package/src/integrations/chat-sdk/outbound.ts +272 -5
  219. package/src/integrations/chat-sdk/relay.ts +4 -1
  220. package/src/integrations/chat-sdk/router.ts +28 -2
  221. package/src/integrations/chat-sdk/steering.ts +9 -0
  222. package/src/integrations/chat-sdk/templates.ts +14 -0
  223. package/src/integrations/chat-sdk/thread-buffer.ts +4 -0
  224. package/src/mcp-limits.ts +20 -0
  225. package/src/pages/browser-sdk.ts +8 -3
  226. package/src/pages/feedback-overlay.ts +1 -1
  227. package/src/providers/acp-targets.ts +1 -1
  228. package/src/providers/amp-adapter.ts +1 -1
  229. package/src/providers/codex-adapter.ts +106 -119
  230. package/src/providers/codex-agents-md.ts +35 -229
  231. package/src/providers/cursor-adapter.ts +1 -1
  232. package/src/providers/pi-mono-adapter.ts +5 -53
  233. package/src/realtime/transport.ts +1 -2
  234. package/src/script-workflows/workflow-ctx.ts +1 -0
  235. package/src/scripts-runtime/types/stdlib.d.ts +2 -0
  236. package/src/scripts-runtime/types/swarm-extension.d.ts +14 -4
  237. package/src/scripts-runtime/types/swarm-sdk.d.ts +2 -0
  238. package/src/server-user.ts +2 -0
  239. package/src/server.ts +2 -0
  240. package/src/slack/assistant.ts +2 -2
  241. package/src/slack/files.ts +2 -0
  242. package/src/slack/handlers.test.ts +158 -0
  243. package/src/slack/handlers.ts +23 -32
  244. package/src/slack/inbound-files.ts +39 -6
  245. package/src/slack/thread-buffer.ts +2 -1
  246. package/src/tasks/context-key.ts +61 -1
  247. package/src/tasks/task-terminal-effects.ts +21 -17
  248. package/src/tests/apps-card-grid.test.ts +197 -0
  249. package/src/tests/apps-catalog-additions.test.ts +120 -0
  250. package/src/tests/apps-tree.test.ts +129 -0
  251. package/src/tests/bitbucket-auth.test.ts +156 -0
  252. package/src/tests/bitbucket-handlers.test.ts +425 -0
  253. package/src/tests/bitbucket-webhook.test.ts +220 -0
  254. package/src/tests/chat-sdk-attachments-reply.test.ts +52 -0
  255. package/src/tests/chat-sdk-config.test.ts +11 -0
  256. package/src/tests/chat-sdk-facts.test.ts +23 -0
  257. package/src/tests/chat-sdk-inbound.test.ts +597 -4
  258. package/src/tests/chat-sdk-outbound-files.test.ts +652 -0
  259. package/src/tests/chat-sdk-telegram-markdown.test.ts +92 -0
  260. package/src/tests/codex-adapter.test.ts +167 -242
  261. package/src/tests/config-api-only-keys.test.ts +9 -0
  262. package/src/tests/context-key.test.ts +50 -0
  263. package/src/tests/core-auth.test.ts +5 -0
  264. package/src/tests/cors-allowlist.test.ts +1 -1
  265. package/src/tests/db-task-read-contract.test.ts +7 -0
  266. package/src/tests/db-task-write-contract.test.ts +6 -0
  267. package/src/tests/docs-index-footprint.test.js +3 -3
  268. package/src/tests/extensions-example-slack-reply-gate.test.ts +262 -91
  269. package/src/tests/fixtures/bitbucket/pullrequest-comment-created-inline.json +174 -0
  270. package/src/tests/fixtures/bitbucket/pullrequest-comment-created.json +170 -0
  271. package/src/tests/fixtures/bitbucket/pullrequest-created-mention.json +132 -0
  272. package/src/tests/fixtures/bitbucket/pullrequest-created-reviewer.json +140 -0
  273. package/src/tests/fixtures/bitbucket/pullrequest-fulfilled.json +134 -0
  274. package/src/tests/fixtures/bitbucket/pullrequest-rejected.json +133 -0
  275. package/src/tests/generate-mcp-docs.test.ts +101 -0
  276. package/src/tests/gitlab-handlers.test.ts +3 -3
  277. package/src/tests/gitlab-vcs-db.test.ts +4 -4
  278. package/src/tests/heartbeat-checklist.test.ts +33 -0
  279. package/src/tests/heartbeat-jev-gate-credentials.test.ts +44 -0
  280. package/src/tests/heartbeat-jev-gate.test.ts +242 -1
  281. package/src/tests/integration-disable-flags.test.ts +36 -0
  282. package/src/tests/kv-page-proxy.test.ts +91 -5
  283. package/src/tests/launch-password-rejection.test.ts +2 -2
  284. package/src/tests/mcp-input-caps.test.ts +294 -0
  285. package/src/tests/mcp-tools.test.ts +1 -1
  286. package/src/tests/memory-chunked-search-starvation.test.ts +200 -0
  287. package/src/tests/memory-embedding-input-cap.test.ts +114 -0
  288. package/src/tests/memory.test.ts +14 -0
  289. package/src/tests/onboarding.test.ts +12 -0
  290. package/src/tests/page-guest-session.test.ts +19 -8
  291. package/src/tests/page-proxy-authed.test.ts +1 -1
  292. package/src/tests/page-proxy.test.ts +17 -10
  293. package/src/tests/page-session.test.ts +102 -1
  294. package/src/tests/pages-authed-mode.test.ts +82 -12
  295. package/src/tests/pages-password-mode.test.ts +5 -5
  296. package/src/tests/pi-codemode.test.ts +8 -2
  297. package/src/tests/preload-env.test.ts +57 -0
  298. package/src/tests/preload.ts +21 -0
  299. package/src/tests/prompt-template-remaining.test.ts +38 -0
  300. package/src/tests/realtime-transport.test.ts +13 -7
  301. package/src/tests/run-root-coverage.test.ts +39 -0
  302. package/src/tests/runner-bitbucket-vcs.test.ts +191 -0
  303. package/src/tests/runner-skills-refresh.test.ts +58 -0
  304. package/src/tests/script-runs-http.test.ts +84 -0
  305. package/src/tests/scripts-http.test.ts +10 -1
  306. package/src/tests/secret-scrubber.test.ts +15 -0
  307. package/src/tests/sessions-move.test.ts +576 -0
  308. package/src/tests/sessions-tree.test.ts +493 -0
  309. package/src/tests/skill-files-http.test.ts +50 -0
  310. package/src/tests/skills-bundle.test.ts +99 -0
  311. package/src/tests/swarm-config-export-to-env.test.ts +23 -3
  312. package/src/tests/task-audio-transcription.test.ts +299 -0
  313. package/src/tests/task-completion-memory-chunking.test.ts +434 -0
  314. package/src/tests/task-terminal-secret-scrub.test.ts +6 -1
  315. package/src/tests/transcription.test.ts +299 -0
  316. package/src/tests/vcs-bitbucket.test.ts +91 -0
  317. package/src/tests/vcs-provider-isolation.test.ts +123 -0
  318. package/src/tests/vcs-provider.test.ts +16 -1
  319. package/src/tests/vcs-tracking.test.ts +3 -3
  320. package/src/tests/workflow-foreach-bodies.test.ts +637 -0
  321. package/src/tests/workflow-foreach-concurrency.test.ts +342 -0
  322. package/src/tests/workflow-foreach-retry.test.ts +537 -0
  323. package/src/tests/workflow-foreach.test.ts +44 -23
  324. package/src/tests/workflow-step-claims.test.ts +6 -6
  325. package/src/tests/workflow-system-one-decision-providers.test.ts +98 -1
  326. package/src/tests/workflow-system-one-decision.test.ts +2 -1
  327. package/src/tests/workflow-tla-races.test.ts +2 -2
  328. package/src/tools/chat-access.ts +10 -0
  329. package/src/tools/chat-post.ts +31 -11
  330. package/src/tools/chat-reply.ts +32 -10
  331. package/src/tools/memory-search.ts +3 -1
  332. package/src/tools/script-delete.ts +1 -1
  333. package/src/tools/system-one-decide.ts +17 -4
  334. package/src/tools/workflows/create-workflow.ts +5 -3
  335. package/src/types.ts +9 -2
  336. package/src/utils/config-env-export.ts +4 -1
  337. package/src/utils/page-session.ts +133 -20
  338. package/src/utils/secret-scrubber.ts +4 -0
  339. package/src/utils/skills-refresh.ts +83 -43
  340. package/src/utils/transcription.ts +246 -0
  341. package/src/vcs/bitbucket.ts +58 -0
  342. package/src/vcs/index.ts +2 -0
  343. package/src/vcs/types.ts +2 -2
  344. package/src/workflows/approval-resolution.ts +4 -1
  345. package/src/workflows/checkpoint.ts +9 -1
  346. package/src/workflows/constants.ts +3 -0
  347. package/src/workflows/definition.ts +48 -17
  348. package/src/workflows/engine.ts +25 -15
  349. package/src/workflows/executors/foreach.ts +45 -99
  350. package/src/workflows/executors/system-one-decision.ts +28 -7
  351. package/src/workflows/executors/system-one-providers.ts +39 -4
  352. package/src/workflows/foreach-body.ts +146 -0
  353. package/src/workflows/foreach-driver.ts +552 -0
  354. package/src/workflows/foreach-join.ts +206 -7
  355. package/src/workflows/node-timeout.ts +21 -0
  356. package/src/workflows/recovery.ts +68 -3
  357. package/src/workflows/resume.ts +221 -43
  358. package/src/workflows/retry-poller.ts +39 -24
  359. package/src/workflows/step-input.ts +59 -0
  360. package/src/workflows/task-step-routing.ts +114 -56
  361. package/templates/extensions/slack-reply-gate/README.md +24 -22
  362. package/templates/extensions/slack-reply-gate/hooks.ts +146 -95
  363. package/templates/extensions/slack-reply-gate/manifest.json +2 -2
  364. package/templates/skills/apps/SKILL.md +28 -6
  365. package/templates/skills/apps/content.md +28 -6
  366. package/templates/skills/chat-sdk/content.md +6 -2
  367. package/templates/skills/code-quality/content.md +2 -1
  368. package/templates/skills/heartbeat-runbook/content.md +5 -5
  369. package/templates/skills/pages/content.md +6 -4
  370. package/templates/skills/slack-interaction/content.md +5 -0
  371. package/templates/skills/swarm-scripts/SKILL.md +2 -2
  372. package/templates/skills/swarm-scripts/content.md +2 -2
  373. package/templates/skills/work-on-task/SKILL.md +1 -1
  374. package/templates/skills/work-on-task/content.md +1 -1
  375. package/dist/cli-51y09c6p.js +0 -2657
  376. package/dist/cli-yw91rnmw.js +0 -13
  377. package/dist/handlers-0pvee9nw.js +0 -39
  378. package/dist/index-gdh8jrdm.js +0 -43
  379. package/dist/index-hg0x2cdv.js +0 -40
  380. package/dist/index-thm0y6a8.js +0 -41
  381. package/dist/lead-ee8vazz2.js +0 -77
  382. package/dist/worker-16s75hm5.js +0 -77
package/src/vcs/types.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * VCS Provider types — shared across GitHub/GitLab/Azure DevOps integration.
2
+ * VCS Provider types — shared across GitHub/GitLab/Azure DevOps/Bitbucket integration.
3
3
  */
4
4
 
5
- export type VcsProvider = "github" | "gitlab" | "azure-devops";
5
+ export type VcsProvider = "github" | "gitlab" | "azure-devops" | "bitbucket";
@@ -1,6 +1,7 @@
1
1
  import type { WorkflowDefinition } from "../types";
2
2
  import type { ApprovalOutcome, ResolvedApproval } from "./executors/base";
3
3
  import type { ExecutorRegistry } from "./executors/registry";
4
+ import { resolveStepNode } from "./foreach-body";
4
5
 
5
6
  /**
6
7
  * Step output and output port for a step whose approval request resolved.
@@ -18,7 +19,9 @@ export function shapeApprovalResolution(
18
19
  parkedOutput: unknown,
19
20
  approval: ResolvedApproval,
20
21
  ): ApprovalOutcome {
21
- const node = definition.nodes.find((candidate) => candidate.id === nodeId);
22
+ // A foreach child resolves to its body node, so a system-one-decision body
23
+ // keeps its own shaping.
24
+ const node = resolveStepNode(definition, nodeId);
22
25
  if (node && registry.has(node.type)) {
23
26
  const shaped = registry.get(node.type).resolveApproval(parkedOutput, approval);
24
27
  if (shaped) return shaped;
@@ -83,11 +83,16 @@ export async function checkpointStepFailure(
83
83
 
84
84
  /**
85
85
  * Checkpoint a step entering waiting state (async executor).
86
+ *
87
+ * `keepRunStatus` parks the step and persists the context but leaves the run
88
+ * `running`: a foreach parent parks before its driver starts the children, and
89
+ * the run must not read as `waiting` until they are dispatched.
86
90
  */
87
91
  export async function checkpointStepWaiting(
88
92
  runId: string,
89
93
  stepId: string,
90
94
  ctx: Record<string, unknown>,
95
+ options: { keepRunStatus?: boolean } = {},
91
96
  ): Promise<boolean> {
92
97
  return getDbClient().transaction(async () => {
93
98
  const step = await getDbClient().get<{ id: string }>(
@@ -103,7 +108,10 @@ export async function checkpointStepWaiting(
103
108
  );
104
109
  if (!step) return false;
105
110
 
106
- await updateWorkflowRun(runId, { status: "waiting", context: ctx });
111
+ await updateWorkflowRun(
112
+ runId,
113
+ options.keepRunStatus ? { context: ctx } : { status: "waiting", context: ctx },
114
+ );
107
115
  return true;
108
116
  });
109
117
  }
@@ -1 +1,4 @@
1
1
  export const FAILED_TASK_OUTPUT_PREFIX = "[FAILED:";
2
+
3
+ /** `waitFor` of a foreach parent that materialized its children; the driver starts them. */
4
+ export const FOREACH_ITEMS_WAIT_FOR = "foreach.items";
@@ -11,6 +11,12 @@ import {
11
11
  systemOneStaticShapeViolations,
12
12
  } from "./executors/system-one-decision";
13
13
  import { systemOneExtractionStaticShapeViolations } from "./executors/system-one-extraction";
14
+ import {
15
+ FOREACH_FAILURE_POLICIES,
16
+ type ForeachFailurePolicy,
17
+ foreachBodyTypeError,
18
+ isForeachBodyType,
19
+ } from "./foreach-body";
14
20
 
15
21
  /** Extract all target node IDs from a node's `next` field */
16
22
  export function getNextTargets(next: string | string[] | Record<string, string>): string[] {
@@ -220,7 +226,7 @@ export function validateDefinition(
220
226
  ...systemOneRetryViolations(node),
221
227
  );
222
228
  if (node.type === "foreach") {
223
- validateForeachNode(node, errors);
229
+ validateForeachNode(node, errors, registry);
224
230
  // A legacy `#` id may stay editable as a NORMAL node, but never as a foreach
225
231
  // parent: its children would be `a#b#item`, and parseSyntheticNodeId splits on
226
232
  // the FIRST `#`, so the join would resolve them to parent "a" and never close.
@@ -312,20 +318,25 @@ export function validateDefinition(
312
318
 
313
319
  reportStaticConfigIssues(node, registry.get(node.type).configSchema, node.config, errors);
314
320
 
315
- // A foreach body is executed by the agent-task executor per item — hold its
316
- // static config to the same schema a top-level agent-task node gets, so an
317
- // invalid field (priority: 101, tags: "x") fails at authoring instead of
321
+ // A foreach body is executed by its own executor per item — hold its
322
+ // static config to the same schema a top-level node of that type gets, so
323
+ // an invalid field (priority: 101, tags: "x") fails at authoring instead of
318
324
  // after the fan-out materialized. Interpolated fields defer as usual.
319
- if (node.type === "foreach" && registry.has("agent-task")) {
320
- const body = node.config.body;
321
- const bodyConfig =
322
- typeof body === "object" && body !== null && !Array.isArray(body)
323
- ? (body as Record<string, unknown>).config
324
- : undefined;
325
+ const body = node.type === "foreach" ? node.config.body : undefined;
326
+ const bodyRecord =
327
+ typeof body === "object" && body !== null && !Array.isArray(body)
328
+ ? (body as Record<string, unknown>)
329
+ : undefined;
330
+ if (
331
+ bodyRecord &&
332
+ typeof bodyRecord.type === "string" &&
333
+ isForeachBodyType(bodyRecord.type, registry)
334
+ ) {
335
+ const bodyConfig = bodyRecord.config;
325
336
  if (typeof bodyConfig === "object" && bodyConfig !== null && !Array.isArray(bodyConfig)) {
326
337
  reportStaticConfigIssues(
327
338
  node,
328
- registry.get("agent-task").configSchema,
339
+ registry.get(bodyRecord.type).configSchema,
329
340
  bodyConfig,
330
341
  errors,
331
342
  "config.body.config",
@@ -401,10 +412,27 @@ function reportStaticConfigIssues(
401
412
  }
402
413
  }
403
414
 
404
- function validateForeachNode(node: WorkflowNode, errors: string[]): void {
415
+ function validateForeachNode(
416
+ node: WorkflowNode,
417
+ errors: string[],
418
+ registry?: ExecutorRegistry,
419
+ ): void {
405
420
  const config = node.config;
406
- if (Object.hasOwn(config, "concurrency")) {
407
- errors.push(`Node "${node.id}" foreach concurrency is not supported in v1`);
421
+ if (
422
+ config.concurrency !== undefined &&
423
+ !(Number.isInteger(config.concurrency) && (config.concurrency as number) >= 1)
424
+ ) {
425
+ errors.push(
426
+ `Node "${node.id}" foreach config.concurrency must be an integer of 1 or more, got ${JSON.stringify(config.concurrency)}`,
427
+ );
428
+ }
429
+ if (
430
+ config.failurePolicy !== undefined &&
431
+ !FOREACH_FAILURE_POLICIES.includes(config.failurePolicy as ForeachFailurePolicy)
432
+ ) {
433
+ errors.push(
434
+ `Node "${node.id}" foreach config.failurePolicy must be one of ${FOREACH_FAILURE_POLICIES.map((p) => `"${p}"`).join(", ")}, got ${JSON.stringify(config.failurePolicy)}`,
435
+ );
408
436
  }
409
437
 
410
438
  // The asynchronous join (joinForeach) checkpoints the aggregate and routes
@@ -436,8 +464,11 @@ function validateForeachNode(node: WorkflowNode, errors: string[]): void {
436
464
  return;
437
465
  }
438
466
  const bodyRecord = body as Record<string, unknown>;
439
- if (bodyRecord.type !== "agent-task") {
440
- errors.push(`Node "${node.id}" foreach config.body.type must be "agent-task" in v1`);
467
+ if (typeof bodyRecord.type !== "string" || bodyRecord.type.length === 0) {
468
+ errors.push(`Node "${node.id}" foreach config.body.type must be a non-empty string`);
469
+ } else {
470
+ const typeError = foreachBodyTypeError(node.id, bodyRecord.type, registry);
471
+ if (typeError) errors.push(typeError);
441
472
  }
442
473
  if (
443
474
  typeof bodyRecord.config !== "object" ||
@@ -448,7 +479,7 @@ function validateForeachNode(node: WorkflowNode, errors: string[]): void {
448
479
  return;
449
480
  }
450
481
  const bodyConfig = bodyRecord.config as Record<string, unknown>;
451
- if (typeof bodyConfig.template !== "string") {
482
+ if (bodyRecord.type === "agent-task" && typeof bodyConfig.template !== "string") {
452
483
  errors.push(`Node "${node.id}" foreach agent-task body.config.template must be a string`);
453
484
  }
454
485
  }
@@ -25,6 +25,7 @@ import { telemetry } from "../telemetry";
25
25
  import type { Workflow, WorkflowDefinition, WorkflowNode, WorkflowRunStep } from "../types";
26
26
  import { checkpointStep, checkpointStepFailure, checkpointStepWaiting } from "./checkpoint";
27
27
  import { loadCompletedStepRouting } from "./completed-step-routing";
28
+ import { FOREACH_ITEMS_WAIT_FOR } from "./constants";
28
29
  import { shouldSkipCooldown } from "./cooldown";
29
30
  import { findEntryNodes, getNextTargets, getSuccessors, resolveValidationPort } from "./definition";
30
31
  import type { AsyncExecutorResult } from "./executors/base";
@@ -34,15 +35,16 @@ import {
34
35
  systemOneRetryViolations,
35
36
  systemOneUnresolvedError,
36
37
  } from "./executors/system-one-decision";
38
+ import { parkAndDriveForeach } from "./foreach-driver";
37
39
  import { FOREACH_TERMINAL_STEP_STATUSES, resolveForeachParent } from "./foreach-join";
38
40
  import { getSecretInputKeys, redactSecretsForStorage, resolveInputs } from "./input";
39
41
  import { validateJsonSchema } from "./json-schema-validator";
40
42
  import { getMaxWorkflowStepsPerRun } from "./limits";
43
+ import { resolveNodeTimeoutMs } from "./node-timeout";
41
44
  import { findWorkflowReadinessProblems, formatReadinessRunError } from "./readiness";
42
45
  import { deepInterpolate } from "./template";
43
46
  import { runStepValidation, type ValidationRunResult } from "./validation";
44
47
 
45
- const DEFAULT_TIMEOUT_MS = 30_000;
46
48
  // Read dynamically, NOT captured at module load: `src/http/index.ts` imports
47
49
  // this module before `loadGlobalConfigsIntoEnv()` hydrates `swarm_config` into
48
50
  // `process.env`, so a module-level const would permanently ignore DB-saved
@@ -510,14 +512,19 @@ async function walkGraphOwned(
510
512
  const hasPendingRetries = finalSteps.some(
511
513
  (s) => s.status === "failed" && s.nextRetryAt != null,
512
514
  );
513
- const failedSteps = finalSteps.filter((s) => s.status === "failed" && s.nextRetryAt == null);
515
+ // A foreach child's outcome is in its parent's aggregate; the parent step
516
+ // is authoritative for the run outcome.
517
+ const isForeachChild = (s: WorkflowRunStep) => resolveForeachParent(def, s.nodeId) !== null;
518
+ const failedSteps = finalSteps.filter(
519
+ (s) => s.status === "failed" && s.nextRetryAt == null && !isForeachChild(s),
520
+ );
514
521
  // Exclude entry/trigger nodes when checking for completed steps — a trigger
515
522
  // completing doesn't mean a meaningful branch succeeded. Without this filter,
516
523
  // a linear workflow (trigger → mustPass validator → action) would be marked
517
524
  // as partial-failure instead of failed when the validator fails.
518
525
  const entryNodeIds = new Set(findEntryNodes(def).map((n) => n.id));
519
526
  const hasCompletedSteps = finalSteps.some(
520
- (s) => s.status === "completed" && !entryNodeIds.has(s.nodeId),
527
+ (s) => s.status === "completed" && !entryNodeIds.has(s.nodeId) && !isForeachChild(s),
521
528
  );
522
529
 
523
530
  if (hasWaitingSteps) {
@@ -880,18 +887,7 @@ async function runClaimedStep(
880
887
  inputCtx: interpolationCtx,
881
888
  };
882
889
 
883
- // Inline script nodes historically expose their wall-clock budget as
884
- // `config.timeout`, while the workflow watchdog reads `config.timeoutMs` for
885
- // every other executor. Keep the outer watchdog aligned with the script
886
- // executor so a script configured for (for example) 90s is not killed by the
887
- // engine's 30s default first.
888
- const configuredTimeoutMs =
889
- typeof node.config?.timeoutMs === "number"
890
- ? node.config.timeoutMs
891
- : node.type === "script" && typeof node.config?.timeout === "number"
892
- ? node.config.timeout
893
- : undefined;
894
- const timeoutMs = configuredTimeoutMs ?? DEFAULT_TIMEOUT_MS;
890
+ const timeoutMs = resolveNodeTimeoutMs(node);
895
891
 
896
892
  let result: Awaited<ReturnType<typeof executor.run>>;
897
893
  try {
@@ -949,6 +945,20 @@ async function runClaimedStep(
949
945
 
950
946
  // Check for async result
951
947
  if ("async" in result && (result as AsyncExecutorResult).async) {
948
+ if ((result as AsyncExecutorResult).waitFor === FOREACH_ITEMS_WAIT_FOR) {
949
+ // The foreach executor materialized its children; park the parent and
950
+ // start them. Instant bodies run here, each against its own budget, and
951
+ // can close the join before the drive returns.
952
+ const drive = await parkAndDriveForeach({
953
+ runId,
954
+ stepId,
955
+ parentNodeId: node.id,
956
+ ctx,
957
+ registry,
958
+ });
959
+ if (drive.joined) return { outcome: "completed", successors: drive.successors };
960
+ return { outcome: drive.parked ? "waiting" : "completed", successors: [] };
961
+ }
952
962
  const waiting = await checkpointStepWaiting(runId, stepId, ctx);
953
963
  // A child run that finished before the step parked had its event dropped.
954
964
  if (waiting && (result as AsyncExecutorResult).waitFor === "workflow.child.finished") {
@@ -1,19 +1,22 @@
1
1
  import { z } from "zod";
2
+ import { createForeachChildSteps } from "../../be/db/workflow-runs";
2
3
  import type { ExecutorMeta } from "../../types";
3
- import { deepInterpolate } from "../../utils/template";
4
+ import { FOREACH_ITEMS_WAIT_FOR } from "../constants";
4
5
  import { getSuccessors } from "../definition";
6
+ import { foreachBodyTypeError } from "../foreach-body";
5
7
  import {
6
8
  buildForeachAggregate,
7
9
  FOREACH_TERMINAL_STEP_STATUSES,
8
10
  parseSyntheticNodeId,
9
11
  } from "../foreach-join";
10
12
  import { getMaxWorkflowStepsPerRun } from "../limits";
11
- import { AgentTaskExecutor } from "./agent-task";
12
13
  import type { AsyncExecutorResult, ExecutorResult } from "./base";
13
14
  import { BaseExecutor } from "./base";
14
15
 
16
+ // Any registered executor except `foreach` (see foreach-body.ts); the guard in
17
+ // execute() and definition validation enforce the policy.
15
18
  const ForeachBodySchema = z.object({
16
- type: z.literal("agent-task"),
19
+ type: z.string().min(1),
17
20
  config: z.record(z.string(), z.unknown()),
18
21
  });
19
22
 
@@ -21,7 +24,12 @@ export const ForeachConfigSchema = z.object({
21
24
  over: z.array(z.record(z.string(), z.unknown())),
22
25
  itemKey: z.string().min(1),
23
26
  body: ForeachBodySchema,
24
- concurrency: z.never().optional(),
27
+ /** Items in flight at once (`running` or `waiting` children). Unbounded when absent. */
28
+ concurrency: z.number().int().min(1).optional(),
29
+ /** Defaults from the workflow `onNodeFailure` (see effectiveFailurePolicy). */
30
+ // Literal, not FOREACH_FAILURE_POLICIES: foreach-body.ts sits in an import
31
+ // cycle with this module and would be read before it initializes.
32
+ failurePolicy: z.enum(["fail-fast", "collect-outcomes"]).optional(),
25
33
  });
26
34
 
27
35
  const ForeachResultSchema = z.object({
@@ -34,6 +42,8 @@ export const ForeachOutputSchema = z.object({
34
42
  results: z.array(ForeachResultSchema),
35
43
  okCount: z.number().int().nonnegative(),
36
44
  failedCount: z.number().int().nonnegative(),
45
+ /** Items cancelled before they finished (fail-fast queued items, cancelled bodies). Counted in failedCount too. */
46
+ cancelledCount: z.number().int().nonnegative().optional(),
37
47
  });
38
48
 
39
49
  export type ForeachOutput = z.infer<typeof ForeachOutputSchema>;
@@ -49,7 +59,7 @@ export class ForeachExecutor extends BaseExecutor<
49
59
 
50
60
  protected async execute(
51
61
  config: z.infer<typeof ForeachConfigSchema>,
52
- context: Readonly<Record<string, unknown>>,
62
+ _context: Readonly<Record<string, unknown>>,
53
63
  meta: ExecutorMeta,
54
64
  ): Promise<ExecutorResult<ForeachOutput>> {
55
65
  if (config.over.length === 0) {
@@ -77,16 +87,19 @@ export class ForeachExecutor extends BaseExecutor<
77
87
  seenItemKeys.add(itemKey);
78
88
  }
79
89
 
80
- const agentTaskExecutor = new AgentTaskExecutor(this.deps);
90
+ const bodyTypeError = foreachBodyTypeError(meta.nodeId, config.body.type);
91
+ if (bodyTypeError) return { status: "failed", error: bodyTypeError };
92
+
81
93
  const runSteps = await this.deps.db.getWorkflowRunStepsByRunId(meta.runId);
82
- const childStepsByNodeId = new Map(
94
+ const existingChildNodeIds = new Set(
83
95
  runSteps
84
96
  .filter((step) => parseSyntheticNodeId(step.nodeId)?.parentNodeId === meta.nodeId)
85
- .map((step) => [step.nodeId, step]),
97
+ .map((step) => step.nodeId),
98
+ );
99
+ const missingItems = items.filter(
100
+ ({ itemKey }) => !existingChildNodeIds.has(`${meta.nodeId}#${itemKey}`),
86
101
  );
87
- const childCountToCreate = items.filter(
88
- ({ itemKey }) => !childStepsByNodeId.has(`${meta.nodeId}#${itemKey}`),
89
- ).length;
102
+ const childCountToCreate = missingItems.length;
90
103
  const maxSteps = getMaxWorkflowStepsPerRun();
91
104
  // walkGraph's circuit breaker rejects any post-join walk once allSteps.length
92
105
  // >= maxSteps, so a fan-out with a successor that lands exactly on the cap would
@@ -107,79 +120,26 @@ export class ForeachExecutor extends BaseExecutor<
107
120
  };
108
121
  }
109
122
 
110
- // Materialize EVERY child step row before dispatching ANY child task. A task
111
- // dispatched for an early child can complete (and trigger joinForeach) while
112
- // later children are still being set up — the join must always see the full
113
- // child set, with not-yet-dispatched children in a non-terminal state.
114
- for (const { index, itemKey } of items) {
115
- const childNodeId = `${meta.nodeId}#${itemKey}`;
116
- if (childStepsByNodeId.has(childNodeId)) continue;
117
- const childStepId = crypto.randomUUID();
118
- const childStep = await this.deps.db.createWorkflowRunStep({
119
- id: childStepId,
120
- runId: meta.runId,
121
- nodeId: childNodeId,
122
- nodeType: "agent-task",
123
- input: { itemKey, index },
124
- });
125
- await this.deps.db.updateWorkflowRunStep(childStepId, {
126
- idempotencyKey: `${meta.runId}:${childNodeId}:0`,
127
- });
128
- childStepsByNodeId.set(childNodeId, childStep);
129
- }
130
-
131
- for (const { item, index, itemKey } of items) {
132
- const childNodeId = `${meta.nodeId}#${itemKey}`;
133
- const childStep = childStepsByNodeId.get(childNodeId)!;
134
-
135
- // Re-walks must not regress terminal children or disturb their aggregate
136
- // inputs. Retry resets only the failed child to pending before arriving here.
137
- if (FOREACH_TERMINAL_STEP_STATUSES.has(childStep.status)) continue;
138
-
139
- // The deferred per-item pass interpolates against the node's declared-inputs
140
- // context (meta.inputCtx) plus item/index — NOT the full run context — so
141
- // body.config obeys the same explicit-dataflow boundary as normal node
142
- // config instead of silently reading undeclared upstream outputs.
143
- const bodyCtx = { ...(meta.inputCtx ?? context), item, index };
144
- const childInterpolation = deepInterpolate(config.body.config, bodyCtx);
145
- if (childInterpolation.unresolved.length > 0) {
146
- console.warn(
147
- `[workflow] Step ${childNodeId}: unresolved interpolation tokens: ${childInterpolation.unresolved.join(", ")}`,
148
- );
149
- await this.deps.db.updateWorkflowRunStep(childStep.id, {
150
- diagnostics: JSON.stringify({ unresolvedTokens: childInterpolation.unresolved }),
151
- });
152
- }
153
-
154
- const linkedTask = await this.deps.db.getTaskByWorkflowRunStepId(childStep.id);
155
- if (linkedTask?.status === "failed" || linkedTask?.status === "cancelled") {
156
- // A retry needs a fresh task. Detaching the stale terminal task keeps the
157
- // executor's one-task-per-step de-duplication deterministic and scoped.
158
- await this.deps.db.detachTaskFromWorkflowRunStep(linkedTask.id);
159
- }
160
-
161
- const childResult = await agentTaskExecutor.run({
162
- config: childInterpolation.value as Record<string, unknown>,
163
- context,
164
- meta: {
165
- ...meta,
166
- stepId: childStep.id,
123
+ // Materialize EVERY child row, queued (`pending`), in one transaction and
124
+ // before any child starts. An early child can complete (and trigger the
125
+ // join) while later children are still being set up, so the join must
126
+ // always see the full child set. The item value is not persisted: the
127
+ // driver looks it up by key in the re-interpolated `over`, which keeps
128
+ // resolved secrets out of step rows.
129
+ await createForeachChildSteps(
130
+ meta.runId,
131
+ meta.nodeId,
132
+ missingItems.map(({ index, itemKey }) => {
133
+ const childNodeId = `${meta.nodeId}#${itemKey}`;
134
+ return {
135
+ id: crypto.randomUUID(),
167
136
  nodeId: childNodeId,
168
- },
169
- });
170
- if (childResult.status !== "success") {
171
- throw new Error(childResult.error ?? `foreach child "${itemKey}" failed to start`);
172
- }
173
- if ("async" in childResult) {
174
- await this.deps.db.updateWorkflowRunStep(childStep.id, { status: "waiting" });
175
- } else {
176
- await this.deps.db.updateWorkflowRunStep(childStep.id, {
177
- status: "completed",
178
- output: normalizeExistingTaskOutput(childResult.output),
179
- finishedAt: new Date().toISOString(),
180
- });
181
- }
182
- }
137
+ nodeType: config.body.type,
138
+ input: { itemKey, index, attempt: 0 },
139
+ idempotencyKey: `${meta.runId}:${childNodeId}:0`,
140
+ };
141
+ }),
142
+ );
183
143
 
184
144
  const childSteps = (await this.deps.db.getWorkflowRunStepsByRunId(meta.runId)).filter(
185
145
  (step) => parseSyntheticNodeId(step.nodeId)?.parentNodeId === meta.nodeId,
@@ -188,26 +148,12 @@ export class ForeachExecutor extends BaseExecutor<
188
148
  return { status: "success", output: buildForeachAggregate(childSteps) };
189
149
  }
190
150
 
151
+ // The engine parks the parent and the foreach driver starts the children.
191
152
  return {
192
153
  status: "success",
193
154
  async: true,
194
- waitFor: "task.completed",
155
+ waitFor: FOREACH_ITEMS_WAIT_FOR,
195
156
  correlationId: meta.stepId,
196
157
  } as AsyncExecutorResult<ForeachOutput>;
197
158
  }
198
159
  }
199
-
200
- function normalizeExistingTaskOutput(output: unknown): unknown {
201
- if (typeof output !== "object" || output === null) return output;
202
- const record = output as Record<string, unknown>;
203
- if (typeof record.taskOutput !== "string") return output;
204
- try {
205
- const parsed = JSON.parse(record.taskOutput);
206
- if (typeof parsed === "object" && parsed !== null) {
207
- return { ...record, taskOutput: parsed };
208
- }
209
- } catch {
210
- // Non-structured task output remains a string.
211
- }
212
- return output;
213
- }
@@ -18,8 +18,8 @@ import {
18
18
  } from "./human-in-the-loop";
19
19
  import { SYSTEM_ONE_EXTRACTION_NODE_TYPE } from "./system-one-extraction";
20
20
  import {
21
+ resolveSystemOneDefaultProvider,
21
22
  resolveSystemOneTarget,
22
- SYSTEM_ONE_DEFAULT_PROVIDER,
23
23
  SYSTEM_ONE_PROVIDER_IDS,
24
24
  SYSTEM_ONE_PROVIDERS,
25
25
  type SystemOneProvider,
@@ -164,7 +164,11 @@ export type SystemOneHumanReview = z.infer<typeof HumanReviewSchema>;
164
164
 
165
165
  export const SystemOneDecisionConfigSchema = z
166
166
  .strictObject({
167
- provider: z.enum(SYSTEM_ONE_PROVIDER_IDS).default(SYSTEM_ONE_DEFAULT_PROVIDER),
167
+ /**
168
+ * Decision host. Unset means the `SYSTEM_ONE_DEFAULT_PROVIDER` global config
169
+ * value, read at run time (`typesafe` when that is unset too).
170
+ */
171
+ provider: z.enum(SYSTEM_ONE_PROVIDER_IDS).optional(),
168
172
  /** Provider-specific model id. Unset means the provider's default. */
169
173
  model: z.string().min(1).optional(),
170
174
  state: SystemOneStateSchema,
@@ -235,7 +239,9 @@ export const SystemOneDecisionConfigSchema = z
235
239
  }
236
240
  }
237
241
  // Provider limits. A {{token}} model is skipped at save and checked again at run,
238
- // where the interpolated config is parsed by this same schema.
242
+ // where the interpolated config is parsed by this same schema. With no provider
243
+ // the default is read at run time, so `decide` checks the limits there.
244
+ if (config.provider === undefined) return;
239
245
  const modelProblem = systemOneModelProblem(
240
246
  config.provider,
241
247
  config.model ?? (SYSTEM_ONE_PROVIDERS[config.provider] as SystemOneProvider).defaultModel,
@@ -847,6 +853,11 @@ export class SystemOneDecisionExecutor extends BaseExecutor<
847
853
  return { db: this.deps.db, env: this.options.env ?? process.env };
848
854
  }
849
855
 
856
+ /** The provider a config without `provider` uses, read now so a settings change applies at once. */
857
+ private defaultProvider(): Promise<SystemOneProviderId> {
858
+ return resolveSystemOneDefaultProvider(this.providerContext());
859
+ }
860
+
850
861
  private resolveTarget(id: SystemOneProviderId, model?: string) {
851
862
  return resolveSystemOneTarget(id, this.providerContext(), {
852
863
  apiKey: this.options.getApiKey,
@@ -863,9 +874,10 @@ export class SystemOneDecisionExecutor extends BaseExecutor<
863
874
  nodes: readonly ExecutorReadinessNode[],
864
875
  ): Promise<ExecutorReadinessProblem[]> {
865
876
  const nodeIdsByProvider = new Map<SystemOneProviderId, string[]>();
877
+ const defaultId = await this.defaultProvider();
866
878
  for (const node of nodes) {
867
879
  // An unknown provider is a config error the schema reports; it names no credential.
868
- const id = systemOneProviderOf(node.config);
880
+ const id = systemOneProviderOf(node.config, defaultId);
869
881
  if (id) nodeIdsByProvider.set(id, [...(nodeIdsByProvider.get(id) ?? []), node.id]);
870
882
  }
871
883
  const problems: ExecutorReadinessProblem[] = [];
@@ -903,16 +915,25 @@ export class SystemOneDecisionExecutor extends BaseExecutor<
903
915
  */
904
916
  async decide(
905
917
  config: SystemOneDecisionConfig,
906
- ): Promise<{ ok: true; output: SystemOneDecisionOutput } | { ok: false; error: string }> {
907
- const providerId = config.provider;
918
+ ): Promise<
919
+ | { ok: true; output: SystemOneDecisionOutput }
920
+ | { ok: false; error: string; deploymentRefused?: true }
921
+ > {
922
+ const providerId = config.provider ?? (await this.defaultProvider());
908
923
  const provider: SystemOneProvider = SYSTEM_ONE_PROVIDERS[providerId];
909
924
 
910
925
  // A provider with no default sends no `model` when the node sets none.
911
926
  const model = config.model ?? provider.defaultModel;
912
927
  const modelProblem = systemOneModelProblem(providerId, model);
913
928
  if (modelProblem) return { ok: false, error: modelProblem };
929
+ // Checked at save only for a named provider; a defaulted one is checked here.
930
+ const limitProblem = systemOneQuestionLimitProblem(
931
+ providerId,
932
+ Object.keys(config.questions).length,
933
+ );
934
+ if (limitProblem) return { ok: false, error: limitProblem };
914
935
  const target = await this.resolveTarget(providerId, model);
915
- if (!target.ok) return { ok: false, error: target.error };
936
+ if (!target.ok) return target;
916
937
  const { apiKey, endpoint, keyName } = target;
917
938
 
918
939
  const wire = provider.wire ?? SYSTEM_ONE_WIRE;
@@ -288,15 +288,41 @@ export const SYSTEM_ONE_PROVIDERS = Object.fromEntries(
288
288
  export const SYSTEM_ONE_PROVIDER_IDS = hostsServing("decide");
289
289
  export const SYSTEM_ONE_EXTRACT_PROVIDER_IDS = hostsServing("extract");
290
290
 
291
+ /** The decision provider when neither the call nor `SYSTEM_ONE_DEFAULT_PROVIDER` names one. */
291
292
  export const SYSTEM_ONE_DEFAULT_PROVIDER: SystemOneProviderId = "typesafe";
292
293
  export const SYSTEM_ONE_EXTRACT_DEFAULT_PROVIDER: SystemOneExtractProviderId = "cloudflare";
293
294
 
295
+ /** Global config key naming the decision provider a node or call without `provider` uses. */
296
+ export const SYSTEM_ONE_DEFAULT_PROVIDER_CONFIG_KEY = "SYSTEM_ONE_DEFAULT_PROVIDER";
297
+
294
298
  export function isSystemOneProviderId(value: unknown): value is SystemOneProviderId {
295
299
  return (
296
300
  typeof value === "string" && (SYSTEM_ONE_PROVIDER_IDS as readonly string[]).includes(value)
297
301
  );
298
302
  }
299
303
 
304
+ /**
305
+ * The decision provider for a node or call that names none: the global
306
+ * `SYSTEM_ONE_DEFAULT_PROVIDER` row, then the same key in the environment, then
307
+ * `typesafe`. Read at run time, so changing the setting needs no workflow edit.
308
+ * A value that is not a decide host is skipped; the config guard refuses it on save.
309
+ */
310
+ export async function resolveSystemOneDefaultProvider(
311
+ ctx: SystemOneProviderContext,
312
+ ): Promise<SystemOneProviderId> {
313
+ let stored: string | undefined;
314
+ try {
315
+ stored = await readGlobalConfig(ctx.db, SYSTEM_ONE_DEFAULT_PROVIDER_CONFIG_KEY);
316
+ } catch {
317
+ // An unreadable row falls back like an unset one: the default stays usable.
318
+ }
319
+ for (const raw of [stored, ctx.env[SYSTEM_ONE_DEFAULT_PROVIDER_CONFIG_KEY]]) {
320
+ const id = raw?.trim().toLowerCase();
321
+ if (isSystemOneProviderId(id)) return id;
322
+ }
323
+ return SYSTEM_ONE_DEFAULT_PROVIDER;
324
+ }
325
+
300
326
  export function isSystemOneExtractProviderId(value: unknown): value is SystemOneExtractProviderId {
301
327
  return (
302
328
  typeof value === "string" &&
@@ -304,11 +330,15 @@ export function isSystemOneExtractProviderId(value: unknown): value is SystemOne
304
330
  );
305
331
  }
306
332
 
307
- /** Provider a raw node config selects. Undefined when the field is present but not a known id. */
333
+ /**
334
+ * Provider a raw node config selects, `defaultId` when it names none. Undefined
335
+ * when the field is present but not a known id.
336
+ */
308
337
  export function systemOneProviderOf(
309
338
  config: Record<string, unknown>,
339
+ defaultId: SystemOneProviderId = SYSTEM_ONE_DEFAULT_PROVIDER,
310
340
  ): SystemOneProviderId | undefined {
311
- if (config.provider === undefined) return SYSTEM_ONE_DEFAULT_PROVIDER;
341
+ if (config.provider === undefined) return defaultId;
312
342
  return isSystemOneProviderId(config.provider) ? config.provider : undefined;
313
343
  }
314
344
 
@@ -489,7 +519,12 @@ export type SystemOneTarget =
489
519
  /** The secret name the key was read under, for a rejection message. */
490
520
  keyName: string;
491
521
  }
492
- | { ok: false; error: string };
522
+ | {
523
+ ok: false;
524
+ error: string;
525
+ /** This deployment refuses the host whatever its credential (`deploymentProblem`). */
526
+ deploymentRefused?: true;
527
+ };
493
528
 
494
529
  export interface SystemOneTargetOverrides {
495
530
  /** Replaces the credential lookup. */
@@ -517,7 +552,7 @@ export async function resolveSystemOneTarget(
517
552
  const capability = overrides.capability ?? "decide";
518
553
  const service = serviceOf(id, capability);
519
554
  const deploymentProblem = host.deploymentProblem?.(ctx.env);
520
- if (deploymentProblem) return { ok: false, error: deploymentProblem };
555
+ if (deploymentProblem) return { ok: false, error: deploymentProblem, deploymentRefused: true };
521
556
 
522
557
  const errors: string[] = [];
523
558