@desplega.ai/agent-swarm 1.165.0 → 1.167.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 (296) hide show
  1. package/dist/acp-adapter-zpvy6321.js +34 -0
  2. package/dist/{actions-jna0b3wm.js → actions-fq2zrwtf.js} +30 -34
  3. package/dist/{amp-adapter-0eve81pz.js → amp-adapter-5gbk7501.js} +7 -7
  4. package/dist/{anthropic-messages-hst62ws9.js → anthropic-messages-13yn502s.js} +4 -4
  5. package/dist/{app-75hm5cfa.js → app-nctetn1e.js} +11 -11
  6. package/dist/{assistant-yzd51j33.js → assistant-0aq8gjvj.js} +31 -35
  7. package/dist/{azure-openai-responses-mh8tvf78.js → azure-openai-responses-9st34pfz.js} +5 -5
  8. package/dist/{boot-reembed-z158xq32.js → boot-reembed-1ze5853n.js} +14 -16
  9. package/dist/{boot-reembed-2ex2mcsh.js → boot-reembed-fmp5ww69.js} +15 -16
  10. package/dist/boot-scrub-sweep-cbp8xd4e.js +212 -0
  11. package/dist/{boot-seeds-2bmnh18p.js → boot-seeds-22tx8a53.js} +13 -12
  12. package/dist/{claude-adapter-jbj1mycz.js → claude-adapter-0jhgeh1d.js} +25 -25
  13. package/dist/{claude-managed-adapter-17ggz09t.js → claude-managed-adapter-tzwxy803.js} +5 -5
  14. package/dist/{claude-managed-setup-ptm1nzm2.js → claude-managed-setup-nrhfx5b0.js} +5 -5
  15. package/dist/{claude-sdk-session-9re1g7r2.js → claude-sdk-session-wh77rh83.js} +2632 -2614
  16. package/dist/{cli-zsh093hk.js → cli-014vns2y.js} +63 -1
  17. package/dist/{cli-qvfk83fy.js → cli-13xwcqvj.js} +2 -2
  18. package/dist/{cli-8gpbwy09.js → cli-1yrcfa5s.js} +5 -5
  19. package/dist/{cli-xtgtyafv.js → cli-577bxkf6.js} +212 -451
  20. package/dist/{cli-ma3zvh39.js → cli-5qkcdv8c.js} +6118 -5874
  21. package/dist/{cli-cwxhbf43.js → cli-5t5my0h8.js} +85 -2
  22. package/dist/{cli-zvckv750.js → cli-69egj654.js} +2 -2
  23. package/dist/{cli-9xb89xca.js → cli-6bpvv8h4.js} +9 -9
  24. package/dist/{cli-90v942q1.js → cli-6qxmjpem.js} +8 -8
  25. package/dist/{cli-9866bd07.js → cli-6vfz44sr.js} +1 -1
  26. package/dist/{cli-32bca7nr.js → cli-7avy4v8w.js} +1 -1
  27. package/dist/{cli-xb3c6aat.js → cli-7mhy3ths.js} +21 -18
  28. package/dist/{cli-q38n4bq0.js → cli-95zs84qw.js} +3 -3
  29. package/dist/cli-a35hzdve.js +33 -0
  30. package/dist/{cli-x3x0qwky.js → cli-bxqez1p0.js} +51 -23
  31. package/dist/{cli-prt9emct.js → cli-c2tpw9ck.js} +3 -3
  32. package/dist/{cli-0n4fasz0.js → cli-c8r99akr.js} +16 -16
  33. package/dist/{cli-5anb0ev3.js → cli-cexctgn5.js} +89 -26
  34. package/dist/{cli-bns5c8k9.js → cli-cf2vgn9d.js} +105 -91
  35. package/dist/{cli-ng97fjq7.js → cli-cmxkbzzs.js} +33 -7
  36. package/dist/{cli-nexwh8zg.js → cli-g7k7zbcz.js} +12 -5
  37. package/dist/{cli-mmzwwptd.js → cli-hf5s9pgf.js} +1 -1
  38. package/dist/{cli-nvvfd8bq.js → cli-jbzbr36n.js} +4091 -3793
  39. package/dist/{cli-zn2v3t8v.js → cli-jvmtxqtc.js} +27 -15
  40. package/dist/{cli-pkke6geq.js → cli-km4x66j0.js} +83 -65
  41. package/dist/cli-kw0jcfje.js +516 -0
  42. package/dist/{cli-28smfvbn.js → cli-m7vmeepg.js} +32 -19
  43. package/dist/{cli-hpryr6em.js → cli-mb75wpxf.js} +1 -1
  44. package/dist/{cli-qdv8kgz7.js → cli-mkdsy1nq.js} +14018 -3333
  45. package/dist/{cli-b0f70z24.js → cli-mn8qkd8v.js} +1 -1
  46. package/dist/{cli-420bdgw0.js → cli-mv2520nm.js} +1 -1
  47. package/dist/{cli-n9ehc28t.js → cli-mxry91sa.js} +31 -10
  48. package/dist/{cli-wfajtmf7.js → cli-mxvnnv5q.js} +596 -13
  49. package/dist/cli-nkqjj42q.js +104 -0
  50. package/dist/{cli-e7t4snpa.js → cli-p8rt8spp.js} +5 -5
  51. package/dist/{cli-gh5yqzpe.js → cli-pkj1w46r.js} +12 -7
  52. package/dist/cli-q1vnz8wv.js +96 -0
  53. package/dist/{cli-fj86xr3c.js → cli-qd97v6z2.js} +2 -2
  54. package/dist/{cli-ba66wyhy.js → cli-qzmzwegq.js} +60 -20
  55. package/dist/{cli-5b2fjjzh.js → cli-r7eeddkq.js} +14 -9
  56. package/dist/{cli-8ssjez7e.js → cli-rhy7z1rp.js} +22 -11
  57. package/dist/{cli-4mevd1hf.js → cli-s9y81s7j.js} +5 -5
  58. package/dist/{cli-59jn5twa.js → cli-tc5zec7v.js} +7 -7
  59. package/dist/{cli-ttbsse3f.js → cli-tmhsxhb8.js} +1 -1
  60. package/dist/{cli-k6xx3pve.js → cli-tx2zvn23.js} +4 -4
  61. package/dist/{cli-pjxnr1jn.js → cli-ty4bzkqr.js} +5 -5
  62. package/dist/{cli-nfygw434.js → cli-v1pwxreq.js} +55 -52
  63. package/dist/{cli-ftry7975.js → cli-v1w7pbd8.js} +3 -2
  64. package/dist/{cli-10zmp1p7.js → cli-vr5avem2.js} +27 -32
  65. package/dist/{cli-bzr0ftht.js → cli-wnb9b2g3.js} +19 -0
  66. package/dist/cli-wt7ccy4r.js +2654 -0
  67. package/dist/{cli-50eg13rj.js → cli-wydz3feg.js} +549 -321
  68. package/dist/{cli-krqm5f11.js → cli-xezn9nkh.js} +16 -13
  69. package/dist/{cli-8kt0bmgx.js → cli-xf713zz0.js} +2 -2
  70. package/dist/{cli-x7afk5wm.js → cli-xvc3q9br.js} +1 -1
  71. package/dist/{cli-9s3h836g.js → cli-xvdgasgy.js} +1 -1
  72. package/dist/{cli-j0pdrmwg.js → cli-y0kjpfm9.js} +3 -3
  73. package/dist/{cli-6zcfw0x7.js → cli-y195hv1a.js} +1 -1
  74. package/dist/{cli-amwhs8h2.js → cli-ykt8f7va.js} +22 -9
  75. package/dist/{cli-1kzfvd1z.js → cli-ymmc1zxg.js} +15 -11
  76. package/dist/{cli-xnzrzets.js → cli-ypgba1a8.js} +2 -2
  77. package/dist/{cli-bdv39kgj.js → cli-z1dqhc9y.js} +31 -20
  78. package/dist/{cli-fw6vdcq5.js → cli-zp67qg5x.js} +34 -1
  79. package/dist/{cli-qbksc292.js → cli-zy39d433.js} +2 -0
  80. package/dist/cli.js +36 -32
  81. package/dist/{cloudflare-workers-ai-system-one-tt484t61.js → cloudflare-workers-ai-system-one-sta45jf9.js} +7 -5
  82. package/dist/{codex-adapter-3qnxshje.js → codex-adapter-cgesnjkr.js} +20 -20
  83. package/dist/{codex-login-brkxhhdg.js → codex-login-g1cyt5zm.js} +2 -2
  84. package/dist/{codex-session-runner-612wnapp.js → codex-session-runner-8kd7ddhr.js} +20 -20
  85. package/dist/{commands-2ybfcxkj.js → commands-h6zaaxym.js} +11 -10
  86. package/dist/{cursor-adapter-p3jgdehv.js → cursor-adapter-v8mm6bp5.js} +7 -7
  87. package/dist/{db-f4ans0jj.js → db-qg3myn53.js} +13 -10
  88. package/dist/{devin-adapter-dhzt01ff.js → devin-adapter-bffjj5n4.js} +2 -2
  89. package/dist/{dispatcher-pk182apd.js → dispatcher-7vnwj9qw.js} +28 -32
  90. package/dist/{dsh-adapter-7twaeps1.js → dsh-adapter-tz6hfp5r.js} +7 -7
  91. package/dist/{e2b-y8t3jzyz.js → e2b-nd50eccg.js} +1 -1
  92. package/dist/{easter-egg-3d-mtpnw06s.js → easter-egg-3d-66zxervv.js} +2 -2
  93. package/dist/{event-bus-w7ac4n3c.js → event-bus-vnfthafg.js} +2 -2
  94. package/dist/{execute-s4ccjq3n.js → execute-h4jgevyw.js} +61 -11
  95. package/dist/{google-generative-ai-d6nphg3q.js → google-generative-ai-b8zxrpax.js} +5 -5
  96. package/dist/{google-vertex-cpshkref.js → google-vertex-nrdd697t.js} +5 -5
  97. package/dist/grok-adapter-jdt2dz2s.js +39 -0
  98. package/dist/{handlers-g2wnr4t9.js → handlers-2qtkpncr.js} +31 -35
  99. package/dist/{hook-f7jt9vnm.js → hook-51yfcgsv.js} +48 -39
  100. package/dist/{hook-bbdpnkb4.js → hook-qnbdgwvd.js} +15 -17
  101. package/dist/{http-w40q4w94.js → http-jsmtag08.js} +536 -231
  102. package/dist/{index-056j5kzn.js → index-2t9kesn1.js} +13 -12
  103. package/dist/{index-cj1r9q83.js → index-bt8wkv1w.js} +29 -33
  104. package/dist/{index-7n70jwbb.js → index-cx1sb9bq.js} +42 -35
  105. package/dist/{index-9bfssf54.js → index-ekywyv2v.js} +28 -32
  106. package/dist/{index-fdk1t4d6.js → index-n0rxa4ca.js} +11962 -9900
  107. package/dist/{index-k8xc083e.js → index-t4g06w93.js} +28 -32
  108. package/dist/{keepalive-1tj10tf4.js → keepalive-4aezx4bj.js} +16 -12
  109. package/dist/{lead-st0bg7v4.js → lead-r44bkpm5.js} +52 -56
  110. package/dist/{llama-cpp-classify-tgj35k8d.js → llama-cpp-classify-6erb7wwv.js} +3 -1
  111. package/dist/{maintenance-nz71zats.js → maintenance-grz5jrzh.js} +12 -13
  112. package/dist/{mistral-conversations-8445ddjn.js → mistral-conversations-059y51zt.js} +4 -4
  113. package/dist/{node-f0ykebbv.js → node-qm9eb6z0.js} +25 -7
  114. package/dist/{oauth-refresh-sweep-aefzwvvq.js → oauth-refresh-sweep-wm4ctjfj.js} +15 -11
  115. package/dist/{onboard-kgpr9mbn.js → onboard-v60zgqf0.js} +2 -2
  116. package/dist/{openai-codex-responses-9ynm88zy.js → openai-codex-responses-jzxkta9b.js} +8 -7
  117. package/dist/{openai-completions-nhqwjmaw.js → openai-completions-663yncfq.js} +4 -4
  118. package/dist/openai-decisions-vj9nkpf8.js +162 -0
  119. package/dist/{openai-responses-xx1c4kgm.js → openai-responses-m3g43rrt.js} +5 -5
  120. package/dist/{opencode-adapter-0c8e70dk.js → opencode-adapter-yjyqynzf.js} +8 -8
  121. package/dist/{openrouter-images-8yx3487g.js → openrouter-images-kr60n6b9.js} +1 -1
  122. package/dist/{otel-impl-x8zp4y5a.js → otel-impl-ghh4dyf8.js} +2 -2
  123. package/dist/{pi-messages-vxdt8v7j.js → pi-messages-xszk613n.js} +1 -1
  124. package/dist/{pi-mono-adapter-t280ah5c.js → pi-mono-adapter-0eg8kw2c.js} +69 -52
  125. package/dist/{pricing-refresh-9e0q7cwk.js → pricing-refresh-mjrqvnvw.js} +12 -11
  126. package/dist/{rbac-roles-790j3ga8.js → rbac-roles-axv4d74x.js} +11 -10
  127. package/dist/{rbac-roles-kcp6zge2.js → rbac-roles-fd5tskj9.js} +11 -10
  128. package/dist/{render-v2-tch4nefj.js → render-v2-k50j33qd.js} +11 -11
  129. package/dist/{runtime-v2t1r79e.js → runtime-h9ha1mq6.js} +4 -4
  130. package/dist/{setup-rcm303ns.js → setup-4syh0pd2.js} +2 -2
  131. package/dist/{typesafe-system-one-datfr82p.js → typesafe-system-one-e988vty7.js} +7 -5
  132. package/dist/{virtual-modules-wm8mxdxx.js → virtual-modules-ftr9kwe8.js} +11 -11
  133. package/dist/{work-objects-ctg7t70w.js → work-objects-jcdbg8rr.js} +32 -36
  134. package/dist/{worker-5dp0xm55.js → worker-x8j8c9sx.js} +52 -56
  135. package/dist/{x-v3yw66zs.js → x-ma4gnfgy.js} +2 -2
  136. package/openapi.json +5 -2
  137. package/package.json +15 -11
  138. package/src/be/automation-preflight.ts +9 -4
  139. package/src/be/boot-scrub-sweep.ts +302 -0
  140. package/src/be/crypto/key-bootstrap.ts +1 -1
  141. package/src/be/db/approvals.ts +15 -1
  142. package/src/be/db/codex-oauth-device-flows.ts +40 -0
  143. package/src/be/db/config-secrets.ts +31 -0
  144. package/src/be/db/runtime.ts +4 -0
  145. package/src/be/db/script-runs.ts +15 -0
  146. package/src/be/db/tasks/write.ts +1 -1
  147. package/src/be/db-queries/mcp-oauth.ts +5 -0
  148. package/src/be/db-queries/oauth.ts +77 -0
  149. package/src/be/db.ts +162 -59
  150. package/src/be/events.ts +17 -1
  151. package/src/be/memory/index-content.ts +2 -2
  152. package/src/be/memory/providers/openai-embedding.ts +7 -2
  153. package/src/be/memory/providers/sqlite-store.ts +63 -11
  154. package/src/be/memory/raters/llm-summarizer.ts +7 -2
  155. package/src/be/memory/types.ts +14 -0
  156. package/src/be/migrations/201_workflow_replay_columns.sql +10 -0
  157. package/src/be/migrations/202_codex_oauth_device_flows.sql +24 -0
  158. package/src/be/modelsdev-cache.json +61 -0
  159. package/src/be/scripts/db.ts +28 -4
  160. package/src/be/scripts/source-secrets.ts +37 -0
  161. package/src/be/scrub-json.ts +32 -0
  162. package/src/be/sealed-json.ts +59 -0
  163. package/src/be/secret-registry.ts +131 -0
  164. package/src/be/seed-pricing.ts +84 -53
  165. package/src/be/swarm-config-guard.ts +13 -0
  166. package/src/be/workflow-replay.ts +77 -0
  167. package/src/commands/provider-credentials.ts +28 -5
  168. package/src/commands/runner.ts +7 -12
  169. package/src/heartbeat/heartbeat.ts +91 -19
  170. package/src/hooks/hook.ts +72 -27
  171. package/src/http/codex-oauth-device.ts +8 -10
  172. package/src/http/db-query-bounded.ts +3 -0
  173. package/src/http/db-query-shared.ts +107 -15
  174. package/src/http/db-query.ts +10 -2
  175. package/src/http/index.ts +23 -6
  176. package/src/http/mcp.ts +5 -0
  177. package/src/http/onboarding.ts +2 -0
  178. package/src/http/script-runs.ts +9 -1
  179. package/src/http/scripts.ts +15 -0
  180. package/src/http/workflows.ts +3 -2
  181. package/src/oauth/mcp-wrapper.ts +1 -1
  182. package/src/providers/acp-adapter.ts +25 -11
  183. package/src/providers/acp-target-catalog.ts +35 -1
  184. package/src/providers/acp-targets.ts +78 -7
  185. package/src/providers/amp-adapter.ts +1 -1
  186. package/src/providers/claude-adapter.ts +26 -0
  187. package/src/providers/codex-adapter.ts +30 -3
  188. package/src/providers/codex-oauth/auth-json.ts +21 -4
  189. package/src/providers/codex-oauth/types.ts +2 -0
  190. package/src/providers/cursor-adapter.ts +1 -1
  191. package/src/providers/dsh-adapter.ts +10 -1
  192. package/src/providers/grok-adapter.ts +18 -2
  193. package/src/providers/opencode-adapter.ts +21 -5
  194. package/src/providers/pi-mono-extension.ts +56 -25
  195. package/src/providers/pricing-sources.md +2 -0
  196. package/src/script-workflows/supervisor.ts +11 -2
  197. package/src/scripts-runtime/eval-harness.ts +2 -12
  198. package/src/scripts-runtime/executors/quickjs-bundle.ts +152 -0
  199. package/src/scripts-runtime/executors/quickjs-prelude.ts +285 -0
  200. package/src/scripts-runtime/executors/quickjs-runner.ts +580 -0
  201. package/src/scripts-runtime/executors/quickjs-worker.ts +81 -0
  202. package/src/scripts-runtime/executors/quickjs.ts +196 -0
  203. package/src/scripts-runtime/executors/registry.ts +2 -0
  204. package/src/scripts-runtime/executors/sourcemap.ts +79 -0
  205. package/src/scripts-runtime/script-error-hints.ts +14 -0
  206. package/src/server.ts +66 -4
  207. package/src/tests/acp-adapter.test.ts +277 -12
  208. package/src/tests/boot-scrub-sweep.test.ts +390 -0
  209. package/src/tests/check-text-columns.test.ts +145 -0
  210. package/src/tests/ci-unit-test-report.test.ts +73 -0
  211. package/src/tests/claude-default-route.test.ts +75 -0
  212. package/src/tests/claude-hook-output.test.ts +233 -0
  213. package/src/tests/claude-sdk-process.fixture.ts +13 -2
  214. package/src/tests/codex-adapter-pool-auth-revalidate.test.ts +4 -2
  215. package/src/tests/codex-adapter.test.ts +166 -26
  216. package/src/tests/codex-oauth-auth-timestamps.test.ts +76 -0
  217. package/src/tests/codex-oauth-device.test.ts +23 -37
  218. package/src/tests/codex-oauth.test.ts +4 -1
  219. package/src/tests/context-window.test.ts +2 -2
  220. package/src/tests/credential-check.test.ts +51 -1
  221. package/src/tests/db-query-denylist.test.ts +202 -0
  222. package/src/tests/db-task-read-contract.test.ts +1 -0
  223. package/src/tests/db-task-write-contract.test.ts +1 -0
  224. package/src/tests/dsh-adapter.test.ts +23 -1
  225. package/src/tests/enabled-tools.test.ts +269 -0
  226. package/src/tests/entrypoint-acp-target-binary.test.ts +31 -1
  227. package/src/tests/entrypoint-codex-oauth-seed.test.ts +12 -0
  228. package/src/tests/events-scrub.test.ts +88 -0
  229. package/src/tests/fixtures/secret-corpus.ts +320 -0
  230. package/src/tests/grok-adapter.test.ts +13 -0
  231. package/src/tests/haiku-5-5-pricing.test.ts +38 -0
  232. package/src/tests/heartbeat-checklist.test.ts +158 -1
  233. package/src/tests/onboarding.test.ts +44 -0
  234. package/src/tests/opencode-adapter.test.ts +66 -3
  235. package/src/tests/opencode-plugin.test.ts +37 -0
  236. package/src/tests/openrouter-attribution-call-sites.test.ts +204 -0
  237. package/src/tests/openrouter-base-url.test.ts +30 -39
  238. package/src/tests/pi-mono-extension.test.ts +161 -2
  239. package/src/tests/pricing-refresh.test.ts +70 -1
  240. package/src/tests/reasoning-effort.test.ts +3 -1
  241. package/src/tests/script-connections.test.ts +5 -0
  242. package/src/tests/script-executor-conformance.test.ts +3 -0
  243. package/src/tests/script-executor-quickjs.test.ts +442 -0
  244. package/src/tests/script-executor-registry.test.ts +6 -1
  245. package/src/tests/script-runs-http.test.ts +88 -1
  246. package/src/tests/script-runs-scrub.test.ts +275 -0
  247. package/src/tests/scripts-http.test.ts +82 -1
  248. package/src/tests/scrubbed-text-12e.test.ts +289 -0
  249. package/src/tests/sealed-key-guard.test.ts +166 -0
  250. package/src/tests/secret-canary-sweep.test.ts +958 -0
  251. package/src/tests/secret-registry.test.ts +355 -0
  252. package/src/tests/secret-rules.test.ts +230 -0
  253. package/src/tests/secret-scrubber.test.ts +3 -2
  254. package/src/tests/telemetry-trigger.test.ts +12 -1
  255. package/src/tests/workflow-llm.test.ts +14 -0
  256. package/src/tests/workflow-replay.test.ts +518 -0
  257. package/src/tests/workflow-scrub.test.ts +242 -0
  258. package/src/tests/workflow-system-one-decision-providers.test.ts +33 -0
  259. package/src/tools/db-query.ts +1 -1
  260. package/src/tools/inject-learning.ts +1 -1
  261. package/src/tools/request-human-input.ts +7 -2
  262. package/src/tools/workflows/get-workflow-run.ts +3 -2
  263. package/src/types.ts +1 -1
  264. package/src/utils/enabled-tools.ts +27 -0
  265. package/src/utils/internal-ai/complete-structured.ts +11 -3
  266. package/src/utils/openrouter-base-url.ts +23 -0
  267. package/src/utils/secret-rules.generated.ts +2055 -0
  268. package/src/utils/secret-scrubber.ts +363 -29
  269. package/src/workflows/engine.ts +39 -4
  270. package/src/workflows/executors/raw-llm.ts +1 -0
  271. package/src/workflows/executors/system-one-decision.ts +2 -0
  272. package/src/workflows/executors/validate.ts +1 -0
  273. package/src/workflows/executors/workflow-llm.ts +9 -2
  274. package/src/workflows/recovery.ts +10 -1
  275. package/src/workflows/resume.ts +62 -3
  276. package/src/workflows/retry-poller.ts +3 -0
  277. package/templates/skills/agent-browser/SKILL.md +2 -0
  278. package/templates/skills/agent-browser/content.md +2 -0
  279. package/templates/skills/code-quality/content.md +1 -0
  280. package/templates/skills/heartbeat-runbook/content.md +1 -1
  281. package/templates/skills/work-on-task/SKILL.md +4 -0
  282. package/templates/skills/work-on-task/content.md +4 -0
  283. package/templates/skills/workflow-iterate/content.md +9 -0
  284. package/dist/acp-adapter-w6zws6me.js +0 -23
  285. package/dist/boot-scrub-logs-246yn076.js +0 -99
  286. package/dist/cli-3a4m1aws.js +0 -572
  287. package/dist/cli-3dpj7atn.js +0 -11
  288. package/dist/cli-a87b04k8.js +0 -951
  289. package/dist/cli-cvk6yzw5.js +0 -40
  290. package/dist/cli-gvcgmx69.js +0 -183
  291. package/dist/cli-h1spv8v4.js +0 -427
  292. package/dist/cli-jqt9hrnz.js +0 -168
  293. package/dist/cli-yhmwmv0g.js +0 -9281
  294. package/dist/grok-adapter-yt9pab3k.js +0 -29
  295. package/src/be/boot-scrub-logs.ts +0 -132
  296. package/dist/{codex-hook-d2rkvf39.js → codex-hook-15dgc4cb.js} +1 -1
