@desplega.ai/agent-swarm 2.5.3 → 2.6.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 (349) hide show
  1. package/README.md +2 -2
  2. package/dist/{acp-adapter-gr2fcyyj.js → acp-adapter-dgfakfpw.js} +9 -9
  3. package/dist/{actions-wv766ghf.js → actions-fbqma8aa.js} +21 -21
  4. package/dist/{amp-adapter-speb906t.js → amp-adapter-en0jy6hs.js} +4 -4
  5. package/dist/{app-ag29n14f.js → app-k8a5cgpp.js} +3 -3
  6. package/dist/{assistant-3x8bz3eg.js → assistant-a16433gr.js} +30 -26
  7. package/dist/{boot-reembed-jsmfrpzf.js → boot-reembed-6c1sjfnr.js} +1 -1
  8. package/dist/{boot-reembed-5b3d2eea.js → boot-reembed-f4nn561n.js} +1 -1
  9. package/dist/{boot-scrub-sweep-s73gerxy.js → boot-scrub-sweep-d4w9gajf.js} +4 -4
  10. package/dist/{boot-seeds-5zmax2wr.js → boot-seeds-9bmkspyw.js} +4 -4
  11. package/dist/{claude-adapter-hswdnm3t.js → claude-adapter-nys1kjxa.js} +9 -9
  12. package/dist/{claude-managed-adapter-cvbzj1h6.js → claude-managed-adapter-55207973.js} +3 -3
  13. package/dist/{claude-managed-setup-3k8m3hhc.js → claude-managed-setup-3cm35gey.js} +11 -4
  14. package/dist/{claude-sdk-session-fkbyk66x.js → claude-sdk-session-nw5yd8cp.js} +9313 -9260
  15. package/dist/{cli-210xd1f4.js → cli-03gg9pd1.js} +4 -4
  16. package/dist/{cli-kxpfx2xr.js → cli-0z1xjzwa.js} +2 -2
  17. package/dist/cli-1tem6ns9.js +894 -0
  18. package/dist/{cli-25j01dpj.js → cli-24j1jeeb.js} +3 -3
  19. package/dist/{cli-ta8bd7th.js → cli-2sjhpsns.js} +2 -2
  20. package/dist/{cli-p859hcc8.js → cli-39aet655.js} +2 -2
  21. package/dist/{cli-g1cw7xtt.js → cli-39rrw8yp.js} +1 -1
  22. package/dist/{cli-e2ar4bp1.js → cli-3aqd4jaa.js} +121 -47
  23. package/dist/{cli-jjznk3f7.js → cli-3fwqy71j.js} +69 -35
  24. package/dist/{cli-p9rafdrb.js → cli-3gfvmzht.js} +7 -7
  25. package/dist/{cli-03vpq45j.js → cli-3ndmpxxv.js} +2 -2
  26. package/dist/{cli-aecdcr77.js → cli-4hxrf0p4.js} +228 -74
  27. package/dist/{cli-mz9afxxf.js → cli-4rf0f5vg.js} +15 -3
  28. package/dist/{cli-qzme5949.js → cli-5rj83bam.js} +284 -202
  29. package/dist/{cli-fsdprwrg.js → cli-5sckpqcx.js} +2 -2
  30. package/dist/{cli-cz5nmdxt.js → cli-683y0b3a.js} +2 -2
  31. package/dist/{cli-3ngdk5te.js → cli-6y51ryva.js} +2 -2
  32. package/dist/{cli-n4c8e3s5.js → cli-708wdy32.js} +1 -1
  33. package/dist/{cli-pasyvjmm.js → cli-737pyqxr.js} +1 -1
  34. package/dist/{cli-gx4rm0qx.js → cli-7enjwya9.js} +1 -1
  35. package/dist/{cli-6x9ma5w5.js → cli-803188pb.js} +1 -1
  36. package/dist/{cli-avdvgcay.js → cli-9abdgn5t.js} +28 -9
  37. package/dist/{cli-99xgw26r.js → cli-a2rr0fmg.js} +1 -1
  38. package/dist/{cli-e5bxnjps.js → cli-a34pr3w1.js} +7 -7
  39. package/dist/{cli-502eqv93.js → cli-abkm71vr.js} +1 -1
  40. package/dist/{cli-mmhj8vd7.js → cli-ag2yrnt5.js} +11 -2
  41. package/dist/{cli-n45mr7zh.js → cli-aj8djyqt.js} +5 -5
  42. package/dist/{cli-1fc85mvt.js → cli-art7c1zh.js} +5 -5
  43. package/dist/{cli-40jsnrjm.js → cli-az25fn5f.js} +10 -8
  44. package/dist/{cli-9s05422w.js → cli-bqztfyve.js} +1 -1
  45. package/dist/{cli-bn0rewsy.js → cli-c081bj3b.js} +2 -2
  46. package/dist/{cli-svrj71pg.js → cli-c2habvsf.js} +12 -12
  47. package/dist/{cli-s49hpn37.js → cli-ca6banwz.js} +85 -19
  48. package/dist/{cli-hhxjnjzs.js → cli-cz37jqss.js} +12 -10
  49. package/dist/{cli-6n8axjv1.js → cli-d2dpf95n.js} +2 -2
  50. package/dist/{cli-tx00c0h6.js → cli-dqygeeh5.js} +17 -1
  51. package/dist/{cli-z21whrrg.js → cli-emhjcj2y.js} +17 -17
  52. package/dist/{cli-e8jyq7d8.js → cli-esakngh8.js} +7 -3
  53. package/dist/{cli-ha9rmwgq.js → cli-fb8m699c.js} +2 -2
  54. package/dist/{cli-35gtk1p6.js → cli-ffvsj2qw.js} +44 -13
  55. package/dist/{cli-ce0ak9ms.js → cli-g20jjwt4.js} +26 -26
  56. package/dist/{cli-kxawjrxg.js → cli-g8ka5j4j.js} +772 -194
  57. package/dist/{cli-vwn81jbs.js → cli-gf95q3bm.js} +5 -5
  58. package/dist/{cli-s53vp709.js → cli-gnsb1zxa.js} +2 -2
  59. package/dist/{cli-nwyw3zhg.js → cli-gxee0bqa.js} +1 -1
  60. package/dist/{cli-gpv60278.js → cli-hfeb68z4.js} +7 -7
  61. package/dist/{cli-0ebey9hg.js → cli-j6rktc91.js} +1 -1
  62. package/dist/{cli-ewm3pwbq.js → cli-k21gx5ht.js} +46 -23
  63. package/dist/{cli-egjggehm.js → cli-kf4jree9.js} +2 -2
  64. package/dist/{cli-dabehxhp.js → cli-kt907da6.js} +1 -1
  65. package/dist/{cli-p0rzjkya.js → cli-m5a9m0ak.js} +32 -13
  66. package/dist/{cli-fzcn3d87.js → cli-m70bajzr.js} +3 -3
  67. package/dist/{cli-w1zwctj2.js → cli-mtnj4fwa.js} +11 -5
  68. package/dist/{cli-gpsz7g88.js → cli-mvjz5an3.js} +16 -12
  69. package/dist/{cli-hcpj2xqz.js → cli-n7asmce6.js} +225 -3
  70. package/dist/{cli-y561h5a9.js → cli-nn6vx2mq.js} +5 -5
  71. package/dist/cli-nq677csy.js +140 -0
  72. package/dist/{cli-a5ebc95n.js → cli-pfk92eqz.js} +18 -15
  73. package/dist/{cli-p6zs9h4y.js → cli-rr7zx19j.js} +767 -249
  74. package/dist/{cli-qgf8emrn.js → cli-sqma6vw1.js} +10 -7
  75. package/dist/{cli-0xk505e8.js → cli-sxgtf5fw.js} +1 -1
  76. package/dist/{cli-nzfnt2gs.js → cli-tf1n0r0b.js} +1 -1
  77. package/dist/{cli-qqzmjxkf.js → cli-v49fkghp.js} +11 -6
  78. package/dist/{cli-x77gen2p.js → cli-w2656ka1.js} +7 -7
  79. package/dist/{cli-5kqnemen.js → cli-yvy48785.js} +1 -1
  80. package/dist/{cli-8s5wx3nz.js → cli-zqne1qcj.js} +44 -17
  81. package/dist/cli.js +22 -22
  82. package/dist/{codex-adapter-n8ka66vf.js → codex-adapter-d95q4qsn.js} +7 -7
  83. package/dist/{codex-hook-e2he7aca.js → codex-hook-brpnycn5.js} +1 -1
  84. package/dist/{codex-login-rn7c5gxt.js → codex-login-8cg0vhf2.js} +1 -1
  85. package/dist/{codex-session-runner-fsebts37.js → codex-session-runner-gdwd8h0c.js} +8 -8
  86. package/dist/{commands-t04rt2pc.js → commands-jqmgk54b.js} +2 -2
  87. package/dist/{controller-rgna04r2.js → controller-z3jq589f.js} +30 -4
  88. package/dist/{controllers-connect-bqm1rtft.js → controllers-connect-r2za39ef.js} +23 -23
  89. package/dist/{cursor-adapter-swr2k95y.js → cursor-adapter-4mr4jgkw.js} +2 -3
  90. package/dist/{db-a4m7haw0.js → db-ppv2tze7.js} +3 -3
  91. package/dist/{db-24ck09xy.js → db-w36wwb5g.js} +4 -2
  92. package/dist/{devin-adapter-4ycy4vrf.js → devin-adapter-byen2cxt.js} +1 -1
  93. package/dist/{dispatcher-nwf9ss0q.js → dispatcher-pv5h77vf.js} +20 -20
  94. package/dist/{docker-nfy2gc8t.js → docker-fdzyewpz.js} +4 -4
  95. package/dist/{dsh-adapter-kpdnhjst.js → dsh-adapter-d4pdpb3r.js} +2 -3
  96. package/dist/{e2b-9gkdhm3c.js → e2b-222hsajc.js} +2 -2
  97. package/dist/{e2b-cjtf8k1z.js → e2b-fn3zacvt.js} +2 -2
  98. package/dist/{easter-egg-3d-e4qv8dxj.js → easter-egg-3d-4sp4dvmc.js} +1 -1
  99. package/dist/{event-bus-3z78ep5n.js → event-bus-pghg8hqy.js} +1 -1
  100. package/dist/{execute-0xpz8950.js → execute-jev839y1.js} +1 -1
  101. package/dist/{external-channel-j1e2p34p.js → external-channel-y6nkzhnn.js} +2 -2
  102. package/dist/{grok-adapter-qtjz0whk.js → grok-adapter-t08b9ntm.js} +10 -10
  103. package/dist/handlers-eqsnmv95.js +41 -0
  104. package/dist/{heartbeat-vtesda5d.js → heartbeat-vn31xwdk.js} +23 -23
  105. package/dist/{hook-b2hhj6rd.js → hook-6d0zbvvt.js} +6 -5
  106. package/dist/{hook-8wqaszda.js → hook-cpjs8q51.js} +13 -7
  107. package/dist/{http-mxcqhhcs.js → http-9c6medda.js} +1085 -136
  108. package/dist/{index-spwxjgay.js → index-38t35h0n.js} +20 -20
  109. package/dist/{index-1r8wfpzs.js → index-g6k9tctj.js} +1 -1
  110. package/dist/{index-srg522m6.js → index-k0yt1tvq.js} +20 -20
  111. package/dist/{index-31ceyf42.js → index-pkma1yf8.js} +1 -1
  112. package/dist/{index-n1vbvta9.js → index-qhjf2htm.js} +29 -24
  113. package/dist/index-yqgbfgaj.js +43 -0
  114. package/dist/{keepalive-dsd8rtgx.js → keepalive-em0117bp.js} +4 -4
  115. package/dist/lead-23e7z59t.js +79 -0
  116. package/dist/{maintenance-jqmqwnet.js → maintenance-kbme3qy9.js} +1 -1
  117. package/dist/{oauth-refresh-sweep-q2c3bnvm.js → oauth-refresh-sweep-xysj4yse.js} +3 -3
  118. package/dist/{onboard-w4yd7s8t.js → onboard-p6q0vz97.js} +3 -3
  119. package/dist/{opencode-adapter-9tppvxar.js → opencode-adapter-esv3k9yn.js} +7 -7
  120. package/dist/{otel-impl-f36byzbd.js → otel-impl-54qhqh1z.js} +2 -2
  121. package/dist/{pi-mono-adapter-tqsge1ay.js → pi-mono-adapter-mymbwhj0.js} +41 -14
  122. package/dist/{pricing-refresh-xvak5p4v.js → pricing-refresh-yb75wv33.js} +3 -3
  123. package/dist/{rbac-roles-gfn30fb8.js → rbac-roles-1vp2mw4p.js} +2 -2
  124. package/dist/{rbac-roles-682k2mt5.js → rbac-roles-dexhc9x8.js} +1 -1
  125. package/dist/render-v2-tjay5hhf.js +16 -0
  126. package/dist/{resume-2dvq348g.js → resume-8ayf37g5.js} +11 -9
  127. package/dist/{runtime-bt5pkpm1.js → runtime-hqe5cqcd.js} +1 -1
  128. package/dist/{session-access-7f21ekq9.js → session-access-fdhp987s.js} +4 -4
  129. package/dist/session-source-backfill-qgtc86z1.js +110 -0
  130. package/dist/{setup-epffxtbk.js → setup-4rrmqcq3.js} +1 -1
  131. package/dist/{virtual-modules-mnfbh5mr.js → virtual-modules-52km00d6.js} +2 -2
  132. package/dist/{work-objects-xz1a5rqk.js → work-objects-p28fgd70.js} +23 -23
  133. package/dist/worker-e3w7vye1.js +79 -0
  134. package/dist/{x-pbpezr0s.js → x-wxrzvzx8.js} +2 -2
  135. package/openapi.json +1323 -16
  136. package/package.json +5 -5
  137. package/plugin/commands/create-pr.md +2 -2
  138. package/plugin/pi-skills/create-pr/SKILL.md +2 -2
  139. package/src/assets/source-key.ts +95 -0
  140. package/src/be/db/api-keys.ts +80 -0
  141. package/src/be/db/approvals.ts +48 -0
  142. package/src/be/db/session-labels.ts +28 -0
  143. package/src/be/db/session-moves.ts +5 -6
  144. package/src/be/db/session-tree.ts +153 -14
  145. package/src/be/db/tasks/read.ts +11 -0
  146. package/src/be/db/tasks/write.ts +21 -6
  147. package/src/be/db/workflow-idempotency.ts +183 -0
  148. package/src/be/db/workflow-runs.ts +500 -7
  149. package/src/be/db-retention.ts +61 -6
  150. package/src/be/db.ts +126 -131
  151. package/src/be/memory/providers/openai-embedding.ts +5 -1
  152. package/src/be/migrations/242_workflow_idempotency_keys.sql +40 -0
  153. package/src/be/migrations/244_api_key_validation.sql +4 -0
  154. package/src/be/onboarding.ts +4 -0
  155. package/src/be/scripts/typecheck.ts +1 -1
  156. package/src/be/sealed-json.ts +1 -0
  157. package/src/be/seed-scripts/catalog/compound-insights.inline.ts +2 -2
  158. package/src/be/seed-scripts/catalog/compound-insights.ts +2 -2
  159. package/src/be/seed-skills/bundled-files.generated.json +1 -1
  160. package/src/be/session-source-backfill.ts +163 -0
  161. package/src/be/sql-connections.ts +7 -1
  162. package/src/be/swarm-config-guard.ts +3 -0
  163. package/src/be/users.ts +9 -0
  164. package/src/comb/review-batch.ts +28 -1
  165. package/src/comb/templates.ts +7 -2
  166. package/src/commands/claude-managed-setup.ts +7 -1
  167. package/src/commands/controller.ts +33 -0
  168. package/src/commands/credential-refresh.ts +76 -1
  169. package/src/commands/credential-wait.ts +2 -2
  170. package/src/commands/provider-credentials.ts +210 -170
  171. package/src/commands/runner.ts +31 -6
  172. package/src/extensions/apply-task-create.ts +64 -11
  173. package/src/extensions/contract-types.generated.ts +1 -1
  174. package/src/extensions/contract.ts +30 -0
  175. package/src/extensions/dispatcher.ts +4 -0
  176. package/src/extensions/post-bridge.ts +8 -0
  177. package/src/hooks/context-bound-note.ts +196 -0
  178. package/src/hooks/hook.ts +12 -0
  179. package/src/http/all-routes.ts +2 -0
  180. package/src/http/api-keys.ts +53 -0
  181. package/src/http/assets.ts +15 -0
  182. package/src/http/codex-oauth-accounts.ts +191 -0
  183. package/src/http/comb.ts +15 -2
  184. package/src/http/config.ts +49 -17
  185. package/src/http/core.ts +6 -0
  186. package/src/http/fleet-benchmark.ts +5 -2
  187. package/src/http/idempotency-key.ts +117 -0
  188. package/src/http/index.ts +11 -0
  189. package/src/http/integrations.ts +5 -1
  190. package/src/http/kv.ts +8 -3
  191. package/src/http/onboarding.ts +6 -1
  192. package/src/http/pool-keys.ts +354 -0
  193. package/src/http/sessions.ts +37 -12
  194. package/src/http/stats.ts +1 -0
  195. package/src/http/status.ts +26 -12
  196. package/src/http/tasks.ts +3 -0
  197. package/src/http/used-by.ts +7 -2
  198. package/src/http/workflows.ts +207 -33
  199. package/src/integrations/chat-sdk/inbound.ts +27 -0
  200. package/src/kv-reserved-namespaces.ts +19 -0
  201. package/src/prompts/context-bound-note.ts +48 -0
  202. package/src/prompts/session-templates.ts +25 -1
  203. package/src/providers/acp-targets.ts +1 -1
  204. package/src/providers/amp-adapter.ts +30 -8
  205. package/src/providers/claude-adapter.ts +34 -5
  206. package/src/providers/claude-managed-adapter.ts +42 -9
  207. package/src/providers/claude-sdk-session.ts +10 -0
  208. package/src/providers/claude-session-events.ts +13 -0
  209. package/src/providers/codex-adapter.ts +83 -11
  210. package/src/providers/opencode-adapter.ts +18 -0
  211. package/src/providers/pi-mono-adapter.ts +35 -0
  212. package/src/runtime-providers/docker.ts +6 -2
  213. package/src/scheduler/scheduler.ts +3 -2
  214. package/src/scripts-runtime/swarm-sdk.ts +7 -1
  215. package/src/scripts-runtime/types/stdlib.d.ts +1 -1
  216. package/src/scripts-runtime/types/swarm-extension.d.ts +52 -8
  217. package/src/scripts-runtime/types/swarm-sdk.d.ts +1 -1
  218. package/src/slack/assistant.ts +7 -0
  219. package/src/slack/channel-labels.ts +74 -0
  220. package/src/slack/handlers.ts +8 -0
  221. package/src/slack/inbound-files.ts +5 -2
  222. package/src/slack/thread-buffer.ts +4 -0
  223. package/src/tasks/sibling-awareness.ts +12 -3
  224. package/src/telemetry-context.ts +5 -6
  225. package/src/telemetry-identity.ts +3 -3
  226. package/src/tests/amp-adapter.test.ts +97 -0
  227. package/src/tests/api-keys-validation.test.ts +170 -0
  228. package/src/tests/approval-requests.test.ts +10 -7
  229. package/src/tests/approval-sweeps.test.ts +12 -6
  230. package/src/tests/base-prompt.test.ts +1 -1
  231. package/src/tests/claude-hook-output.test.ts +79 -1
  232. package/src/tests/claude-managed-adapter.test.ts +321 -0
  233. package/src/tests/claude-routes-e2e.test.ts +7 -2
  234. package/src/tests/codex-adapter.test.ts +163 -0
  235. package/src/tests/codex-credential-pools.test.ts +120 -0
  236. package/src/tests/codex-gateway-route.test.ts +165 -0
  237. package/src/tests/codex-oauth-accounts.test.ts +357 -0
  238. package/src/tests/comb-review-batch.test.ts +63 -2
  239. package/src/tests/config-pool-keys.test.ts +533 -0
  240. package/src/tests/context-bound-note.test.ts +269 -0
  241. package/src/tests/credential-check.test.ts +290 -2
  242. package/src/tests/credential-refresh.test.ts +172 -1
  243. package/src/tests/db-retention.test.ts +9 -3
  244. package/src/tests/db-task-read-contract.test.ts +1 -0
  245. package/src/tests/db-task-write-contract.test.ts +1 -3
  246. package/src/tests/docs-index-footprint.test.js +3 -3
  247. package/src/tests/extensions-pre-task.test.ts +325 -0
  248. package/src/tests/fetch-resolved-env.test.ts +17 -0
  249. package/src/tests/fixtures/extensions/record-blocks/hooks.ts +10 -0
  250. package/src/tests/fixtures/extensions/record-blocks/manifest.json +7 -0
  251. package/src/tests/fixtures/extensions/record-task-create/hooks.ts +9 -0
  252. package/src/tests/fixtures/extensions/record-task-create/manifest.json +7 -0
  253. package/src/tests/fixtures/fake-amp.ts +22 -8
  254. package/src/tests/fixtures/held-template-render.ts +42 -0
  255. package/src/tests/fixtures/workflow-idempotency-trigger.child.ts +73 -0
  256. package/src/tests/fleet-benchmark.test.ts +8 -0
  257. package/src/tests/get-workflow-run-tool.test.ts +98 -0
  258. package/src/tests/integrations-http.test.ts +32 -2
  259. package/src/tests/internal-ai/complete-structured.test.ts +37 -1
  260. package/src/tests/internal-ai/credentials.test.ts +23 -0
  261. package/src/tests/kv-http.test.ts +32 -0
  262. package/src/tests/kv-tool.test.ts +25 -0
  263. package/src/tests/multi-runtime-registration.test.ts +12 -12
  264. package/src/tests/onboarding.test.ts +108 -1
  265. package/src/tests/openai-embedding-credentials.test.ts +35 -0
  266. package/src/tests/openapi-route-headers.test.ts +2 -0
  267. package/src/tests/opencode-adapter.test.ts +134 -0
  268. package/src/tests/openrouter-attribution-call-sites.test.ts +2 -2
  269. package/src/tests/org-website.test.ts +36 -0
  270. package/src/tests/pi-mono-adapter.test.ts +105 -0
  271. package/src/tests/prompt-template-session.test.ts +4 -3
  272. package/src/tests/provider-key-probes.test.ts +181 -0
  273. package/src/tests/rbac-e2e-helpers.test.ts +88 -1
  274. package/src/tests/rbac-e2e-helpers.ts +36 -11
  275. package/src/tests/runner-push-delivery.test.ts +25 -0
  276. package/src/tests/runtime-controller-liveness.test.ts +133 -0
  277. package/src/tests/runtime-providers-docker.test.ts +2 -0
  278. package/src/tests/script-connections.test.ts +5 -0
  279. package/src/tests/script-run-config-scope.test.ts +91 -3
  280. package/src/tests/script-runs-retention.test.ts +212 -0
  281. package/src/tests/sdk-allowlist.test.ts +39 -0
  282. package/src/tests/session-logs.test.ts +70 -0
  283. package/src/tests/session-source-backfill.test.ts +429 -0
  284. package/src/tests/session-source-keys.test.ts +236 -0
  285. package/src/tests/session-source-labels.test.ts +419 -0
  286. package/src/tests/sessions-detail-batch.test.ts +2 -0
  287. package/src/tests/sessions-move.test.ts +131 -0
  288. package/src/tests/sessions-tree.test.ts +336 -2
  289. package/src/tests/slack-inbound-files.test.ts +103 -23
  290. package/src/tests/status.test.ts +101 -18
  291. package/src/tests/tasks-workflow-run-filter.test.ts +146 -0
  292. package/src/tests/test-net.ts +31 -0
  293. package/src/tests/transcription.test.ts +8 -0
  294. package/src/tests/used-by-route.test.ts +11 -0
  295. package/src/tests/workflow-cascade-cancel.test.ts +1341 -0
  296. package/src/tests/workflow-foreach-supersede-retry.test.ts +488 -0
  297. package/src/tests/workflow-http-v2.test.ts +131 -0
  298. package/src/tests/workflow-idempotency.test.ts +915 -0
  299. package/src/tests/workflow-step-order.test.ts +92 -0
  300. package/src/tests/workflow-sub-workflow.test.ts +30 -0
  301. package/src/tools/get-tasks.ts +8 -0
  302. package/src/tools/kv/kv-read-auth.ts +4 -2
  303. package/src/tools/task-action.ts +1 -1
  304. package/src/tools/workflows/cancel-workflow-run.ts +1 -1
  305. package/src/tools/workflows/get-workflow-run.ts +14 -4
  306. package/src/types.ts +24 -0
  307. package/src/utils/company-profile.ts +51 -1
  308. package/src/utils/context-window.ts +8 -6
  309. package/src/utils/credentials.ts +5 -2
  310. package/src/utils/internal-ai/complete-structured.ts +5 -0
  311. package/src/utils/internal-ai/credentials.ts +4 -3
  312. package/src/utils/provider-key-probes.ts +328 -0
  313. package/src/utils/transcription.ts +4 -1
  314. package/src/workflows/approval-cancel.ts +15 -13
  315. package/src/workflows/approval-notifications.ts +5 -1
  316. package/src/workflows/checkpoint.ts +6 -3
  317. package/src/workflows/engine.ts +30 -8
  318. package/src/workflows/executors/foreach.ts +2 -2
  319. package/src/workflows/executors/sub-workflow.ts +8 -1
  320. package/src/workflows/foreach-driver.ts +10 -3
  321. package/src/workflows/foreach-join.ts +29 -6
  322. package/src/workflows/recovery.ts +6 -1
  323. package/src/workflows/resume.ts +168 -56
  324. package/src/workflows/retry-poller.ts +49 -3
  325. package/src/workflows/task-step-routing.ts +29 -7
  326. package/templates/ai-toolbox.manifest.json +8 -8
  327. package/templates/community/open-prompt-showreel/PLAYBOOK.md +5 -6
  328. package/templates/community/open-prompt-showreel/lead-prompt.md +1 -1
  329. package/templates/community/open-prompt-showreel/skills/open-prompt-showreel/SKILL.md +4 -4
  330. package/templates/skills/agent-browser/SKILL.md +4 -4
  331. package/templates/skills/agent-browser/content.md +4 -4
  332. package/templates/skills/code-quality/config.json +1 -1
  333. package/templates/skills/code-quality/content.md +2 -2
  334. package/templates/skills/delegate-work/content.md +29 -25
  335. package/templates/skills/delegate-work/files/scripts/codex-exec.sh +5 -5
  336. package/templates/skills/qa/content.md +2 -2
  337. package/templates/skills/researching/content.md +4 -0
  338. package/templates/skills/swarm-scripts/SKILL.md +1 -0
  339. package/templates/skills/swarm-scripts/content.md +1 -0
  340. package/templates/skills/work-on-task/SKILL.md +1 -1
  341. package/templates/skills/work-on-task/content.md +1 -1
  342. package/dist/cli-7421jzsw.js +0 -84
  343. package/dist/cli-e45e4m6b.js +0 -413
  344. package/dist/cli-zw88zjba.js +0 -307
  345. package/dist/handlers-19xr6dnz.js +0 -41
  346. package/dist/index-kw2mmat7.js +0 -43
  347. package/dist/lead-zyerk1x2.js +0 -79
  348. package/dist/render-v2-hgytz8nn.js +0 -12
  349. package/dist/worker-42t8emjt.js +0 -79
