@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
@@ -29,6 +29,7 @@ import {
29
29
  getDbClient,
30
30
  getInstanceActivity,
31
31
  getLiveAgentCounts,
32
+ getReadyAgentCounts,
32
33
  hasFirstCompletedTask,
33
34
  listAgentsWithCredStatusByProvider,
34
35
  NOT_EXTENSION_AGENT_SQL,
@@ -121,6 +122,12 @@ export type StatusIdentity = z.infer<typeof StatusIdentitySchema>;
121
122
  export const StatusActivitySchema = z.object({
122
123
  agents_online: z.number().int().nonnegative(),
123
124
  leads_online: z.number().int().nonnegative(),
125
+ /**
126
+ * Leads that can take work now (status `idle` or `busy`). Unlike
127
+ * `leads_online`, this does not depend on a recent heartbeat: an idle lead
128
+ * stops updating its activity time.
129
+ */
130
+ leads_ready: z.number().int().nonnegative(),
124
131
  recent_tasks_count: z.number().int().nonnegative(),
125
132
  });
126
133
  export type StatusActivity = z.infer<typeof StatusActivitySchema>;
@@ -194,12 +201,13 @@ export type AutomationStatus = z.infer<typeof AutomationStatusSchema>;
194
201
  /**
195
202
  * Phase 2: Aggregate health derived from setup milestones.
196
203
  *
197
- * - `broken` — harness or workers blocking (creds missing, no live workers ever).
198
- * - `degraded` — at least one optional integration `unverified`/`configured`
199
- * while another is configured, OR harness creds present but
200
- * the latest live test failed or never ran (`configured`).
201
- * - `ok` — harness + workers `verified`; no integration left in
202
- * `configured` state.
204
+ * - `broken`: harness or workers blocking (creds missing, no live workers ever).
205
+ * - `degraded`: no lead can take work (none is `idle` or `busy`), OR at
206
+ * least one optional integration `unverified`/`configured` while another
207
+ * is configured, OR harness creds present but the latest live test failed
208
+ * or never ran (`configured`).
209
+ * - `ok`: harness + workers `verified`, a lead can take work, and no
210
+ * integration left in `configured` state.
203
211
  */
204
212
  export const StatusHealthSchema = z.enum(["ok", "degraded", "broken"]);
205
213
  export type StatusHealth = z.infer<typeof StatusHealthSchema>;
@@ -692,19 +700,23 @@ export async function buildSetup(
692
700
  * Decision matrix:
693
701
  * - Harness `unverified` (no creds at all) → `broken` — the swarm cannot run a task.
694
702
  * - Workers `unverified` (no agents ever) → `broken` — same reason.
703
+ * - No lead can take work (`readyLeads` is 0) → `degraded`. Tasks wait until a lead is online.
695
704
  * - Harness `configured` (creds present, never live-tested) → `degraded`.
696
705
  * - Any of {slack, github, linear, jira} `configured` (i.e. half-set-up) → `degraded`.
697
706
  * - Otherwise → `ok`.
698
707
  *
699
708
  * Notes:
709
+ * - `readyLeads` counts leads with status `idle` or `busy`
710
+ * (`getReadyAgentCounts`). It does not use the heartbeat time: an agent
711
+ * updates `lastActivityAt` only while a session runs, so an idle, healthy
712
+ * lead looks stale.
700
713
  * - Worker `configured` (fleet exists but missed recent heartbeats) does NOT
701
- * degrade the rollup. Heartbeat drift is a runtime concern surfaced on the
702
- * /agents page and the dashboard canvas — not a setup health signal.
714
+ * degrade the rollup on its own, for the same reason.
703
715
  * - Integrations in `unverified` are NOT degrading on their own — most
704
716
  * deployments don't connect every integration. They only nudge the rollup if
705
717
  * paired with another integration in `configured` (the brainstorm contract).
706
718
  */
707
- export function computeHealth(setup: SetupMilestone[]): StatusHealth {
719
+ export function computeHealth(setup: SetupMilestone[], readyLeads: number): StatusHealth {
708
720
  const byId = new Map(setup.map((m) => [m.id, m] as const));
709
721
  const harness = byId.get("harness");
710
722
  const workers = byId.get("workers");
@@ -714,6 +726,7 @@ export function computeHealth(setup: SetupMilestone[]): StatusHealth {
714
726
  if (!workers || workers.state === "unverified") return "broken";
715
727
 
716
728
  // `degraded` rules.
729
+ if (readyLeads === 0) return "degraded";
717
730
  if (harness.state === "configured") return "degraded";
718
731
 
719
732
  for (const id of ["slack", "github", "linear", "jira"] as const) {
@@ -740,12 +753,13 @@ export async function buildStatusPayload(): Promise<StatusResponse> {
740
753
  .map((automation) => toStatus(preflightAutomation(automation, automationSetup)))
741
754
  .sort((a, b) => a.name.localeCompare(b.name) || a.kind.localeCompare(b.kind));
742
755
  const activity = await getInstanceActivity();
756
+ const readyLeads = (await getReadyAgentCounts()).leads;
743
757
  const instance = await getInstanceFeedbackSignals();
744
758
  return {
745
759
  runtime_pools: { enabled: isRuntimePoolsEnabled(), supervisor: runtimeSupervisorStatus() },
746
760
  identity: buildIdentity(),
747
761
  setup,
748
- activity,
762
+ activity: { ...activity, leads_ready: readyLeads },
749
763
  agent_fs: {
750
764
  configured: !!process.env.AGENT_FS_API_URL,
751
765
  base_url: process.env.AGENT_FS_API_URL ?? null,
@@ -753,7 +767,7 @@ export async function buildStatusPayload(): Promise<StatusResponse> {
753
767
  comb: getCombStatus(await combServiceUserId),
754
768
  },
755
769
  automations,
756
- health: computeHealth(setup),
770
+ health: computeHealth(setup, readyLeads),
757
771
  telemetry: { enabled: isTelemetryEnabled() },
758
772
  instance,
759
773
  };
@@ -797,7 +811,7 @@ const getStatus = route({
797
811
  pattern: ["status"],
798
812
  summary: "Identity + setup readiness + live activity for the swarm dashboard",
799
813
  description:
800
- "Single source of truth consumed by the UI home page. Identity comes from SWARM_* envs; setup milestones each emit `unverified | configured | verified`; automations report `running | needs_setup` from the same runtime preflight used at dispatch; activity counts agents alive in the last 5 min and tasks created in the last 24h; agent_fs reports whether AGENT_FS_API_URL is set, plus the Comb settings (COMB_ENABLED, the browser-facing agent-fs URL, the shared org and drive ids, and the agent-fs user id of the swarm service account); telemetry reports the effective ANONYMIZED_TELEMETRY opt-out state.",
814
+ "Single source of truth consumed by the UI home page. Identity comes from SWARM_* envs; setup milestones each emit `unverified | configured | verified`; automations report `running | needs_setup` from the same runtime preflight used at dispatch; activity counts agents alive in the last 5 min, leads that can take work (status idle or busy), and tasks created in the last 24h; agent_fs reports whether AGENT_FS_API_URL is set, plus the Comb settings (COMB_ENABLED, the browser-facing agent-fs URL, the shared org and drive ids, and the agent-fs user id of the swarm service account); telemetry reports the effective ANONYMIZED_TELEMETRY opt-out state.",
801
815
  tags: ["Status"],
802
816
  responses: {
803
817
  200: { description: "Status payload", schema: StatusResponseSchema },
package/src/http/tasks.ts CHANGED
@@ -227,6 +227,8 @@ const listTasks = route({
227
227
  status: z.string().optional(),
228
228
  agentId: z.string().optional(),
229
229
  scheduleId: z.string().optional(),
230
+ /** Only tasks of this workflow run (indexed; independent of fleet size). */
231
+ workflowRunId: z.string().uuid().optional(),
230
232
  key: AssetKeySchema.optional(),
231
233
  keyPrefix: AssetKeySchema.optional(),
232
234
  search: z.string().optional(),
@@ -976,6 +978,7 @@ export async function handleTasks(
976
978
  status,
977
979
  agentId: parsed.query.agentId || undefined,
978
980
  scheduleId: parsed.query.scheduleId || undefined,
981
+ workflowRunId: parsed.query.workflowRunId,
979
982
  key: parsed.query.key,
980
983
  keyPrefix: parsed.query.keyPrefix,
981
984
  search: parsed.query.search || undefined,
@@ -2,7 +2,7 @@ import type { IncomingMessage, ServerResponse } from "node:http";
2
2
  import { z } from "zod";
3
3
  import { getDbClient, upsertSwarmConfig } from "../be/db";
4
4
  import { isTelemetryEnabled, trackRelayed } from "../telemetry";
5
- import { USER_FUNCTIONS } from "../utils/company-profile";
5
+ import { reportedUserFunction, USER_FUNCTIONS } from "../utils/company-profile";
6
6
  import { orgIdentityStatus } from "../utils/org-identity";
7
7
  import { normalizeOrgWebsite, TELEMETRY_PROXY_ORIGIN } from "../utils/org-website";
8
8
  import { ensureConfigAdmin } from "./config";
@@ -129,7 +129,12 @@ export async function handleUsedBy(
129
129
  ...(orgDomain ? { org_domain: orgDomain } : {}),
130
130
  granted,
131
131
  ...(granted
132
- ? { contact_email: contactEmail, contact_function: contactFunction, quote }
132
+ ? {
133
+ contact_email: contactEmail,
134
+ // The proxy rejects a job function its catalog does not list yet.
135
+ contact_function: reportedUserFunction(contactFunction),
136
+ quote,
137
+ }
133
138
  : {}),
134
139
  changed_at: changedAt,
135
140
  }),
@@ -5,6 +5,7 @@ import { resolveHttpAuditUserId } from "../be/audit-user";
5
5
  import {
6
6
  createWorkflow,
7
7
  deleteWorkflow,
8
+ getDbClient,
8
9
  getWorkflow,
9
10
  getWorkflowRun,
10
11
  getWorkflowRunStepsByRunId,
@@ -15,7 +16,21 @@ import {
15
16
  type updateWorkflow,
16
17
  withFavoriteFlags,
17
18
  } from "../be/db";
18
- import { getWorkflowRunParent } from "../be/db/workflow-runs";
19
+ import {
20
+ assertWorkflowIdempotencyKeyUnused,
21
+ IdempotencyKeyTakenError,
22
+ recordAcceptedWorkflowOperation,
23
+ type WorkflowIdempotencyClaim,
24
+ workflowRequestFingerprint,
25
+ } from "../be/db/workflow-idempotency";
26
+ import {
27
+ getWorkflowRunParent,
28
+ getWorkflowRunStepsSlimByRunId,
29
+ getWorkflowRunSummary,
30
+ getWorkflowRunTree,
31
+ WORKFLOW_RUN_TREE_MAX_DEPTH,
32
+ WorkflowRunTreeError,
33
+ } from "../be/db/workflow-runs";
19
34
  import {
20
35
  AssetKeySchema,
21
36
  AutomationIntegrationIdSchema,
@@ -31,6 +46,7 @@ import {
31
46
  WorkflowRunSchema,
32
47
  WorkflowRunStatusSchema,
33
48
  WorkflowRunStepSchema,
49
+ WorkflowRunStepSlimSchema,
34
50
  WorkflowRunSummarySchema,
35
51
  WorkflowSchema,
36
52
  WorkflowVersionSchema,
@@ -46,6 +62,11 @@ import { cancelWorkflowRun, retryFailedRun } from "../workflows/resume";
46
62
  import { handleWebhookTrigger, WebhookError } from "../workflows/triggers";
47
63
  import { snapshotAndUpdateWorkflow } from "../workflows/version";
48
64
  import { resolveHttpFavoriteOwner } from "./favorite-owner";
65
+ import {
66
+ idempotencyKeyHeader,
67
+ readIdempotencyKey,
68
+ replayAcceptedOperation,
69
+ } from "./idempotency-key";
49
70
  import { route } from "./route-def";
50
71
  import { jsonError, parseBody, triggerSchemaErrorResponse } from "./utils";
51
72
 
@@ -96,6 +117,13 @@ const WorkflowRunPageSchema = z.object({
96
117
  }),
97
118
  });
98
119
 
120
+ /** Mirrors `WorkflowRunTree` in src/be/db/workflow-runs.ts. */
121
+ const WorkflowRunTreeSchema = z.object({
122
+ rootRunId: z.string(),
123
+ runs: z.array(WorkflowRunSummarySchema),
124
+ hasOpenWork: z.boolean(),
125
+ });
126
+
99
127
  const SuccessResponseSchema = z.object({ success: z.literal(true) });
100
128
 
101
129
  /** Adds `warnings` to a just-saved workflow when a node in it cannot run yet. */
@@ -142,6 +170,7 @@ const createWorkflowRoute = route({
142
170
  pattern: ["api", "workflows"],
143
171
  summary: "Create a new workflow",
144
172
  tags: ["Workflows"],
173
+ headers: idempotencyKeyHeader("creates one workflow"),
145
174
  body: z.object({
146
175
  name: z.string().min(1),
147
176
  key: AssetKeySchema.optional(),
@@ -159,7 +188,9 @@ const createWorkflowRoute = route({
159
188
  }),
160
189
  responses: {
161
190
  201: { description: "Workflow created", schema: WorkflowSaveResponseSchema },
162
- 400: { description: "Invalid definition" },
191
+ 400: { description: "Invalid definition, or a malformed Idempotency-Key" },
192
+ 409: { description: "Idempotency-Key was already used for a different request" },
193
+ 410: { description: "The workflow created under this Idempotency-Key was deleted" },
163
194
  },
164
195
  });
165
196
 
@@ -272,16 +303,21 @@ const triggerWorkflowRoute = route({
272
303
  path: "/api/workflows/{id}/trigger",
273
304
  pattern: ["api", "workflows", null, "trigger"],
274
305
  summary: "Trigger a workflow execution",
306
+ description:
307
+ "With an `Idempotency-Key`, a retry returns the accepted run's `runId` and `skipped` without starting, resuming or waiting for anything, even after the workflow was disabled or edited and whatever state the run is in now.",
275
308
  tags: ["Workflows"],
276
309
  params: z.object({ id: z.string() }),
310
+ headers: idempotencyKeyHeader("starts one run"),
277
311
  responses: {
278
312
  201: {
279
313
  description: "Workflow run started (or skipped if cooldown active)",
280
314
  schema: z.object({ runId: z.string(), skipped: z.boolean() }),
281
315
  },
282
- 400: { description: "Workflow is disabled" },
316
+ 400: { description: "Workflow is disabled, or a malformed Idempotency-Key" },
283
317
  401: { description: "Unauthorized" },
284
318
  404: { description: "Workflow not found" },
319
+ 409: { description: "Idempotency-Key was already used for a different request" },
320
+ 410: { description: "The workflow triggered under this Idempotency-Key was deleted" },
285
321
  },
286
322
  });
287
323
 
@@ -328,22 +364,50 @@ const getWorkflowRunRoute = route({
328
364
  path: "/api/workflow-runs/{id}",
329
365
  pattern: ["api", "workflow-runs", null],
330
366
  summary: "Get a workflow run with steps (includes retry columns)",
367
+ description:
368
+ "Returns the run with its `context` and every step with its `input` by default. A step's `input` is a snapshot of the context when the step started, so in runs with loops (polling, `wait`, iterations) the response grows with steps × context size. Pass `fields=slim` to drop `run.context` and `steps[].input`; outputs, errors, diagnostics, retry fields and `parent` stay.",
331
369
  tags: ["Workflows"],
332
370
  params: z.object({ id: z.string() }),
371
+ query: z.object({
372
+ /** `slim` omits `run.context` and `steps[].input`; default is full. */
373
+ fields: z.enum(["full", "slim"]).optional(),
374
+ }),
333
375
  responses: {
334
376
  200: {
335
377
  description: "Workflow run details with steps including retry info",
336
- schema: z.object({
337
- run: WorkflowRunSchema,
338
- steps: z.array(WorkflowRunStepSchema),
339
- /** Set on a child run: the run and `sub-workflow` step that started it. */
340
- parent: WorkflowRunParentSchema.optional(),
341
- }),
378
+ schema: z.union([
379
+ z.object({
380
+ run: WorkflowRunSchema,
381
+ steps: z.array(WorkflowRunStepSchema),
382
+ /** Set on a child run: the run and `sub-workflow` step that started it. */
383
+ parent: WorkflowRunParentSchema.optional(),
384
+ }),
385
+ z.object({
386
+ run: WorkflowRunSummarySchema,
387
+ steps: z.array(WorkflowRunStepSlimSchema),
388
+ parent: WorkflowRunParentSchema.optional(),
389
+ }),
390
+ ]),
342
391
  },
343
392
  404: { description: "Run not found" },
344
393
  },
345
394
  });
346
395
 
396
+ const getWorkflowRunTreeRoute = route({
397
+ method: "get",
398
+ path: "/api/workflow-runs/{id}/tree",
399
+ pattern: ["api", "workflow-runs", null, "tree"],
400
+ summary: "Get a workflow run and every sub-workflow run below it",
401
+ description: `Follows stored parent links (\`parentStepId\` → step → run) through every attempt of every step, terminal runs included. \`runs\` holds list rows without \`context\`, root first. \`hasOpenWork\` is true while any run in the tree is live, has a pending, running or waiting step, a queued retry, an open task or a pending approval; after a cancel it turns false once the tree has settled. Both come from one read snapshot. The tree is not paginated. Links that form a cycle or nest deeper than ${WORKFLOW_RUN_TREE_MAX_DEPTH} levels return 409.`,
402
+ tags: ["Workflows"],
403
+ params: z.object({ id: z.string() }),
404
+ responses: {
405
+ 200: { description: "The run tree", schema: WorkflowRunTreeSchema },
406
+ 404: { description: "Run not found" },
407
+ 409: { description: "Stored parent links form a cycle or nest past the depth bound" },
408
+ },
409
+ });
410
+
347
411
  const retryWorkflowRunRoute = route({
348
412
  method: "post",
349
413
  path: "/api/workflow-runs/{id}/retry",
@@ -574,8 +638,21 @@ export async function handleWorkflows(
574
638
  }
575
639
 
576
640
  if (createWorkflowRoute.match(req.method, pathSegments)) {
641
+ const idempotency = readIdempotencyKey(req, res, myAgentId);
642
+ if (idempotency === null) return true;
577
643
  const parsed = await createWorkflowRoute.parse(req, res, pathSegments, queryParams);
578
644
  if (!parsed) return true;
645
+ const claim: WorkflowIdempotencyClaim | undefined = idempotency && {
646
+ ...idempotency,
647
+ operation: "workflow.create",
648
+ targetId: "",
649
+ fingerprint: workflowRequestFingerprint("workflow.create", parsed.body),
650
+ };
651
+ const replayCreate = (body: unknown) =>
652
+ createWorkflowRoute.respond(res, 201, body as z.input<typeof WorkflowSaveResponseSchema>);
653
+ // Before validation: a retry gets the original result even if the same
654
+ // definition would no longer validate.
655
+ if (claim && (await replayAcceptedOperation(res, claim, replayCreate))) return true;
579
656
 
580
657
  // Validate definition structure
581
658
  const validation = validateDefinition(parsed.body.definition, getExecutorRegistry());
@@ -603,27 +680,55 @@ export async function handleWorkflows(
603
680
  throw error;
604
681
  }
605
682
 
606
- const workflow = await createWorkflow(
607
- {
608
- key,
609
- name: parsed.body.name,
610
- description: parsed.body.description,
611
- definition: parsed.body.definition,
612
- triggers: parsed.body.triggers,
613
- cooldown: parsed.body.cooldown,
614
- input: parsed.body.input,
615
- triggerSchema: parsed.body.triggerSchema,
616
- params: parsed.body.params,
617
- requiredParams: parsed.body.requiredParams,
618
- requires: parsed.body.requires,
619
- dir: parsed.body.dir,
620
- vcsRepo: parsed.body.vcsRepo,
621
- createdByAgentId: myAgentId ?? undefined,
622
- createdBy: (await resolveHttpAuditUserId(req, myAgentId)) ?? undefined,
623
- },
624
- "api",
625
- );
626
- createWorkflowRoute.respond(res, 201, await withSaveWarnings(workflow));
683
+ const data: Parameters<typeof createWorkflow>[0] = {
684
+ key,
685
+ name: parsed.body.name,
686
+ description: parsed.body.description,
687
+ definition: parsed.body.definition,
688
+ triggers: parsed.body.triggers,
689
+ cooldown: parsed.body.cooldown,
690
+ input: parsed.body.input,
691
+ triggerSchema: parsed.body.triggerSchema,
692
+ params: parsed.body.params,
693
+ requiredParams: parsed.body.requiredParams,
694
+ requires: parsed.body.requires,
695
+ dir: parsed.body.dir,
696
+ vcsRepo: parsed.body.vcsRepo,
697
+ createdByAgentId: myAgentId ?? undefined,
698
+ createdBy: (await resolveHttpAuditUserId(req, myAgentId)) ?? undefined,
699
+ };
700
+ if (!claim) {
701
+ const workflow = await createWorkflow(data, "api");
702
+ createWorkflowRoute.respond(res, 201, await withSaveWarnings(workflow));
703
+ return true;
704
+ }
705
+
706
+ // Warnings are computed before the transaction (a readiness check may
707
+ // call out) and stored with the workflow, so a retry gets this snapshot,
708
+ // not the workflow as it is by then.
709
+ const warnings = await workflowSaveWarnings(parsed.body.definition, getExecutorRegistry());
710
+ let saved: z.input<typeof WorkflowSaveResponseSchema>;
711
+ try {
712
+ saved = await getDbClient().transaction(async () => {
713
+ // Before the insert: a concurrent winner's workflow holds the same name.
714
+ await assertWorkflowIdempotencyKeyUnused(claim);
715
+ const workflow = await createWorkflow(data, "api");
716
+ const body = warnings.length > 0 ? { ...workflow, warnings } : workflow;
717
+ await recordAcceptedWorkflowOperation(claim, {
718
+ workflowId: workflow.id,
719
+ status: 201,
720
+ body,
721
+ });
722
+ return body;
723
+ });
724
+ } catch (err) {
725
+ // A concurrent request with this key committed first; nothing of this one did.
726
+ if (err instanceof IdempotencyKeyTakenError) {
727
+ if (await replayAcceptedOperation(res, claim, replayCreate)) return true;
728
+ }
729
+ throw err;
730
+ }
731
+ createWorkflowRoute.respond(res, 201, saved);
627
732
  return true;
628
733
  }
629
734
 
@@ -858,8 +963,26 @@ export async function handleWorkflows(
858
963
  }
859
964
 
860
965
  if (triggerWorkflowRoute.match(req.method, pathSegments)) {
966
+ const idempotency = readIdempotencyKey(req, res, myAgentId);
967
+ if (idempotency === null) return true;
861
968
  const parsed = await triggerWorkflowRoute.parse(req, res, pathSegments, queryParams);
862
969
  if (!parsed) return true;
970
+ let claim: WorkflowIdempotencyClaim | undefined;
971
+ let keyedBody: Record<string, unknown> | undefined;
972
+ const replayTrigger = (body: unknown) =>
973
+ triggerWorkflowRoute.respond(res, 201, body as { runId: string; skipped: boolean });
974
+ if (idempotency) {
975
+ keyedBody = await parseBody<Record<string, unknown>>(req);
976
+ claim = {
977
+ ...idempotency,
978
+ operation: "workflow.trigger",
979
+ targetId: parsed.params.id,
980
+ fingerprint: workflowRequestFingerprint("workflow.trigger", keyedBody),
981
+ };
982
+ // Before the enabled, cooldown, readiness and trigger-schema checks: an
983
+ // accepted trigger replays even after the workflow changed.
984
+ if (await replayAcceptedOperation(res, claim, replayTrigger)) return true;
985
+ }
863
986
  const workflow = await getWorkflow(parsed.params.id);
864
987
  if (!workflow) {
865
988
  res.writeHead(404);
@@ -870,20 +993,45 @@ export async function handleWorkflows(
870
993
  jsonError(res, "Workflow is disabled", 400);
871
994
  return true;
872
995
  }
873
- const body = await parseBody<Record<string, unknown>>(req);
996
+ // Not `??`: a JSON `null` body is a parsed body too, and reading the
997
+ // already-ended stream again would wait forever.
998
+ const body = idempotency ? keyedBody : await parseBody<Record<string, unknown>>(req);
874
999
  let runId: string;
1000
+ let accepted: { runId: string; skipped: boolean } | undefined;
875
1001
  try {
876
1002
  runId = await startWorkflowExecution(workflow, body, getExecutorRegistry(), {
877
1003
  triggerType: "api",
878
1004
  requestedByUserId: (await resolveHttpAuditUserId(req, myAgentId)) ?? undefined,
1005
+ // Recorded in the run's own transaction, before the walk starts.
1006
+ accept: claim
1007
+ ? async (run) => {
1008
+ const response = { runId: run.runId, skipped: run.skipped };
1009
+ await recordAcceptedWorkflowOperation(claim, {
1010
+ workflowId: workflow.id,
1011
+ runId: run.runId,
1012
+ status: 201,
1013
+ body: response,
1014
+ });
1015
+ accepted = response;
1016
+ }
1017
+ : undefined,
879
1018
  });
880
1019
  } catch (err) {
881
1020
  if (err instanceof TriggerSchemaError) {
882
1021
  triggerSchemaErrorResponse(res, err.message, err.validationErrors);
883
1022
  return true;
884
1023
  }
1024
+ // A concurrent request with this key committed its run first; this one
1025
+ // created none and walks nothing.
1026
+ if (claim && err instanceof IdempotencyKeyTakenError) {
1027
+ if (await replayAcceptedOperation(res, claim, replayTrigger)) return true;
1028
+ }
885
1029
  throw err;
886
1030
  }
1031
+ if (accepted) {
1032
+ triggerWorkflowRoute.respond(res, 201, accepted);
1033
+ return true;
1034
+ }
887
1035
 
888
1036
  // Check if skipped due to cooldown
889
1037
  const run = await getWorkflowRun(runId);
@@ -909,18 +1057,44 @@ export async function handleWorkflows(
909
1057
  const parsed = await getWorkflowRunRoute.parse(req, res, pathSegments, queryParams);
910
1058
  if (!parsed) return true;
911
1059
  // Display view: the scrubbed context and step outputs, never replay state.
912
- const run = await getWorkflowRun(parsed.params.id, "display");
1060
+ // `fields=slim` skips the context and step-input columns in SQL too.
1061
+ const slim = parsed.query.fields === "slim";
1062
+ const run = slim
1063
+ ? await getWorkflowRunSummary(parsed.params.id)
1064
+ : await getWorkflowRun(parsed.params.id, "display");
913
1065
  if (!run) {
914
1066
  res.writeHead(404);
915
1067
  res.end();
916
1068
  return true;
917
1069
  }
918
- const steps = await getWorkflowRunStepsByRunId(parsed.params.id, "display");
1070
+ const steps = slim
1071
+ ? await getWorkflowRunStepsSlimByRunId(parsed.params.id)
1072
+ : await getWorkflowRunStepsByRunId(parsed.params.id, "display");
919
1073
  const parent = run.parentStepId ? await getWorkflowRunParent(run.parentStepId) : null;
920
1074
  getWorkflowRunRoute.respond(res, 200, { run, steps, ...(parent ? { parent } : {}) });
921
1075
  return true;
922
1076
  }
923
1077
 
1078
+ if (getWorkflowRunTreeRoute.match(req.method, pathSegments)) {
1079
+ const parsed = await getWorkflowRunTreeRoute.parse(req, res, pathSegments, queryParams);
1080
+ if (!parsed) return true;
1081
+ let tree: Awaited<ReturnType<typeof getWorkflowRunTree>>;
1082
+ try {
1083
+ tree = await getWorkflowRunTree(parsed.params.id);
1084
+ } catch (err) {
1085
+ if (!(err instanceof WorkflowRunTreeError)) throw err;
1086
+ jsonError(res, err.message, 409);
1087
+ return true;
1088
+ }
1089
+ if (!tree) {
1090
+ res.writeHead(404);
1091
+ res.end();
1092
+ return true;
1093
+ }
1094
+ getWorkflowRunTreeRoute.respond(res, 200, tree);
1095
+ return true;
1096
+ }
1097
+
924
1098
  if (retryWorkflowRunRoute.match(req.method, pathSegments)) {
925
1099
  const parsed = await retryWorkflowRunRoute.parse(req, res, pathSegments, queryParams);
926
1100
  if (!parsed) return true;
@@ -1,5 +1,7 @@
1
1
  import type { Adapter, Message, Thread } from "chat";
2
2
  import { z } from "zod";
3
+ import { sessionGroupKey } from "@/assets/key";
4
+ import { sessionSourceKey } from "@/assets/source-key";
3
5
  import {
4
6
  getAgentById,
5
7
  getAgentWorkingOnContextKey,
@@ -9,6 +11,7 @@ import {
9
11
  getLeadAgent,
10
12
  isExtensionAgent,
11
13
  } from "@/be/db";
14
+ import { rememberSessionLabel } from "@/be/db/session-labels";
12
15
  import { resolveIdentity } from "@/be/identity";
13
16
  import { recordUnmappedIdentity } from "@/be/unmapped-identities";
14
17
  import { dispatchPre } from "@/extensions/dispatcher";
@@ -373,6 +376,20 @@ async function targetFromExtension(
373
376
  * Runs outside any transaction, so `pre.chat.route` is dispatched here, after
374
377
  * the flush and before any task exists.
375
378
  */
379
+ /**
380
+ * The source group label a Telegram or Discord message gives: the DM sender,
381
+ * or a Telegram group title. Slack labels come from the Slack lookup instead.
382
+ */
383
+ function chatSourceLabel(adapter: string, item: ChatBufferedMessage): string | undefined {
384
+ if (adapter !== "telegram" && adapter !== "discord") return undefined;
385
+ const { facts } = item;
386
+ if (facts.chatType === "dm" || item.isDirectMessage)
387
+ return facts.from ? `DM · ${facts.from}` : undefined;
388
+ if (adapter === "telegram" && (facts.chatType === "group" || facts.chatType === "supergroup"))
389
+ return facts.title || undefined;
390
+ return undefined;
391
+ }
392
+
376
393
  export async function flushChatThread(
377
394
  threadId: string,
378
395
  items: ChatBufferedMessage[],
@@ -538,6 +555,7 @@ export async function flushChatThread(
538
555
  const latestActive = await getLatestActiveTaskByContextKey(contextKey, "chat");
539
556
  const dependsOn = !immediate && latestActive ? [latestActive.id] : undefined;
540
557
  let created = 0;
558
+ let sourceKey: string | null = null;
541
559
  for (const match of target.matches) {
542
560
  const options: CreateTaskOptions = {
543
561
  agentId: match.agent?.id ?? null,
@@ -564,6 +582,7 @@ export async function flushChatThread(
564
582
  };
565
583
  try {
566
584
  const { task, unattached } = await createChatTaskWithFiles(description, options, inbound);
585
+ sourceKey ??= sessionSourceKey(task);
567
586
  created++;
568
587
  console.log(
569
588
  `[chat-sdk] ${threadId}: task ${task.id} for ${match.agent ? `agent ${match.agent.id}` : "no agent (queued)"} (${match.matchedText}, dependsOn: ${dependsOn?.join(", ") ?? "none"}${unattached.length ? `, ${unattached.length} file(s) not attached` : ""})`,
@@ -578,6 +597,14 @@ export async function flushChatThread(
578
597
  }
579
598
  }
580
599
  }
600
+ const label = chatSourceLabel(adapter, requester);
601
+ if (sourceKey && label) {
602
+ // Every message refreshes the label, so a renamed group shows its new title.
603
+ // The error may echo the label, which can hold a person's name: do not log it.
604
+ await rememberSessionLabel(sessionGroupKey(sourceKey), label).catch(() => {
605
+ console.warn("[chat-sdk] Could not save a session source label");
606
+ });
607
+ }
581
608
  if (created > 0) {
582
609
  await rememberChatThreadRequester(threadId, requester.userId);
583
610
  await subscribe(thread);
@@ -12,8 +12,20 @@ const RESERVED_FAMILIES = new Map([
12
12
  ["worker", "namespace is reserved for worker nudges"],
13
13
  // Durable dedupe for SLACK_BLOCK_EXTERNAL_CHANNELS alerts (src/slack/external-channel.ts).
14
14
  ["slack-external-alert", "namespace is reserved for Slack external channel alerts"],
15
+ // Source group labels (src/be/db/session-labels.ts). Private, see PRIVATE_FAMILIES.
16
+ [
17
+ "session-labels",
18
+ "namespace is reserved for session source labels; GET /api/session-tree returns them",
19
+ ],
15
20
  ]);
16
21
 
22
+ /**
23
+ * Reserved families that generic KV callers may not read either. Their rows
24
+ * hold data that another route returns only to callers with access (a
25
+ * source label names a private channel or a DM partner).
26
+ */
27
+ const PRIVATE_FAMILIES = new Set(["session-labels"]);
28
+
17
29
  export function isReservedNamespace(namespace: string): boolean {
18
30
  return reservedNamespaceError(namespace) !== null;
19
31
  }
@@ -22,6 +34,13 @@ export function reservedNamespaceError(namespace: string): string | null {
22
34
  return RESERVED_FAMILIES.get(namespace.split(":", 1)[0] ?? "") ?? null;
23
35
  }
24
36
 
37
+ /** The error a generic KV read of a private family gets, or null. */
38
+ export function privateNamespaceError(namespace: string): string | null {
39
+ return PRIVATE_FAMILIES.has(namespace.split(":", 1)[0] ?? "")
40
+ ? reservedNamespaceError(namespace)
41
+ : null;
42
+ }
43
+
25
44
  export function reservedRoomKeyError(key: string): string | null {
26
45
  return key.startsWith("_room/")
27
46
  ? "room snapshot keys are reserved; use the room endpoints"