@desplega.ai/agent-swarm 2.6.0 → 2.7.1

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 (324) hide show
  1. package/README.md +2 -0
  2. package/dist/{acp-adapter-dgfakfpw.js → acp-adapter-8dn5tq56.js} +6 -6
  3. package/dist/{actions-fbqma8aa.js → actions-3r6gw7dy.js} +18 -18
  4. package/dist/{amp-adapter-en0jy6hs.js → amp-adapter-wjtq413p.js} +3 -3
  5. package/dist/{anthropic-messages-txs190dd.js → anthropic-messages-n4cby1rz.js} +4 -4
  6. package/dist/{app-k8a5cgpp.js → app-d53yhykk.js} +3 -3
  7. package/dist/{assistant-a16433gr.js → assistant-ksrawj3k.js} +20 -20
  8. package/dist/{boot-reembed-6c1sjfnr.js → boot-reembed-fxfw17m2.js} +11 -3
  9. package/dist/{boot-reembed-f4nn561n.js → boot-reembed-k5y5xbj0.js} +15 -8
  10. package/dist/{boot-scrub-sweep-d4w9gajf.js → boot-scrub-sweep-se41j9fd.js} +3 -3
  11. package/dist/{boot-seeds-9bmkspyw.js → boot-seeds-r3kwwv8b.js} +4 -4
  12. package/dist/{claude-adapter-nys1kjxa.js → claude-adapter-ahmyf231.js} +8 -8
  13. package/dist/{claude-managed-adapter-55207973.js → claude-managed-adapter-5knamegc.js} +3 -3
  14. package/dist/{claude-managed-setup-3cm35gey.js → claude-managed-setup-mdc7dy6b.js} +3 -3
  15. package/dist/{claude-sdk-session-nw5yd8cp.js → claude-sdk-session-n5wen9zk.js} +8729 -8600
  16. package/dist/{cli-683y0b3a.js → cli-0dqtv2jg.js} +2 -2
  17. package/dist/{cli-a34pr3w1.js → cli-0tww7dnz.js} +2 -2
  18. package/dist/{cli-w2656ka1.js → cli-0w4e4v58.js} +7 -7
  19. package/dist/{cli-5sckpqcx.js → cli-2sjm3t4r.js} +1 -1
  20. package/dist/{cli-gf2850q7.js → cli-3cywb44q.js} +18 -1
  21. package/dist/{cli-8wkb09q5.js → cli-3k9jej0c.js} +2 -2
  22. package/dist/{cli-3aqd4jaa.js → cli-3v2cyqb8.js} +3 -3
  23. package/dist/{cli-emhjcj2y.js → cli-5qt9vzmq.js} +89 -20
  24. package/dist/{cli-abkm71vr.js → cli-5rp1w4cr.js} +1 -1
  25. package/dist/{cli-gf95q3bm.js → cli-6vwjsetx.js} +4 -4
  26. package/dist/{cli-k21gx5ht.js → cli-6w2yrrc0.js} +10 -10
  27. package/dist/{cli-m70bajzr.js → cli-75tdydgv.js} +1 -1
  28. package/dist/{cli-5rj83bam.js → cli-8feswf2v.js} +47 -36
  29. package/dist/{cli-sxgtf5fw.js → cli-8wd3mbyj.js} +1 -1
  30. package/dist/{cli-ca6banwz.js → cli-9csfthbw.js} +10 -8
  31. package/dist/{cli-dqygeeh5.js → cli-b41p764a.js} +31 -3
  32. package/dist/{cli-m5a9m0ak.js → cli-c1t533hh.js} +3 -3
  33. package/dist/{cli-2sjhpsns.js → cli-c28jxpcv.js} +1 -1
  34. package/dist/{cli-bqztfyve.js → cli-cb50g9cd.js} +1 -1
  35. package/dist/{cli-ag2yrnt5.js → cli-d8gaphtr.js} +2 -2
  36. package/dist/{cli-9abdgn5t.js → cli-dgtfysne.js} +3 -3
  37. package/dist/{cli-nq677csy.js → cli-dscnhn18.js} +1 -1
  38. package/dist/{cli-3fwqy71j.js → cli-dv11pgpv.js} +39 -32
  39. package/dist/{cli-v49fkghp.js → cli-ekptgk2m.js} +6 -6
  40. package/dist/{cli-c2habvsf.js → cli-f3mzy662.js} +17 -17
  41. package/dist/{cli-zqne1qcj.js → cli-fk7k8s9x.js} +2 -2
  42. package/dist/{cli-1tem6ns9.js → cli-gt4h8cxw.js} +3 -2
  43. package/dist/{cli-vbhgkw9h.js → cli-h3e7bhvn.js} +3 -3
  44. package/dist/{cli-ffvsj2qw.js → cli-hkxqdmnb.js} +3 -3
  45. package/dist/{cli-a2rr0fmg.js → cli-hpkvgdad.js} +7 -4
  46. package/dist/{cli-art7c1zh.js → cli-hq2v4rr5.js} +37 -9
  47. package/dist/{cli-sqma6vw1.js → cli-k1zb7raw.js} +5 -5
  48. package/dist/{cli-4rvr7zfx.js → cli-k4awgvzm.js} +5 -0
  49. package/dist/{cli-3ndmpxxv.js → cli-k7d3hvya.js} +2 -2
  50. package/dist/{cli-3gfvmzht.js → cli-kbtdftbw.js} +74 -26
  51. package/dist/{cli-hfeb68z4.js → cli-kj69b9tw.js} +34 -6
  52. package/dist/{cli-az25fn5f.js → cli-kk9jy4nm.js} +2 -2
  53. package/dist/{cli-39aet655.js → cli-m52t4cta.js} +1 -1
  54. package/dist/{cli-cz37jqss.js → cli-mdadewqp.js} +5 -5
  55. package/dist/{cli-esakngh8.js → cli-nf6k60sn.js} +1 -1
  56. package/dist/{cli-j6rktc91.js → cli-nzdf09js.js} +1 -1
  57. package/dist/{cli-rr7zx19j.js → cli-prcyzecg.js} +12 -12
  58. package/dist/{cli-4hxrf0p4.js → cli-pzafyqtk.js} +393 -62
  59. package/dist/{cli-g8ka5j4j.js → cli-qvaa1gjp.js} +173 -52
  60. package/dist/{cli-d2dpf95n.js → cli-rg4b2g6j.js} +1 -1
  61. package/dist/{cli-kf4jree9.js → cli-rm66vqdz.js} +1 -1
  62. package/dist/{cli-4rf0f5vg.js → cli-s504c3w7.js} +5 -3
  63. package/dist/{cli-nn6vx2mq.js → cli-swkx9jm2.js} +7 -5
  64. package/dist/{cli-g20jjwt4.js → cli-thsaq4pd.js} +16 -16
  65. package/dist/{cli-aj8djyqt.js → cli-twjnyvs8.js} +2 -2
  66. package/dist/{cli-03gg9pd1.js → cli-typdpe8g.js} +4 -4
  67. package/dist/{cli-mtnj4fwa.js → cli-vg8pwmsd.js} +6 -6
  68. package/dist/{cli-gnsb1zxa.js → cli-vhpckzvp.js} +1 -1
  69. package/dist/{cli-7enjwya9.js → cli-vz78s5wm.js} +1 -1
  70. package/dist/{cli-dv97kdzv.js → cli-wdxgsdz5.js} +2 -2
  71. package/dist/{cli-7a6p4e65.js → cli-wtjg6je3.js} +1 -1
  72. package/dist/{cli-6y51ryva.js → cli-x3cngb1h.js} +44 -12
  73. package/dist/{cli-n7asmce6.js → cli-xbz6mcg4.js} +3 -3
  74. package/dist/{cli-24j1jeeb.js → cli-y4tdxsqb.js} +3 -3
  75. package/dist/{cli-mvjz5an3.js → cli-yqrsp3ny.js} +7 -7
  76. package/dist/cli.js +31 -21
  77. package/dist/{codex-adapter-d95q4qsn.js → codex-adapter-7wepzq15.js} +6 -6
  78. package/dist/{codex-hook-brpnycn5.js → codex-hook-pbdh4xhx.js} +2 -2
  79. package/dist/{codex-session-runner-gdwd8h0c.js → codex-session-runner-02sb49e4.js} +6 -6
  80. package/dist/{commands-jqmgk54b.js → commands-3rcspxct.js} +2 -2
  81. package/dist/{controller-z3jq589f.js → controller-8ar4avd6.js} +3 -3
  82. package/dist/{controllers-connect-r2za39ef.js → controllers-connect-ajvh9h0v.js} +19 -19
  83. package/dist/{cursor-adapter-4mr4jgkw.js → cursor-adapter-gd0sa7bh.js} +1 -1
  84. package/dist/{db-w36wwb5g.js → db-dv9j7ts5.js} +2 -2
  85. package/dist/{db-ppv2tze7.js → db-z26vgdvj.js} +3 -3
  86. package/dist/{dispatcher-pv5h77vf.js → dispatcher-8m0htjq8.js} +17 -17
  87. package/dist/{dsh-adapter-d4pdpb3r.js → dsh-adapter-y8gyk130.js} +1 -1
  88. package/dist/{e2b-fn3zacvt.js → e2b-74vg9b7e.js} +1 -1
  89. package/dist/{e2b-222hsajc.js → e2b-jyay8wzn.js} +1 -1
  90. package/dist/{easter-egg-3d-4sp4dvmc.js → easter-egg-3d-28f880xx.js} +1 -1
  91. package/dist/{execute-jev839y1.js → execute-9seb79vh.js} +1 -1
  92. package/dist/{external-channel-y6nkzhnn.js → external-channel-smz7jyth.js} +2 -2
  93. package/dist/{getMachineId-bsd-brn0xym3.js → getMachineId-bsd-whfre6gr.js} +3 -3
  94. package/dist/{getMachineId-darwin-t339sfxm.js → getMachineId-darwin-5zjynb19.js} +3 -3
  95. package/dist/{getMachineId-linux-5eq95d8b.js → getMachineId-linux-8mksrrsq.js} +3 -3
  96. package/dist/{getMachineId-unsupported-qavrtc7j.js → getMachineId-unsupported-fxtqsdt2.js} +3 -3
  97. package/dist/{getMachineId-win-q7fe1wx7.js → getMachineId-win-nzfmz857.js} +3 -3
  98. package/dist/{grok-adapter-t08b9ntm.js → grok-adapter-z0nnvtbn.js} +7 -7
  99. package/dist/handlers-pspryh6g.js +41 -0
  100. package/dist/{heartbeat-vn31xwdk.js → heartbeat-f928aqyp.js} +20 -20
  101. package/dist/{hook-cpjs8q51.js → hook-d7c605r4.js} +7 -7
  102. package/dist/{hook-6d0zbvvt.js → hook-q9hv5xf9.js} +5 -5
  103. package/dist/{http-9c6medda.js → http-q6j4k989.js} +1000 -251
  104. package/dist/{index-k0yt1tvq.js → index-5nb266jr.js} +17 -17
  105. package/dist/{index-ysd45kgc.js → index-8fxt5eah.js} +10 -10
  106. package/dist/{index-zgvqzjsw.js → index-cekzwhez.js} +5 -5
  107. package/dist/{index-38t35h0n.js → index-ch0c1xff.js} +17 -17
  108. package/dist/{index-pkma1yf8.js → index-ebnjhxh1.js} +1 -1
  109. package/dist/{index-yqgbfgaj.js → index-ed8dy8rn.js} +20 -20
  110. package/dist/{index-qhjf2htm.js → index-ejqdkkqx.js} +262 -96
  111. package/dist/{index-dr94zbnh.js → index-farm1zsn.js} +32661 -29443
  112. package/dist/{index-g6k9tctj.js → index-yn1yxg85.js} +1 -1
  113. package/dist/{keepalive-em0117bp.js → keepalive-zzc3hks1.js} +3 -3
  114. package/dist/lead-c0pjcaw4.js +79 -0
  115. package/dist/{maintenance-kbme3qy9.js → maintenance-kqsxzpzx.js} +1 -1
  116. package/dist/{oauth-refresh-sweep-xysj4yse.js → oauth-refresh-sweep-7qt0g55z.js} +8 -3
  117. package/dist/{onboard-p6q0vz97.js → onboard-k7x13fqg.js} +121 -8
  118. package/dist/{opencode-adapter-esv3k9yn.js → opencode-adapter-tqvrtter.js} +5 -5
  119. package/dist/{otel-impl-54qhqh1z.js → otel-impl-sqkf6k3b.js} +97 -97
  120. package/dist/{pi-mono-adapter-mymbwhj0.js → pi-mono-adapter-m2qnafxm.js} +89 -14
  121. package/dist/{pricing-refresh-yb75wv33.js → pricing-refresh-wkpwhgvs.js} +3 -3
  122. package/dist/{rbac-roles-dexhc9x8.js → rbac-roles-dpn0c0wj.js} +7 -4
  123. package/dist/{rbac-roles-1vp2mw4p.js → rbac-roles-wk7xd6a6.js} +2 -2
  124. package/dist/{render-v2-tjay5hhf.js → render-v2-ex2m4g2k.js} +3 -3
  125. package/dist/{resume-8ayf37g5.js → resume-725g9b0b.js} +7 -7
  126. package/dist/{runtime-hqe5cqcd.js → runtime-kajas15g.js} +1 -1
  127. package/dist/{session-access-fdhp987s.js → session-access-0j2ezjje.js} +8 -4
  128. package/dist/{session-source-backfill-qgtc86z1.js → session-source-backfill-62202awe.js} +2 -2
  129. package/dist/{setup-4rrmqcq3.js → setup-9sj11ax6.js} +1 -1
  130. package/dist/{virtual-modules-52km00d6.js → virtual-modules-c6fcq6zr.js} +2 -2
  131. package/dist/{work-objects-p28fgd70.js → work-objects-0y07495y.js} +20 -20
  132. package/dist/worker-d4q9sqbc.js +79 -0
  133. package/openapi.json +1536 -407
  134. package/package.json +7 -5
  135. package/src/be/automation-preflight.ts +2 -0
  136. package/src/be/connector-codes.ts +65 -22
  137. package/src/be/db/api-keys.ts +9 -0
  138. package/src/be/db/workflow-runs.ts +36 -5
  139. package/src/be/db.ts +80 -22
  140. package/src/be/memory/boot-reembed.ts +4 -1
  141. package/src/be/memory/constants.ts +6 -0
  142. package/src/be/memory/embedding-recovery.ts +48 -0
  143. package/src/be/memory/index-content.ts +5 -2
  144. package/src/be/memory/providers/openai-embedding.ts +65 -14
  145. package/src/be/memory/task-learning.ts +257 -0
  146. package/src/be/memory/types.ts +13 -2
  147. package/src/be/migrations/245_script_executor.sql +3 -0
  148. package/src/be/migrations/246_connector_code_kind.sql +11 -0
  149. package/src/be/oauth-refresh-sweep.ts +2 -1
  150. package/src/be/onboarding.ts +125 -15
  151. package/src/be/rbac-roles.ts +4 -1
  152. package/src/be/scripts/boot-reembed.ts +2 -0
  153. package/src/be/scripts/db.ts +14 -4
  154. package/src/be/scripts/embeddings.ts +4 -2
  155. package/src/be/scripts/run-saved.ts +1 -0
  156. package/src/be/scripts/typecheck.ts +9 -3
  157. package/src/be/seed/agent-fs-provision.ts +75 -14
  158. package/src/be/seed-skills/index.ts +7 -0
  159. package/src/be/swarm-config-guard.ts +3 -0
  160. package/src/cli.tsx +9 -0
  161. package/src/comb/config.ts +6 -1
  162. package/src/commands/onboard/agent-fs-version.ts +5 -0
  163. package/src/commands/onboard/compose-generator.ts +117 -4
  164. package/src/commands/onboard/env-generator.ts +9 -0
  165. package/src/commands/onboard/tei-image.ts +12 -0
  166. package/src/commands/onboard/types.ts +7 -0
  167. package/src/commands/onboard.tsx +11 -1
  168. package/src/commands/provider-credentials.ts +2 -1
  169. package/src/commands/runner.ts +14 -1
  170. package/src/heartbeat/heartbeat.ts +14 -1
  171. package/src/heartbeat/script-runner-probe.ts +47 -0
  172. package/src/http/agentfs-proxy.ts +369 -0
  173. package/src/http/all-routes.ts +1 -0
  174. package/src/http/api-keys.ts +24 -9
  175. package/src/http/codex-oauth-device.ts +8 -1
  176. package/src/http/fs.ts +64 -10
  177. package/src/http/idempotency-key.ts +1 -1
  178. package/src/http/inbox-state.ts +27 -1
  179. package/src/http/index.ts +7 -4
  180. package/src/http/memory.ts +7 -4
  181. package/src/http/onboarding.ts +139 -11
  182. package/src/http/pages-public.ts +38 -5
  183. package/src/http/scripts.ts +27 -0
  184. package/src/http/session-access.ts +24 -0
  185. package/src/http/sessions.ts +10 -6
  186. package/src/http/tasks.ts +85 -11
  187. package/src/http/telemetry-ui.ts +1 -1
  188. package/src/http/user-roles.ts +4 -1
  189. package/src/http/users.ts +272 -3
  190. package/src/http/utils.ts +22 -3
  191. package/src/http/workflows.ts +10 -0
  192. package/src/http/x.ts +5 -0
  193. package/src/prompts/base-prompt.ts +45 -10
  194. package/src/prompts/session-templates.ts +43 -5
  195. package/src/providers/amp-adapter.ts +1 -1
  196. package/src/providers/codex-adapter.ts +6 -1
  197. package/src/providers/cursor-adapter.ts +1 -1
  198. package/src/providers/dsh-adapter.ts +1 -1
  199. package/src/providers/pi-mono-adapter.ts +99 -14
  200. package/src/rbac/legacy-policy.ts +1 -0
  201. package/src/rbac/permissions.ts +4 -0
  202. package/src/scheduler/scheduler.ts +2 -1
  203. package/src/scripts-runtime/eval-harness.ts +8 -1
  204. package/src/scripts-runtime/executors/native.ts +22 -16
  205. package/src/scripts-runtime/executors/quickjs-prelude.ts +103 -1
  206. package/src/scripts-runtime/executors/quickjs-runner.ts +2 -1
  207. package/src/scripts-runtime/executors/quickjs.ts +7 -0
  208. package/src/scripts-runtime/executors/registry.ts +32 -0
  209. package/src/scripts-runtime/executors/types.ts +7 -0
  210. package/src/scripts-runtime/host-validated-args.ts +133 -0
  211. package/src/scripts-runtime/loader.ts +30 -3
  212. package/src/scripts-runtime/swarm-sdk.ts +9 -2
  213. package/src/scripts-runtime/types/stdlib.d.ts +9 -1
  214. package/src/scripts-runtime/types/swarm-sdk.d.ts +9 -1
  215. package/src/tests/agent-fs-provision-seeder.test.ts +261 -92
  216. package/src/tests/agentfs-proxy.test.ts +1106 -0
  217. package/src/tests/api-key-status-by-suffix.test.ts +242 -0
  218. package/src/tests/base-prompt.test.ts +139 -4
  219. package/src/tests/codex-adapter.test.ts +46 -8
  220. package/src/tests/codex-oauth-device.test.ts +12 -1
  221. package/src/tests/comb-config-guard.test.ts +20 -0
  222. package/src/tests/comb-links.test.ts +79 -1
  223. package/src/tests/comb-status.test.ts +44 -0
  224. package/src/tests/community-linear-delivery-loop.test.ts +2099 -0
  225. package/src/tests/credential-check.test.ts +4 -2
  226. package/src/tests/docs-index-footprint.test.js +3 -3
  227. package/src/tests/docs-index.test.ts +135 -0
  228. package/src/tests/dsh-adapter.test.ts +1 -1
  229. package/src/tests/fetch-resolved-env.test.ts +16 -2
  230. package/src/tests/fixtures/onboard/baseline-states.ts +68 -0
  231. package/src/tests/fixtures/onboard/content.compose.yml.txt +190 -0
  232. package/src/tests/fixtures/onboard/content.env.txt +29 -0
  233. package/src/tests/fixtures/onboard/dev.compose.yml.txt +155 -0
  234. package/src/tests/fixtures/onboard/dev.env.txt +28 -0
  235. package/src/tests/fixtures/onboard/full-all.compose.yml.txt +582 -0
  236. package/src/tests/fixtures/onboard/full-all.env.txt +59 -0
  237. package/src/tests/fixtures/onboard/full.compose.yml.txt +435 -0
  238. package/src/tests/fixtures/onboard/full.env.txt +36 -0
  239. package/src/tests/fixtures/onboard/research.compose.yml.txt +155 -0
  240. package/src/tests/fixtures/onboard/research.env.txt +28 -0
  241. package/src/tests/fixtures/onboard/solo.compose.yml.txt +85 -0
  242. package/src/tests/fixtures/onboard/solo.env.txt +26 -0
  243. package/src/tests/heartbeat-checklist.test.ts +89 -0
  244. package/src/tests/heartbeat-script-runner-probe.test.ts +83 -0
  245. package/src/tests/http-log-scrubbing.test.ts +34 -1
  246. package/src/tests/http-task-write-ownership.test.ts +5 -1
  247. package/src/tests/invites-http.test.ts +464 -0
  248. package/src/tests/memory-embedding-input-cap.test.ts +122 -1
  249. package/src/tests/memory-embedding-recovery.test.ts +137 -0
  250. package/src/tests/oauth-refresh-sweep.test.ts +34 -1
  251. package/src/tests/onboard-compose.test.ts +162 -0
  252. package/src/tests/onboard-env.test.ts +24 -0
  253. package/src/tests/onboard-non-interactive.test.ts +29 -0
  254. package/src/tests/onboard-tei-image.test.ts +36 -0
  255. package/src/tests/onboarding.test.ts +451 -4
  256. package/src/tests/openai-embedding-provider.test.ts +105 -2
  257. package/src/tests/opencode-adapter.test.ts +2 -1
  258. package/src/tests/pages-frame-ancestors.test.ts +162 -0
  259. package/src/tests/pi-codemode.test.ts +2 -1
  260. package/src/tests/pi-mono-adapter.test.ts +54 -0
  261. package/src/tests/pi-mono-session-cost-on-abort.test.ts +270 -0
  262. package/src/tests/pi-native-mcp.test.ts +13 -1
  263. package/src/tests/pi-tool-exposure.test.ts +8 -8
  264. package/src/tests/preload-env.test.ts +4 -0
  265. package/src/tests/preload.ts +4 -0
  266. package/src/tests/probe-tool-schema-tokens.test.ts +144 -0
  267. package/src/tests/prompt-template-session.test.ts +20 -3
  268. package/src/tests/rbac-admission-e2e.test.ts +22 -6
  269. package/src/tests/rbac-admission.test.ts +342 -4
  270. package/src/tests/rbac-engine.test.ts +1 -0
  271. package/src/tests/rbac-roles.test.ts +9 -1
  272. package/src/tests/request-composition-derive.test.ts +329 -0
  273. package/src/tests/script-connections.test.ts +2 -0
  274. package/src/tests/script-executor-conformance.test.ts +36 -1
  275. package/src/tests/script-executor-quickjs-pool.test.ts +50 -0
  276. package/src/tests/script-executor-quickjs.test.ts +140 -0
  277. package/src/tests/script-host-validated-args-native.test.ts +196 -0
  278. package/src/tests/script-per-script-executor.test.ts +415 -0
  279. package/src/tests/scripts-external-api.test.ts +45 -0
  280. package/src/tests/sdk-allowlist.test.ts +3 -2
  281. package/src/tests/self-improvement.test.ts +4 -4
  282. package/src/tests/send-task-requester-gate.test.ts +64 -3
  283. package/src/tests/sessions-move.test.ts +39 -1
  284. package/src/tests/store-progress-learning.test.ts +381 -0
  285. package/src/tests/ui-logs-parser.test.ts +180 -0
  286. package/src/tests/whoami-route.test.ts +41 -0
  287. package/src/tests/workflow-engine-v2.test.ts +116 -1
  288. package/src/tests/workflow-mcp-trigger-schema.test.ts +209 -0
  289. package/src/tests/workflow-run-finished-event.test.ts +263 -0
  290. package/src/tests/workflow-swarm-script.test.ts +34 -0
  291. package/src/tools/inject-learning.ts +1 -1
  292. package/src/tools/memory-edit.ts +1 -1
  293. package/src/tools/memory-search.ts +1 -1
  294. package/src/tools/script-common.ts +1 -0
  295. package/src/tools/script-run.ts +7 -0
  296. package/src/tools/script-upsert.ts +7 -0
  297. package/src/tools/store-progress.ts +71 -1
  298. package/src/tools/utils.ts +12 -1
  299. package/src/tools/workflows/trigger-workflow.ts +104 -10
  300. package/src/types.ts +12 -1
  301. package/src/utils/constants.ts +39 -0
  302. package/src/utils/credentials.ts +5 -2
  303. package/src/utils/task-attachment-links.ts +15 -3
  304. package/src/workflows/engine.ts +18 -16
  305. package/src/workflows/executors/swarm-script.ts +5 -0
  306. package/src/workflows/foreach-driver.ts +5 -11
  307. package/src/workflows/node-timeout.ts +30 -0
  308. package/templates/community/linear-delivery-loop/PLAYBOOK.md +323 -0
  309. package/templates/community/linear-delivery-loop/README.md +38 -0
  310. package/templates/community/linear-delivery-loop/config.example.json +31 -0
  311. package/templates/community/linear-delivery-loop/lead-prompt.md +34 -0
  312. package/templates/community/linear-delivery-loop/schedule.json +10 -0
  313. package/templates/community/linear-delivery-loop/scripts/linear-delivery-setup.ts +148 -0
  314. package/templates/community/linear-delivery-loop/scripts/linear-delivery-tick.ts +1781 -0
  315. package/templates/skills/swarm-docs/config.json +14 -0
  316. package/templates/skills/swarm-docs/content.md +144 -0
  317. package/dist/handlers-eqsnmv95.js +0 -41
  318. package/dist/lead-23e7z59t.js +0 -79
  319. package/dist/worker-e3w7vye1.js +0 -79
  320. package/dist/{azure-openai-responses-9kfwhmn7.js → azure-openai-responses-43asa23s.js} +0 -0
  321. package/dist/{node-vxx896fa.js → node-t28cc56b.js} +0 -0
  322. package/dist/{openai-codex-responses-79s8xnbh.js → openai-codex-responses-jhr3sjtm.js} +3 -3
  323. package/dist/{openai-completions-tdbyts0x.js → openai-completions-gs6yn37k.js} +3 -3
  324. /package/dist/{openai-responses-dm3t5vqp.js → openai-responses-jqm9vwwk.js} +0 -0
