@desplega.ai/agent-swarm 2.4.5 → 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 (272) hide show
  1. package/README.md +4 -2
  2. package/dist/{acp-adapter-2wdkktv6.js → acp-adapter-11gp67nr.js} +8 -6
  3. package/dist/{actions-c40ebcm4.js → actions-8p2kze8j.js} +23 -21
  4. package/dist/{amp-adapter-wb67m95z.js → amp-adapter-z4qvhacj.js} +2 -2
  5. package/dist/{anthropic-messages-g6pqmqk4.js → anthropic-messages-txs190dd.js} +17 -20
  6. package/dist/{app-353vre8g.js → app-0qp0a2gk.js} +3 -3
  7. package/dist/{assistant-sj1njh23.js → assistant-jedd3ktm.js} +24 -22
  8. package/dist/{azure-openai-responses-9dbmj346.js → azure-openai-responses-9kfwhmn7.js} +18 -13
  9. package/dist/{boot-reembed-qdkvxeem.js → boot-reembed-4xwq5tg0.js} +1 -1
  10. package/dist/{boot-reembed-b9c3az3c.js → boot-reembed-n4wq5dd2.js} +1 -1
  11. package/dist/{boot-scrub-sweep-m3wh8eks.js → boot-scrub-sweep-69h90xcq.js} +4 -4
  12. package/dist/{boot-seeds-j2273s54.js → boot-seeds-3vn0m9sg.js} +4 -4
  13. package/dist/{claude-adapter-m7q0a085.js → claude-adapter-221mvad5.js} +8 -6
  14. package/dist/{claude-managed-adapter-5ptb6tzs.js → claude-managed-adapter-gt6pa35d.js} +2 -2
  15. package/dist/{claude-managed-setup-smrqk5j5.js → claude-managed-setup-c3hp36ph.js} +4 -4
  16. package/dist/{claude-sdk-session-38xnz21e.js → claude-sdk-session-8thjsk5e.js} +9012 -8928
  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-dk44dxec.js → cli-0y6f0ckq.js} +1 -1
  20. package/dist/{cli-kpw04va0.js → cli-0z3vjrpm.js} +1 -1
  21. package/dist/{cli-94gwyg7m.js → cli-179xcyky.js} +9 -9
  22. package/dist/{cli-3r8samv3.js → cli-1f0gp9vz.js} +3 -2
  23. package/dist/{cli-4zfqgjc5.js → cli-1pr672hz.js} +2 -2
  24. package/dist/{cli-0bqteszy.js → cli-2nf3b08t.js} +328 -25
  25. package/dist/{cli-43x64hv3.js → cli-36j4jnk3.js} +4 -4
  26. package/dist/{cli-k8g16gz3.js → cli-3qv8naq8.js} +115 -119
  27. package/dist/cli-3zwye9sn.js +1193 -0
  28. package/dist/{cli-60cgh1rv.js → cli-459fk97x.js} +7 -7
  29. package/dist/{cli-v8fjakwf.js → cli-4w24f7z3.js} +1 -1
  30. package/dist/{cli-n72rg53p.js → cli-5kzk3472.js} +812 -418
  31. package/dist/{cli-h7gc0b4r.js → cli-5m84zd2k.js} +55 -18
  32. package/dist/{cli-j99azg3g.js → cli-5rvg1ddk.js} +5 -5
  33. package/dist/{cli-dvkmp9bt.js → cli-6ctwrf1z.js} +20 -10
  34. package/dist/{cli-848hxdwj.js → cli-6rg2jn2y.js} +2 -1
  35. package/dist/{cli-fv5hepyy.js → cli-7bwtc6yx.js} +3 -3
  36. package/dist/{cli-ms85309d.js → cli-7g40g8hq.js} +11 -11
  37. package/dist/{cli-xpa64rek.js → cli-92mqenkc.js} +2362 -159
  38. package/dist/{cli-018r4jq6.js → cli-9ce2xw98.js} +8 -8
  39. package/dist/{cli-1syn2xy1.js → cli-bgdtv62r.js} +32 -18
  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-wt0jf9hm.js → cli-fcc5qe3f.js} +553 -193
  44. package/dist/{cli-ftc1g5tj.js → cli-fes51ya2.js} +3 -3
  45. package/dist/{cli-3a45zcgm.js → cli-ftmq4nvk.js} +2 -2
  46. package/dist/{cli-37j04k05.js → cli-fy1jyyym.js} +13 -13
  47. package/dist/{cli-s95dknxh.js → cli-g1cw7xtt.js} +1 -1
  48. package/dist/{cli-74c1wpm0.js → cli-gpv60278.js} +1 -1
  49. package/dist/{cli-pemywhfq.js → cli-ha9rmwgq.js} +2 -1
  50. package/dist/{cli-g35m2xwm.js → cli-j1eghqfm.js} +2 -2
  51. package/dist/{cli-qfxc84gh.js → cli-jpxsrxcb.js} +1 -1
  52. package/dist/{cli-ejs3m1sx.js → cli-kxpfx2xr.js} +1 -1
  53. package/dist/{cli-p14m2cnp.js → cli-mbfj11fq.js} +1 -1
  54. package/dist/{cli-pnp2j5zg.js → cli-mmhj8vd7.js} +2 -2
  55. package/dist/{cli-xf234ws8.js → cli-n4c8e3s5.js} +2 -0
  56. package/dist/{cli-f01h25yt.js → cli-n6kmqngw.js} +2 -2
  57. package/dist/{cli-v0yrfmdh.js → cli-nnvjb17s.js} +3 -3
  58. package/dist/{cli-1dmcq2eh.js → cli-nzfnt2gs.js} +1 -1
  59. package/dist/{cli-625tb7aq.js → cli-p859hcc8.js} +2 -2
  60. package/dist/{cli-smvcw9z5.js → cli-pasyvjmm.js} +1 -1
  61. package/dist/cli-pkaqk57w.js +128 -0
  62. package/dist/{cli-8nyhf7za.js → cli-qqzmjxkf.js} +129 -105
  63. package/dist/{cli-9yqgm9nd.js → cli-r25qpaxt.js} +161 -63
  64. package/dist/{cli-eww120g8.js → cli-rh5smt8t.js} +18 -4
  65. package/dist/{cli-qwkcwjdn.js → cli-rycdaj0a.js} +1 -1
  66. package/dist/{cli-a92r1127.js → cli-s4sntwt4.js} +2 -2
  67. package/dist/{cli-be2161h1.js → cli-segs7hx9.js} +6 -6
  68. package/dist/{cli-36ckw0wx.js → cli-stxdrqs2.js} +2 -2
  69. package/dist/{cli-z7d29rw9.js → cli-sy8f9705.js} +4 -4
  70. package/dist/{cli-1m17dc9d.js → cli-ta8bd7th.js} +2 -2
  71. package/dist/{cli-st8aw1m6.js → cli-we0ra057.js} +4 -4
  72. package/dist/{cli-7c9qs530.js → cli-x21dqn18.js} +5 -5
  73. package/dist/{cli-1sp861s4.js → cli-x6vf4sad.js} +11 -11
  74. package/dist/{cli-d2mb5s8a.js → cli-x77gen2p.js} +680 -440
  75. package/dist/{cli-tw4p90jb.js → cli-xawhpgc9.js} +5 -5
  76. package/dist/{cli-f0g3pgvf.js → cli-xd4eqtr2.js} +2 -2
  77. package/dist/{cli-1f3qzw6y.js → cli-xt768r5m.js} +2 -2
  78. package/dist/{cli-cxxe61f6.js → cli-y4x8ww6s.js} +2 -2
  79. package/dist/{cli-bnes4z6b.js → cli-y9jvxfhk.js} +3 -3
  80. package/dist/{cli-qwhy2qma.js → cli-z63a269p.js} +2 -2
  81. package/dist/{cli-sm8rses9.js → cli-zfdp01hn.js} +8 -5
  82. package/dist/cli.js +29 -27
  83. package/dist/{cloudflare-workers-ai-system-one-ch2wxfcp.js → cloudflare-workers-ai-system-one-43v9qkjk.js} +7 -2
  84. package/dist/{codex-adapter-tn6k5mkg.js → codex-adapter-tcg1q3jk.js} +6 -4
  85. package/dist/{codex-login-m819c41r.js → codex-login-rn7c5gxt.js} +1 -1
  86. package/dist/{codex-session-runner-7vjt7jbw.js → codex-session-runner-xfx4wd9x.js} +7 -5
  87. package/dist/{commands-t7rz9146.js → commands-ew83nj70.js} +2 -2
  88. package/dist/{controller-m2jasma5.js → controller-gnq1k1mf.js} +4 -4
  89. package/dist/{controllers-connect-9ftpex99.js → controllers-connect-tw1dan9r.js} +25 -23
  90. package/dist/{cursor-adapter-mjv79e4z.js → cursor-adapter-yq5xgfcg.js} +1 -1
  91. package/dist/{db-6sfrn9g3.js → db-7gbc53fd.js} +14 -2
  92. package/dist/{db-rn673r06.js → db-t86xrnes.js} +3 -3
  93. package/dist/{devin-adapter-zgqadvnm.js → devin-adapter-4ycy4vrf.js} +1 -1
  94. package/dist/{dispatcher-c155xz0p.js → dispatcher-64bdyhps.js} +22 -20
  95. package/dist/{docker-fkgtr75t.js → docker-nfy2gc8t.js} +2 -2
  96. package/dist/{dsh-adapter-3mmszc1x.js → dsh-adapter-tvhc4k2d.js} +1 -1
  97. package/dist/{e2b-16d6ch39.js → e2b-9gkdhm3c.js} +2 -2
  98. package/dist/{e2b-zn0b4w5w.js → e2b-cjtf8k1z.js} +2 -2
  99. package/dist/{easter-egg-3d-5cgynaja.js → easter-egg-3d-e4qv8dxj.js} +1 -1
  100. package/dist/{event-bus-9vpk8gmq.js → event-bus-3z78ep5n.js} +1 -1
  101. package/dist/{execute-t4e6m3rw.js → execute-0xpz8950.js} +85 -23
  102. package/dist/{external-channel-35k6tkv8.js → external-channel-e9nw24zv.js} +2 -2
  103. package/dist/{google-generative-ai-bmq8gkf3.js → google-generative-ai-mk9vcpe1.js} +15 -15
  104. package/dist/{google-vertex-b1rvp52s.js → google-vertex-q580kz8k.js} +20 -20
  105. package/dist/{grok-adapter-tfrq7tg1.js → grok-adapter-v82srhrx.js} +9 -7
  106. package/dist/handlers-c9ajzhbn.js +41 -0
  107. package/dist/{heartbeat-dyg4npkp.js → heartbeat-2vqs1319.js} +25 -23
  108. package/dist/{hook-x26b31bg.js → hook-01nk13wx.js} +7 -5
  109. package/dist/{hook-drspbwpe.js → hook-jzpheegg.js} +9 -7
  110. package/dist/{http-pk972xyw.js → http-fc1k6ec8.js} +544 -76
  111. package/dist/index-07ef7nkm.js +42 -0
  112. package/dist/{index-g6tbb6ve.js → index-31ceyf42.js} +1 -1
  113. package/dist/index-7yts8n9s.js +43 -0
  114. package/dist/index-q3t40ryv.js +46 -0
  115. package/dist/{index-arytgj5q.js → index-sepja052.js} +1 -1
  116. package/dist/{index-jwxkkex2.js → index-zdycv3ja.js} +13 -11
  117. package/dist/{index-g216q6jr.js → index-zsyx51ap.js} +1 -4
  118. package/dist/{keepalive-7yrng7px.js → keepalive-5nh2rhmr.js} +4 -4
  119. package/dist/lead-aayq64jn.js +79 -0
  120. package/dist/{llama-cpp-classify-vv43gswy.js → llama-cpp-classify-ee7vmd7q.js} +4 -1
  121. package/dist/{maintenance-e6cemdqt.js → maintenance-hjhzdt3k.js} +1 -1
  122. package/dist/{mistral-conversations-1medfwv7.js → mistral-conversations-sd0xf1tn.js} +13 -13
  123. package/dist/{node-de6akczr.js → node-jzjj7r9x.js} +1 -1
  124. package/dist/{oauth-refresh-sweep-yw3xdbcj.js → oauth-refresh-sweep-y3n1knmd.js} +3 -3
  125. package/dist/{onboard-0a9y5yrc.js → onboard-1zq3m7db.js} +2 -2
  126. package/dist/{openai-codex-responses-de9pvs4d.js → openai-codex-responses-79s8xnbh.js} +29 -23
  127. package/dist/{openai-completions-mtqge3nf.js → openai-completions-tdbyts0x.js} +21 -20
  128. package/dist/{openai-decisions-ygk4kkxd.js → openai-decisions-bqhnjyfz.js} +2 -1
  129. package/dist/{openai-responses-dkyng71g.js → openai-responses-dm3t5vqp.js} +26 -17
  130. package/dist/{opencode-adapter-x4tqzhkh.js → opencode-adapter-e56086m1.js} +7 -5
  131. package/dist/{openrouter-images-wvv7camj.js → openrouter-images-ssttr1y4.js} +6 -3
  132. package/dist/{otel-impl-trjpkfb7.js → otel-impl-h9yyza47.js} +2 -2
  133. package/dist/{pi-messages-nhh1pkz2.js → pi-messages-3nf8bq1a.js} +6 -4
  134. package/dist/{pi-mono-adapter-mcfkn9vf.js → pi-mono-adapter-tzps8f38.js} +9 -37
  135. package/dist/{pricing-refresh-d2b5my0y.js → pricing-refresh-nevhat8h.js} +3 -3
  136. package/dist/{rbac-roles-nxh5ahwk.js → rbac-roles-4y6ngvzq.js} +1 -1
  137. package/dist/{rbac-roles-hey3yv9d.js → rbac-roles-exbg8xb6.js} +2 -2
  138. package/dist/{render-v2-rr3p2atg.js → render-v2-cfsh13z3.js} +3 -3
  139. package/dist/{resume-2efkf1s1.js → resume-5s2t0dbs.js} +10 -8
  140. package/dist/{runtime-xe3aqy4x.js → runtime-bt5pkpm1.js} +1 -1
  141. package/dist/{session-access-s2mttxpw.js → session-access-dnbyp3cn.js} +6 -4
  142. package/dist/{setup-vypma5yt.js → setup-1wj1y77j.js} +1 -1
  143. package/dist/{typesafe-system-one-2hgpayzy.js → typesafe-system-one-bre38hvs.js} +4 -2
  144. package/dist/{virtual-modules-4r3zkc20.js → virtual-modules-mnfbh5mr.js} +8 -2
  145. package/dist/{work-objects-rvhckdkp.js → work-objects-7mg6216g.js} +25 -23
  146. package/dist/worker-nwb789mg.js +79 -0
  147. package/dist/{x-y45mthwn.js → x-pbpezr0s.js} +2 -2
  148. package/openapi.json +785 -5
  149. package/package.json +9 -9
  150. package/src/assets/key.ts +17 -0
  151. package/src/azure-devops/handlers.ts +1 -1
  152. package/src/be/db/approvals.ts +18 -0
  153. package/src/be/db/session-moves.ts +164 -0
  154. package/src/be/db/session-tree.ts +384 -0
  155. package/src/be/db/tasks/read.ts +40 -4
  156. package/src/be/db/workflow-runs.ts +96 -0
  157. package/src/be/db.ts +80 -236
  158. package/src/be/onboarding.ts +3 -0
  159. package/src/be/skill-sync.ts +43 -2
  160. package/src/be/swarm-config-guard.ts +1 -0
  161. package/src/bitbucket/api.ts +81 -0
  162. package/src/bitbucket/auth.ts +64 -0
  163. package/src/bitbucket/handlers.ts +434 -0
  164. package/src/bitbucket/index.ts +28 -0
  165. package/src/bitbucket/templates.ts +134 -0
  166. package/src/bitbucket/types.ts +65 -0
  167. package/src/commands/runner.ts +55 -3
  168. package/src/e2b/env.ts +1 -0
  169. package/src/extensions/contract-types.generated.ts +1 -1
  170. package/src/extensions/contract.ts +2 -2
  171. package/src/extensions/post-bridge.ts +1 -0
  172. package/src/github/handlers.ts +6 -6
  173. package/src/gitlab/handlers.ts +5 -5
  174. package/src/http/assets.ts +14 -2
  175. package/src/http/core.ts +12 -0
  176. package/src/http/index.ts +4 -0
  177. package/src/http/onboarding.ts +1 -0
  178. package/src/http/sessions.ts +366 -12
  179. package/src/http/skills.ts +48 -1
  180. package/src/http/tasks.ts +1 -1
  181. package/src/http/users.ts +1 -0
  182. package/src/http/webhooks.ts +141 -0
  183. package/src/integrations/chat-sdk/adapters.ts +19 -3
  184. package/src/integrations/chat-sdk/facts.ts +30 -12
  185. package/src/integrations/chat-sdk/outbound.ts +38 -1
  186. package/src/providers/acp-targets.ts +1 -1
  187. package/src/providers/amp-adapter.ts +1 -1
  188. package/src/providers/pi-mono-adapter.ts +5 -53
  189. package/src/scripts-runtime/types/swarm-extension.d.ts +6 -4
  190. package/src/slack/handlers.test.ts +158 -0
  191. package/src/slack/handlers.ts +19 -28
  192. package/src/tasks/context-key.ts +61 -1
  193. package/src/tests/bitbucket-auth.test.ts +156 -0
  194. package/src/tests/bitbucket-handlers.test.ts +425 -0
  195. package/src/tests/bitbucket-webhook.test.ts +220 -0
  196. package/src/tests/chat-sdk-inbound.test.ts +39 -0
  197. package/src/tests/chat-sdk-telegram-markdown.test.ts +92 -0
  198. package/src/tests/context-key.test.ts +50 -0
  199. package/src/tests/core-auth.test.ts +5 -0
  200. package/src/tests/db-task-read-contract.test.ts +7 -0
  201. package/src/tests/db-task-write-contract.test.ts +6 -0
  202. package/src/tests/docs-index-footprint.test.js +3 -3
  203. package/src/tests/fixtures/bitbucket/pullrequest-comment-created-inline.json +174 -0
  204. package/src/tests/fixtures/bitbucket/pullrequest-comment-created.json +170 -0
  205. package/src/tests/fixtures/bitbucket/pullrequest-created-mention.json +132 -0
  206. package/src/tests/fixtures/bitbucket/pullrequest-created-reviewer.json +140 -0
  207. package/src/tests/fixtures/bitbucket/pullrequest-fulfilled.json +134 -0
  208. package/src/tests/fixtures/bitbucket/pullrequest-rejected.json +133 -0
  209. package/src/tests/generate-mcp-docs.test.ts +101 -0
  210. package/src/tests/gitlab-handlers.test.ts +3 -3
  211. package/src/tests/gitlab-vcs-db.test.ts +4 -4
  212. package/src/tests/integration-disable-flags.test.ts +36 -0
  213. package/src/tests/mcp-tools.test.ts +1 -1
  214. package/src/tests/onboarding.test.ts +12 -0
  215. package/src/tests/pi-codemode.test.ts +8 -2
  216. package/src/tests/preload-env.test.ts +57 -0
  217. package/src/tests/preload.ts +21 -0
  218. package/src/tests/prompt-template-remaining.test.ts +38 -0
  219. package/src/tests/run-root-coverage.test.ts +39 -0
  220. package/src/tests/runner-bitbucket-vcs.test.ts +191 -0
  221. package/src/tests/runner-skills-refresh.test.ts +58 -0
  222. package/src/tests/secret-scrubber.test.ts +15 -0
  223. package/src/tests/sessions-move.test.ts +576 -0
  224. package/src/tests/sessions-tree.test.ts +493 -0
  225. package/src/tests/skill-files-http.test.ts +50 -0
  226. package/src/tests/skills-bundle.test.ts +99 -0
  227. package/src/tests/swarm-config-export-to-env.test.ts +2 -1
  228. package/src/tests/vcs-bitbucket.test.ts +91 -0
  229. package/src/tests/vcs-provider-isolation.test.ts +123 -0
  230. package/src/tests/vcs-provider.test.ts +16 -1
  231. package/src/tests/vcs-tracking.test.ts +3 -3
  232. package/src/tests/workflow-foreach-bodies.test.ts +637 -0
  233. package/src/tests/workflow-foreach-concurrency.test.ts +342 -0
  234. package/src/tests/workflow-foreach-retry.test.ts +537 -0
  235. package/src/tests/workflow-foreach.test.ts +44 -23
  236. package/src/tests/workflow-step-claims.test.ts +6 -6
  237. package/src/tests/workflow-tla-races.test.ts +2 -2
  238. package/src/tools/script-delete.ts +1 -1
  239. package/src/tools/workflows/create-workflow.ts +5 -3
  240. package/src/types.ts +3 -2
  241. package/src/utils/config-env-export.ts +1 -0
  242. package/src/utils/secret-scrubber.ts +4 -0
  243. package/src/utils/skills-refresh.ts +83 -43
  244. package/src/vcs/bitbucket.ts +58 -0
  245. package/src/vcs/index.ts +2 -0
  246. package/src/vcs/types.ts +2 -2
  247. package/src/workflows/approval-resolution.ts +4 -1
  248. package/src/workflows/checkpoint.ts +9 -1
  249. package/src/workflows/constants.ts +3 -0
  250. package/src/workflows/definition.ts +48 -17
  251. package/src/workflows/engine.ts +25 -15
  252. package/src/workflows/executors/foreach.ts +45 -99
  253. package/src/workflows/foreach-body.ts +146 -0
  254. package/src/workflows/foreach-driver.ts +552 -0
  255. package/src/workflows/foreach-join.ts +206 -7
  256. package/src/workflows/node-timeout.ts +21 -0
  257. package/src/workflows/recovery.ts +68 -3
  258. package/src/workflows/resume.ts +221 -43
  259. package/src/workflows/retry-poller.ts +39 -24
  260. package/src/workflows/step-input.ts +59 -0
  261. package/src/workflows/task-step-routing.ts +114 -56
  262. package/templates/skills/slack-interaction/content.md +5 -0
  263. package/templates/skills/work-on-task/SKILL.md +1 -1
  264. package/templates/skills/work-on-task/content.md +1 -1
  265. package/dist/cli-9hstj09h.js +0 -2657
  266. package/dist/cli-yw91rnmw.js +0 -13
  267. package/dist/handlers-7gw64dxw.js +0 -39
  268. package/dist/index-bgdyvcnv.js +0 -40
  269. package/dist/index-gabbcdn2.js +0 -43
  270. package/dist/index-tfb26zhy.js +0 -41
  271. package/dist/lead-7e02z839.js +0 -77
  272. package/dist/worker-5snytpbd.js +0 -77
