@desplega.ai/agent-swarm 2.5.4 → 2.7.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 (354) hide show
  1. package/README.md +4 -2
  2. package/dist/{acp-adapter-ek38cwsv.js → acp-adapter-ma1js5n7.js} +9 -9
  3. package/dist/{actions-92mepq03.js → actions-1vhmtvg9.js} +21 -21
  4. package/dist/{amp-adapter-9gaxg015.js → amp-adapter-sxqqvmdk.js} +4 -4
  5. package/dist/{app-ctn8gsgx.js → app-mwx1d5a9.js} +3 -3
  6. package/dist/{assistant-zyr5kz47.js → assistant-yx1p6g82.js} +30 -26
  7. package/dist/{boot-reembed-w75maga2.js → boot-reembed-5h37a2c2.js} +1 -1
  8. package/dist/{boot-reembed-dwzfttdr.js → boot-reembed-7h78vmvv.js} +2 -1
  9. package/dist/{boot-scrub-sweep-e9kq43kf.js → boot-scrub-sweep-8mawd4gw.js} +4 -4
  10. package/dist/{boot-seeds-x28v3fhb.js → boot-seeds-ca84dhnn.js} +4 -4
  11. package/dist/{claude-adapter-f0afqnne.js → claude-adapter-wqt4hfkq.js} +9 -9
  12. package/dist/{claude-managed-adapter-x81a7r65.js → claude-managed-adapter-6fjqtr2z.js} +2 -2
  13. package/dist/{claude-managed-setup-bqzwjd9x.js → claude-managed-setup-rqv98sea.js} +10 -3
  14. package/dist/{claude-sdk-session-7b7whkaq.js → claude-sdk-session-f29f3b2x.js} +11 -11
  15. package/dist/{cli-sxgtf5fw.js → cli-07q03xm2.js} +1 -1
  16. package/dist/{cli-kxpfx2xr.js → cli-0z1xjzwa.js} +2 -2
  17. package/dist/{cli-g9vd9ece.js → cli-1hhbzkbb.js} +14 -16
  18. package/dist/{cli-qqzmjxkf.js → cli-1m9x8dr5.js} +11 -6
  19. package/dist/{cli-jbnn4vxb.js → cli-2pvzmas1.js} +3 -3
  20. package/dist/{cli-x77gen2p.js → cli-2y7v133v.js} +7 -7
  21. package/dist/{cli-dew6x255.js → cli-39aet655.js} +2 -2
  22. package/dist/{cli-g1cw7xtt.js → cli-39rrw8yp.js} +1 -1
  23. package/dist/{cli-jb30dzvk.js → cli-48gxjmcz.js} +530 -190
  24. package/dist/{cli-pzatvw94.js → cli-54sj64fc.js} +2 -2
  25. package/dist/{cli-bvpgr9ny.js → cli-60sty27p.js} +5 -5
  26. package/dist/{cli-asq5ydm4.js → cli-6a9qfa8b.js} +411 -69
  27. package/dist/{cli-277c31dw.js → cli-6mgdba42.js} +225 -3
  28. package/dist/{cli-19202kew.js → cli-6srv653c.js} +5 -5
  29. package/dist/{cli-n4c8e3s5.js → cli-708wdy32.js} +1 -1
  30. package/dist/{cli-pasyvjmm.js → cli-737pyqxr.js} +1 -1
  31. package/dist/{cli-6x9ma5w5.js → cli-803188pb.js} +1 -1
  32. package/dist/{cli-hh5we352.js → cli-86ehn4s5.js} +7 -7
  33. package/dist/{cli-9s05422w.js → cli-8mbc9za9.js} +1 -1
  34. package/dist/{cli-ff1tm9n9.js → cli-8wmj18cz.js} +1 -1
  35. package/dist/{cli-1r5ad7j5.js → cli-9gsm2rzp.js} +12 -12
  36. package/dist/{cli-mz9afxxf.js → cli-9pshx6rd.js} +17 -3
  37. package/dist/{cli-s682zgqz.js → cli-a6ds2epe.js} +1 -1
  38. package/dist/{cli-6a1xg1p7.js → cli-a89zdqz9.js} +2 -2
  39. package/dist/{cli-03vpq45j.js → cli-ay40dnj3.js} +2 -2
  40. package/dist/{cli-tsma7p8v.js → cli-aypd09nf.js} +2 -2
  41. package/dist/{cli-ethgy7ha.js → cli-b3yzyc57.js} +2 -2
  42. package/dist/{cli-bn0rewsy.js → cli-c081bj3b.js} +2 -2
  43. package/dist/{cli-mmhj8vd7.js → cli-c668vvcf.js} +11 -2
  44. package/dist/{cli-sbwsxvtd.js → cli-d20qvpr2.js} +2 -2
  45. package/dist/{cli-c952c7ac.js → cli-e1595bgx.js} +3 -3
  46. package/dist/{cli-621ce7kw.js → cli-f12mr20z.js} +3 -3
  47. package/dist/{cli-dqygeeh5.js → cli-f1e0ptjj.js} +10 -1
  48. package/dist/{cli-xzfq4at3.js → cli-f4dbh7a5.js} +16 -12
  49. package/dist/{cli-ha9rmwgq.js → cli-fb8m699c.js} +2 -2
  50. package/dist/{cli-5bnsc0e1.js → cli-gsfk58nw.js} +5 -5
  51. package/dist/cli-gt4h8cxw.js +895 -0
  52. package/dist/{cli-nwyw3zhg.js → cli-gxee0bqa.js} +1 -1
  53. package/dist/{cli-25n3mv5d.js → cli-h5jeg46p.js} +6 -6
  54. package/dist/{cli-gn5af0x5.js → cli-he9gbdx0.js} +62 -12
  55. package/dist/{cli-gbfxp48k.js → cli-j0ppywdr.js} +42 -35
  56. package/dist/{cli-k4tjsdx9.js → cli-j3y0x2n4.js} +277 -199
  57. package/dist/{cli-7p4jxgtt.js → cli-j9bpmykh.js} +75 -27
  58. package/dist/{cli-dabehxhp.js → cli-kt907da6.js} +1 -1
  59. package/dist/{cli-wx2nj19m.js → cli-kv7sbc2c.js} +6 -6
  60. package/dist/{cli-abs8ndtr.js → cli-m30k9777.js} +26 -26
  61. package/dist/{cli-cz5nmdxt.js → cli-m5e802r7.js} +2 -2
  62. package/dist/{cli-0shx9j93.js → cli-pfk92eqz.js} +2 -2
  63. package/dist/{cli-xtrz6bzs.js → cli-ptfq202x.js} +5 -3
  64. package/dist/{cli-6n8axjv1.js → cli-qj442fws.js} +2 -2
  65. package/dist/{cli-c61mbdds.js → cli-rd5m70gj.js} +4 -4
  66. package/dist/{cli-eftzyh9y.js → cli-s09j9vp5.js} +2 -2
  67. package/dist/{cli-m4ecdxvz.js → cli-s0mgx214.js} +7 -7
  68. package/dist/{cli-wghjkr0e.js → cli-sgqm2582.js} +10 -7
  69. package/dist/{cli-2655k16q.js → cli-sjctjr48.js} +2 -2
  70. package/dist/{cli-nzfnt2gs.js → cli-tf1n0r0b.js} +1 -1
  71. package/dist/{cli-x0f2v69c.js → cli-twctnkng.js} +12 -7
  72. package/dist/{cli-j6rktc91.js → cli-v6shg7na.js} +1 -1
  73. package/dist/{cli-gx4rm0qx.js → cli-vz78s5wm.js} +1 -1
  74. package/dist/{cli-gwhjmh8g.js → cli-wqr7cea4.js} +5 -5
  75. package/dist/{cli-e42vss7b.js → cli-x37r7qwf.js} +16 -8
  76. package/dist/{cli-4b2z6n0m.js → cli-yd2nreqw.js} +17 -8
  77. package/dist/{cli-64mh9mf3.js → cli-ymjpkxgn.js} +69 -17
  78. package/dist/{cli-5kqnemen.js → cli-yvy48785.js} +1 -1
  79. package/dist/{cli-9j129brq.js → cli-yxbsmx56.js} +6 -6
  80. package/dist/cli.js +22 -20
  81. package/dist/{codex-adapter-wch12q99.js → codex-adapter-zk59x2zh.js} +7 -7
  82. package/dist/{codex-hook-brpnycn5.js → codex-hook-xyjhvxwd.js} +1 -1
  83. package/dist/{codex-login-rn7c5gxt.js → codex-login-8cg0vhf2.js} +1 -1
  84. package/dist/{codex-session-runner-41pfcr95.js → codex-session-runner-kh6s4j3n.js} +8 -8
  85. package/dist/{commands-9849dkj6.js → commands-68et1qv2.js} +2 -2
  86. package/dist/{controller-ja0sd95p.js → controller-fyrbnzg4.js} +4 -4
  87. package/dist/{controllers-connect-xp05dj6x.js → controllers-connect-nep9h2xv.js} +23 -23
  88. package/dist/{cursor-adapter-623qxqew.js → cursor-adapter-yss3pg7t.js} +2 -2
  89. package/dist/{db-w759qmx0.js → db-28gjyvxj.js} +4 -2
  90. package/dist/{db-47vj40dy.js → db-8ftwvzba.js} +3 -3
  91. package/dist/{devin-adapter-4ycy4vrf.js → devin-adapter-byen2cxt.js} +1 -1
  92. package/dist/{dispatcher-7b8e4z2j.js → dispatcher-hayexmkr.js} +20 -20
  93. package/dist/{docker-s84hjs91.js → docker-fdzyewpz.js} +2 -2
  94. package/dist/{dsh-adapter-2ezw3xea.js → dsh-adapter-46wawff9.js} +2 -2
  95. package/dist/{e2b-9gkdhm3c.js → e2b-222hsajc.js} +2 -2
  96. package/dist/{e2b-cjtf8k1z.js → e2b-fn3zacvt.js} +2 -2
  97. package/dist/{easter-egg-3d-e4qv8dxj.js → easter-egg-3d-8d1e0h4m.js} +1 -1
  98. package/dist/{event-bus-3z78ep5n.js → event-bus-pghg8hqy.js} +1 -1
  99. package/dist/{execute-0xpz8950.js → execute-cjrfxfnm.js} +1 -1
  100. package/dist/{external-channel-e2s028da.js → external-channel-xbmxcg09.js} +2 -2
  101. package/dist/{grok-adapter-ys7gaqee.js → grok-adapter-ag4wks2a.js} +10 -10
  102. package/dist/handlers-tzmevqb5.js +41 -0
  103. package/dist/{heartbeat-w3mntghj.js → heartbeat-psfvcgwp.js} +23 -23
  104. package/dist/{hook-g7kvv6x9.js → hook-8pz04697.js} +6 -6
  105. package/dist/{hook-3jjywnpb.js → hook-cyq4mryn.js} +8 -8
  106. package/dist/{http-tkp7d4kb.js → http-haj1nd6k.js} +921 -169
  107. package/dist/{index-q7m26kvs.js → index-jwt2bj6f.js} +265 -97
  108. package/dist/{index-3n8m9ea2.js → index-q52f47pe.js} +1 -1
  109. package/dist/{index-31ceyf42.js → index-syvhnvtw.js} +1 -1
  110. package/dist/index-y1ajx04a.js +43 -0
  111. package/dist/{index-1ef1myq2.js → index-z5h22pes.js} +20 -20
  112. package/dist/{index-xdaahthb.js → index-zyq8ct52.js} +20 -20
  113. package/dist/{keepalive-v4ta8wgg.js → keepalive-xdvsamvj.js} +4 -4
  114. package/dist/lead-hqg57kdp.js +79 -0
  115. package/dist/{maintenance-84n1ex21.js → maintenance-4y2xzh3a.js} +1 -1
  116. package/dist/{oauth-refresh-sweep-vjnnandw.js → oauth-refresh-sweep-jd4r7zbs.js} +9 -4
  117. package/dist/{onboard-sfmpe4np.js → onboard-18447q9q.js} +6 -6
  118. package/dist/{opencode-adapter-jmpst4q4.js → opencode-adapter-26b2frdf.js} +7 -7
  119. package/dist/{otel-impl-v6cc0zve.js → otel-impl-f9tgr8yw.js} +2 -2
  120. package/dist/{pi-mono-adapter-fk8fkcf6.js → pi-mono-adapter-qn6qbga1.js} +38 -17
  121. package/dist/{pricing-refresh-fsk1e1zc.js → pricing-refresh-dw2tgb53.js} +3 -3
  122. package/dist/{rbac-roles-prmkdnkw.js → rbac-roles-aygm2760.js} +2 -2
  123. package/dist/{rbac-roles-zxmxqa39.js → rbac-roles-s4nwj2m3.js} +1 -1
  124. package/dist/render-v2-yfvdfr0x.js +16 -0
  125. package/dist/{resume-frdtgfa0.js → resume-1bmvjbf3.js} +9 -9
  126. package/dist/{runtime-bt5pkpm1.js → runtime-f7tpzedy.js} +1 -1
  127. package/dist/{session-access-fpez4y5q.js → session-access-r5garej6.js} +4 -4
  128. package/dist/session-source-backfill-424wzv0r.js +110 -0
  129. package/dist/{setup-mq6m3m8q.js → setup-8tygvxkq.js} +1 -1
  130. package/dist/{virtual-modules-mnfbh5mr.js → virtual-modules-xsw323b3.js} +2 -2
  131. package/dist/{work-objects-kfahhjsq.js → work-objects-ggnghpgf.js} +23 -23
  132. package/dist/worker-sqjd1nqf.js +79 -0
  133. package/dist/{x-pbpezr0s.js → x-wxrzvzx8.js} +2 -2
  134. package/openapi.json +1324 -14
  135. package/package.json +5 -3
  136. package/src/assets/source-key.ts +95 -0
  137. package/src/be/automation-preflight.ts +2 -0
  138. package/src/be/db/api-keys.ts +89 -0
  139. package/src/be/db/session-labels.ts +28 -0
  140. package/src/be/db/session-moves.ts +5 -6
  141. package/src/be/db/session-tree.ts +153 -14
  142. package/src/be/db/tasks/read.ts +11 -0
  143. package/src/be/db/tasks/write.ts +6 -1
  144. package/src/be/db/workflow-runs.ts +219 -6
  145. package/src/be/db-retention.ts +61 -6
  146. package/src/be/db.ts +135 -132
  147. package/src/be/memory/providers/openai-embedding.ts +5 -1
  148. package/src/be/memory/task-learning.ts +155 -0
  149. package/src/be/migrations/244_api_key_validation.sql +4 -0
  150. package/src/be/migrations/245_script_executor.sql +3 -0
  151. package/src/be/oauth-refresh-sweep.ts +2 -1
  152. package/src/be/onboarding.ts +4 -0
  153. package/src/be/scripts/boot-reembed.ts +2 -0
  154. package/src/be/scripts/db.ts +14 -4
  155. package/src/be/scripts/embeddings.ts +2 -0
  156. package/src/be/scripts/run-saved.ts +1 -0
  157. package/src/be/scripts/typecheck.ts +10 -4
  158. package/src/be/seed-skills/index.ts +7 -0
  159. package/src/be/session-source-backfill.ts +163 -0
  160. package/src/be/swarm-config-guard.ts +1 -0
  161. package/src/comb/review-batch.ts +28 -1
  162. package/src/comb/templates.ts +7 -2
  163. package/src/commands/claude-managed-setup.ts +7 -1
  164. package/src/commands/credential-refresh.ts +76 -1
  165. package/src/commands/credential-wait.ts +2 -2
  166. package/src/commands/onboard/compose-generator.ts +5 -3
  167. package/src/commands/provider-credentials.ts +212 -171
  168. package/src/commands/runner.ts +25 -4
  169. package/src/extensions/apply-task-create.ts +8 -1
  170. package/src/extensions/contract-types.generated.ts +1 -1
  171. package/src/extensions/contract.ts +5 -0
  172. package/src/heartbeat/heartbeat.ts +14 -1
  173. package/src/heartbeat/script-runner-probe.ts +47 -0
  174. package/src/http/all-routes.ts +2 -0
  175. package/src/http/api-keys.ts +77 -9
  176. package/src/http/assets.ts +15 -0
  177. package/src/http/codex-oauth-accounts.ts +191 -0
  178. package/src/http/comb.ts +15 -2
  179. package/src/http/config.ts +1 -1
  180. package/src/http/core.ts +6 -0
  181. package/src/http/fleet-benchmark.ts +5 -2
  182. package/src/http/idempotency-key.ts +1 -1
  183. package/src/http/index.ts +14 -3
  184. package/src/http/integrations.ts +5 -1
  185. package/src/http/kv.ts +8 -3
  186. package/src/http/onboarding.ts +6 -1
  187. package/src/http/pages-public.ts +38 -5
  188. package/src/http/pool-keys.ts +354 -0
  189. package/src/http/scripts.ts +27 -0
  190. package/src/http/sessions.ts +37 -12
  191. package/src/http/stats.ts +1 -0
  192. package/src/http/status.ts +26 -12
  193. package/src/http/tasks.ts +3 -0
  194. package/src/http/used-by.ts +7 -2
  195. package/src/http/workflows.ts +88 -9
  196. package/src/http/x.ts +5 -0
  197. package/src/integrations/chat-sdk/inbound.ts +27 -0
  198. package/src/kv-reserved-namespaces.ts +19 -0
  199. package/src/prompts/base-prompt.ts +21 -1
  200. package/src/prompts/session-templates.ts +17 -3
  201. package/src/providers/acp-targets.ts +1 -1
  202. package/src/providers/amp-adapter.ts +1 -1
  203. package/src/providers/claude-adapter.ts +5 -1
  204. package/src/providers/claude-managed-adapter.ts +16 -9
  205. package/src/providers/codex-adapter.ts +71 -11
  206. package/src/providers/dsh-adapter.ts +1 -1
  207. package/src/providers/pi-mono-adapter.ts +41 -11
  208. package/src/scheduler/scheduler.ts +5 -3
  209. package/src/scripts-runtime/eval-harness.ts +8 -1
  210. package/src/scripts-runtime/executors/native.ts +22 -16
  211. package/src/scripts-runtime/executors/quickjs-prelude.ts +103 -1
  212. package/src/scripts-runtime/executors/quickjs-runner.ts +2 -1
  213. package/src/scripts-runtime/executors/quickjs.ts +7 -0
  214. package/src/scripts-runtime/executors/registry.ts +32 -0
  215. package/src/scripts-runtime/executors/types.ts +7 -0
  216. package/src/scripts-runtime/host-validated-args.ts +133 -0
  217. package/src/scripts-runtime/loader.ts +30 -3
  218. package/src/scripts-runtime/swarm-sdk.ts +16 -3
  219. package/src/scripts-runtime/types/stdlib.d.ts +10 -2
  220. package/src/scripts-runtime/types/swarm-extension.d.ts +17 -8
  221. package/src/scripts-runtime/types/swarm-sdk.d.ts +10 -2
  222. package/src/slack/assistant.ts +7 -0
  223. package/src/slack/channel-labels.ts +74 -0
  224. package/src/slack/handlers.ts +8 -0
  225. package/src/slack/inbound-files.ts +5 -2
  226. package/src/slack/thread-buffer.ts +4 -0
  227. package/src/tasks/sibling-awareness.ts +12 -3
  228. package/src/telemetry-context.ts +5 -6
  229. package/src/telemetry-identity.ts +3 -3
  230. package/src/tests/api-key-status-by-suffix.test.ts +242 -0
  231. package/src/tests/api-keys-validation.test.ts +170 -0
  232. package/src/tests/base-prompt.test.ts +70 -2
  233. package/src/tests/claude-managed-adapter.test.ts +46 -0
  234. package/src/tests/claude-routes-e2e.test.ts +7 -2
  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/community-linear-delivery-loop.test.ts +2099 -0
  240. package/src/tests/config-pool-keys.test.ts +533 -0
  241. package/src/tests/credential-check.test.ts +294 -4
  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/docs-index.test.ts +135 -0
  248. package/src/tests/dsh-adapter.test.ts +1 -1
  249. package/src/tests/extensions-pre-task.test.ts +53 -1
  250. package/src/tests/fetch-resolved-env.test.ts +16 -2
  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/fleet-benchmark.test.ts +8 -0
  254. package/src/tests/get-workflow-run-tool.test.ts +98 -0
  255. package/src/tests/heartbeat-checklist.test.ts +89 -0
  256. package/src/tests/heartbeat-script-runner-probe.test.ts +83 -0
  257. package/src/tests/integrations-http.test.ts +32 -2
  258. package/src/tests/internal-ai/complete-structured.test.ts +37 -1
  259. package/src/tests/internal-ai/credentials.test.ts +23 -0
  260. package/src/tests/kv-http.test.ts +32 -0
  261. package/src/tests/kv-tool.test.ts +25 -0
  262. package/src/tests/multi-runtime-registration.test.ts +12 -12
  263. package/src/tests/oauth-refresh-sweep.test.ts +34 -1
  264. package/src/tests/onboard-compose.test.ts +20 -0
  265. package/src/tests/onboarding.test.ts +108 -1
  266. package/src/tests/openai-embedding-credentials.test.ts +35 -0
  267. package/src/tests/opencode-adapter.test.ts +2 -1
  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/pages-frame-ancestors.test.ts +162 -0
  271. package/src/tests/pi-codemode.test.ts +2 -1
  272. package/src/tests/pi-mono-adapter.test.ts +54 -0
  273. package/src/tests/pi-native-mcp.test.ts +13 -1
  274. package/src/tests/pi-tool-exposure.test.ts +8 -8
  275. package/src/tests/probe-tool-schema-tokens.test.ts +144 -0
  276. package/src/tests/prompt-template-session.test.ts +4 -3
  277. package/src/tests/provider-key-probes.test.ts +181 -0
  278. package/src/tests/rbac-e2e-helpers.test.ts +88 -1
  279. package/src/tests/rbac-e2e-helpers.ts +36 -11
  280. package/src/tests/request-composition-derive.test.ts +329 -0
  281. package/src/tests/script-connections.test.ts +4 -0
  282. package/src/tests/script-executor-conformance.test.ts +36 -1
  283. package/src/tests/script-executor-quickjs-pool.test.ts +50 -0
  284. package/src/tests/script-executor-quickjs.test.ts +140 -0
  285. package/src/tests/script-host-validated-args-native.test.ts +196 -0
  286. package/src/tests/script-per-script-executor.test.ts +415 -0
  287. package/src/tests/script-runs-retention.test.ts +212 -0
  288. package/src/tests/scripts-external-api.test.ts +45 -0
  289. package/src/tests/sdk-allowlist.test.ts +42 -2
  290. package/src/tests/self-improvement.test.ts +4 -4
  291. package/src/tests/session-source-backfill.test.ts +429 -0
  292. package/src/tests/session-source-keys.test.ts +236 -0
  293. package/src/tests/session-source-labels.test.ts +419 -0
  294. package/src/tests/sessions-detail-batch.test.ts +2 -0
  295. package/src/tests/sessions-move.test.ts +131 -0
  296. package/src/tests/sessions-tree.test.ts +299 -2
  297. package/src/tests/slack-inbound-files.test.ts +103 -23
  298. package/src/tests/status.test.ts +101 -18
  299. package/src/tests/store-progress-learning.test.ts +288 -0
  300. package/src/tests/tasks-workflow-run-filter.test.ts +146 -0
  301. package/src/tests/test-net.ts +31 -0
  302. package/src/tests/transcription.test.ts +8 -0
  303. package/src/tests/ui-logs-parser.test.ts +180 -0
  304. package/src/tests/used-by-route.test.ts +11 -0
  305. package/src/tests/workflow-cascade-cancel.test.ts +172 -0
  306. package/src/tests/workflow-engine-v2.test.ts +116 -1
  307. package/src/tests/workflow-http-v2.test.ts +131 -0
  308. package/src/tests/workflow-mcp-trigger-schema.test.ts +209 -0
  309. package/src/tests/workflow-run-finished-event.test.ts +263 -0
  310. package/src/tests/workflow-step-order.test.ts +92 -0
  311. package/src/tests/workflow-sub-workflow.test.ts +30 -0
  312. package/src/tests/workflow-swarm-script.test.ts +34 -0
  313. package/src/tools/get-tasks.ts +8 -0
  314. package/src/tools/kv/kv-read-auth.ts +4 -2
  315. package/src/tools/script-common.ts +1 -0
  316. package/src/tools/script-run.ts +7 -0
  317. package/src/tools/script-upsert.ts +7 -0
  318. package/src/tools/store-progress.ts +67 -1
  319. package/src/tools/task-action.ts +1 -1
  320. package/src/tools/workflows/get-workflow-run.ts +14 -4
  321. package/src/tools/workflows/trigger-workflow.ts +104 -10
  322. package/src/types.ts +36 -1
  323. package/src/utils/company-profile.ts +51 -1
  324. package/src/utils/credentials.ts +10 -4
  325. package/src/utils/internal-ai/complete-structured.ts +5 -0
  326. package/src/utils/internal-ai/credentials.ts +4 -3
  327. package/src/utils/provider-key-probes.ts +328 -0
  328. package/src/utils/transcription.ts +4 -1
  329. package/src/workflows/engine.ts +18 -16
  330. package/src/workflows/executors/sub-workflow.ts +8 -1
  331. package/src/workflows/executors/swarm-script.ts +5 -0
  332. package/src/workflows/foreach-driver.ts +5 -11
  333. package/src/workflows/node-timeout.ts +30 -0
  334. package/templates/ai-toolbox.manifest.json +2 -2
  335. package/templates/community/linear-delivery-loop/PLAYBOOK.md +323 -0
  336. package/templates/community/linear-delivery-loop/README.md +38 -0
  337. package/templates/community/linear-delivery-loop/config.example.json +31 -0
  338. package/templates/community/linear-delivery-loop/lead-prompt.md +34 -0
  339. package/templates/community/linear-delivery-loop/schedule.json +10 -0
  340. package/templates/community/linear-delivery-loop/scripts/linear-delivery-setup.ts +148 -0
  341. package/templates/community/linear-delivery-loop/scripts/linear-delivery-tick.ts +1781 -0
  342. package/templates/skills/researching/content.md +2 -0
  343. package/templates/skills/swarm-docs/config.json +14 -0
  344. package/templates/skills/swarm-docs/content.md +144 -0
  345. package/templates/skills/swarm-scripts/SKILL.md +1 -0
  346. package/templates/skills/swarm-scripts/content.md +1 -0
  347. package/dist/cli-7421jzsw.js +0 -84
  348. package/dist/cli-e45e4m6b.js +0 -413
  349. package/dist/cli-zw88zjba.js +0 -307
  350. package/dist/handlers-wy1dr6ja.js +0 -41
  351. package/dist/index-nmm79qgr.js +0 -43
  352. package/dist/lead-sawnyqxk.js +0 -79
  353. package/dist/render-v2-pjwxvnkw.js +0 -12
  354. package/dist/worker-v5e5rxj3.js +0 -79