@@ -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;
@@ -1018,6 +1041,8 @@ export class PiMonoSession implements ProviderSession {
1018
1041
  private emptyTurnReprompted = false;
1019
1042
  /** `abort()` was called: never prompt a session someone is trying to stop. */
1020
1043
  private abortRequested = false;
1044
+ /** Pending `waitForIdle()` resolvers released when `agent_end` fires or `abort()` is called. */
1045
+ private idleResolvers = new Set<() => void>();
1021
1046
  /** Removes this session's own models.json once the session ends. */
1022
1047
  private disposeModels: () => Promise<void>;
1023
1048
  /** Fed each assistant request's prompt size; steers the one-time context-bound note. */
@@ -1116,6 +1141,10 @@ export class PiMonoSession implements ProviderSession {
1116
1141
  if (endMsg.role === "assistant") {
1117
1142
  const turn = describeAssistantTurn(event.message);
1118
1143
  this.lastAssistantTurn = turn;
1144
+ // Ahead of the turn's text and tool lines: the chip rides on the
1145
+ // call's first row.
1146
+ const usageLine = callUsageLine(event.message);
1147
+ if (usageLine) this.emit({ type: "raw_log", content: usageLine });
1119
1148
  if (endMsg.stopReason === "error") {
1120
1149
  // Candidate terminal failure. May still be cleared by a successful
1121
1150
  // retry (auto_retry_end success / a later good message_end).
@@ -1222,6 +1251,14 @@ export class PiMonoSession implements ProviderSession {
1222
1251
  case "tool_execution_end":
1223
1252
  if (this.pendingTerminalStoreProgress.delete(event.toolCallId) && !event.isError) {
1224
1253
  this.terminalStoreProgressDone = true;
1254
+ const snapshotCost = this.safeBuildCostData(false);
1255
+ if (snapshotCost) {
1256
+ this.emit({
1257
+ type: "result",
1258
+ cost: snapshotCost,
1259
+ isError: false,
1260
+ });
1261
+ }
1225
1262
  }
1226
1263
  this.emit({
1227
1264
  type: "raw_log",
@@ -1327,6 +1364,24 @@ export class PiMonoSession implements ProviderSession {
1327
1364
  };
1328
1365
  } catch (err) {
1329
1366
  const errorMessage = err instanceof Error ? err.message : String(err);
1367
+ if (this.abortRequested && this.terminalStoreProgressDone) {
1368
+ const cost = this.safeBuildCostData(false);
1369
+ if (cost) {
1370
+ this.emit({
1371
+ type: "result",
1372
+ cost,
1373
+ isError: false,
1374
+ });
1375
+ }
1376
+ return {
1377
+ exitCode: 0,
1378
+ sessionId: this._sessionId,
1379
+ cost,
1380
+ output: this.lastAssistantText || undefined,
1381
+ isError: false,
1382
+ appliedReasoningEffort: this.appliedReasoningEffort,
1383
+ };
1384
+ }
1330
1385
  // Defense-in-depth: AWS SDK failures surface as structured events (handled
1331
1386
  // above in runSession), not thrown exceptions, so this catch is for genuine
1332
1387
  // unexpected throws (MCP / transport / etc). Still classify in case an AWS
@@ -1341,9 +1396,11 @@ export class PiMonoSession implements ProviderSession {
1341
1396
  }
1342
1397
  this.emit({ type: "raw_stderr", content: `[pi-mono] Error: ${errorMessage}\n` });
1343
1398
 
1399
+ const cost = this.safeBuildCostData(true);
1344
1400
  return {
1345
1401
  exitCode: 1,
1346
1402
  sessionId: this._sessionId,
1403
+ cost,
1347
1404
  isError: true,
1348
1405
  errorCategory: awsCatchError?.category,
1349
1406
  failureReason: scrubSecrets(awsCatchError?.message ?? errorMessage),
@@ -1426,16 +1483,38 @@ export class PiMonoSession implements ProviderSession {
1426
1483
  return;
1427
1484
  }
1428
1485
 
1429
- // Subscribe and wait for agent_end
1486
+ let settled = false;
1487
+ const finish = () => {
1488
+ if (settled) return;
1489
+ settled = true;
1490
+ unsub();
1491
+ this.idleResolvers.delete(finish);
1492
+ resolve();
1493
+ };
1494
+
1495
+ // Subscribe and wait for agent_end (or abort)
1430
1496
  const unsub = this.agentSession.subscribe((event) => {
1431
1497
  if (event.type === "agent_end") {
1432
- unsub();
1433
- resolve();
1498
+ finish();
1434
1499
  }
1435
1500
  });
1501
+ this.idleResolvers.add(finish);
1502
+ if (this.abortRequested) {
1503
+ finish();
1504
+ }
1436
1505
  });
1437
1506
  }
1438
1507
 
1508
+ private safeBuildCostData(isError: boolean): CostData | undefined {
1509
+ try {
1510
+ const stats = this.agentSession.getSessionStats();
1511
+ if (!stats) return undefined;
1512
+ return { ...this.buildCostData(stats), isError };
1513
+ } catch {
1514
+ return undefined;
1515
+ }
1516
+ }
1517
+
1439
1518
  private buildCostData(stats: SessionStats): CostData {
1440
1519
  return {
1441
1520
  sessionId: "", // Runner overrides with runner session ID
@@ -1475,6 +1554,9 @@ export class PiMonoSession implements ProviderSession {
1475
1554
 
1476
1555
  async abort(): Promise<void> {
1477
1556
  this.abortRequested = true;
1557
+ for (const resolve of [...this.idleResolvers]) {
1558
+ resolve();
1559
+ }
1478
1560
  await this.agentSession.abort();
1479
1561
  }
1480
1562
 
@@ -1684,12 +1766,15 @@ function escapeValues(values: unknown): Record<string, string> {
1684
1766
  /**
1685
1767
  * Map installed servers (`fetchInstalledMcpServers(..., "claude")` entries)
1686
1768
  * 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.
1769
+ * HTTP, which is how the previous pi client already reached them. With
1770
+ * `deferred`, their tools load through `tool_search` like non-core swarm
1771
+ * tools; otherwise they are declared up front.
1689
1772
  */
1690
1773
  export function toPiMcpServers(
1691
1774
  installed: Record<string, Record<string, unknown>> | null,
1775
+ options: { deferred?: boolean } = {},
1692
1776
  ): Record<string, McpServerConfig> {
1777
+ const exposure = options.deferred ? "deferred" : "direct";
1693
1778
  const servers: Record<string, McpServerConfig> = {};
1694
1779
  for (const [name, entry] of Object.entries(installed ?? {})) {
1695
1780
  if (typeof entry.command === "string") {
@@ -1698,14 +1783,14 @@ export function toPiMcpServers(
1698
1783
  command: entry.command,
1699
1784
  args: Array.isArray(entry.args) ? entry.args.map(String) : [],
1700
1785
  env: escapeValues(entry.env),
1701
- exposure: "direct",
1786
+ exposure,
1702
1787
  };
1703
1788
  } else if (typeof entry.url === "string") {
1704
1789
  servers[name] = {
1705
1790
  type: "http",
1706
1791
  url: entry.url,
1707
1792
  headers: escapeValues(entry.headers),
1708
- exposure: "direct",
1793
+ exposure,
1709
1794
  };
1710
1795
  }
1711
1796
  }
@@ -1860,7 +1945,7 @@ export class PiMonoAdapter implements ProviderAdapter {
1860
1945
  config.apiUrl && config.apiKey && config.agentId
1861
1946
  ? await fetchInstalledMcpServers(config.apiUrl, config.apiKey, config.agentId, "claude")
1862
1947
  : null;
1863
- const piMcpServers = toPiMcpServers(installedMcpServers);
1948
+ const piMcpServers = toPiMcpServers(installedMcpServers, { deferred: deferTools });
1864
1949
  if (Object.keys(piMcpServers).length > 0) {
1865
1950
  console.log(
1866
1951
  `\x1b[2m[${config.role}]\x1b[0m Registering ${Object.keys(piMcpServers).length} installed MCP server(s) with pi`,
@@ -340,6 +340,7 @@ export const LEGACY_POLICY = {
340
340
  "mcp-oauth.authorize.any": anyAuthenticated,
341
341
  "kv.write.any": leadOrOwnNamespace,
342
342
  "comb.presence": operatorOrUser,
343
+ "fs.join.own": anyAuthenticated,
343
344
  "page.delete.any": leadOrResourceOwner,
344
345
  "app.manage": anyAuthenticated,
345
346
  "app.use": anyAuthenticated,
@@ -314,6 +314,10 @@ export const PERMISSIONS = {
314
314
  description: "Use dashboard presence rooms for Comb drives.",
315
315
  namespace: "comb",
316
316
  },
317
+ "fs.join.own": {
318
+ description: "Join the swarm's shared agent-fs drive with your own account email.",
319
+ namespace: "fs",
320
+ },
317
321
  "page.delete.any": {
318
322
  description: "Delete a page the caller does not own.",
319
323
  namespace: "page",
@@ -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
  }
@@ -106,6 +106,7 @@ async function executeScheduleScript(schedule: ScheduledTask): Promise<void> {
106
106
  source: script.source,
107
107
  args,
108
108
  fsMode: "none",
109
+ executor: script.executor,
109
110
  agentId,
110
111
  egressSecrets: credentials.egressSecrets,
111
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;