@@ -17,14 +17,40 @@ import {
17
17
  } from "../types";
18
18
  import { checkpointStep, checkpointStepFailure } from "./checkpoint";
19
19
  import { getSuccessors } from "./definition";
20
- import { joinForeach, resolveForeachParent } from "./foreach-join";
20
+ import type { ExecutorRegistry } from "./executors/registry";
21
+ import { type ForeachSettleResult, resolveForeachParent, settleForeachChild } from "./foreach-join";
21
22
 
22
- export interface PortStepRoutingResult {
23
- /** False when the step was already claimed or its run is terminal. */
23
+ export interface TaskStepRoutingResult {
24
+ /**
25
+ * False when another handler already moved the step out of `waiting` — the
26
+ * caller must not route successors again. The same task terminal event can
27
+ * reach two resume paths (the DB-emitted bus event and a direct emit, or a
28
+ * recovery sweep racing a live event); only the first one may route.
29
+ */
24
30
  claimed: boolean;
31
+ foreachChild: boolean;
32
+ joined: boolean;
25
33
  successors: WorkflowNode[];
34
+ /** True when this call failed the run: nothing may route or re-park it. */
35
+ runFailed: boolean;
26
36
  }
27
37
 
38
+ const UNCLAIMED: TaskStepRoutingResult = {
39
+ claimed: false,
40
+ foreachChild: false,
41
+ joined: false,
42
+ successors: [],
43
+ runFailed: false,
44
+ };
45
+
46
+ /**
47
+ * Port-routed resume result. `claimed` is false when the step was already
48
+ * claimed or its run is terminal. A foreach child (`foreachChild`) routes
49
+ * nothing unless its settlement closed the join (`joined`), in which case
50
+ * `successors` are the parent's.
51
+ */
52
+ export type PortStepRoutingResult = TaskStepRoutingResult;
53
+
28
54
  /**
29
55
  * True while `taskId` is still the task bound to `stepId`. A retry detaches
30
56
  * the failed task and the poller binds a fresh one to the same step, so a
@@ -37,24 +63,41 @@ async function isTaskBoundToStep(taskId: string, stepId: string): Promise<boolea
37
63
  }
38
64
 
39
65
  /**
40
- * Atomically fail a waiting step and its run. Returns false when another
41
- * handler already moved the step out of `waiting` — the same task terminal
42
- * event can reach both the live bus listener and a recovery sweep, and a
43
- * blind write here would stomp a run another handler is already advancing.
44
- * With `ownerTaskId`, also returns false when that task is no longer the one
45
- * bound to the step (a stale snapshot of a step that was since redriven).
66
+ * Atomically fail a waiting step and its run. Returns `claimed: false` when
67
+ * another handler already moved the step out of `waiting` — the same task
68
+ * terminal event can reach both the live bus listener and a recovery sweep,
69
+ * and a blind write here would stomp a run another handler is already
70
+ * advancing. With `ownerTaskId`, also unclaimed when that task is no longer
71
+ * the one bound to the step (a stale snapshot of a step that was since
72
+ * redriven).
73
+ *
74
+ * A synthetic foreach child settles `failed` through `settleForeachChild`
75
+ * instead: under `onNodeFailure: "fail"` that fails the run as before; under
76
+ * `"continue"` the item counts as failed and the join may close, so the
77
+ * caller walks `successors` when `joined`.
46
78
  */
47
79
  export async function failStepAndRunIfWaiting(
80
+ def: WorkflowDefinition,
48
81
  stepId: string,
49
82
  runId: string,
50
83
  reason: string,
51
84
  ownerTaskId?: string,
52
- ): Promise<boolean> {
85
+ options: { ctx?: Record<string, unknown>; registry?: ExecutorRegistry } = {},
86
+ ): Promise<TaskStepRoutingResult> {
53
87
  const now = new Date().toISOString();
54
- return await getDbClient().transaction(async () => {
88
+ return await getDbClient().transaction(async (): Promise<TaskStepRoutingResult> => {
55
89
  const current = await getWorkflowRunStep(stepId);
56
- if (!current || current.status !== "waiting") return false;
57
- if (ownerTaskId && !(await isTaskBoundToStep(ownerTaskId, stepId))) return false;
90
+ if (!current || current.status !== "waiting") return UNCLAIMED;
91
+ if (ownerTaskId && !(await isTaskBoundToStep(ownerTaskId, stepId))) return UNCLAIMED;
92
+ if (resolveForeachParent(def, current.nodeId)) {
93
+ const ctx =
94
+ options.ctx ?? (((await getWorkflowRun(runId))?.context ?? {}) as Record<string, unknown>);
95
+ return toRoutingResult(
96
+ await settleForeachChild(def, runId, stepId, { status: "failed", error: reason }, ctx, {
97
+ registry: options.registry,
98
+ }),
99
+ );
100
+ }
58
101
  await updateWorkflowRunStep(stepId, {
59
102
  status: "failed",
60
103
  error: reason,
@@ -65,10 +108,20 @@ export async function failStepAndRunIfWaiting(
65
108
  error: reason,
66
109
  finishedAt: now,
67
110
  });
68
- return true;
111
+ return { claimed: true, foreachChild: false, joined: false, successors: [], runFailed: true };
69
112
  });
70
113
  }
71
114
 
115
+ function toRoutingResult(settle: ForeachSettleResult): TaskStepRoutingResult {
116
+ return {
117
+ claimed: settle.claimed,
118
+ foreachChild: true,
119
+ joined: settle.joined,
120
+ successors: settle.successors,
121
+ runFailed: settle.runFailed,
122
+ };
123
+ }
124
+
72
125
  /**
73
126
  * Checkpoint a waiting step with a port-based result and resolve its
74
127
  * successors. Mirrors `completeTaskStepAndResolveSuccessors`: callers check
@@ -84,17 +137,41 @@ export async function checkpointPortStepAndResolveSuccessors(
84
137
  output: unknown,
85
138
  nextPort: string,
86
139
  ctx: Record<string, unknown>,
140
+ registry?: ExecutorRegistry,
87
141
  ): Promise<PortStepRoutingResult> {
88
142
  return await getDbClient().transaction(async (): Promise<PortStepRoutingResult> => {
89
143
  const current = await getWorkflowRunStep(stepId);
90
- if (!current || current.status !== "waiting") return { claimed: false, successors: [] };
144
+ if (!current || current.status !== "waiting") return UNCLAIMED;
91
145
  const run = await getWorkflowRun(runId);
92
146
  if (!run || (run.status !== "waiting" && run.status !== "running")) {
93
- return { claimed: false, successors: [] };
147
+ return UNCLAIMED;
148
+ }
149
+ // A foreach child's port (approval rejected/timeout, wait event/timeout)
150
+ // is stored for display and still counts as a completed item: the body's
151
+ // ports do not route, the item's consumer reads the output.
152
+ if (resolveForeachParent(def, nodeId)) {
153
+ return toRoutingResult(
154
+ await settleForeachChild(
155
+ def,
156
+ runId,
157
+ stepId,
158
+ { status: "completed", output, nextPort },
159
+ ctx,
160
+ {
161
+ registry,
162
+ },
163
+ ),
164
+ );
94
165
  }
95
166
  await checkpointStep(runId, stepId, nodeId, { output, nextPort }, ctx);
96
167
  await updateWorkflowRun(runId, { status: "running" });
97
- return { claimed: true, successors: getSuccessors(def, nodeId, nextPort) };
168
+ return {
169
+ claimed: true,
170
+ foreachChild: false,
171
+ joined: true,
172
+ successors: getSuccessors(def, nodeId, nextPort),
173
+ runFailed: false,
174
+ };
98
175
  });
99
176
  }
100
177
 
@@ -179,26 +256,6 @@ async function isSupersedeCut(taskId: string, reason: string): Promise<boolean>
179
256
  return task?.failureReason === SUPERSEDED_WORKFLOW_TASK_REASON;
180
257
  }
181
258
 
182
- export interface TaskStepRoutingResult {
183
- /**
184
- * False when another handler already moved the step out of `waiting` — the
185
- * caller must not route successors again. The same task terminal event can
186
- * reach two resume paths (the DB-emitted bus event and a direct emit, or a
187
- * recovery sweep racing a live event); only the first one may route.
188
- */
189
- claimed: boolean;
190
- foreachChild: boolean;
191
- joined: boolean;
192
- successors: WorkflowNode[];
193
- }
194
-
195
- const UNCLAIMED: TaskStepRoutingResult = {
196
- claimed: false,
197
- foreachChild: false,
198
- joined: false,
199
- successors: [],
200
- };
201
-
202
259
  /**
203
260
  * Persist an agent-task result and resolve its next nodes. Synthetic foreach
204
261
  * children never checkpoint into workflow context; only their parent join does.
@@ -211,6 +268,7 @@ export async function completeTaskStepAndResolveSuccessors(
211
268
  ctx: Record<string, unknown>,
212
269
  failureReason?: string,
213
270
  ownerTaskId?: string,
271
+ registry?: ExecutorRegistry,
214
272
  ): Promise<TaskStepRoutingResult> {
215
273
  // The task step, optional foreach join checkpoint, workflow context, and
216
274
  // running status must commit together. A crash after this transaction is
@@ -223,25 +281,24 @@ export async function completeTaskStepAndResolveSuccessors(
223
281
  // A stale snapshot of a step since redriven to a fresh task must not route it.
224
282
  if (ownerTaskId && !(await isTaskBoundToStep(ownerTaskId, step.id))) return UNCLAIMED;
225
283
 
226
- const foreachParent = resolveForeachParent(def, step.nodeId);
227
- if (foreachParent) {
228
- await updateWorkflowRunStep(step.id, {
229
- status: "completed",
230
- output,
231
- // onNodeFailure:"continue" completions persist the failure reason as
232
- // explicit metadata — the join classifies children on THIS, not on
233
- // whether user-controlled output text happens to start with "[FAILED:".
234
- ...(failureReason !== undefined ? { error: failureReason } : {}),
235
- finishedAt: new Date().toISOString(),
236
- });
237
- const join = await joinForeach(def, runId, step, ctx);
238
- await updateWorkflowRun(runId, { status: "running" });
239
- return {
240
- claimed: true,
241
- foreachChild: true,
242
- joined: join.joined,
243
- successors: join.successors,
244
- };
284
+ if (resolveForeachParent(def, step.nodeId)) {
285
+ // onNodeFailure:"continue" completions persist the failure reason as
286
+ // explicit metadata — the join classifies children on THIS, not on
287
+ // whether user-controlled output text happens to start with "[FAILED:".
288
+ return toRoutingResult(
289
+ await settleForeachChild(
290
+ def,
291
+ runId,
292
+ step.id,
293
+ {
294
+ status: "completed",
295
+ output,
296
+ ...(failureReason !== undefined ? { error: failureReason } : {}),
297
+ },
298
+ ctx,
299
+ { registry },
300
+ ),
301
+ );
245
302
  }
246
303
 
247
304
  await checkpointStep(runId, step.id, step.nodeId, { output }, ctx);
@@ -251,6 +308,7 @@ export async function completeTaskStepAndResolveSuccessors(
251
308
  foreachChild: false,
252
309
  joined: true,
253
310
  successors: getSuccessors(def, step.nodeId),
311
+ runFailed: false,
254
312
  };
255
313
  });
256
314
  }
@@ -37,6 +37,11 @@ The task carries `slackChannelId` and `slackThreadTs` in its metadata. A follow-
37
37
 
38
38
  Files attached to your task already come with download commands in the task message. Use those before `slack-download-file`. `slack-read` on your task also attaches the files in the thread and gives each one a `fetchCommand`. Don't look for Slack files under `/app/shared` or `/workspace/shared/downloads`: those paths are the API server's disk.
39
39
 
40
+ ## Message limits
41
+
42
+ - Text sent without `blocks` is split into sections under Slack's 3,000-character section limit for you. With explicit `blocks`, keep each section's text at or under 3,000 characters yourself.
43
+ - Truncate user text by code point (`Array.from(s).slice(0, n).join("")`), never with `s.slice(0, n)`: cutting an emoji in half leaves a lone UTF-16 surrogate, and Slack rejects the blocks with `invalid_blocks_format`.
44
+
40
45
  ## Unknown user
41
46
 
42
47
  A task without `requestedByUserId` came from a Slack user the swarm does not know. Register them with `manage-user`, using the Slack user ID and display name from the task metadata, then continue. When you learn a requester's stable preferences (language, tone, verbosity), the lead stores them in the user's `comms` field with `manage-user`.
@@ -13,7 +13,7 @@ When the task names a skill (`researching`, `planning`, `implementing`), use it.
13
13
 
14
14
  Finish the task with one of the four endings in your operating contract: `completed`, `defer-task`, `request-human-input`, or `failed`. Then stop.
15
15
 
16
- Resuming (a resume task, or work cut by a graceful shutdown): read the parent's last progress line, then re-read live state once (PR state and head SHA, branch, `git status`) before new work. Re-run controls only if the head moved. Never redo a pushed commit or open a second PR for the same branch. Store a progress line with branch, PR number and head SHA each time you push. A document task has no branch: its live state is the brief's agent-fs output path and the task attachments. Run `agent-fs ls` on that path, download any partial and extend it; never re-run collection steps that already produced artifacts. If your task's `model` differs from the parent's, say so in the output, because the result's provenance changed.
16
+ Resuming (a resume task, or work cut by a graceful shutdown): read the parent's last progress line, then re-read live state once (PR state and head SHA, branch, `git status`) before new work. Re-run controls only if the head moved. Never redo a pushed commit or open a second PR for the same branch. Store a progress line with branch, PR number and head SHA each time you push. A document task has no branch: its live state is the brief's agent-fs output path and the task attachments. Run `agent-fs ls` on that path, download any partial and extend it. Then list the task's local work directory: a shutdown can leave the newest draft only there, or leave cache files half-written. Parse every cache file, delete the ones that fail, and re-run only the stages whose output is missing. Never re-run collection steps that already produced artifacts. If your task's `model` differs from the parent's, say so in the output, because the result's provenance changed.
17
17
 
18
18
  Write times in replies, outputs and defer notes in the requester's timezone when the Requester Profile names one, never raw UTC.
19
19
 
@@ -8,7 +8,7 @@ When the task names a skill (`researching`, `planning`, `implementing`), use it.
8
8
 
9
9
  Finish the task with one of the four endings in your operating contract: `completed`, `defer-task`, `request-human-input`, or `failed`. Then stop.
10
10
 
11
- Resuming (a resume task, or work cut by a graceful shutdown): read the parent's last progress line, then re-read live state once (PR state and head SHA, branch, `git status`) before new work. Re-run controls only if the head moved. Never redo a pushed commit or open a second PR for the same branch. Store a progress line with branch, PR number and head SHA each time you push. A document task has no branch: its live state is the brief's agent-fs output path and the task attachments. Run `agent-fs ls` on that path, download any partial and extend it; never re-run collection steps that already produced artifacts. If your task's `model` differs from the parent's, say so in the output, because the result's provenance changed.
11
+ Resuming (a resume task, or work cut by a graceful shutdown): read the parent's last progress line, then re-read live state once (PR state and head SHA, branch, `git status`) before new work. Re-run controls only if the head moved. Never redo a pushed commit or open a second PR for the same branch. Store a progress line with branch, PR number and head SHA each time you push. A document task has no branch: its live state is the brief's agent-fs output path and the task attachments. Run `agent-fs ls` on that path, download any partial and extend it. Then list the task's local work directory: a shutdown can leave the newest draft only there, or leave cache files half-written. Parse every cache file, delete the ones that fail, and re-run only the stages whose output is missing. Never re-run collection steps that already produced artifacts. If your task's `model` differs from the parent's, say so in the output, because the result's provenance changed.
12
12
 
13
13
  Write times in replies, outputs and defer notes in the requester's timezone when the Requester Profile names one, never raw UTC.
14
14