@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
@@ -30,6 +30,13 @@
30
30
 
31
31
  import { existsSync as nodeExistsSync } from "node:fs";
32
32
  import { join } from "node:path";
33
+ import {
34
+ CODEX_GATEWAY_API_KEY,
35
+ deriveDefaultRoute,
36
+ type ModelRoute,
37
+ routeCredentialStatus,
38
+ routeUnsetEnv,
39
+ } from "@desplega/model-routing";
33
40
  import { buildRatingsFromLlm, fetchRetrievalsForTask, postRatings } from "../be/memory/raters/llm";
34
41
  import { contextBoundNoteThreshold, createContextBoundNote } from "../hooks/context-bound-note";
35
42
  import { getApiKey } from "../utils/api-key";
@@ -38,6 +45,7 @@ import {
38
45
  clampContextPercent,
39
46
  computeContextUsedUnified,
40
47
  } from "../utils/context-window";
48
+ import { selectCredential } from "../utils/credentials";
41
49
  import { isEnvFlagEnabled, isSessionSummarySkipped } from "../utils/env-flag";
42
50
  import { SessionErrorTracker } from "../utils/error-tracker";
43
51
  import { summarizeSession as runSummarize } from "../utils/internal-ai";
@@ -82,6 +90,32 @@ import type {
82
90
 
83
91
  type CodexConfig = Record<string, CodexConfigValue | undefined>;
84
92
 
93
+ /** `model_provider` id of the generated gateway block. */
94
+ const CODEX_GATEWAY_PROVIDER_ID = "swarm-gateway";
95
+
96
+ /**
97
+ * Config overrides that send Codex through the gateway route. Codex 0.164
98
+ * ignores `OPENAI_BASE_URL` and env keys for its built-in `openai` provider, so
99
+ * the gateway needs its own provider block. Verified on 0.164 against a local
100
+ * gateway: Codex sends `Authorization: Bearer $CODEX_GATEWAY_API_KEY` to
101
+ * `<base_url>/responses` and never the auth.json OAuth token. `wire_api`
102
+ * defaults to `responses` (`chat` is rejected); it is set to state intent.
103
+ */
104
+ export function codexGatewayConfig(route: ModelRoute): CodexConfig {
105
+ return {
106
+ model_provider: CODEX_GATEWAY_PROVIDER_ID,
107
+ model_providers: {
108
+ [CODEX_GATEWAY_PROVIDER_ID]: {
109
+ name: "Swarm gateway",
110
+ base_url: route.baseUrl,
111
+ env_key: CODEX_GATEWAY_API_KEY,
112
+ wire_api: "responses",
113
+ requires_openai_auth: false,
114
+ },
115
+ },
116
+ };
117
+ }
118
+
85
119
  /** Plugin id the worker image installs (`codex plugin add context-mode@context-mode`). */
86
120
  const CONTEXT_MODE_CODEX_PLUGIN = "context-mode@context-mode";
87
121
 
@@ -187,7 +221,10 @@ export type CodexThreadFactory = (options: {
187
221
  }) => Promise<CodexThread> | CodexThread;
188
222
 
189
223
  /**
190
- * Codex satisfies its credential requirement by ANY of:
224
+ * A gateway route (`CODEX_GATEWAY_BASE_URL` + `CODEX_GATEWAY_API_KEY`) wins
225
+ * and alone decides readiness: a gateway URL without its key is not ready,
226
+ * whatever OAuth or auth.json is present. Otherwise Codex satisfies its
227
+ * credential requirement by ANY of:
191
228
  * 1. `~/.codex/auth.json` already exists on disk (the canonical state once
192
229
  * `codex login` has run).
193
230
  * 2. `OPENAI_API_KEY` is set — the entrypoint will run
@@ -204,6 +241,13 @@ export function checkCodexCredentials(
204
241
  env: Record<string, string | undefined>,
205
242
  opts: CredCheckOptions = {},
206
243
  ): CredStatus {
244
+ const gateway = deriveDefaultRoute("codex", env);
245
+ if (gateway) {
246
+ const status = routeCredentialStatus(gateway, env);
247
+ return status.ready
248
+ ? { ready: true, missing: [], satisfiedBy: "env", hint: status.hint }
249
+ : { ready: false, missing: status.missing, hint: status.hint };
250
+ }
207
251
  const homeDir = opts.homeDir ?? env.HOME ?? "/root";
208
252
  const existsSync = opts.fs?.existsSync ?? nodeExistsSync;
209
253
  const authFile = `${homeDir}/.codex/auth.json`;
@@ -1965,14 +2009,21 @@ export async function createInProcessCodexSession(
1965
2009
  preSessionEvents.push(event);
1966
2010
  };
1967
2011
 
1968
- const mergedConfig = await buildCodexConfig(config, resolvedModel, bufferedEmit);
2012
+ const routeEnv = config.env ?? process.env;
2013
+ const gateway = deriveDefaultRoute("codex", routeEnv);
2014
+ const mergedConfig = {
2015
+ ...(await buildCodexConfig(config, resolvedModel, bufferedEmit)),
2016
+ ...(gateway ? codexGatewayConfig(gateway) : {}),
2017
+ };
1969
2018
 
1970
2019
  // Auth resolution. `codex_oauth` (in the swarm config store) wins over
1971
2020
  // `OPENAI_API_KEY` so users can keep an OpenAI key set for embeddings
1972
2021
  // without it shadowing their ChatGPT login. The entrypoint already runs
1973
2022
  // this same precedence at boot — this block handles local dev (where
1974
2023
  // the entrypoint didn't run) and any case where auth.json is stale.
1975
- const authMode = await resolveCodexAuthMode(config, bufferedEmit);
2024
+ // A gateway route skips it: no OAuth is restored for a session whose traffic
2025
+ // goes to the gateway.
2026
+ const authMode = gateway ? null : await resolveCodexAuthMode(config, bufferedEmit);
1976
2027
 
1977
2028
  // The app-server receives an explicit environment. Construct a
1978
2029
  // minimal env explicitly so the spawned Codex CLI can find its binary
@@ -1999,7 +2050,14 @@ export async function createInProcessCodexSession(
1999
2050
  ...buildOtelTraceparentEnv(config.env ?? process.env),
2000
2051
  SWARM_CODEX_APP_SERVER: "1",
2001
2052
  };
2002
- if (authMode === "chatgpt") delete env.OPENAI_API_KEY;
2053
+ if (gateway) {
2054
+ for (const key of routeUnsetEnv("codex", gateway)) delete env[key];
2055
+ const gatewayKey = routeEnv[CODEX_GATEWAY_API_KEY];
2056
+ if (gatewayKey) env[CODEX_GATEWAY_API_KEY] = gatewayKey;
2057
+ } else if (authMode === "chatgpt") delete env.OPENAI_API_KEY;
2058
+ else if (env.OPENAI_API_KEY) {
2059
+ env.OPENAI_API_KEY = selectCredential(env.OPENAI_API_KEY, undefined, "OPENAI_API_KEY").selected;
2060
+ }
2003
2061
 
2004
2062
  const codexPathOverride = process.env.CODEX_PATH_OVERRIDE;
2005
2063
 
@@ -2151,6 +2209,11 @@ class CodexSubprocessSession implements ProviderSession {
2151
2209
  };
2152
2210
 
2153
2211
  const apiKey = getApiKey();
2212
+ const providerKeys: Record<string, string> = {};
2213
+ for (const keyVar of ["OPENAI_API_KEY", "OPENROUTER_API_KEY", "ANTHROPIC_API_KEY"]) {
2214
+ const value = config.env?.[keyVar] ?? process.env[keyVar];
2215
+ if (value) providerKeys[keyVar] = selectCredential(value, undefined, keyVar).selected;
2216
+ }
2154
2217
 
2155
2218
  this.proc = registerProcessGroup(
2156
2219
  Bun.spawn(argv, {
@@ -2168,13 +2231,7 @@ class CodexSubprocessSession implements ProviderSession {
2168
2231
  ...(process.env.MCP_BASE_URL ? { MCP_BASE_URL: process.env.MCP_BASE_URL } : {}),
2169
2232
  ...(apiKey ? { AGENT_SWARM_API_KEY: apiKey, API_KEY: apiKey } : {}),
2170
2233
  // Embedding / summarization paths read these:
2171
- ...(process.env.OPENAI_API_KEY ? { OPENAI_API_KEY: process.env.OPENAI_API_KEY } : {}),
2172
- ...(process.env.OPENROUTER_API_KEY
2173
- ? { OPENROUTER_API_KEY: process.env.OPENROUTER_API_KEY }
2174
- : {}),
2175
- ...(process.env.ANTHROPIC_API_KEY
2176
- ? { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY }
2177
- : {}),
2234
+ ...providerKeys,
2178
2235
  ...(process.env.CODEX_PATH_OVERRIDE
2179
2236
  ? { CODEX_PATH_OVERRIDE: process.env.CODEX_PATH_OVERRIDE }
2180
2237
  : {}),
@@ -2475,6 +2532,9 @@ export class CodexAdapter implements ProviderAdapter {
2475
2532
  }
2476
2533
 
2477
2534
  async createSession(config: ProviderSessionConfig): Promise<ProviderSession> {
2535
+ if (deriveDefaultRoute("codex", config.env ?? process.env)) {
2536
+ console.log("\x1b[2m[codex]\x1b[0m Using credential: gateway");
2537
+ }
2478
2538
  if (this.bypassSubprocess) {
2479
2539
  return createInProcessCodexSession(config, {
2480
2540
  skillsDir: this.skillsDir,
@@ -37,7 +37,7 @@ import type {
37
37
  } from "./types";
38
38
 
39
39
  // npm's `latest` is older and lacks the JSON/stdin contract used here.
40
- export const DSH_PACKAGE = "@deepseek-ai/dsh@0.2.1-rc.3";
40
+ export const DSH_PACKAGE = "@deepseek-ai/dsh@0.2.1-rc.4";
41
41
 
42
42
  export function checkDshCredentials(env: Record<string, string | undefined>): CredStatus {
43
43
  return env.DEEPSEEK_API_KEY?.trim() || env.OPENROUTER_API_KEY?.trim()
@@ -382,14 +382,15 @@ type ToolStructuredContent = NonNullable<AgentToolResult["structuredContent"]>;
382
382
  export const SWARM_TOOL_NAMESPACE = { name: "agent-swarm" } as const;
383
383
 
384
384
  /**
385
- * `PI_TOOL_DEFERRAL`: hide non-core swarm tools behind pi's `tool_search`.
386
- * Off by default until a pilot measures the prompt-cache cost of mid-session
387
- * tool-set changes. Read from `process.env` by both the adapter traits (which
388
- * pick the prompt's tool-discovery line) and `createSession`, so the prompt
389
- * and the session always agree.
385
+ * `PI_TOOL_DEFERRAL`: hide non-core swarm tools, and installed MCP server
386
+ * tools, behind pi's `tool_search`. On by default: the deferred schemas cost
387
+ * more on every request than the one partial cache miss a load causes.
388
+ * `false` declares every tool up front. Read from `process.env` by both the
389
+ * adapter traits (which pick the prompt's tool-discovery line) and
390
+ * `createSession`, so the prompt and the session always agree.
390
391
  */
391
392
  export function isPiToolDeferralEnabled(env: NodeJS.ProcessEnv = process.env): boolean {
392
- return parseEnvFlag(env.PI_TOOL_DEFERRAL, false);
393
+ return parseEnvFlag(env.PI_TOOL_DEFERRAL, true);
393
394
  }
394
395
 
395
396
  /**
@@ -919,6 +920,28 @@ function requestContextTokens(message: unknown): number | undefined {
919
920
  return usage.input + cacheRead + cacheWrite;
920
921
  }
921
922
 
923
+ /**
924
+ * The session-log line for one LLM call's usage (token counts only, never
925
+ * content). The dashboard reads it as that call's cache-hit chip.
926
+ */
927
+ function callUsageLine(message: unknown): string | undefined {
928
+ const usage = (message as { usage?: Record<string, unknown> } | null)?.usage;
929
+ if (typeof usage?.input !== "number") return undefined;
930
+ const count = (value: unknown) =>
931
+ typeof value === "number" && Number.isFinite(value) ? value : 0;
932
+ return JSON.stringify({
933
+ type: "call_usage",
934
+ harness: "pi",
935
+ usage: {
936
+ input: count(usage.input),
937
+ cacheRead: count(usage.cacheRead),
938
+ cacheWrite: count(usage.cacheWrite),
939
+ output: count(usage.output),
940
+ reasoning: count(usage.reasoning),
941
+ },
942
+ });
943
+ }
944
+
922
945
  function describeAssistantTurn(message: unknown): AssistantTurnShape {
923
946
  const msg = (message ?? {}) as {
924
947
  content?: unknown;
@@ -1116,6 +1139,10 @@ export class PiMonoSession implements ProviderSession {
1116
1139
  if (endMsg.role === "assistant") {
1117
1140
  const turn = describeAssistantTurn(event.message);
1118
1141
  this.lastAssistantTurn = turn;
1142
+ // Ahead of the turn's text and tool lines: the chip rides on the
1143
+ // call's first row.
1144
+ const usageLine = callUsageLine(event.message);
1145
+ if (usageLine) this.emit({ type: "raw_log", content: usageLine });
1119
1146
  if (endMsg.stopReason === "error") {
1120
1147
  // Candidate terminal failure. May still be cleared by a successful
1121
1148
  // retry (auto_retry_end success / a later good message_end).
@@ -1684,12 +1711,15 @@ function escapeValues(values: unknown): Record<string, string> {
1684
1711
  /**
1685
1712
  * Map installed servers (`fetchInstalledMcpServers(..., "claude")` entries)
1686
1713
  * to pi MCP configs. pi speaks stdio and streamable HTTP; `sse` entries map to
1687
- * HTTP, which is how the previous pi client already reached them. Tools stay
1688
- * `direct`, as before: the prompt lists these servers as in the tool list.
1714
+ * HTTP, which is how the previous pi client already reached them. With
1715
+ * `deferred`, their tools load through `tool_search` like non-core swarm
1716
+ * tools; otherwise they are declared up front.
1689
1717
  */
1690
1718
  export function toPiMcpServers(
1691
1719
  installed: Record<string, Record<string, unknown>> | null,
1720
+ options: { deferred?: boolean } = {},
1692
1721
  ): Record<string, McpServerConfig> {
1722
+ const exposure = options.deferred ? "deferred" : "direct";
1693
1723
  const servers: Record<string, McpServerConfig> = {};
1694
1724
  for (const [name, entry] of Object.entries(installed ?? {})) {
1695
1725
  if (typeof entry.command === "string") {
@@ -1698,14 +1728,14 @@ export function toPiMcpServers(
1698
1728
  command: entry.command,
1699
1729
  args: Array.isArray(entry.args) ? entry.args.map(String) : [],
1700
1730
  env: escapeValues(entry.env),
1701
- exposure: "direct",
1731
+ exposure,
1702
1732
  };
1703
1733
  } else if (typeof entry.url === "string") {
1704
1734
  servers[name] = {
1705
1735
  type: "http",
1706
1736
  url: entry.url,
1707
1737
  headers: escapeValues(entry.headers),
1708
- exposure: "direct",
1738
+ exposure,
1709
1739
  };
1710
1740
  }
1711
1741
  }
@@ -1860,7 +1890,7 @@ export class PiMonoAdapter implements ProviderAdapter {
1860
1890
  config.apiUrl && config.apiKey && config.agentId
1861
1891
  ? await fetchInstalledMcpServers(config.apiUrl, config.apiKey, config.agentId, "claude")
1862
1892
  : null;
1863
- const piMcpServers = toPiMcpServers(installedMcpServers);
1893
+ const piMcpServers = toPiMcpServers(installedMcpServers, { deferred: deferTools });
1864
1894
  if (Object.keys(piMcpServers).length > 0) {
1865
1895
  console.log(
1866
1896
  `\x1b[2m[${config.role}]\x1b[0m Registering ${Object.keys(piMcpServers).length} installed MCP server(s) with pi`,
@@ -75,7 +75,7 @@ function resolveExecutorRegistry(): ExecutorRegistry | null {
75
75
  * scripts-runtime — no agent/LLM in the loop. Reuses the same `runScriptWithRunCredential()`
76
76
  * path as the `swarm-script` workflow executor.
77
77
  */
78
- async function executeScheduleScript(schedule: ScheduledTask): Promise<void> {
78
+ export async function executeScheduleScript(schedule: ScheduledTask): Promise<void> {
79
79
  if (!schedule.scriptName) {
80
80
  throw new Error(`Schedule "${schedule.name}" has no scriptName (targetType=script)`);
81
81
  }
@@ -91,8 +91,9 @@ async function executeScheduleScript(schedule: ScheduledTask): Promise<void> {
91
91
  agentId,
92
92
  scriptName: schedule.scriptName,
93
93
  // A pointer, not a copy: script schedules fire as often as every minute and
94
- // script_runs has no retention sweep, so copying the source on every fire
95
- // would grow the table by tens of MB a day. script_versions keeps the text.
94
+ // script_runs retention (SCRIPT_RUNS_RETENTION_DAYS) is off by default, so
95
+ // copying the source on every fire would grow the table by tens of MB a
96
+ // day. script_versions keeps the text.
96
97
  source: `// Scheduled run of '${script.name}' v${script.version} (global, contentHash ${script.contentHash}) by schedule '${schedule.name}'.\n// Full source: script_versions row for this version.\n`,
97
98
  args,
98
99
  startedAt: new Date().toISOString(),
@@ -105,6 +106,7 @@ async function executeScheduleScript(schedule: ScheduledTask): Promise<void> {
105
106
  source: script.source,
106
107
  args,
107
108
  fsMode: "none",
109
+ executor: script.executor,
108
110
  agentId,
109
111
  egressSecrets: credentials.egressSecrets,
110
112
  userConfig: credentials.userConfig,
@@ -1,6 +1,7 @@
1
1
  import { buildCtx } from "./ctx";
2
2
  import { patchFetchWithEgressSubstitution } from "./egress-secrets";
3
3
  import type { SwarmConfigPayload } from "./executors/types";
4
+ import { isHostValidationEquivalent } from "./host-validated-args";
4
5
  import { ctxSignatureHintFor } from "./script-error-hints";
5
6
  import { SwarmConfig } from "./swarm-config";
6
7
 
@@ -131,8 +132,14 @@ try {
131
132
  );
132
133
  }
133
134
 
135
+ const hostValidatedArgs = process.env.SWARM_SCRIPT_HOST_VALIDATED_ARGS === "1";
134
136
  let validatedArgs = parsedArgs;
135
- if (mod.argsSchema && typeof mod.argsSchema === "object" && "parse" in mod.argsSchema) {
137
+ if (
138
+ mod.argsSchema &&
139
+ typeof mod.argsSchema === "object" &&
140
+ "parse" in mod.argsSchema &&
141
+ !(hostValidatedArgs && isHostValidationEquivalent(mod.argsSchema, parsedArgs))
142
+ ) {
136
143
  try {
137
144
  // biome-ignore lint/suspicious/noExplicitAny: argsSchema is a Zod schema at runtime
138
145
  validatedArgs = (mod.argsSchema as any).parse(parsedArgs);
@@ -173,23 +173,21 @@ export class NativeScriptExecutor implements ScriptExecutor {
173
173
  let removeAbortListener: (() => void) | undefined;
174
174
  let proc: Bun.Subprocess<"pipe", "pipe", "pipe"> | undefined;
175
175
 
176
- try {
177
- if (killed) {
178
- return {
179
- result: undefined,
180
- stdout: "",
181
- stderr: "",
182
- truncated: { stdout: false, stderr: false },
183
- durationMs: Date.now() - start,
184
- exitCode: 1,
185
- error: "killed",
186
- };
187
- }
176
+ const killedOutput = (): ExecutorOutput => ({
177
+ result: undefined,
178
+ stdout: "",
179
+ stderr: "",
180
+ truncated: { stdout: false, stderr: false },
181
+ durationMs: Date.now() - start,
182
+ exitCode: 1,
183
+ error: "killed",
184
+ });
188
185
 
189
- await Bun.write(argsFile, JSON.stringify(input.args ?? null));
190
- await Bun.write(sourceFile, input.source);
191
- await writeBareImportShims(tmpdir);
186
+ try {
187
+ if (killed) return killedOutput();
192
188
 
189
+ // Listen before the first await: an abort during the file writes below
190
+ // must stop the spawn, not land between the writes and the listener.
193
191
  const onExternalAbort = () => {
194
192
  killed = true;
195
193
  if (proc) void terminateProcessGroup(proc.pid);
@@ -197,12 +195,19 @@ export class NativeScriptExecutor implements ScriptExecutor {
197
195
  input.signal?.addEventListener("abort", onExternalAbort, { once: true });
198
196
  removeAbortListener = () => input.signal?.removeEventListener("abort", onExternalAbort);
199
197
 
198
+ await Bun.write(argsFile, JSON.stringify(input.args ?? null));
199
+ await Bun.write(sourceFile, input.source);
200
+ await writeBareImportShims(tmpdir);
201
+
202
+ // No await between this check and the spawn below.
203
+ if (killed) return killedOutput();
204
+
200
205
  const timeout = setTimeout(() => {
201
206
  timedOut = true;
202
207
  if (proc) void terminateProcessGroup(proc.pid);
203
208
  }, input.resources.wallClockMs);
204
209
 
205
- const harnessEnv = {
210
+ const harnessEnv: Record<string, string> = {
206
211
  PATH: process.env.PATH ?? "/usr/bin:/bin",
207
212
  HOME: process.env.HOME ?? "/tmp",
208
213
  LANG: process.env.LANG ?? "C.UTF-8",
@@ -214,6 +219,7 @@ export class NativeScriptExecutor implements ScriptExecutor {
214
219
  SWARM_SCRIPT_RESULT_FILE: resultFile,
215
220
  SWARM_SCRIPT_ERROR_FILE: errorFile,
216
221
  SWARM_SCRIPT_STARTED_FILE: startedFile,
222
+ ...(input.hostValidatedArgs ? { SWARM_SCRIPT_HOST_VALIDATED_ARGS: "1" } : {}),
217
223
  };
218
224
 
219
225
  proc = registerProcessGroup(
@@ -308,14 +308,116 @@ define("__swarm_ctx", ctx);
308
308
  * zod `argsSchema` check, the same contract as `eval-harness.ts`.
309
309
  */
310
310
  export const SANDBOX_EPILOGUE = String.raw`(async () => {
311
+ const isStructuralSchemaNode = (schema) => {
312
+ if (!schema || typeof schema !== "object") return false;
313
+ const def = schema._zod && schema._zod.def;
314
+ if (!def || typeof def !== "object") return false;
315
+ if (def.coerce) return false;
316
+ if (Array.isArray(def.checks) && def.checks.length > 0) return false;
317
+ switch (def.type) {
318
+ case "string":
319
+ case "number":
320
+ return !def.format;
321
+ case "boolean":
322
+ case "literal":
323
+ case "enum":
324
+ return true;
325
+ case "optional":
326
+ case "nullable":
327
+ return isStructuralSchemaNode(def.innerType);
328
+ case "array":
329
+ return isStructuralSchemaNode(def.element);
330
+ case "object": {
331
+ const rawShape = typeof def.shape === "function" ? def.shape() : def.shape;
332
+ if (!rawShape || typeof rawShape !== "object") return false;
333
+ return Object.values(rawShape).every(isStructuralSchemaNode);
334
+ }
335
+ default:
336
+ return false;
337
+ }
338
+ };
339
+ const isHostValidationEquivalent = (schema, value) => {
340
+ if (!schema || typeof schema !== "object") return false;
341
+ const def = schema._zod && schema._zod.def;
342
+ if (!def || typeof def !== "object") return false;
343
+ if (def.coerce) return false;
344
+ if (Array.isArray(def.checks) && def.checks.length > 0) return false;
345
+ switch (def.type) {
346
+ case "string":
347
+ return typeof value === "string" && !def.format;
348
+ case "number":
349
+ return typeof value === "number" && Number.isFinite(value) && !def.format;
350
+ case "boolean":
351
+ return typeof value === "boolean";
352
+ case "literal": {
353
+ const rawValues = def.values;
354
+ const values = Array.isArray(rawValues)
355
+ ? rawValues
356
+ : rawValues instanceof Set
357
+ ? Array.from(rawValues)
358
+ : [];
359
+ return (
360
+ values.length > 0 &&
361
+ values.some((v) => v === value && typeof v !== "bigint" && typeof v !== "symbol")
362
+ );
363
+ }
364
+ case "enum": {
365
+ if (schema._zod && schema._zod.values instanceof Set) {
366
+ return schema._zod.values.has(value);
367
+ }
368
+ const entriesObj = def.entries && typeof def.entries === "object" ? def.entries : null;
369
+ if (!entriesObj) return false;
370
+ const entries = Object.values(entriesObj).filter(
371
+ (v) => !(typeof v === "string" && typeof entriesObj[v] === "number"),
372
+ );
373
+ return entries.length > 0 && entries.includes(value);
374
+ }
375
+ case "optional":
376
+ return value === undefined
377
+ ? isStructuralSchemaNode(def.innerType)
378
+ : isHostValidationEquivalent(def.innerType, value);
379
+ case "nullable":
380
+ return value === null
381
+ ? isStructuralSchemaNode(def.innerType)
382
+ : isHostValidationEquivalent(def.innerType, value);
383
+ case "array":
384
+ return (
385
+ Array.isArray(value) &&
386
+ isStructuralSchemaNode(def.element) &&
387
+ value.every((item) => isHostValidationEquivalent(def.element, item))
388
+ );
389
+ case "object": {
390
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
391
+ const shape = typeof def.shape === "function" ? def.shape() : def.shape;
392
+ if (!shape || typeof shape !== "object") return false;
393
+ for (const key of Object.keys(value)) {
394
+ if (!Object.hasOwn(shape, key)) return false;
395
+ }
396
+ for (const [key, propSchema] of Object.entries(shape)) {
397
+ if (!Object.hasOwn(value, key) && key in value) return false;
398
+ const propValue = value[key];
399
+ if (!isHostValidationEquivalent(propSchema, propValue)) return false;
400
+ }
401
+ return true;
402
+ }
403
+ default:
404
+ return false;
405
+ }
406
+ };
311
407
  const mod = globalThis.__swarm_module;
312
408
  if (!mod || typeof mod.default !== "function") {
313
409
  throw new Error(
314
410
  "Swarm script must export a default function. Script must export default async function (args, ctx): args FIRST, ctx second.",
315
411
  );
316
412
  }
413
+ const hostValidatedArgs = globalThis.__swarm_host_validated_args === true;
317
414
  let args = globalThis.__swarm_args;
318
- if (mod.argsSchema && typeof mod.argsSchema === "object" && "parse" in mod.argsSchema) {
415
+ if (
416
+ mod.argsSchema &&
417
+ typeof mod.argsSchema === "object" &&
418
+ "parse" in mod.argsSchema &&
419
+ !(hostValidatedArgs && isHostValidationEquivalent(mod.argsSchema, args))
420
+ ) {
319
421
  try {
320
422
  args = mod.argsSchema.parse(args);
321
423
  } catch (error) {
@@ -29,6 +29,7 @@ import type {
29
29
  export type QuickJSJob = {
30
30
  source: string;
31
31
  args: unknown;
32
+ hostValidatedArgs?: boolean;
32
33
  configPayload: SwarmConfigPayload;
33
34
  resources: ScriptResourcePolicy;
34
35
  network: ExecutorInput["network"];
@@ -656,7 +657,7 @@ async function evaluateJob(
656
657
 
657
658
  let timer: ReturnType<typeof setTimeout> | undefined;
658
659
  try {
659
- const init = `globalThis.__swarm_shape = ${JSON.stringify(shape)};\nglobalThis.__swarm_args = ${JSON.stringify(args)};`;
660
+ const init = `globalThis.__swarm_shape = ${JSON.stringify(shape)};\nglobalThis.__swarm_args = ${JSON.stringify(args)};\nglobalThis.__swarm_host_validated_args = ${Boolean(job.hostValidatedArgs)};`;
660
661
  const ok =
661
662
  evaluate(init, "swarm-init.js") &&
662
663
  evaluate(SANDBOX_WEB_SHIMS, "swarm-web-shims.js") &&
@@ -146,6 +146,12 @@ export class QuickJSScriptExecutor implements ScriptExecutor {
146
146
  `quickjs worker failed to start: ${error instanceof Error ? error.message : String(error)}`,
147
147
  );
148
148
  }
149
+ // An abort while the worker booted fired before the listener below
150
+ // exists. From here to postMessage nothing awaits.
151
+ if (input.signal?.aborted) {
152
+ pool.release(slot);
153
+ return emptyOutput("killed");
154
+ }
149
155
 
150
156
  const id = ++requestSeq;
151
157
  const start = performance.now();
@@ -196,6 +202,7 @@ export class QuickJSScriptExecutor implements ScriptExecutor {
196
202
  job: {
197
203
  source: input.source,
198
204
  args: input.args,
205
+ ...(input.hostValidatedArgs ? { hostValidatedArgs: true } : {}),
199
206
  configPayload: input.configPayload,
200
207
  resources: input.resources,
201
208
  network: input.network,
@@ -7,6 +7,27 @@ const EXECUTORS: Record<string, () => ScriptExecutor> = {
7
7
  quickjs: () => new QuickJSScriptExecutor(),
8
8
  };
9
9
 
10
+ export function getDeploymentScriptExecutorName(env: NodeJS.ProcessEnv = process.env): string {
11
+ return (env.SCRIPT_EXECUTOR ?? "native").trim();
12
+ }
13
+
14
+ /**
15
+ * Operator sandbox ceiling: when the deployment sets `SCRIPT_EXECUTOR=quickjs`,
16
+ * individual scripts or agent calls may not escalate to the less-restricted
17
+ * `native` executor. When the deployment runs `native` (the default), scripts
18
+ * may opt down into `quickjs`.
19
+ */
20
+ export function isExecutorAllowedByDeployment(
21
+ requested: string | null | undefined,
22
+ env: NodeJS.ProcessEnv = process.env,
23
+ ): boolean {
24
+ if (!requested) return true;
25
+ if (getDeploymentScriptExecutorName(env) === "quickjs" && requested.trim() === "native") {
26
+ return false;
27
+ }
28
+ return true;
29
+ }
30
+
10
31
  export function getScriptExecutor(name = process.env.SCRIPT_EXECUTOR ?? "native"): ScriptExecutor {
11
32
  const factory = EXECUTORS[name.trim()];
12
33
  if (!factory) {
@@ -16,3 +37,14 @@ export function getScriptExecutor(name = process.env.SCRIPT_EXECUTOR ?? "native"
16
37
  }
17
38
  return factory();
18
39
  }
40
+
41
+ export function resolveScriptExecutor(
42
+ requested?: string | null,
43
+ env: NodeJS.ProcessEnv = process.env,
44
+ ): ScriptExecutor {
45
+ if (!isExecutorAllowedByDeployment(requested, env)) {
46
+ throw new Error('Executor "native" is not permitted when deployment SCRIPT_EXECUTOR="quickjs"');
47
+ }
48
+ const effective = requested ?? env.SCRIPT_EXECUTOR ?? "native";
49
+ return getScriptExecutor(effective);
50
+ }
@@ -3,6 +3,7 @@ import type { ScriptApiConnectionDescriptor, ScriptMcpConnectionDescriptor } fro
3
3
  import type { FailedCredentialBinding, ResolvedCredentialBinding } from "../credential-broker";
4
4
 
5
5
  export type ScriptFsMode = "none" | "workspace-rw";
6
+ export type ScriptExecutorName = "native" | "quickjs";
6
7
 
7
8
  export type EgressSecretEntry = ResolvedCredentialBinding;
8
9
 
@@ -49,6 +50,12 @@ export type ScriptResourcePolicy = {
49
50
  export type ExecutorInput = {
50
51
  source: string;
51
52
  args: unknown;
53
+ /**
54
+ * Trusted host-only flag indicating `args` already passed host JSON Schema
55
+ * validation for this run. Executors may skip `argsSchema.parse(args)` only
56
+ * when this flag is true AND the runtime `argsSchema` is purely structural.
57
+ */
58
+ hostValidatedArgs?: boolean;
52
59
  configPayload: SwarmConfigPayload;
53
60
  resources: ScriptResourcePolicy;
54
61
  fsMode: ScriptFsMode;