package/src/be/db.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { Database } from "bun:sqlite";
2
2
  import pkg from "../../package.json";
3
- import { defaultAssetKey, normalizeAssetKey, sessionGroupKey } from "../assets/key";
3
+ import { defaultAssetKey, normalizeAssetKey } from "../assets/key";
4
+ import { sessionSourceKey, sessionThreadGroupKey } from "../assets/source-key";
4
5
  import {
5
6
  generateDefaultClaudeMd,
6
7
  generateDefaultIdentityMd,
@@ -207,6 +208,7 @@ import {
207
208
  sessionCountQuery,
208
209
  sessionPageFields,
209
210
  sessionSummaryQuery,
211
+ threadRootsSql,
210
212
  } from "./db/session-tree";
211
213
  import { searchSkillRowsRanked } from "./db/skill-search";
212
214
  import { classifyTaskHumanFree, reclassifyTaskHumanFree } from "./db/tasks/human-free";
@@ -222,7 +224,19 @@ import {
222
224
  rowToAgentTaskSummary,
223
225
  } from "./db/tasks/read";
224
226
  import { configureTaskWriteDependencies, failTask } from "./db/tasks/write";
225
- import { emitChildRunFinished, wakeParentsOfDeletedRuns } from "./db/workflow-runs";
227
+ import { tombstoneWorkflowIdempotencyKeys } from "./db/workflow-idempotency";
228
+ import {
229
+ emitChildRunFinished,
230
+ getRunLineageBlock,
231
+ getStepExecutionBlock,
232
+ rowToWorkflowRun,
233
+ rowToWorkflowRunStep,
234
+ WORKFLOW_RUN_SUMMARY_COLUMNS,
235
+ type WorkflowRunRow,
236
+ type WorkflowRunStepRow,
237
+ WorkflowStepNotExecutableError,
238
+ wakeParentsOfDeletedRuns,
239
+ } from "./db/workflow-runs";
226
240
  import { scrubJsonValue } from "./scrub-json";
227
241
  import { openSealedJson, sealedJsonForDisplay, sealJson } from "./sealed-json";
228
242
  import { configSecretName, registerStoredSecret } from "./secret-registry";
@@ -235,7 +249,7 @@ import {
235
249
  } from "./swarm-config-guard";
236
250
  import { emitTaskStarted } from "./task-lifecycle-events";
237
251
  import { publishWorkerConfigNudge, publishWorkerNudge } from "./worker-nudges";
238
- import { defineWorkflowPayload, type WorkflowPayloadView } from "./workflow-replay";
252
+ import type { WorkflowPayloadView } from "./workflow-replay";
239
253
 
240
254
  export {
241
255
  refreshActiveSessionOnActivity,
@@ -278,7 +292,7 @@ export {
278
292
  updateAgentStatus,
279
293
  updateAgentStatusFromCapacity,
280
294
  } from "./db/agents";
281
- export { recordKeySeatMismatch } from "./db/api-keys";
295
+ export { recordKeySeatMismatch, recordKeyValidation } from "./db/api-keys";
282
296
  export {
283
297
  type ApprovalRequestSummary,
284
298
  type ApprovalVote,
@@ -2490,6 +2504,8 @@ export async function createTaskExtended(
2490
2504
  );
2491
2505
  }
2492
2506
  let requestedByUserIdInherited = false;
2507
+ // The parent key is read again inside the INSERT transaction (see below).
2508
+ let keyInherited = false;
2493
2509
  // True only when `options.routingAffinity` ends up populated purely via
2494
2510
  // the plain parent-fallback inherit below (child declared no affinity of
2495
2511
  // its own, the parent wasn't lead-only, AND the parent's affinity actually
@@ -2654,6 +2670,7 @@ export async function createTaskExtended(
2654
2670
  }
2655
2671
  if (parent.key && !options.key) {
2656
2672
  options.key = parent.key;
2673
+ keyInherited = true;
2657
2674
  }
2658
2675
  if (parent.contextKey && !options.contextKey) {
2659
2676
  options.contextKey = parent.contextKey;
@@ -2843,7 +2860,18 @@ export async function createTaskExtended(
2843
2860
  }
2844
2861
 
2845
2862
  const auditUserId = getCurrentRequestUserId() ?? null;
2846
- const assetKey = normalizeAssetKey(options?.key ?? defaultAssetKey("task", id));
2863
+ const assetKey = normalizeAssetKey(
2864
+ options?.key ??
2865
+ (options?.parentTaskId
2866
+ ? null
2867
+ : sessionSourceKey({
2868
+ id,
2869
+ slackChannelId: options?.slackChannelId,
2870
+ slackThreadTs: options?.slackThreadTs,
2871
+ contextKey: options?.contextKey,
2872
+ })) ??
2873
+ defaultAssetKey("task", id),
2874
+ );
2847
2875
  // The Linear tracker dedup guard and the INSERT commit as one unit. Two
2848
2876
  // distinct webhook deliveries for the same issue are dispatched
2849
2877
  // fire-and-forget (src/linear/webhook.ts), nothing serializes them, and
@@ -2855,11 +2883,32 @@ export async function createTaskExtended(
2855
2883
  const claim = await getDbClient().transaction<
2856
2884
  { existing: ExistingTrackerContextWork } | { row: AgentTaskRow }
2857
2885
  >(async () => {
2886
+ // A workflow step's task commits only while the step may still start
2887
+ // work. Checked in the INSERT's transaction: a cascade cancel of the
2888
+ // step's run tree either commits first and is seen here, or sees and
2889
+ // cancels this task.
2890
+ if (options?.workflowRunStepId) {
2891
+ const blocked = await getStepExecutionBlock(options.workflowRunStepId);
2892
+ if (blocked) throw new WorkflowStepNotExecutableError(options.workflowRunStepId, blocked);
2893
+ }
2858
2894
  const existingTrackerWork = options?.bypassTrackerContextDedup
2859
2895
  ? null
2860
2896
  : await findExistingLinearTrackerContextWork(options?.contextKey);
2861
2897
  if (existingTrackerWork) return { existing: existingTrackerWork };
2862
2898
 
2899
+ // A chain move (a user move, the source key backfill) can commit between
2900
+ // the parent read above and this write lock. Its chain query did not see
2901
+ // this child, so take the parent's key as it is now.
2902
+ const parentKey =
2903
+ keyInherited && options?.parentTaskId
2904
+ ? (
2905
+ await getDbClient().get<{ key: string }>('SELECT "key" FROM agent_tasks WHERE id = ?', [
2906
+ options.parentTaskId,
2907
+ ])
2908
+ )?.key
2909
+ : undefined;
2910
+ const insertKey = parentKey ? normalizeAssetKey(parentKey) : assetKey;
2911
+
2863
2912
  let routingDecisionSnapshot: RoutingDecisionSnapshot | null = null;
2864
2913
  try {
2865
2914
  routingDecisionSnapshot = await captureRoutingDecisionSnapshot(options);
@@ -2890,7 +2939,7 @@ export async function createTaskExtended(
2890
2939
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING *`,
2891
2940
  [
2892
2941
  id,
2893
- assetKey,
2942
+ insertKey,
2894
2943
  options?.agentId ?? null,
2895
2944
  options?.creatorAgentId ?? null,
2896
2945
  // Free text from every task source; scrub at the single INSERT.
@@ -4739,7 +4788,7 @@ export async function createSessionLogs(logs: {
4739
4788
  for (let start = 0; start < logs.lines.length; start += BATCH_SIZE) {
4740
4789
  // Yield to the event loop between batches.
4741
4790
  if (start > 0) await new Promise<void>((r) => setTimeout(r, 5));
4742
- const batch: { content: string; lineNumber: number }[] = [];
4791
+ const batch: string[] = [];
4743
4792
  const end = Math.min(start + BATCH_SIZE, logs.lines.length);
4744
4793
  for (let i = start; i < end; i++) {
4745
4794
  const line = logs.lines[i];
@@ -4748,10 +4797,22 @@ export async function createSessionLogs(logs: {
4748
4797
  // content that is already scrubbed at the adapter emit site. We scrub
4749
4798
  // again here so any future write path that bypasses the adapter still
4750
4799
  // lands clean text in the persistent session_logs table.
4751
- batch.push({ content: scrubSecrets(line), lineNumber: i });
4800
+ batch.push(scrubSecrets(line));
4752
4801
  }
4753
4802
  await getDbClient().transaction(async (tx) => {
4754
- for (const { content, lineNumber } of batch) {
4803
+ // The worker flushes one (sessionId, iteration) in many POSTs, so the
4804
+ // lineNumber continues from the newest stored row instead of restarting
4805
+ // at the POST's index. Reading the newest row by rowid walks the
4806
+ // sessionId index backwards and stops at the first hit, where MAX()
4807
+ // would scan the whole session. Rows written here are monotonic in
4808
+ // rowid, so this equals MAX + 1 for them.
4809
+ const last = await tx.get<{ lineNumber: number }>(
4810
+ `SELECT lineNumber FROM session_logs WHERE sessionId = ? AND iteration = ?
4811
+ ORDER BY rowid DESC LIMIT 1`,
4812
+ [logs.sessionId, logs.iteration],
4813
+ );
4814
+ let lineNumber = last ? last.lineNumber + 1 : 0;
4815
+ for (const content of batch) {
4755
4816
  await tx.run(
4756
4817
  `INSERT INTO session_logs (id, taskId, sessionId, iteration, cli, content, lineNumber, createdAt)
4757
4818
  VALUES (?, ?, ?, ?, ?, ?, ?, strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))`,
@@ -4762,7 +4823,7 @@ export async function createSessionLogs(logs: {
4762
4823
  logs.iteration,
4763
4824
  logs.cli,
4764
4825
  content,
4765
- lineNumber,
4826
+ lineNumber++,
4766
4827
  ],
4767
4828
  );
4768
4829
  }
@@ -4770,6 +4831,10 @@ export async function createSessionLogs(logs: {
4770
4831
  }
4771
4832
  }
4772
4833
 
4834
+ // Session log reads order by rowid, which is insert order. Rows written before
4835
+ // lineNumber was assigned server-side restart at 0 on every worker flush, so
4836
+ // only rowid orders them. The API never runs VACUUM (runbooks/db-retention.md);
4837
+ // a manual VACUUM may renumber rowids but copies rows in rowid order.
4773
4838
  export async function getSessionLogsByTaskId(
4774
4839
  taskId: string,
4775
4840
  limit?: number,
@@ -4777,16 +4842,16 @@ export async function getSessionLogsByTaskId(
4777
4842
  if (typeof limit === "number" && limit > 0) {
4778
4843
  const rows = await getDbClient().query<SessionLogRow>(
4779
4844
  `SELECT * FROM (
4780
- SELECT * FROM session_logs WHERE taskId = ?
4781
- ORDER BY iteration DESC, lineNumber DESC
4845
+ SELECT rowid AS seq, * FROM session_logs WHERE taskId = ?
4846
+ ORDER BY rowid DESC
4782
4847
  LIMIT ?
4783
- ) ORDER BY iteration ASC, lineNumber ASC`,
4848
+ ) ORDER BY seq ASC`,
4784
4849
  [taskId, limit],
4785
4850
  );
4786
4851
  return rows.map(rowToSessionLog);
4787
4852
  }
4788
4853
  const rows = await getDbClient().query<SessionLogRow>(
4789
- "SELECT * FROM session_logs WHERE taskId = ? ORDER BY iteration ASC, lineNumber ASC",
4854
+ "SELECT * FROM session_logs WHERE taskId = ? ORDER BY rowid ASC",
4790
4855
  [taskId],
4791
4856
  );
4792
4857
  return rows.map(rowToSessionLog);
@@ -4797,7 +4862,7 @@ export async function getSessionLogsBySession(
4797
4862
  iteration: number,
4798
4863
  ): Promise<SessionLog[]> {
4799
4864
  const rows = await getDbClient().query<SessionLogRow>(
4800
- "SELECT * FROM session_logs WHERE sessionId = ? AND iteration = ? ORDER BY lineNumber ASC",
4865
+ "SELECT * FROM session_logs WHERE sessionId = ? AND iteration = ? ORDER BY rowid ASC",
4801
4866
  [sessionId, iteration],
4802
4867
  );
4803
4868
  return rows.map(rowToSessionLog);
@@ -7790,7 +7855,9 @@ async function deleteWorkflowRows(id: string, source?: "api" | "mcp"): Promise<b
7790
7855
  await reclassifyTaskHumanFree(linkedTaskIds);
7791
7856
  // 4. Delete every user's favorite of it (no FK to the workflow)
7792
7857
  await client.run("DELETE FROM user_favorites WHERE itemType = 'workflow' AND itemId = ?", [id]);
7793
- // 5. Delete workflow
7858
+ // 5. Keep Idempotency-Keys that created or triggered it taken, without the stored response
7859
+ await tombstoneWorkflowIdempotencyKeys(id);
7860
+ // 6. Delete workflow
7794
7861
  const result = await client.run("DELETE FROM workflows WHERE id = ?", [id]);
7795
7862
  const deleted = result.changes > 0;
7796
7863
  if (deleted) {
@@ -7826,53 +7893,6 @@ export async function getWorkflowsByScheduleId(scheduleId: string): Promise<Work
7826
7893
  // Workflow Run CRUD
7827
7894
  // ============================================================================
7828
7895
 
7829
- type WorkflowRunRow = {
7830
- id: string;
7831
- workflowId: string;
7832
- status: string;
7833
- triggerType: string;
7834
- triggerData: string | null;
7835
- /** Absent from list rows: see `WORKFLOW_RUN_SUMMARY_COLUMNS`. */
7836
- context?: string | null;
7837
- /** Sealed exact `context` (migration 201). Absent from list rows. */
7838
- context_replay?: string | null;
7839
- error: string | null;
7840
- created_by: string | null;
7841
- startedAt: string;
7842
- lastUpdatedAt: string;
7843
- finishedAt: string | null;
7844
- /** The `sub-workflow` step that started this run (migration 203). */
7845
- parentStepId?: string | null;
7846
- /** That step's attempt (`retryCount`) when it started this run (migration 240). */
7847
- parentStepAttempt?: number | null;
7848
- };
7849
-
7850
- /**
7851
- * `view` picks the `context` copy: `replay` (default) is the exact sealed value
7852
- * the engine resumes from; `display` is the scrubbed column for API, MCP and UI
7853
- * responses. See `src/be/workflow-replay.ts`.
7854
- */
7855
- function rowToWorkflowRun(row: WorkflowRunRow, view: WorkflowPayloadView = "replay"): WorkflowRun {
7856
- const run: WorkflowRun = {
7857
- id: row.id,
7858
- workflowId: row.workflowId,
7859
- status: row.status as WorkflowRunStatus,
7860
- triggerData: row.triggerData ? JSON.parse(row.triggerData) : undefined,
7861
- context: undefined,
7862
- error: row.error ?? undefined,
7863
- createdBy: row.created_by ?? undefined,
7864
- startedAt: normalizeDateRequired(row.startedAt),
7865
- lastUpdatedAt: normalizeDateRequired(row.lastUpdatedAt),
7866
- finishedAt: normalizeDate(row.finishedAt) ?? undefined,
7867
- parentStepId: row.parentStepId ?? undefined,
7868
- parentStepAttempt: row.parentStepId ? (row.parentStepAttempt ?? 0) : undefined,
7869
- };
7870
- return defineWorkflowPayload(run, "context", row.context, row.context_replay, view, {
7871
- runId: row.id,
7872
- target: "its context",
7873
- });
7874
- }
7875
-
7876
7896
  export async function createWorkflowRun(data: {
7877
7897
  id: string;
7878
7898
  workflowId: string;
@@ -7998,12 +8018,6 @@ export type WorkflowRunListOptions = {
7998
8018
  includeContext?: boolean;
7999
8019
  };
8000
8020
 
8001
- // Everything but `context`. `triggerData` stays: it is small, and stored
8002
- // scripts read it off list rows (dedupe by trigger, failure reports). The
8003
- // parent link and attempt stay so a list row identifies a child's invocation.
8004
- const WORKFLOW_RUN_SUMMARY_COLUMNS =
8005
- "id, workflowId, status, triggerType, triggerData, error, created_by, startedAt, lastUpdatedAt, finishedAt, parentStepId, parentStepAttempt";
8006
-
8007
8021
  export type WorkflowRunPage = {
8008
8022
  runs: WorkflowRun[];
8009
8023
  page: {
@@ -8088,56 +8102,6 @@ export async function listWorkflowRunsPage(
8088
8102
  // Workflow Run Step CRUD
8089
8103
  // ============================================================================
8090
8104
 
8091
- type WorkflowRunStepRow = {
8092
- id: string;
8093
- runId: string;
8094
- nodeId: string;
8095
- nodeType: string;
8096
- status: string;
8097
- input: string | null;
8098
- output: string | null;
8099
- /** Sealed exact `output` (migration 201). */
8100
- output_replay: string | null;
8101
- error: string | null;
8102
- startedAt: string;
8103
- finishedAt: string | null;
8104
- retryCount: number;
8105
- maxRetries: number;
8106
- nextRetryAt: string | null;
8107
- idempotencyKey: string | null;
8108
- diagnostics: string | null;
8109
- nextPort: string | null;
8110
- };
8111
-
8112
- /** `view` picks the `output` copy, as in `rowToWorkflowRun`. */
8113
- function rowToWorkflowRunStep(
8114
- row: WorkflowRunStepRow,
8115
- view: WorkflowPayloadView = "replay",
8116
- ): WorkflowRunStep {
8117
- const step: WorkflowRunStep = {
8118
- id: row.id,
8119
- runId: row.runId,
8120
- nodeId: row.nodeId,
8121
- nodeType: row.nodeType,
8122
- status: row.status as WorkflowRunStepStatus,
8123
- input: row.input ? JSON.parse(row.input) : undefined,
8124
- output: undefined,
8125
- error: row.error ?? undefined,
8126
- startedAt: normalizeDateRequired(row.startedAt),
8127
- finishedAt: normalizeDate(row.finishedAt) ?? undefined,
8128
- retryCount: row.retryCount,
8129
- maxRetries: row.maxRetries,
8130
- nextRetryAt: normalizeDate(row.nextRetryAt) ?? undefined,
8131
- idempotencyKey: row.idempotencyKey ?? undefined,
8132
- diagnostics: row.diagnostics ?? undefined,
8133
- nextPort: row.nextPort ?? undefined,
8134
- };
8135
- return defineWorkflowPayload(step, "output", row.output, row.output_replay, view, {
8136
- runId: row.runId,
8137
- target: `the output of step ${row.id} (node ${row.nodeId})`,
8138
- });
8139
- }
8140
-
8141
8105
  export async function createWorkflowRunStep(data: {
8142
8106
  id: string;
8143
8107
  runId: string;
@@ -8251,7 +8215,7 @@ export async function getWorkflowRunStepsByRunId(
8251
8215
  view: WorkflowPayloadView = "replay",
8252
8216
  ): Promise<WorkflowRunStep[]> {
8253
8217
  const rows = await getDbClient().query<WorkflowRunStepRow>(
8254
- "SELECT * FROM workflow_run_steps WHERE runId = ? ORDER BY startedAt ASC",
8218
+ "SELECT * FROM workflow_run_steps WHERE runId = ? ORDER BY startedAt ASC, rowid ASC",
8255
8219
  [runId],
8256
8220
  );
8257
8221
  return rows.map((row) => rowToWorkflowRunStep(row, view));
@@ -8367,7 +8331,7 @@ export async function getLatestStepForNode(
8367
8331
  nodeId: string,
8368
8332
  ): Promise<WorkflowRunStep | null> {
8369
8333
  const row = await getDbClient().get<WorkflowRunStepRow>(
8370
- "SELECT * FROM workflow_run_steps WHERE runId = ? AND nodeId = ? ORDER BY startedAt DESC LIMIT 1",
8334
+ "SELECT * FROM workflow_run_steps WHERE runId = ? AND nodeId = ? ORDER BY startedAt DESC, rowid DESC LIMIT 1",
8371
8335
  [runId, nodeId],
8372
8336
  );
8373
8337
  return row ? rowToWorkflowRunStep(row) : null;
@@ -9787,15 +9751,29 @@ export async function createApprovalRequest(data: {
9787
9751
  const row = await getDbClient().transaction(async () => {
9788
9752
  let status: ApprovalRequest["status"] = "pending";
9789
9753
  let resolutionReason: string | null = null;
9790
- if (data.requireActionableWorkflow && data.workflowRunId && data.workflowRunStepId) {
9791
- const run = await getWorkflowRun(data.workflowRunId);
9792
- const step = await getWorkflowRunStep(data.workflowRunStepId);
9754
+ // A request linked to an existing run, with or without a step, is born
9755
+ // cancelled unless the run is live and its tree is not cancelled: a
9756
+ // pending request in a cancelled tree would be open work no cancel may
9757
+ // leave behind. A missing run is refused only on the workflow (HITL) path.
9758
+ const actionable = data.requireActionableWorkflow && data.workflowRunStepId;
9759
+ const run = data.workflowRunId ? await getWorkflowRun(data.workflowRunId, "display") : null;
9760
+ if (data.workflowRunId && (run || actionable)) {
9761
+ const step = actionable ? await getWorkflowRunStep(data.workflowRunStepId!) : undefined;
9793
9762
  if (!run || (run.status !== "running" && run.status !== "waiting")) {
9794
9763
  status = "cancelled";
9795
9764
  resolutionReason = run?.error ?? `Workflow run is ${run?.status ?? "missing"}`;
9796
- } else if (!step || step.runId !== run.id || step.status !== "running") {
9765
+ } else if (
9766
+ step !== undefined &&
9767
+ (!step || step.runId !== run.id || step.status !== "running")
9768
+ ) {
9797
9769
  status = "cancelled";
9798
9770
  resolutionReason = `Human-in-the-loop step is ${step?.status ?? "missing"}`;
9771
+ } else {
9772
+ const blocked = await getRunLineageBlock(run.id);
9773
+ if (blocked) {
9774
+ status = "cancelled";
9775
+ resolutionReason = blocked;
9776
+ }
9799
9777
  }
9800
9778
  }
9801
9779
 
@@ -11695,6 +11673,8 @@ export interface ApiKeyStatus {
11695
11673
  /** Auth failures in a row since the last success or clear. */
11696
11674
  consecutiveAuthFailures: number;
11697
11675
  lastAuthFailureAt: string | null;
11676
+ lastValidationError: string | null;
11677
+ lastValidationFailedAt: string | null;
11698
11678
  createdAt: string;
11699
11679
  updatedAt: string;
11700
11680
  }
@@ -11720,6 +11700,7 @@ function rowToApiKeyStatus(row: ApiKeyStatusRow): ApiKeyStatus {
11720
11700
 
11721
11701
  export interface AvailableKeyIndicesResult {
11722
11702
  availableIndices: number[];
11703
+ rejectedIndices: number[];
11723
11704
  /** Indices excluded only by an active model-scoped window block (not key-wide). */
11724
11705
  modelBlockedIndices: number[];
11725
11706
  /** ISO of the earliest resetsAt among modelBlockedIndices, or null when none. */
@@ -11768,8 +11749,9 @@ export async function getAvailableKeyIndices(
11768
11749
  rateLimitWindows: string | null;
11769
11750
  plan: string | null;
11770
11751
  authFailureSeq: number;
11752
+ lastValidationFailedAt: string | null;
11771
11753
  }>(
11772
- `SELECT keyIndex, status, rateLimitWindows, plan, authFailureSeq FROM api_key_status
11754
+ `SELECT keyIndex, status, rateLimitWindows, plan, authFailureSeq, lastValidationFailedAt FROM api_key_status
11773
11755
  WHERE keyType = ? AND scope = ? AND scopeId = ?`,
11774
11756
  [keyType, scope, effectiveScopeId],
11775
11757
  );
@@ -11777,6 +11759,12 @@ export async function getAvailableKeyIndices(
11777
11759
  const blockedIndices = new Set(
11778
11760
  rows.filter((r) => r.status === "rate_limited").map((r) => r.keyIndex),
11779
11761
  );
11762
+ const rejectedIndices = [
11763
+ ...new Set(
11764
+ rows.filter((row) => row.lastValidationFailedAt !== null).map((row) => row.keyIndex),
11765
+ ),
11766
+ ];
11767
+ for (const index of rejectedIndices) blockedIndices.add(index);
11780
11768
 
11781
11769
  const modelBlockedIndices: number[] = [];
11782
11770
  let earliestModelResetsAtSec: number | undefined;
@@ -11819,6 +11807,7 @@ export async function getAvailableKeyIndices(
11819
11807
 
11820
11808
  return {
11821
11809
  availableIndices,
11810
+ rejectedIndices,
11822
11811
  modelBlockedIndices,
11823
11812
  seatBlockedIndices,
11824
11813
  earliestModelResetAt:
@@ -12892,7 +12881,8 @@ export async function listTaskTemplates(opts?: {
12892
12881
  /**
12893
12882
  * Walk the chain rooted at `rootTaskId`, ordered by `createdAt` (root first; `rowid` breaks
12894
12883
  * ties as idx_agent_tasks_created did). `CROSS JOIN` keeps the chain as the outer loop: with
12895
- * a plain JOIN, SQLite scanned all of agent_tasks to skip the sort.
12884
+ * a plain JOIN, SQLite scanned all of agent_tasks to skip the sort. Each task carries its
12885
+ * `totalCostUsd`, so the session header can total the whole chain, not only the root.
12896
12886
  */
12897
12887
  export async function getRootTaskChain(rootTaskId: string): Promise<AgentTask[]> {
12898
12888
  const rows = await getDbClient().query<AgentTaskRow>(
@@ -12902,7 +12892,9 @@ export async function getRootTaskChain(rootTaskId: string): Promise<AgentTask[]>
12902
12892
  SELECT t.id FROM agent_tasks t
12903
12893
  JOIN chain c ON t.parentTaskId = c.id
12904
12894
  )
12905
- SELECT t.* FROM chain
12895
+ SELECT t.*,
12896
+ (SELECT SUM(totalCostUsd) FROM session_costs WHERE session_costs.taskId = t.id) AS totalCostUsd
12897
+ FROM chain
12906
12898
  CROSS JOIN agent_tasks t ON t.id = chain.id
12907
12899
  ORDER BY t.createdAt, t.rowid`,
12908
12900
  [rootTaskId],
@@ -12947,6 +12939,7 @@ export async function listRecentSessions(
12947
12939
  opts: ListRecentSessionsOpts = {},
12948
12940
  ): Promise<SessionListItem[] | SessionListItemSummary[]> {
12949
12941
  const query = sessionSummaryQuery(opts);
12942
+ const threadRoots = threadRootsSql();
12950
12943
  const rootRows = await getDbClient().query<
12951
12944
  AgentTaskRow & {
12952
12945
  __chainCount: number;
@@ -12955,15 +12948,17 @@ export async function listRecentSessions(
12955
12948
  __readState: SessionReadState | null;
12956
12949
  __snoozeUntil: string | null;
12957
12950
  __needsYou: number;
12951
+ __threadRoots: number;
12958
12952
  }
12959
12953
  >(
12960
12954
  `SELECT r.*, a.chainCount AS __chainCount, a.lastActivityAt AS __lastActivityAt,
12961
12955
  a.latestStatus AS __latestStatus, a.readState AS __readState,
12962
- a.snoozeUntil AS __snoozeUntil, a.needsYou AS __needsYou
12956
+ a.snoozeUntil AS __snoozeUntil, a.needsYou AS __needsYou,
12957
+ ${threadRoots.sql} AS __threadRoots
12963
12958
  FROM (${query.sql}) a CROSS JOIN agent_tasks r ON r.id = a.root_id
12964
12959
  ORDER BY ${opts.triage === "later" ? "a.snoozeUntil ASC," : ""}
12965
12960
  a.lastActivityAt DESC, r.createdAt DESC, r.id DESC LIMIT ? OFFSET ?`,
12966
- [...query.params, opts.limit ?? 25, opts.offset ?? 0],
12961
+ [...threadRoots.params, ...query.params, opts.limit ?? 25, opts.offset ?? 0],
12967
12962
  );
12968
12963
  const fields = await sessionPageFields(
12969
12964
  rootRows.map((row) => ({
@@ -12979,7 +12974,7 @@ export async function listRecentSessions(
12979
12974
  ...(row.__readState ? { readState: row.__readState } : {}),
12980
12975
  snoozeUntil: row.__snoozeUntil,
12981
12976
  needsYou: !!row.__needsYou,
12982
- groupKey: sessionGroupKey(row.key),
12977
+ groupKey: sessionThreadGroupKey(row.key, row.__threadRoots),
12983
12978
  ...fields.get(row.id),
12984
12979
  }));
12985
12980
  }
@@ -1,4 +1,5 @@
1
1
  import OpenAI from "openai";
2
+ import { selectCredential } from "../../../utils/credentials";
2
3
  import { getOpenRouterAttributionHeaders } from "../../../utils/openrouter-base-url";
3
4
  import { DEFAULT_EMBEDDING_MODEL, EMBEDDING_DIMENSIONS } from "../constants";
4
5
  import type { EmbeddingProvider } from "../types";
@@ -46,7 +47,10 @@ export class OpenAIEmbeddingProvider implements EmbeddingProvider {
46
47
  private readonly apiKey: string | undefined;
47
48
 
48
49
  constructor(config?: OpenAIEmbeddingConfig) {
49
- this.apiKey = config?.apiKey ?? process.env.EMBEDDING_API_KEY ?? process.env.OPENAI_API_KEY;
50
+ const apiKey = config?.apiKey ?? process.env.EMBEDDING_API_KEY ?? process.env.OPENAI_API_KEY;
51
+ this.apiKey = apiKey
52
+ ? selectCredential(apiKey, undefined, "EMBEDDING_API_KEY").selected
53
+ : undefined;
50
54
 
51
55
  this.model = config?.model ?? process.env.EMBEDDING_MODEL ?? "text-embedding-3-small";
52
56
 
@@ -0,0 +1,40 @@
1
+ -- Idempotency-Key records for POST /api/workflows and POST /api/workflows/{id}/trigger.
2
+ --
3
+ -- One row is one operation the server accepted under a client key. It commits
4
+ -- in the same transaction as the workflow (create) or the run and its initial
5
+ -- state (trigger), so a retry after a lost response, a restart or a concurrent
6
+ -- duplicate finds it and gets the original result instead of a second
7
+ -- workflow or run.
8
+ --
9
+ -- The scope is (principal, operation, targetId, keyHash): the caller's stable
10
+ -- identity (never the bearer value), the operation, the triggered workflow id
11
+ -- ('' for create) and a SHA-256 of the client key. requestFingerprint is a
12
+ -- SHA-256 of the canonical request; a different request under the same key is
13
+ -- a conflict. responseBody is the original response, sealed (sealJson). When
14
+ -- the workflow is deleted, responseBody is cleared and resourceDeletedAt set:
15
+ -- the key stays taken and is never accepted again.
16
+ --
17
+ -- No FK to workflows: a row outlives the workflow as that tombstone.
18
+
19
+ CREATE TABLE IF NOT EXISTS workflow_idempotency_keys (
20
+ id TEXT PRIMARY KEY,
21
+ principal TEXT NOT NULL,
22
+ operation TEXT NOT NULL CHECK (operation IN ('workflow.create', 'workflow.trigger')),
23
+ targetId TEXT NOT NULL DEFAULT '',
24
+ keyHash TEXT NOT NULL,
25
+ requestFingerprint TEXT NOT NULL,
26
+ workflowId TEXT NOT NULL,
27
+ runId TEXT,
28
+ responseStatus INTEGER NOT NULL,
29
+ responseBody TEXT,
30
+ resourceDeletedAt TEXT,
31
+ createdAt TEXT NOT NULL,
32
+ created_by TEXT,
33
+ updated_by TEXT
34
+ );
35
+
36
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_workflow_idempotency_keys_scope
37
+ ON workflow_idempotency_keys(principal, operation, targetId, keyHash);
38
+
39
+ CREATE INDEX IF NOT EXISTS idx_workflow_idempotency_keys_workflowId
40
+ ON workflow_idempotency_keys(workflowId);
@@ -0,0 +1,4 @@
1
+ -- Last provider rejection of a pooled key by a worker live test (HTTP 401).
2
+ -- A key with lastValidationFailedAt set is left out of /api/keys/available.
3
+ ALTER TABLE api_key_status ADD COLUMN lastValidationError TEXT;
4
+ ALTER TABLE api_key_status ADD COLUMN lastValidationFailedAt TEXT;
@@ -130,6 +130,10 @@ export const OnboardingSignalsSchema = z.object({
130
130
  state: z.enum(["unverified", "configured", "verified"]),
131
131
  workers: z.number().int().nonnegative(),
132
132
  verifiedWorkers: z.number().int().nonnegative(),
133
+ /** The latest live test failed: its error. */
134
+ error: z.string().nullable(),
135
+ /** The latest live test passed, but some pool keys failed: names them. */
136
+ warning: z.string().nullable(),
133
137
  }),
134
138
  ),
135
139
  embeddings: z.object({
@@ -250,7 +250,7 @@ export interface SwarmSdk {
250
250
  workflow_list(args?: { enabled?: boolean; includeFull?: boolean; consecutiveErrorsMin?: number; lastRunStatus?: "running" | "waiting" | "completed" | "failed" | "skipped" | "cancelled" }): Promise<unknown>;
251
251
  workflow_get(args: { id: string }): Promise<unknown>;
252
252
  workflow_listRuns(args: { workflowId: string; status?: "running" | "waiting" | "completed" | "failed" | "skipped" | "cancelled"; limit?: number; offset?: number }): Promise<unknown>;
253
- workflow_getRun(args: { id: string }): Promise<unknown>;
253
+ workflow_getRun(args: { id: string; fields?: "full" | "slim" }): Promise<unknown>;
254
254
  // --- prompt templates ---
255
255
  prompt_list(args?: { eventType?: string; scope?: "global" | "agent" | "repo"; scopeId?: string; isDefault?: boolean }): Promise<unknown>;
256
256
  prompt_get(args: { id: string }): Promise<unknown>;
@@ -30,6 +30,7 @@ export const SEALED_REPLAY_COLUMNS: readonly { table: string; column: string }[]
30
30
  { table: "workflow_run_steps", column: "output_replay" },
31
31
  { table: "workflow_runs", column: "context_replay" },
32
32
  { table: "approval_requests", column: "responses" },
33
+ { table: "workflow_idempotency_keys", column: "responseBody" },
33
34
  ];
34
35
 
35
36
  /** SQL `EXISTS` arms matching a sealed value in each `SEALED_REPLAY_COLUMNS` entry. */
@@ -574,14 +574,14 @@ export default async function compoundInsights(args: any, ctx: any) {
574
574
  const rows = rowsToObjects(
575
575
  await ctx.swarm.db_query({
576
576
  sql: `WITH raw AS (
577
- SELECT sessionId, iteration, lineNumber, content,
577
+ SELECT rowid AS seq, sessionId, iteration, lineNumber, content,
578
578
  json_extract(content, '$.tool_name') as jsonToolName
579
579
  FROM session_logs
580
580
  WHERE createdAt > ${w}
581
581
  AND (content LIKE '%"type":"tool_use"%' OR json_extract(content, '$.tool_name') IS NOT NULL)
582
582
  )
583
583
  SELECT sessionId, iteration, lineNumber, jsonToolName, content
584
- FROM raw ORDER BY sessionId, iteration, lineNumber LIMIT 100`,
584
+ FROM raw ORDER BY sessionId, iteration, seq LIMIT 100`,
585
585
  }),
586
586
  );
587
587
  const bySession = new Map<string, string[]>();
@@ -574,14 +574,14 @@ export default async function compoundInsights(args: any, ctx: any) {
574
574
  const rows = rowsToObjects(
575
575
  await ctx.swarm.db_query({
576
576
  sql: `WITH raw AS (
577
- SELECT sessionId, iteration, lineNumber, content,
577
+ SELECT rowid AS seq, sessionId, iteration, lineNumber, content,
578
578
  json_extract(content, '$.tool_name') as jsonToolName
579
579
  FROM session_logs
580
580
  WHERE createdAt > ${w}
581
581
  AND (content LIKE '%"type":"tool_use"%' OR json_extract(content, '$.tool_name') IS NOT NULL)
582
582
  )
583
583
  SELECT sessionId, iteration, lineNumber, jsonToolName, content
584
- FROM raw ORDER BY sessionId, iteration, lineNumber LIMIT 100`,
584
+ FROM raw ORDER BY sessionId, iteration, seq LIMIT 100`,
585
585
  }),
586
586
  );
587
587
  const bySession = new Map<string, string[]>();
@@ -18,7 +18,7 @@
18
18
  "delegate-work": [
19
19
  {
20
20
  "path": "scripts/codex-exec.sh",
21
- "content": "#!/usr/bin/env bash\n#\n# codex-exec.sh — invoke `codex exec` (non-interactive) for delegated\n# implementation work. This is the SINGLE SOURCE OF TRUTH for the invocation\n# flags. The delegate-work skill calls only this script; its routing matrix\n# decides WHICH gpt-5.6 variant (-m) and effort (-e) each task gets:\n# luna = mechanical/bounded terra = everyday frozen-spec slice\n# sol = hard/long-horizon effort: medium|high|xhigh|max (never ultra)\n#\n# Usage:\n# codex-exec.sh [-m <model>] [-e <effort>] -C <workdir> -o <last-message-file> [-l <log-file>] \"PROMPT\"\n# printf '%s' \"$PROMPT\" | codex-exec.sh -C <workdir> -o <file> # prompt via stdin (preferred)\n#\n# Notes:\n# * -C sets codex's working root (the git worktree, for parallel isolation).\n# * -o writes codex's final message (its report) to a file you can read back.\n# * Reads the prompt from stdin when no positional PROMPT is given.\n# * In `exec` mode approvals are already \"never\", so nothing hangs waiting for\n# a human. The sandbox mode below bounds what codex may WRITE.\n#\n# Sandbox default: `workspace-write` — codex may freely edit files inside its\n# working root (the worktree) and read elsewhere on disk (so it can read the\n# plan by absolute path), but cannot write outside the workspace. This keeps\n# parallel worktree runs from clobbering each other or the main tree.\n#\n# Env overrides (flags -m/-e take precedence):\n# CODEX_MODEL default: gpt-5.6-sol gpt-5.6-sol | gpt-5.6-terra | gpt-5.6-luna\n# CODEX_EFFORT default: high reasoning effort (never \"fast\"/low)\n# CODEX_SANDBOX default: workspace-write (passed to codex's -s option)\n# CODEX_BYPASS set to 1 to use --dangerously-bypass-approvals-and-sandbox\n# (fully unsandboxed — opt in only when a phase must write\n# outside its worktree; overrides CODEX_SANDBOX)\nset -euo pipefail\n\nWORKDIR=\".\"\nOUTFILE=\"\"\nLOGFILE=\"\"\nPROMPT=\"\"\nMODEL_FLAG=\"\"\nEFFORT_FLAG=\"\"\n\nwhile [[ $# -gt 0 ]]; do\n case \"$1\" in\n -m|--model) MODEL_FLAG=\"$2\"; shift 2 ;;\n -e|--effort) EFFORT_FLAG=\"$2\"; shift 2 ;;\n -C|--cd) WORKDIR=\"$2\"; shift 2 ;;\n -o|--out) OUTFILE=\"$2\"; shift 2 ;;\n -l|--log) LOGFILE=\"$2\"; shift 2 ;;\n --) shift; PROMPT=\"${1:-}\"; break ;;\n -*) echo \"codex-exec.sh: unknown flag: $1\" >&2; exit 2 ;;\n *) PROMPT=\"$1\"; shift ;;\n esac\ndone\n\nMODEL=\"${MODEL_FLAG:-${CODEX_MODEL:-gpt-5.6-sol}}\"\nEFFORT=\"${EFFORT_FLAG:-${CODEX_EFFORT:-high}}\"\nSANDBOX=\"${CODEX_SANDBOX:-workspace-write}\"\n\nargs=( exec\n -m \"$MODEL\"\n -c \"model_reasoning_effort=\\\"$EFFORT\\\"\"\n -C \"$WORKDIR\"\n)\nif [[ \"${CODEX_BYPASS:-0}\" == \"1\" ]]; then\n args+=( --dangerously-bypass-approvals-and-sandbox )\nelse\n args+=( -s \"$SANDBOX\" )\nfi\n[[ -n \"$OUTFILE\" ]] && args+=( -o \"$OUTFILE\" )\n\n# Prompt source: positional arg if present, otherwise stdin (`-`).\nif [[ -n \"$PROMPT\" ]]; then\n set -- \"$PROMPT\"\nelse\n set -- \"-\"\nfi\n\nif [[ -n \"$LOGFILE\" ]]; then\n codex \"${args[@]}\" \"$@\" 2>&1 | tee \"$LOGFILE\"\nelse\n codex \"${args[@]}\" \"$@\"\nfi\n"
21
+ "content": "#!/usr/bin/env bash\n#\n# codex-exec.sh — invoke `codex exec` (non-interactive) for delegated\n# implementation work. This is the SINGLE SOURCE OF TRUTH for the invocation\n# flags. The delegate-work skill calls only this script; its routing matrix\n# decides WHICH gpt-6 variant (-m) and effort (-e) each task gets:\n# gpt-6-luna = trivial/mechanical gpt-6.1-sol = default (medium) to hard (high/xhigh)\n# gpt-6-astra = escalation only effort: medium|high|xhigh|max (never ultra)\n#\n# Usage:\n# codex-exec.sh [-m <model>] [-e <effort>] -C <workdir> -o <last-message-file> [-l <log-file>] \"PROMPT\"\n# printf '%s' \"$PROMPT\" | codex-exec.sh -C <workdir> -o <file> # prompt via stdin (preferred)\n#\n# Notes:\n# * -C sets codex's working root (the git worktree, for parallel isolation).\n# * -o writes codex's final message (its report) to a file you can read back.\n# * Reads the prompt from stdin when no positional PROMPT is given.\n# * In `exec` mode approvals are already \"never\", so nothing hangs waiting for\n# a human. The sandbox mode below bounds what codex may WRITE.\n#\n# Sandbox default: `workspace-write` — codex may freely edit files inside its\n# working root (the worktree) and read elsewhere on disk (so it can read the\n# plan by absolute path), but cannot write outside the workspace. This keeps\n# parallel worktree runs from clobbering each other or the main tree.\n#\n# Env overrides (flags -m/-e take precedence):\n# CODEX_MODEL default: gpt-6.1-sol gpt-6.1-sol | gpt-6-astra | gpt-6-luna\n# CODEX_EFFORT default: high reasoning effort (never \"fast\"/low)\n# CODEX_SANDBOX default: workspace-write (passed to codex's -s option)\n# CODEX_BYPASS set to 1 to use --dangerously-bypass-approvals-and-sandbox\n# (fully unsandboxed — opt in only when a phase must write\n# outside its worktree; overrides CODEX_SANDBOX)\nset -euo pipefail\n\nWORKDIR=\".\"\nOUTFILE=\"\"\nLOGFILE=\"\"\nPROMPT=\"\"\nMODEL_FLAG=\"\"\nEFFORT_FLAG=\"\"\n\nwhile [[ $# -gt 0 ]]; do\n case \"$1\" in\n -m|--model) MODEL_FLAG=\"$2\"; shift 2 ;;\n -e|--effort) EFFORT_FLAG=\"$2\"; shift 2 ;;\n -C|--cd) WORKDIR=\"$2\"; shift 2 ;;\n -o|--out) OUTFILE=\"$2\"; shift 2 ;;\n -l|--log) LOGFILE=\"$2\"; shift 2 ;;\n --) shift; PROMPT=\"${1:-}\"; break ;;\n -*) echo \"codex-exec.sh: unknown flag: $1\" >&2; exit 2 ;;\n *) PROMPT=\"$1\"; shift ;;\n esac\ndone\n\nMODEL=\"${MODEL_FLAG:-${CODEX_MODEL:-gpt-6.1-sol}}\"\nEFFORT=\"${EFFORT_FLAG:-${CODEX_EFFORT:-high}}\"\nSANDBOX=\"${CODEX_SANDBOX:-workspace-write}\"\n\nargs=( exec\n -m \"$MODEL\"\n -c \"model_reasoning_effort=\\\"$EFFORT\\\"\"\n -C \"$WORKDIR\"\n)\nif [[ \"${CODEX_BYPASS:-0}\" == \"1\" ]]; then\n args+=( --dangerously-bypass-approvals-and-sandbox )\nelse\n args+=( -s \"$SANDBOX\" )\nfi\n[[ -n \"$OUTFILE\" ]] && args+=( -o \"$OUTFILE\" )\n\n# Prompt source: positional arg if present, otherwise stdin (`-`).\nif [[ -n \"$PROMPT\" ]]; then\n set -- \"$PROMPT\"\nelse\n set -- \"-\"\nfi\n\nif [[ -n \"$LOGFILE\" ]]; then\n codex \"${args[@]}\" \"$@\" 2>&1 | tee \"$LOGFILE\"\nelse\n codex \"${args[@]}\" \"$@\"\nfi\n"
22
22
  }
23
23
  ],
24
24
  "design-docs": [