@@ -2,8 +2,9 @@ import { afterAll, afterEach, beforeAll, describe, expect, test } from "bun:test
2
2
  import { unlink } from "node:fs/promises";
3
3
  import { closeDb, getActivePricingRow, getDbClient, getLogsByEventType, initDb } from "../be/db";
4
4
  import { getModelsCatalog, resetModelsCatalogForTests } from "../be/models-catalog";
5
- import type { ModelsDevCache } from "../be/modelsdev-cache";
5
+ import { loadModelsDevCache, type ModelsDevCache } from "../be/modelsdev-cache";
6
6
  import { refreshPricingFromModelsDev } from "../be/pricing-refresh";
7
+ import { buildModelsDevSeedRows, buildPricingSeedRows } from "../be/seed-pricing";
7
8
 
8
9
  const TEST_DB_PATH = "./test-pricing-refresh.sqlite";
9
10
 
@@ -201,6 +202,74 @@ describe("models.dev runtime pricing refresh", () => {
201
202
  expect(rows.map((row) => row.effective_from)).toEqual([2_000, 3_000]);
202
203
  });
203
204
 
205
+ test("refreshes when dated snapshots collapse onto one amp key at different prices", async () => {
206
+ // models.dev's live gpt-4o family. Amp strips the dated suffix, so all four
207
+ // land on amp|gpt-4o; the 2024-05-13 snapshot is priced higher. Its key
208
+ // comes first, as upstream serves it, so a first-wins builder would pick it.
209
+ const cache: ModelsDevCache = {
210
+ openai: {
211
+ models: {
212
+ "gpt-4o-2024-05-13": { cost: { input: 5, output: 15 } },
213
+ "gpt-4o": { cost: { input: 2.5, output: 10, cache_read: 1.25 } },
214
+ "gpt-4o-2024-08-06": { cost: { input: 2.5, output: 10, cache_read: 1.25 } },
215
+ "gpt-4o-2024-11-20": { cost: { input: 2.5, output: 10, cache_read: 1.25 } },
216
+ },
217
+ },
218
+ };
219
+
220
+ const result = await refreshPricingFromModelsDev({
221
+ now: 1_000,
222
+ fetchImpl: async () => responseFor(cache, '"etag-collapse"'),
223
+ });
224
+
225
+ expect(result.status).toBe("refreshed");
226
+ expect(await getLogsByEventType("pricing.refresh.failed")).toHaveLength(0);
227
+ // The undated alias wins every token class.
228
+ expect((await getActivePricingRow("amp", "gpt-4o", "input", 1_000))?.pricePerMillionUsd).toBe(
229
+ 2.5,
230
+ );
231
+ expect((await getActivePricingRow("amp", "gpt-4o", "output", 1_000))?.pricePerMillionUsd).toBe(
232
+ 10,
233
+ );
234
+ expect(
235
+ (await getActivePricingRow("amp", "gpt-4o", "cached_input", 1_000))?.pricePerMillionUsd,
236
+ ).toBe(1.25);
237
+ // Providers that keep the dated id still price each snapshot on its own.
238
+ expect(
239
+ (await getActivePricingRow("codex", "gpt-4o-2024-05-13", "input", 1_000))?.pricePerMillionUsd,
240
+ ).toBe(5);
241
+ });
242
+
243
+ test("picks the newest dated snapshot when no undated alias exists", () => {
244
+ const rows = buildModelsDevSeedRows({
245
+ openai: {
246
+ models: {
247
+ "gpt-x-2025-08-07": { cost: { input: 2, output: 8 } },
248
+ "gpt-x-2025-01-01": { cost: { input: 1, output: 4 } },
249
+ },
250
+ },
251
+ }).filter((row) => row.provider === "amp");
252
+
253
+ expect(rows).toEqual([
254
+ { provider: "amp", model: "gpt-x", tokenClass: "input", pricePerMillionUsd: 2 },
255
+ { provider: "amp", model: "gpt-x", tokenClass: "output", pricePerMillionUsd: 8 },
256
+ ]);
257
+ });
258
+
259
+ test("the vendored models.dev snapshot yields one row per (provider, model, tokenClass)", () => {
260
+ const cache = loadModelsDevCache();
261
+ expect(cache).not.toBeNull();
262
+ const rows = buildPricingSeedRows(cache);
263
+ const seen = new Set<string>();
264
+ const duplicates: string[] = [];
265
+ for (const row of rows) {
266
+ const key = `${row.provider}|${row.model}|${row.tokenClass}`;
267
+ if (seen.has(key)) duplicates.push(key);
268
+ seen.add(key);
269
+ }
270
+ expect(duplicates).toEqual([]);
271
+ });
272
+
204
273
  test("writes scrubbed audit log entries for successful refreshes", async () => {
205
274
  await refreshPricingFromModelsDev({
206
275
  now: 1_000,
@@ -305,7 +305,9 @@ describe("Claude CLI shortnames (the tier defaults) take effort like the model t
305
305
  expect(reasoningCapability("claude", "sonnet")).toEqual(
306
306
  reasoningCapability("claude", "claude-sonnet-5-5"),
307
307
  );
308
- expect(reasoningCapability("claude", "haiku").levels).toContain("off");
308
+ expect(reasoningCapability("claude", "haiku")).toEqual(
309
+ reasoningCapability("claude", "claude-haiku-5-5"),
310
+ );
309
311
  expect(reasoningCapability("claude", "fable").supported).toBe(true);
310
312
  });
311
313
 
@@ -610,6 +610,11 @@ describe("script connections", () => {
610
610
  markMigrationApplied(database, "198_permission_audit_guest.sql");
611
611
  // 200 backfills agent_memory, which this migration-112-only fixture does not create.
612
612
  markMigrationApplied(database, "200_backfill_auto_memory_expiry.sql");
613
+ // 201 alters workflow_runs and workflow_run_steps, which this
614
+ // migration-112-only fixture does not create.
615
+ markMigrationApplied(database, "201_workflow_replay_columns.sql");
616
+ // 202 moves rows out of kv_entries, which this migration-112-only fixture does not create.
617
+ markMigrationApplied(database, "202_codex_oauth_device_flows.sql");
613
618
 
614
619
  const id = crypto.randomUUID();
615
620
  const now = new Date().toISOString();
@@ -1,5 +1,6 @@
1
1
  import { describe, expect, test } from "bun:test";
2
2
  import { classifyExit, NativeScriptExecutor } from "../scripts-runtime/executors/native";
3
+ import { QuickJSScriptExecutor } from "../scripts-runtime/executors/quickjs";
3
4
  import type {
4
5
  ExecutorInput,
5
6
  ExecutorOutput,
@@ -128,6 +129,8 @@ function conformance(name: string, makeExecutor: () => ScriptExecutor, spawns: b
128
129
 
129
130
  conformance("native", () => new NativeScriptExecutor(), true);
130
131
  conformance("fake", () => new FakeScriptExecutor(), false);
132
+ // Worker threads and WASM, no Bun.spawn: runs even when the spawn probe fails.
133
+ conformance("quickjs", () => new QuickJSScriptExecutor(), false);
131
134
 
132
135
  describe("native-only executor behavior", () => {
133
136
  test.skipIf(SKIP_SANDBOX_SPAWN_TESTS)("timeout maps to timeout", async () => {
@@ -0,0 +1,442 @@
1
+ import { afterAll, describe, expect, test } from "bun:test";
2
+ import { QuickJSScriptExecutor, quickJSWorkerCount } from "../scripts-runtime/executors/quickjs";
3
+ import type { ExecutorInput } from "../scripts-runtime/executors/types";
4
+ import { DEFAULT_SCRIPT_RESOURCES } from "../scripts-runtime/executors/types";
5
+
6
+ // Behavior specific to the quickjs executor. The shared contract lives in
7
+ // script-executor-conformance.test.ts. No test here spawns a process.
8
+
9
+ const requests: Array<{ method: string; path: string; auth: string | null; agent: string | null }> =
10
+ [];
11
+ const kv = new Map<string, unknown>();
12
+ // Requests to /hang (or kv key "hang") never get a response. The test reads
13
+ // how many of them the client aborted.
14
+ const hang = { started: 0, aborted: 0 };
15
+ const slow = { inFlight: 0, maxInFlight: 0 };
16
+ function hangUntilAborted(req: Request): Promise<Response> {
17
+ hang.started++;
18
+ return new Promise((resolve) => {
19
+ req.signal.addEventListener("abort", () => {
20
+ hang.aborted++;
21
+ resolve(new Response("aborted"));
22
+ });
23
+ });
24
+ }
25
+ async function waitFor(check: () => boolean, timeoutMs = 3_000): Promise<boolean> {
26
+ const until = Date.now() + timeoutMs;
27
+ while (!check()) {
28
+ if (Date.now() > until) return false;
29
+ await Bun.sleep(10);
30
+ }
31
+ return true;
32
+ }
33
+ const server = Bun.serve({
34
+ port: 0,
35
+ async fetch(req) {
36
+ const url = new URL(req.url);
37
+ requests.push({
38
+ method: req.method,
39
+ path: url.pathname,
40
+ auth: req.headers.get("authorization"),
41
+ agent: req.headers.get("x-agent-id"),
42
+ });
43
+ if (url.pathname === "/hang" || url.pathname === "/api/kv/hang") return hangUntilAborted(req);
44
+ if (url.pathname === "/slow") {
45
+ slow.inFlight++;
46
+ slow.maxInFlight = Math.max(slow.maxInFlight, slow.inFlight);
47
+ await Bun.sleep(30);
48
+ slow.inFlight--;
49
+ return Response.json({ ok: true });
50
+ }
51
+ if (url.pathname.startsWith("/api/kv/")) {
52
+ const key = decodeURIComponent(url.pathname.slice("/api/kv/".length));
53
+ if (req.method === "PUT") {
54
+ const body = (await req.json()) as { value: unknown };
55
+ kv.set(key, body.value);
56
+ return Response.json({ key, value: body.value });
57
+ }
58
+ return Response.json({ key, value: kv.get(key) ?? null });
59
+ }
60
+ if (url.pathname === "/echo") {
61
+ return Response.json(
62
+ { method: req.method, body: await req.text(), custom: req.headers.get("x-custom") },
63
+ { headers: { "x-reply": "yes" } },
64
+ );
65
+ }
66
+ return Response.json({ error: "not found" }, { status: 404 });
67
+ },
68
+ });
69
+ afterAll(() => server.stop(true));
70
+
71
+ const baseUrl = `http://127.0.0.1:${server.port}`;
72
+ const executor = new QuickJSScriptExecutor();
73
+
74
+ function input(source: string, overrides: Partial<ExecutorInput> = {}): ExecutorInput {
75
+ return {
76
+ source,
77
+ args: null,
78
+ configPayload: {
79
+ system: {
80
+ apiKey: { value: "quickjs-test-secret", isSecret: true },
81
+ agentId: { value: "agent-q", isSecret: false },
82
+ mcpBaseUrl: { value: baseUrl, isSecret: false },
83
+ },
84
+ user: { REGION: { value: "eu", isSecret: false } },
85
+ },
86
+ resources: { ...DEFAULT_SCRIPT_RESOURCES, wallClockMs: 5_000, ...overrides.resources },
87
+ fsMode: "none",
88
+ network: "open",
89
+ ...overrides,
90
+ };
91
+ }
92
+
93
+ describe("quickjs executor", () => {
94
+ test("runs TypeScript with a tree-shaken zod argsSchema", async () => {
95
+ const source = `import { z } from "zod";
96
+ export const argsSchema = z.object({ email: z.string().email(), n: z.number().int() });
97
+ type Args = z.infer<typeof argsSchema>;
98
+ export default async function (args: Args) {
99
+ return { domain: args.email.split("@")[1], double: args.n * 2 };
100
+ }`;
101
+ const ok = await executor.run({ ...input(source), args: { email: "a@b.co", n: 3 } });
102
+ expect(ok.error).toBeUndefined();
103
+ expect(ok.result).toEqual({ domain: "b.co", double: 6 });
104
+
105
+ const bad = await executor.run({ ...input(source), args: { email: "nope", n: 1.5 } });
106
+ expect(bad.error).toBe("eval_error");
107
+ expect(bad.runtimeError?.message).toContain("argsSchema validation failed:");
108
+ expect(bad.runtimeError?.message).toContain("email");
109
+ });
110
+
111
+ test("accepts args that arrive as a JSON string", async () => {
112
+ const output = await executor.run({
113
+ ...input("export default async (args) => args.x + 1;"),
114
+ args: JSON.stringify({ x: 41 }),
115
+ });
116
+ expect(output.result).toBe(42);
117
+ });
118
+
119
+ test("maps runtime errors back to the original TypeScript line", async () => {
120
+ const source = `type T = { a: number };
121
+
122
+ function explode(value: T): never {
123
+ throw new Error("boom " + value.a);
124
+ }
125
+
126
+ export default async (args: T) => explode(args);
127
+ `;
128
+ const output = await executor.run({ ...input(source), args: { a: 7 } });
129
+ expect(output.error).toBe("eval_error");
130
+ expect(output.runtimeError?.message).toBe("boom 7");
131
+ expect(output.runtimeError?.userScriptLine).toBe(4);
132
+ expect(output.runtimeError?.userFrames[0]?.file).toBe("user-script.ts");
133
+ expect(output.stderr).toContain("user-script.ts:4:");
134
+ });
135
+
136
+ test("adds the (args, ctx) hint when ctx members are read off undefined", async () => {
137
+ const output = await executor.run(
138
+ input("export default async (ctx) => { const { log } = ctx.api; return log; };"),
139
+ );
140
+ expect(output.error).toBe("eval_error");
141
+ expect((output.runtimeError as { ctxSignatureHint?: string }).ctxSignatureHint).toContain(
142
+ "args comes first",
143
+ );
144
+ });
145
+
146
+ test("reports syntax errors as eval_error with the build diagnostic", async () => {
147
+ const output = await executor.run(input("export default async () => { return (; };"));
148
+ expect(output.error).toBe("eval_error");
149
+ expect(output.runtimeError?.name).toBe("BuildError");
150
+ expect(output.stderr.length).toBeGreaterThan(0);
151
+ });
152
+
153
+ test("interrupts a synchronous busy loop and keeps the worker usable", async () => {
154
+ const looped = await executor.run(
155
+ input("export default async () => { while (true) {} };", {
156
+ resources: { ...DEFAULT_SCRIPT_RESOURCES, wallClockMs: 200 },
157
+ }),
158
+ );
159
+ expect(looped.error).toBe("timeout");
160
+ expect(looped.exitCode).toBe(124);
161
+
162
+ const after = await executor.run(input("export default async () => 'alive';"));
163
+ expect(after.result).toBe("alive");
164
+ });
165
+
166
+ test("times out a promise that never settles", async () => {
167
+ const output = await executor.run(
168
+ input("export default async () => new Promise(() => {});", {
169
+ resources: { ...DEFAULT_SCRIPT_RESOURCES, wallClockMs: 150 },
170
+ }),
171
+ );
172
+ expect(output.error).toBe("timeout");
173
+ });
174
+
175
+ test("enforces the heap limit as oom", async () => {
176
+ const output = await executor.run(
177
+ // Growing one array hits the limit in ~100 ms. Many small strings make
178
+ // QuickJS's GC thrash near the limit, which takes seconds to fail.
179
+ input(
180
+ "export default async () => { const a = []; for (let i = 0; i < 1e9; i++) a.push(i); };",
181
+ {
182
+ resources: { ...DEFAULT_SCRIPT_RESOURCES, memoryMb: 16, wallClockMs: 10_000 },
183
+ },
184
+ ),
185
+ );
186
+ expect(output.error).toBe("oom");
187
+ });
188
+
189
+ test("an abort kills a busy script and the pool recovers", async () => {
190
+ const controller = new AbortController();
191
+ setTimeout(() => controller.abort(), 100);
192
+ const output = await executor.run(
193
+ input("export default async () => { while (true) {} };", {
194
+ signal: controller.signal,
195
+ resources: { ...DEFAULT_SCRIPT_RESOURCES, wallClockMs: 10_000 },
196
+ }),
197
+ );
198
+ expect(output.error).toBe("killed");
199
+
200
+ const after = await executor.run(input("export default async () => 'recovered';"));
201
+ expect(after.result).toBe("recovered");
202
+ });
203
+
204
+ test("exposes no process, filesystem, or module globals", async () => {
205
+ // Read off globalThis: Bun.build constant-folds a bare `typeof require` to "function".
206
+ const output = await executor.run(
207
+ input(`export default async () => ({
208
+ process: typeof globalThis.process,
209
+ bun: typeof globalThis.Bun,
210
+ require: typeof globalThis.require,
211
+ })`),
212
+ );
213
+ expect(output.result).toEqual({ process: "undefined", bun: "undefined", require: "undefined" });
214
+ });
215
+
216
+ test("glob and grep fail with a clear message", async () => {
217
+ const output = await executor.run(
218
+ input("export default async (_args, ctx) => ctx.stdlib.glob('*.ts');"),
219
+ );
220
+ expect(output.error).toBe("eval_error");
221
+ expect(output.runtimeError?.message).toContain("not available in the quickjs executor");
222
+ });
223
+
224
+ test("routes console output to stdout and stderr", async () => {
225
+ const output = await executor.run(
226
+ input(
227
+ "export default async (_args, ctx) => { console.log('hello', { a: 1 }); ctx.logger.error('bad'); return 1; };",
228
+ ),
229
+ );
230
+ expect(output.stdout).toBe('hello {"a":1}\n');
231
+ expect(output.stderr).toBe("bad\n");
232
+ });
233
+
234
+ test("setTimeout runs callbacks through the host clock", async () => {
235
+ const output = await executor.run(
236
+ input(`export default async () => {
237
+ const order: string[] = [];
238
+ const cancelled = setTimeout(() => order.push("cancelled"), 5);
239
+ clearTimeout(cancelled);
240
+ await new Promise((resolve) => setTimeout(() => { order.push("fired"); resolve(null); }, 10));
241
+ return order;
242
+ };`),
243
+ );
244
+ expect(output.result).toEqual(["fired"]);
245
+ });
246
+
247
+ test("ctx.swarm calls go through the host with the swarm identity", async () => {
248
+ requests.length = 0;
249
+ const output = await executor.run(
250
+ input(`export default async (_args, ctx) => {
251
+ await ctx.swarm.kv_set({ key: "greeting", value: "hi" });
252
+ const got = await ctx.swarm.kv_get({ key: "greeting" });
253
+ return got.data.value;
254
+ };`),
255
+ );
256
+ expect(output.error).toBeUndefined();
257
+ expect(output.result).toBe("hi");
258
+ expect(requests.map((r) => r.method)).toEqual(["PUT", "GET"]);
259
+ expect(requests.every((r) => r.auth === "Bearer quickjs-test-secret")).toBe(true);
260
+ expect(requests.every((r) => r.agent === "agent-q")).toBe(true);
261
+ });
262
+
263
+ test("fetch returns a Response-like object with headers and a text body", async () => {
264
+ const output = await executor.run(
265
+ input(`export default async () => {
266
+ const response = await fetch("${baseUrl}/echo", {
267
+ method: "POST",
268
+ headers: { "X-Custom": "c1" },
269
+ body: JSON.stringify({ n: 1 }),
270
+ });
271
+ return { status: response.status, ok: response.ok, reply: response.headers.get("x-reply"), body: await response.json() };
272
+ };`),
273
+ );
274
+ expect(output.result).toEqual({
275
+ status: 200,
276
+ ok: true,
277
+ reply: "yes",
278
+ body: { method: "POST", body: '{"n":1}', custom: "c1" },
279
+ });
280
+ });
281
+
282
+ test("fetch rejects non-string bodies", async () => {
283
+ const output = await executor.run(
284
+ input(
285
+ `export default async () => fetch("${baseUrl}/echo", { method: "POST", body: { n: 1 } });`,
286
+ ),
287
+ );
288
+ expect(output.error).toBe("eval_error");
289
+ expect(output.runtimeError?.message).toContain("fetch body must be a string");
290
+ });
291
+
292
+ test("user config values stay redacted until unwrapped", async () => {
293
+ const output = await executor.run(
294
+ input(`export default async (_args, ctx) => {
295
+ const region = ctx.swarm.config.get("REGION");
296
+ return {
297
+ wrapped: region,
298
+ text: String(region),
299
+ value: ctx.stdlib.Redacted.value(region),
300
+ meta: ctx.stdlib.Redacted.meta(region),
301
+ missing: ctx.swarm.config.get("NOPE") === undefined,
302
+ };
303
+ };`),
304
+ );
305
+ expect(output.result).toEqual({
306
+ wrapped: "<redacted>",
307
+ text: "<redacted>",
308
+ value: "eu",
309
+ meta: { type: "user", isSecret: false },
310
+ missing: true,
311
+ });
312
+ });
313
+
314
+ test("a non-serializable result is an eval_error", async () => {
315
+ const output = await executor.run(
316
+ input("export default async () => { const a: any = {}; a.self = a; return a; };"),
317
+ );
318
+ expect(output.error).toBe("eval_error");
319
+ expect(output.runtimeError?.message).toContain("not JSON-serializable");
320
+ });
321
+
322
+ test("recycles a worker whose WASM memory grew past the threshold", async () => {
323
+ await executor.run(input("export default async () => 1;"));
324
+ const before = quickJSWorkerCount();
325
+ const big = await executor.run(
326
+ input(
327
+ "export default async () => { const a = []; for (let i = 0; i < 3e6; i++) a.push({ i }); return a.length; };",
328
+ ),
329
+ );
330
+ expect(big.result).toBe(3e6);
331
+ expect(quickJSWorkerCount()).toBe(before - 1);
332
+
333
+ const after = await executor.run(input("export default async () => 'fresh';"));
334
+ expect(after.result).toBe("fresh");
335
+ });
336
+
337
+ test("host calls only reach own api/mcp/room members", async () => {
338
+ const output = await executor.run(
339
+ input(`export default async () => {
340
+ const call = (path: string) => (globalThis as any).__host_call(path, "[]");
341
+ return [
342
+ JSON.parse(await call("swarm.room.constructor")).error?.message,
343
+ JSON.parse(await call("swarm.constructor")).error?.message,
344
+ JSON.parse(await call("api.__proto__.x")).error?.message,
345
+ ];
346
+ };`),
347
+ );
348
+ expect(output.result).toEqual([
349
+ "ctx.swarm.room.constructor is not a function",
350
+ "ctx.swarm.constructor is not a function",
351
+ "ctx.api.__proto__.x is not a function",
352
+ ]);
353
+ });
354
+
355
+ test("aborts a host fetch the script did not await when the run returns", async () => {
356
+ await executor.run(input("export default async () => 0;"));
357
+ const workers = quickJSWorkerCount();
358
+ const before = { ...hang };
359
+ const output = await executor.run(
360
+ // The 100 ms wait lets the request reach the server before the run ends.
361
+ input(`export default async () => {
362
+ void fetch("${baseUrl}/hang");
363
+ await new Promise((resolve) => setTimeout(resolve, 100));
364
+ return 1;
365
+ };`),
366
+ );
367
+ expect(output.result).toBe(1);
368
+ expect(hang.started).toBe(before.started + 1);
369
+ expect(await waitFor(() => hang.aborted > before.aborted)).toBe(true);
370
+ // The run aborted and settled the fetch itself, so the pool kept the worker.
371
+ // Terminating the worker is only the fallback for calls that do not settle.
372
+ expect(quickJSWorkerCount()).toBe(workers);
373
+ });
374
+
375
+ test("aborts a pending host fetch when the run times out", async () => {
376
+ const before = hang.aborted;
377
+ const output = await executor.run(
378
+ input(`export default async () => (await fetch("${baseUrl}/hang")).text();`, {
379
+ resources: { ...DEFAULT_SCRIPT_RESOURCES, wallClockMs: 200 },
380
+ }),
381
+ );
382
+ expect(output.error).toBe("timeout");
383
+ expect(await waitFor(() => hang.aborted > before)).toBe(true);
384
+
385
+ const after = await executor.run(input("export default async () => 'next';"));
386
+ expect(after.result).toBe("next");
387
+ });
388
+
389
+ test("aborts ctx.swarm host calls too", async () => {
390
+ await executor.run(input("export default async () => 0;"));
391
+ const workers = quickJSWorkerCount();
392
+ const before = { ...hang };
393
+ const output = await executor.run(
394
+ input(
395
+ `export default async (_args, ctx) => {
396
+ void ctx.swarm.kv_get({ key: "hang" });
397
+ await new Promise((resolve) => setTimeout(resolve, 100));
398
+ return 1;
399
+ };`,
400
+ ),
401
+ );
402
+ expect(output.result).toBe(1);
403
+ expect(hang.started).toBe(before.started + 1);
404
+ expect(await waitFor(() => hang.aborted > before.aborted)).toBe(true);
405
+ expect(quickJSWorkerCount()).toBe(workers);
406
+ });
407
+
408
+ test("caps concurrent host calls per run and queues the rest", async () => {
409
+ slow.maxInFlight = 0;
410
+ const output = await executor.run(
411
+ input(`export default async () => {
412
+ const replies = await Promise.all(
413
+ Array.from({ length: 40 }, () => fetch("${baseUrl}/slow").then((r) => r.json())),
414
+ );
415
+ return replies.filter((r: any) => r.ok).length;
416
+ };`),
417
+ );
418
+ expect(output.result).toBe(40);
419
+ expect(slow.maxInFlight).toBeGreaterThan(1);
420
+ expect(slow.maxInFlight).toBeLessThanOrEqual(16);
421
+ });
422
+
423
+ test("rejects host calls beyond the per-run pending limit", async () => {
424
+ const output = await executor.run(
425
+ input(`export default async () => {
426
+ const calls = Array.from({ length: 1100 }, () => fetch("${baseUrl}/slow").then(() => "ok", (e) => e.message));
427
+ const results = await Promise.all(calls);
428
+ return results.filter((r) => r !== "ok").length > 0 ? results.find((r) => r !== "ok") : "none";
429
+ };`),
430
+ );
431
+ expect(output.result).toContain("Too many pending host calls");
432
+ });
433
+
434
+ test("queues runs beyond the pool size", async () => {
435
+ const outputs = await Promise.all(
436
+ Array.from({ length: 12 }, (_, i) =>
437
+ executor.run({ ...input("export default async (args) => args.i * 2;"), args: { i } }),
438
+ ),
439
+ );
440
+ expect(outputs.map((o) => o.result)).toEqual(Array.from({ length: 12 }, (_, i) => i * 2));
441
+ });
442
+ });
@@ -1,5 +1,6 @@
1
1
  import { describe, expect, test } from "bun:test";
2
2
  import { NativeScriptExecutor } from "../scripts-runtime/executors/native";
3
+ import { QuickJSScriptExecutor } from "../scripts-runtime/executors/quickjs";
3
4
  import { getScriptExecutor } from "../scripts-runtime/executors/registry";
4
5
 
5
6
  describe("getScriptExecutor", () => {
@@ -11,7 +12,11 @@ describe("getScriptExecutor", () => {
11
12
  expect(getScriptExecutor("native")).toBeInstanceOf(NativeScriptExecutor);
12
13
  });
13
14
 
15
+ test("returns quickjs when requested", () => {
16
+ expect(getScriptExecutor("quickjs")).toBeInstanceOf(QuickJSScriptExecutor);
17
+ });
18
+
14
19
  test("throws for unknown executors", () => {
15
- expect(() => getScriptExecutor("e2b")).toThrow("Available: native");
20
+ expect(() => getScriptExecutor("e2b")).toThrow("Available: native, quickjs");
16
21
  });
17
22
  });