@@ -50,6 +50,11 @@ export const getTasksInputSchema = z.object({
50
50
  .uuid()
51
51
  .optional()
52
52
  .describe("Filter by schedule ID to find tasks created by a specific schedule."),
53
+ workflowRunId: z
54
+ .string()
55
+ .uuid()
56
+ .optional()
57
+ .describe("Filter by workflow run ID to find the tasks of a single run."),
53
58
  key: AssetKeySchema.optional().describe("Filter by exact logical namespace."),
54
59
  keyPrefix: AssetKeySchema.optional().describe("Filter by namespace subtree."),
55
60
  includeHeartbeat: z
@@ -130,6 +135,7 @@ export async function getTasksHandler(
130
135
  tags,
131
136
  search,
132
137
  scheduleId,
138
+ workflowRunId,
133
139
  key,
134
140
  keyPrefix,
135
141
  includeHeartbeat,
@@ -151,6 +157,7 @@ export async function getTasksHandler(
151
157
  tags,
152
158
  search,
153
159
  scheduleId,
160
+ workflowRunId,
154
161
  key,
155
162
  keyPrefix,
156
163
  includeHeartbeat,
@@ -192,6 +199,7 @@ export async function getTasksHandler(
192
199
  if (tags?.length) filters.push(`tags=[${tags.join(", ")}]`);
193
200
  if (search) filters.push(`search='${search}'`);
194
201
  if (scheduleId) filters.push(`scheduleId='${scheduleId}'`);
202
+ if (workflowRunId) filters.push(`workflowRunId='${workflowRunId}'`);
195
203
  if (key) filters.push(`key='${key}'`);
196
204
  if (keyPrefix) filters.push(`keyPrefix='${keyPrefix}'`);
197
205
 
@@ -1,13 +1,15 @@
1
1
  import { mcpOverflowAuthError } from "@/kv-overflow";
2
+ import { privateNamespaceError } from "@/kv-reserved-namespaces";
2
3
 
3
4
  /**
4
5
  * Ordinary KV namespaces intentionally remain readable across agents. MCP
5
6
  * overflow is different: it contains tool-result business data and is private
6
- * to the authenticated agent whose finalizer created the spill.
7
+ * to the authenticated agent whose finalizer created the spill. Private
8
+ * reserved families (`session-labels`) are not readable here at all.
7
9
  */
8
10
  export function kvReadAuthError(
9
11
  namespace: string,
10
12
  info: { agentId: string | undefined },
11
13
  ): string | null {
12
- return mcpOverflowAuthError(namespace, info.agentId);
14
+ return privateNamespaceError(namespace) ?? mcpOverflowAuthError(namespace, info.agentId);
13
15
  }
@@ -16,6 +16,7 @@ export const SCRIPT_TRANSPORT_ERROR =
16
16
  export const scriptNameSchema = z.string().min(1).max(200);
17
17
  export const scriptScopeSchema = z.enum(["agent", "global"]);
18
18
  export const scriptFsModeSchema = z.enum(["none", "workspace-rw"]);
19
+ export const scriptExecutorSchema = z.enum(["native", "quickjs"]);
19
20
 
20
21
  export const scriptToolOutputSchema = swarmToolOutputSchema({
21
22
  status: z.number().optional(),
@@ -3,6 +3,7 @@ import * as z from "zod";
3
3
  import { createToolRegistrar } from "@/tools/utils";
4
4
  import {
5
5
  proxyScriptsApi,
6
+ scriptExecutorSchema,
6
7
  scriptFsModeSchema,
7
8
  scriptNameSchema,
8
9
  scriptScopeSchema,
@@ -66,6 +67,12 @@ export const registerScriptRunTool = (server: McpServer) => {
66
67
  fsMode: scriptFsModeSchema
67
68
  .default("none")
68
69
  .describe("Filesystem mode. v1 supports none only."),
70
+ executor: scriptExecutorSchema
71
+ .nullable()
72
+ .optional()
73
+ .describe(
74
+ 'Optional executor override ("native" or "quickjs"). When omitted, named scripts use their saved executor or SCRIPT_EXECUTOR.',
75
+ ),
69
76
  idempotencyKey: z
70
77
  .string()
71
78
  .max(200)
@@ -3,6 +3,7 @@ import * as z from "zod";
3
3
  import { createToolRegistrar } from "@/tools/utils";
4
4
  import {
5
5
  proxyScriptsApi,
6
+ scriptExecutorSchema,
6
7
  scriptFsModeSchema,
7
8
  scriptNameSchema,
8
9
  scriptScopeSchema,
@@ -33,6 +34,12 @@ export const registerScriptUpsertTool = (server: McpServer) => {
33
34
  fsMode: scriptFsModeSchema
34
35
  .default("none")
35
36
  .describe("Filesystem mode. v1 supports none only."),
37
+ executor: scriptExecutorSchema
38
+ .nullable()
39
+ .optional()
40
+ .describe(
41
+ 'Optional per-script executor override ("native" or "quickjs"). Null or omitted inherits SCRIPT_EXECUTOR.',
42
+ ),
36
43
  }),
37
44
  outputSchema: scriptToolOutputSchema,
38
45
  },
@@ -13,6 +13,11 @@ import {
13
13
  updateAgentStatusFromCapacity,
14
14
  updateTaskProgress,
15
15
  } from "@/be/db";
16
+ import {
17
+ LEARNING_MAX_TAGS,
18
+ storeTaskLearning,
19
+ type TaskLearningResult,
20
+ } from "@/be/memory/task-learning";
16
21
  import {
17
22
  CitationInputSchema,
18
23
  getTaskCitations,
@@ -114,8 +119,26 @@ export const storeProgressOutputSchema = swarmToolOutputSchema({
114
119
  .describe(
115
120
  "Present only when this progress text reads as blocked-waiting: milliseconds since the task's prior update. Drives the store-progress nudge toward defer-task.",
116
121
  ),
122
+ learning: z
123
+ .looseObject({
124
+ status: z.string().optional(),
125
+ key: z.string().optional(),
126
+ memoryId: z.string().optional(),
127
+ workstream: z.string().optional(),
128
+ error: z.string().optional(),
129
+ })
130
+ .optional()
131
+ .describe(
132
+ "Present when the call carried a learning: stored, replayed (same text already stored for this task), or failed with error.",
133
+ ),
117
134
  });
118
135
 
136
+ function learningSentence(result: TaskLearningResult): string {
137
+ if (result.status === "stored") return `Learning stored at ${result.key}.`;
138
+ if (result.status === "replayed") return `Learning already stored at ${result.key}; skipped.`;
139
+ return `Learning not stored: ${result.error}.`;
140
+ }
141
+
119
142
  export const registerStoreProgressTool = (server: McpServer) => {
120
143
  createToolRegistrar(server)(
121
144
  "store-progress",
@@ -165,6 +188,19 @@ export const registerStoreProgressTool = (server: McpServer) => {
165
188
  .describe(
166
189
  'Claim sources, upserted by index across calls. Use only for factual claims the reader cannot already see in the thread or task tree, 1-2 sources; none on delegation, routing, acks, or status replies. Reference each one in output with [citation:N], or set general: true for a source that backs the whole answer (it renders under "General sources"). ref per kind: task = task UUID; memory = memory UUID (optional quote must appear verbatim in it); github = owner/repo#N, owner/repo@<sha>, or a github.com pull, issues, or commit URL; slack = permalink or channel/ts; agent-fs = file path; page = page id; script-run = script run id; url = http(s) URL. The first completing call is refused, and the task stays in progress, if a marker has no entry, a citation fails validation, or a non-general citation is unreferenced; the response lists each problem. After one refusal, completion proceeds and those markers and sources are dropped from rendered output. At most 50 citations per call/task, refs up to 2048 characters, labels up to 200. Invalid or oversized batches are ignored; existing indices can still be updated at capacity.',
167
190
  ),
191
+ learning: z
192
+ .union([
193
+ z.string(),
194
+ z.object({
195
+ text: z.string(),
196
+ workstream: z.string().optional(),
197
+ tags: z.array(z.string()).optional(),
198
+ }),
199
+ ])
200
+ .optional()
201
+ .describe(
202
+ `A lesson to keep. Add one only when it would change how the next agent works: a gotcha, a dead end, a pattern that worked. Not a progress line. Stored as a swarm memory at /learnings/<workstream>/<slug>, tagged "learning" and tied to this task. The first line of text is the title. At most ~500 tokens and ${LEARNING_MAX_TAGS} kebab tags. workstream defaults to the task's repo name, else "general". Send it on or before the completing call: a call on a finished task stores nothing. A failed learning write is reported in the result and never blocks the progress update.`,
203
+ ),
168
204
  persistMemory: z
169
205
  .boolean()
170
206
  .optional()
@@ -193,6 +229,7 @@ export const registerStoreProgressTool = (server: McpServer) => {
193
229
  failureReason,
194
230
  attachments,
195
231
  citations,
232
+ learning,
196
233
  persistMemory,
197
234
  force,
198
235
  },
@@ -332,6 +369,7 @@ export const registerStoreProgressTool = (server: McpServer) => {
332
369
  }
333
370
  }
334
371
 
372
+ let learningWrite: Promise<TaskLearningResult> | undefined;
335
373
  const result = await getDbClient().transaction(async () => {
336
374
  const agent = await getAgentById(requestInfo.agentId ?? "");
337
375
 
@@ -435,6 +473,29 @@ export const registerStoreProgressTool = (server: McpServer) => {
435
473
  await upsertTaskCitations(taskId, citations);
436
474
  }
437
475
 
476
+ // A finished task takes no new writes, learnings included.
477
+ if (learning && isTerminal) {
478
+ learningWrite = Promise.resolve({
479
+ status: "failed",
480
+ error: "the task is already finished; use memory-store",
481
+ });
482
+ } else if (learning) {
483
+ // Post-commit, so a rolled-back progress write never leaves a learning
484
+ // behind, and a failed learning write never rolls back the progress.
485
+ learningWrite = new Promise((resolve) => {
486
+ getDbClient().afterCommit(() =>
487
+ storeTaskLearning({
488
+ agentId: agent.id,
489
+ isLead: agent.isLead === true,
490
+ task: existingTask,
491
+ learning,
492
+ }).then(resolve, (err) =>
493
+ resolve({ status: "failed", error: scrubSecrets(String(err)) }),
494
+ ),
495
+ );
496
+ });
497
+ }
498
+
438
499
  // Idempotency guard: short-circuit terminal-status writes (completed/failed)
439
500
  // BEFORE any side-effects fire (event emission, memory write, follow-up task,
440
501
  // business-use ensure). Without this, a multi-session race causes duplicate
@@ -627,7 +688,11 @@ export const registerStoreProgressTool = (server: McpServer) => {
627
688
  });
628
689
  }
629
690
 
630
- const { success, message } = result;
691
+ const learningResult = await learningWrite;
692
+ const { success } = result;
693
+ const message = learningResult
694
+ ? `${result.message} ${learningSentence(learningResult)}`
695
+ : result.message;
631
696
  const task = result.task
632
697
  ? {
633
698
  id: result.task.id,
@@ -646,6 +711,7 @@ export const registerStoreProgressTool = (server: McpServer) => {
646
711
  typeof result.blockedWaitingElapsedMs === "number"
647
712
  ? { blockedWaitingElapsedMs: result.blockedWaitingElapsedMs }
648
713
  : {}),
714
+ ...(learningResult ? { learning: learningResult } : {}),
649
715
  };
650
716
  const warnings =
651
717
  success && status === "completed"
@@ -68,7 +68,7 @@ export const taskActionInputSchema = z.object({
68
68
  // For 'create' action:
69
69
  task: z.string().min(1).optional().describe("Task description (required for 'create')."),
70
70
  key: AssetKeySchema.optional().describe(
71
- "Logical namespace for a created task. Defaults to a shared/task:<id>/ resource key.",
71
+ "Logical namespace for a created task. Default: the Slack or chat source key of the task (for example shared/slack/<channel>/thread:<ts>/), else shared/task:<id>/.",
72
72
  ),
73
73
  taskType: z.string().max(50).optional().describe("Task type (e.g., 'bug', 'feature')."),
74
74
  tags: z
@@ -1,6 +1,7 @@
1
1
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { z } from "zod";
3
3
  import { getWorkflowRun, getWorkflowRunStepsByRunId } from "@/be/db";
4
+ import { getWorkflowRunStepsSlimByRunId, getWorkflowRunSummary } from "@/be/db/workflow-runs";
4
5
  import { createToolRegistrar, swarmToolOutputSchema, toolErr, toolOk } from "@/tools/utils";
5
6
 
6
7
  const STEP_VALUE_CAP = 400;
@@ -15,7 +16,7 @@ function stepValuePreview(value: unknown): string | undefined {
15
16
  }
16
17
 
17
18
  function renderSteps(
18
- steps: Awaited<ReturnType<typeof getWorkflowRunStepsByRunId>>,
19
+ steps: Awaited<ReturnType<typeof getWorkflowRunStepsSlimByRunId>>,
19
20
  ): string | undefined {
20
21
  if (steps.length === 0) return undefined;
21
22
  return steps
@@ -46,20 +47,29 @@ export const registerGetWorkflowRunTool = (server: McpServer) => {
46
47
  description: "Get details of a workflow run by ID, including all steps and their statuses.",
47
48
  inputSchema: z.object({
48
49
  id: z.string().uuid().describe("Workflow run ID"),
50
+ fields: z
51
+ .enum(["full", "slim"])
52
+ .optional()
53
+ .describe(
54
+ "`slim` omits run.context and each step's input (a context snapshot per step, large in runs with loops). Outputs, errors and retry fields stay. Default: full.",
55
+ ),
49
56
  }),
50
57
  outputSchema: swarmToolOutputSchema({
51
58
  run: z.unknown().optional(),
52
59
  steps: z.array(z.unknown()).optional(),
53
60
  }),
54
61
  },
55
- async ({ id }) => {
62
+ async ({ id, fields }) => {
56
63
  try {
57
64
  // Display view: the scrubbed context and step outputs, never replay state.
58
- const run = await getWorkflowRun(id, "display");
65
+ const slim = fields === "slim";
66
+ const run = slim ? await getWorkflowRunSummary(id) : await getWorkflowRun(id, "display");
59
67
  if (!run) {
60
68
  return toolErr(`Workflow run not found: ${id}`, { data: { steps: [] } });
61
69
  }
62
- const steps = await getWorkflowRunStepsByRunId(id, "display");
70
+ const steps = slim
71
+ ? await getWorkflowRunStepsSlimByRunId(id)
72
+ : await getWorkflowRunStepsByRunId(id, "display");
63
73
  return toolOk(`Run ${id} status: ${run.status}.`, {
64
74
  details: renderSteps(steps),
65
75
  data: { run, steps },
@@ -2,10 +2,42 @@ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { z } from "zod";
3
3
  import { resolveTaskAuditUserId } from "@/be/audit-user";
4
4
  import { getWorkflow, getWorkflowRun } from "@/be/db";
5
+ import {
6
+ findAcceptedWorkflowOperation,
7
+ IdempotencyKeyTakenError,
8
+ recordAcceptedWorkflowOperation,
9
+ type WorkflowIdempotencyClaim,
10
+ workflowRequestFingerprint,
11
+ } from "@/be/db/workflow-idempotency";
12
+ import { IDEMPOTENCY_KEY_PATTERN } from "@/http/idempotency-key";
5
13
  import { createToolRegistrar, swarmToolOutputSchema, toolErr, toolOk } from "@/tools/utils";
6
14
  import { getExecutorRegistry, startWorkflowExecution } from "@/workflows";
7
15
  import { TriggerSchemaError } from "@/workflows/engine";
8
16
 
17
+ /**
18
+ * Answer from the run already accepted under this key, or null when there is
19
+ * none. Same record and rules as the HTTP trigger's `Idempotency-Key`.
20
+ */
21
+ async function replayAcceptedTrigger(claim: WorkflowIdempotencyClaim) {
22
+ const accepted = await findAcceptedWorkflowOperation(claim);
23
+ if (!accepted) return null;
24
+ if (accepted.fingerprint !== claim.fingerprint) {
25
+ return toolErr("idempotencyKey was already used to trigger this workflow with other data.", {
26
+ data: { idempotencyConflict: true },
27
+ });
28
+ }
29
+ if (accepted.resourceDeletedAt !== null) {
30
+ return toolErr(
31
+ "The workflow this idempotencyKey triggered was deleted; send a new key to start over.",
32
+ );
33
+ }
34
+ const { runId, skipped } = accepted.body as { runId: string; skipped: boolean };
35
+ return toolOk("Run already accepted under this idempotencyKey; nothing new was started.", {
36
+ details: `Replayed the run accepted under this idempotencyKey — run ID: ${runId}.`,
37
+ data: { runId, skipped, replayed: true },
38
+ });
39
+ }
40
+
9
41
  export const registerTriggerWorkflowTool = (server: McpServer) => {
10
42
  createToolRegistrar(server)(
11
43
  "trigger-workflow",
@@ -21,16 +53,55 @@ export const registerTriggerWorkflowTool = (server: McpServer) => {
21
53
  .record(z.string(), z.unknown())
22
54
  .optional()
23
55
  .describe("Optional data to pass as trigger context to the workflow"),
56
+ wait: z
57
+ .boolean()
58
+ .optional()
59
+ .describe(
60
+ "Default true: return after the run's instant steps ran, when it finishes or parks at its first async step. " +
61
+ "false: return as soon as the run is created and walk it in the background. Use false from a script or step that only needs the runId.",
62
+ ),
63
+ idempotencyKey: z
64
+ .string()
65
+ .regex(IDEMPOTENCY_KEY_PATTERN)
66
+ .optional()
67
+ .describe(
68
+ "Optional, 1-255 visible ASCII characters, scoped to the calling agent and this workflow. " +
69
+ "The first trigger accepted under a key starts one run; the key is recorded in the run's own transaction and never expires. " +
70
+ "A retry with the same key and triggerData starts nothing and returns that run (replayed: true), even if the earlier call was killed before it saw the answer. " +
71
+ "The same key with other triggerData is an error with idempotencyConflict: true.",
72
+ ),
24
73
  }),
25
74
  outputSchema: swarmToolOutputSchema({
26
75
  runId: z.string().optional(),
27
76
  skipped: z.boolean().optional(),
77
+ replayed: z.boolean().optional(),
78
+ idempotencyConflict: z.boolean().optional(),
28
79
  validationErrors: z.array(z.string()).optional(),
29
80
  triggerSchema: z.record(z.string(), z.unknown()).optional(),
30
81
  }),
31
82
  },
32
- async ({ id, triggerData }, requestInfo) => {
83
+ async ({ id, triggerData, wait, idempotencyKey }, requestInfo) => {
33
84
  try {
85
+ if (idempotencyKey !== undefined && !requestInfo.agentId) {
86
+ return toolErr("idempotencyKey needs an identified caller (X-Agent-ID).");
87
+ }
88
+ const claim: WorkflowIdempotencyClaim | undefined =
89
+ idempotencyKey === undefined
90
+ ? undefined
91
+ : {
92
+ principal: `agent:${requestInfo.agentId}`,
93
+ key: idempotencyKey,
94
+ operation: "workflow.trigger",
95
+ targetId: id,
96
+ fingerprint: workflowRequestFingerprint("workflow.trigger", triggerData ?? {}),
97
+ };
98
+ if (claim) {
99
+ // Before the enabled, cooldown and trigger-schema checks: an
100
+ // accepted trigger replays even after the workflow changed.
101
+ const replayed = await replayAcceptedTrigger(claim);
102
+ if (replayed) return replayed;
103
+ }
104
+
34
105
  const workflow = await getWorkflow(id);
35
106
  if (!workflow) {
36
107
  return toolErr(`Workflow not found: ${id}`);
@@ -38,21 +109,44 @@ export const registerTriggerWorkflowTool = (server: McpServer) => {
38
109
  if (!workflow.enabled) {
39
110
  return toolErr(`Workflow "${workflow.name}" is disabled.`);
40
111
  }
41
- const runId = await startWorkflowExecution(
42
- workflow,
43
- triggerData ?? {},
44
- getExecutorRegistry(),
45
- {
112
+ let accepted: { runId: string; skipped: boolean } | undefined;
113
+ let runId: string;
114
+ try {
115
+ runId = await startWorkflowExecution(workflow, triggerData ?? {}, getExecutorRegistry(), {
46
116
  triggerType: "manual",
47
117
  requestedByUserId:
48
118
  (await resolveTaskAuditUserId(requestInfo.sourceTaskId, requestInfo.agentId)) ??
49
119
  undefined,
50
- },
51
- );
120
+ detach: wait === false,
121
+ // Recorded in the run's own transaction: the key exists if and
122
+ // only if the run committed.
123
+ accept: claim
124
+ ? async (run) => {
125
+ const response = { runId: run.runId, skipped: run.skipped };
126
+ await recordAcceptedWorkflowOperation(claim, {
127
+ workflowId: workflow.id,
128
+ runId: run.runId,
129
+ status: 201,
130
+ body: response,
131
+ });
132
+ accepted = response;
133
+ }
134
+ : undefined,
135
+ });
136
+ } catch (err) {
137
+ // A concurrent call with this key committed its run first; this one
138
+ // created none.
139
+ if (claim && err instanceof IdempotencyKeyTakenError) {
140
+ const replayed = await replayAcceptedTrigger(claim);
141
+ if (replayed) return replayed;
142
+ }
143
+ throw err;
144
+ }
52
145
 
53
146
  // Check if the run was skipped due to cooldown
54
- const run = await getWorkflowRun(runId);
55
- const skipped = run?.status === "skipped";
147
+ const skipped = accepted
148
+ ? accepted.skipped
149
+ : (await getWorkflowRun(runId))?.status === "skipped";
56
150
 
57
151
  if (skipped) {
58
152
  return toolOk(`Workflow "${workflow.name}" skipped (cooldown).`, {
package/src/types.ts CHANGED
@@ -1341,12 +1341,27 @@ export const AgentSchema = z
1341
1341
  //
1342
1342
  // The cache-hit post-task path does NOT produce a new report; the row's
1343
1343
  // `reportedAt` deliberately stays at the last actual check.
1344
+ /** One pool member's live test. `suffix` is its last 5 chars, as in `api_key_status`. */
1345
+ export const AgentCredStatusKeyResultSchema = z
1346
+ .object({
1347
+ keyType: z.string(),
1348
+ index: z.number(),
1349
+ suffix: z.string(),
1350
+ ok: z.boolean(),
1351
+ error: z.string().optional(),
1352
+ httpStatus: z.number().optional(),
1353
+ latency_ms: z.number(),
1354
+ })
1355
+ .openapi("AgentCredStatusKeyResult");
1356
+
1344
1357
  export const AgentCredStatusLiveTestSchema = z
1345
1358
  .object({
1346
1359
  ok: z.boolean(),
1347
1360
  error: z.string().nullable().default(null),
1348
1361
  latency_ms: z.number(),
1349
1362
  testedAt: z.number(), // unix ms
1363
+ warning: z.string().optional(),
1364
+ keys: z.array(AgentCredStatusKeyResultSchema).optional(),
1350
1365
  })
1351
1366
  .openapi("AgentCredStatusLiveTest");
1352
1367
  export type AgentCredStatusLiveTest = z.infer<typeof AgentCredStatusLiveTestSchema>;
@@ -2267,7 +2282,14 @@ export const ExecutorMetaSchema = z
2267
2282
  inputCtx: z.record(z.string(), z.unknown()).optional(),
2268
2283
  })
2269
2284
  .openapi("ExecutorMeta");
2270
- export type ExecutorMeta = z.infer<typeof ExecutorMetaSchema>;
2285
+ export type ExecutorMeta = z.infer<typeof ExecutorMetaSchema> & {
2286
+ /**
2287
+ * Aborted when the step's watchdog budget runs out (`startStepDeadline`).
2288
+ * Runtime-only, never serialized. An executor with a long or one-shot side
2289
+ * effect should forward it so a timed-out step stops instead of finishing.
2290
+ */
2291
+ signal?: AbortSignal;
2292
+ };
2271
2293
 
2272
2294
  // --- Validation ---
2273
2295
 
@@ -3090,6 +3112,15 @@ export const WorkflowRunStepSchema = z
3090
3112
  .openapi("WorkflowRunStep");
3091
3113
  export type WorkflowRunStep = z.infer<typeof WorkflowRunStepSchema>;
3092
3114
 
3115
+ /**
3116
+ * Step of `GET /api/workflow-runs/{id}?fields=slim`: no `input`, the context
3117
+ * snapshot taken when the step started. Loops repeat it once per iteration.
3118
+ */
3119
+ export const WorkflowRunStepSlimSchema = WorkflowRunStepSchema.omit({ input: true }).openapi(
3120
+ "WorkflowRunStepSlim",
3121
+ );
3122
+ export type WorkflowRunStepSlim = z.infer<typeof WorkflowRunStepSlimSchema>;
3123
+
3093
3124
  // --- Wait State (workflow `wait` node side table) ---
3094
3125
 
3095
3126
  export const WaitModeSchema = z.enum(["time", "event"]);
@@ -3179,6 +3210,9 @@ export type ScriptScope = z.infer<typeof ScriptScopeSchema>;
3179
3210
  export const ScriptFsModeSchema = z.enum(["none", "workspace-rw"]);
3180
3211
  export type ScriptFsMode = z.infer<typeof ScriptFsModeSchema>;
3181
3212
 
3213
+ export const ScriptExecutorNameSchema = z.enum(["native", "quickjs"]);
3214
+ export type ScriptExecutorName = z.infer<typeof ScriptExecutorNameSchema>;
3215
+
3182
3216
  export const ScriptRecordSchema = z
3183
3217
  .object({
3184
3218
  id: z.string(),
@@ -3195,6 +3229,7 @@ export const ScriptRecordSchema = z
3195
3229
  isScratch: z.boolean(),
3196
3230
  typeChecked: z.boolean(),
3197
3231
  fsMode: ScriptFsModeSchema,
3232
+ executor: ScriptExecutorNameSchema.nullable().default(null),
3198
3233
  createdByAgentId: z.string().nullable(),
3199
3234
  createdAt: z.string(),
3200
3235
  updatedAt: z.string(),
@@ -1,6 +1,19 @@
1
+ import type { EventContext } from "../telemetry-contract";
2
+
1
3
  export const COMPANY_TEAM_SIZES = ["1", "2-10", "11-50", "51+"] as const;
2
4
 
3
- export const COMPANY_USE_CASES = ["features", "bugs", "review", "ops", "docs", "data"] as const;
5
+ export const COMPANY_USE_CASES = [
6
+ "features",
7
+ "bugs",
8
+ "review",
9
+ "ops",
10
+ "docs",
11
+ "data",
12
+ "content",
13
+ "research",
14
+ "seo",
15
+ "admin",
16
+ ] as const;
4
17
 
5
18
  export const COMPANY_ACQUISITION_SOURCES = [
6
19
  "search",
@@ -19,6 +32,43 @@ export const USER_FUNCTIONS = [
19
32
  "eng_lead",
20
33
  "founder",
21
34
  "product",
35
+ "marketing",
36
+ "sales",
37
+ "research",
22
38
  "ops",
23
39
  "other",
24
40
  ] as const;
41
+
42
+ // The values the vendored telemetry catalog (`src/telemetry-contract`) lists.
43
+ // The proxy rejects a whole event, feedback submission or benchmark request
44
+ // for one value it does not list. So a newer value stays out of telemetry, as
45
+ // if unset, until the proxy catalog adds it and `sync:telemetry-contract` runs.
46
+ const REPORTED_USE_CASES = [
47
+ "features",
48
+ "bugs",
49
+ "review",
50
+ "ops",
51
+ "docs",
52
+ "data",
53
+ ] as const satisfies readonly NonNullable<EventContext["use_case"]>[];
54
+
55
+ const REPORTED_USER_FUNCTIONS = [
56
+ "engineer",
57
+ "eng_lead",
58
+ "founder",
59
+ "product",
60
+ "ops",
61
+ "other",
62
+ ] as const satisfies readonly NonNullable<EventContext["user_function"]>[];
63
+
64
+ /** `value` when telemetry may report it as a use case, else undefined. */
65
+ export function reportedUseCase(value: unknown): (typeof REPORTED_USE_CASES)[number] | undefined {
66
+ return REPORTED_USE_CASES.find((reported) => reported === value);
67
+ }
68
+
69
+ /** `value` when telemetry may report it as a job function, else undefined. */
70
+ export function reportedUserFunction(
71
+ value: unknown,
72
+ ): (typeof REPORTED_USER_FUNCTIONS)[number] | undefined {
73
+ return REPORTED_USER_FUNCTIONS.find((reported) => reported === value);
74
+ }
@@ -380,6 +380,7 @@ export function validateOpencodeCredentials(
380
380
  /** Per-pool availability, including the model-scoped block breakdown when a model was requested. */
381
381
  export interface AvailabilityInfo {
382
382
  availableIndices: number[];
383
+ rejectedIndices?: number[];
383
384
  modelBlockedIndices?: number[];
384
385
  earliestModelResetAt?: string | null;
385
386
  /** Indices excluded because the key's subscription plan cannot run the model. */
@@ -405,28 +406,33 @@ async function fetchAvailableIndices(
405
406
  for (const envVar of poolVars) {
406
407
  const val = env[envVar];
407
408
  if (val) {
408
- const totalKeys = val.includes(",") ? val.split(",").filter((s) => s.trim()).length : 1;
409
+ // Status is matched by key suffix, so a key at the same index in another
410
+ // agent's or repo's pool never blocks one of ours (issue #2425).
411
+ const keySuffixes = splitCredentialPool(val).map((key) => key.slice(-5));
412
+ const totalKeys = keySuffixes.length;
409
413
  try {
410
414
  const resp = await fetch(
411
- `${apiUrl}/api/keys/available?keyType=${encodeURIComponent(envVar)}&totalKeys=${totalKeys}${modelParam}`,
415
+ `${apiUrl}/api/keys/available?keyType=${encodeURIComponent(envVar)}&totalKeys=${totalKeys}${modelParam}&keySuffixes=${encodeURIComponent(keySuffixes.join(","))}`,
412
416
  { headers: { Authorization: `Bearer ${apiKey}` } },
413
417
  );
414
418
  if (resp.ok) {
415
419
  const data = (await resp.json()) as {
416
420
  availableIndices: number[];
421
+ rejectedIndices?: number[];
417
422
  modelBlockedIndices?: number[];
418
423
  earliestModelResetAt?: string | null;
419
424
  seatBlockedIndices?: number[];
420
425
  };
421
426
  availableIndicesMap[envVar] = {
422
427
  availableIndices: data.availableIndices,
428
+ rejectedIndices: data.rejectedIndices,
423
429
  modelBlockedIndices: data.modelBlockedIndices,
424
430
  earliestModelResetAt: data.earliestModelResetAt,
425
431
  seatBlockedIndices: data.seatBlockedIndices,
426
432
  };
427
433
  if (data.availableIndices.length < totalKeys) {
428
434
  console.log(
429
- `[credentials] ${envVar}: ${data.availableIndices.length}/${totalKeys} keys available (${totalKeys - data.availableIndices.length} rate-limited)`,
435
+ `[credentials] ${envVar}: ${data.availableIndices.length}/${totalKeys} keys available (${totalKeys - data.availableIndices.length} unavailable)`,
430
436
  );
431
437
  }
432
438
  }
@@ -558,7 +564,7 @@ export async function resolveCredentialPools(
558
564
  env[envVar] = result.selected;
559
565
  const availInfo = available ? ` (${available.length} available of ${result.total})` : "";
560
566
  console.log(
561
- `[credentials] Selected ${envVar} credential ${result.index + 1}/${result.total}${availInfo} [...${result.keySuffix}]`,
567
+ `[credentials] Selected ${envVar} credential ${result.index + 1}/${result.total}${availInfo} [...${result.keySuffix}]${info?.rejectedIndices?.length ? ` (${info.rejectedIndices.length} rejected)` : ""}`,
562
568
  );
563
569
  selections.push({
564
570
  ...result,
@@ -20,6 +20,7 @@ import { complete } from "@earendil-works/pi-ai/compat";
20
20
  import { getBuiltinModel as getModel } from "@earendil-works/pi-ai/providers/all";
21
21
  import type { TSchema } from "typebox";
22
22
  import { z } from "zod";
23
+ import { selectCredential } from "../credentials.js";
23
24
  import {
24
25
  DEFAULT_OPENROUTER_BASE_URL,
25
26
  getOpenRouterAttributionHeaders,
@@ -134,6 +135,10 @@ export async function defaultSpawnClaudeCli(
134
135
  if (!env.CLAUDE_CODE_OAUTH_TOKEN && env.AGENT_SWARM_CLAUDE_OAUTH_TOKEN) {
135
136
  env.CLAUDE_CODE_OAUTH_TOKEN = env.AGENT_SWARM_CLAUDE_OAUTH_TOKEN;
136
137
  }
138
+ // API calls and restarted workers can inherit raw pools.
139
+ for (const keyVar of ["CLAUDE_CODE_OAUTH_TOKEN", "ANTHROPIC_API_KEY"]) {
140
+ if (env[keyVar]) env[keyVar] = selectCredential(env[keyVar], undefined, keyVar).selected;
141
+ }
137
142
  // Recursion guard: this shellout is itself a full claude session — on exit it
138
143
  // fires the same global Stop hook, whose session-summary path would spawn
139
144
  // another `claude -p`, recursively (each level holds a ~0.5-1GB node process;