@desplega.ai/agent-swarm 1.164.1 → 1.166.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 (328) hide show
  1. package/README.md +1 -1
  2. package/dist/acp-adapter-yzfxn8vj.js +23 -0
  3. package/dist/{actions-hp2h32dx.js → actions-qz3m692w.js} +24 -28
  4. package/dist/{amp-adapter-zfd7djyx.js → amp-adapter-j1xbmsqz.js} +7 -7
  5. package/dist/{app-w2d26h4f.js → app-43vrdmv8.js} +9 -10
  6. package/dist/{assistant-45pc4en7.js → assistant-ertffmka.js} +25 -29
  7. package/dist/{boot-reembed-k1q2s4fc.js → boot-reembed-998jtpdg.js} +12 -15
  8. package/dist/{boot-reembed-6rhcpsjf.js → boot-reembed-s7k7c6v2.js} +13 -15
  9. package/dist/boot-scrub-sweep-nepsk4t1.js +211 -0
  10. package/dist/{boot-seeds-ex93edx7.js → boot-seeds-vvzs1zhk.js} +12 -12
  11. package/dist/{claude-adapter-hcxd0szm.js → claude-adapter-2zvmekrp.js} +15 -17
  12. package/dist/{claude-managed-adapter-fz7xps2r.js → claude-managed-adapter-s49wbk8t.js} +5 -5
  13. package/dist/{claude-managed-setup-xx6f15h9.js → claude-managed-setup-cvhqq8qn.js} +4 -4
  14. package/dist/{claude-sdk-session-6nb9raem.js → claude-sdk-session-kkbxtw5q.js} +2624 -2606
  15. package/dist/{cli-etbrt8x1.js → cli-0nhdwxd5.js} +4 -4
  16. package/dist/{cli-jeefgppq.js → cli-0qjcsxgz.js} +7 -7
  17. package/dist/{cli-6behs2bt.js → cli-32bca7nr.js} +1 -1
  18. package/dist/{cli-gt8v96qx.js → cli-3ddybbbx.js} +4 -4
  19. package/dist/{acp-adapter-rxvek4ym.js → cli-3qn4r334.js} +317 -35
  20. package/dist/{cli-1d3w4xm3.js → cli-4r1sjfc4.js} +14 -6
  21. package/dist/{cli-691f2rxm.js → cli-66zh26sp.js} +68 -16
  22. package/dist/{cli-et8wdyqh.js → cli-68r1j4sg.js} +9 -9
  23. package/dist/{cli-xb7kyanp.js → cli-8kgvmkak.js} +3 -3
  24. package/dist/{cli-fdpkw8vk.js → cli-8t229cmr.js} +8 -8
  25. package/dist/{cli-gc54qc0p.js → cli-aqcey741.js} +1 -1
  26. package/dist/{cli-4q4cmr4t.js → cli-cpfmqz3n.js} +8 -8
  27. package/dist/{cli-a9bw0xxt.js → cli-df56tqdw.js} +997 -408
  28. package/dist/{cli-n7h1kqpf.js → cli-dmtrxqhg.js} +6 -1
  29. package/dist/{cli-xfb7333f.js → cli-dw2q9f7g.js} +5 -5
  30. package/dist/{cli-4drstbb9.js → cli-ecknp7p1.js} +81 -23
  31. package/dist/{cli-v0pvvhfq.js → cli-efnd2kce.js} +36 -1
  32. package/dist/{cli-1wetf34q.js → cli-f2n17zhp.js} +1 -1
  33. package/dist/{cli-2c8eky5y.js → cli-feh3dkah.js} +1 -1
  34. package/dist/cli-fn697bnt.js +126 -0
  35. package/dist/{cli-v5pr2fgt.js → cli-gt8azyet.js} +3 -3
  36. package/dist/{cli-dbszf3rw.js → cli-h3x613ze.js} +4 -4
  37. package/dist/{cli-nwhb8y2e.js → cli-hf5s9pgf.js} +1 -1
  38. package/dist/cli-j2jbr8af.js +516 -0
  39. package/dist/{cli-agjzafns.js → cli-jhm6hzjn.js} +593 -10
  40. package/dist/{cli-j0tmc908.js → cli-jvj6a0cv.js} +484 -477
  41. package/dist/{cli-xxz89c2n.js → cli-mb75wpxf.js} +1 -1
  42. package/dist/{cli-07s39g1n.js → cli-mv2520nm.js} +1 -1
  43. package/dist/{cli-v90vfwyb.js → cli-mwegs4af.js} +5 -5
  44. package/dist/{cli-bz1gbac9.js → cli-p4ztb80h.js} +2 -2
  45. package/dist/{cli-1wd1n2ya.js → cli-rfphkzh5.js} +1 -1
  46. package/dist/{cli-976r1zqx.js → cli-rggdq8wq.js} +5 -5
  47. package/dist/{cli-3ajw5v5v.js → cli-rsy933yz.js} +6 -6
  48. package/dist/{cli-yj790e19.js → cli-tadfgd0t.js} +8 -1
  49. package/dist/{cli-cn53e7vc.js → cli-ttbsse3f.js} +6 -1
  50. package/dist/{cli-dyqsrqcf.js → cli-v2a8x45d.js} +4 -4
  51. package/dist/{cli-wbvnn6tk.js → cli-v6vv7dy8.js} +91 -9
  52. package/dist/{cli-z2ap2c0c.js → cli-vzc73wet.js} +90 -55
  53. package/dist/cli-wt7ccy4r.js +2654 -0
  54. package/dist/{cli-4cn19jvn.js → cli-x7kpr72a.js} +3 -3
  55. package/dist/{cli-003785v9.js → cli-xvc3q9br.js} +1 -1
  56. package/dist/{cli-v0e7jpr5.js → cli-xvdgasgy.js} +13 -4
  57. package/dist/{cli-asqeg4ve.js → cli-y59h2pk9.js} +2 -2
  58. package/dist/{cli-0wn3ypqv.js → cli-yshaazs1.js} +14121 -3478
  59. package/dist/{cli-wg8yjqtw.js → cli-ysnyhws9.js} +16 -3
  60. package/dist/cli-zp67qg5x.js +92 -0
  61. package/dist/{cli-g3qgrzck.js → cli-zsh093hk.js} +71 -3
  62. package/dist/{cli-ma3zvh39.js → cli-zy7vwsne.js} +6066 -5829
  63. package/dist/cli.js +79 -26
  64. package/dist/{codex-adapter-pcxdn10v.js → codex-adapter-dw1wxqt8.js} +10 -10
  65. package/dist/{codex-hook-b1fb7c2k.js → codex-hook-d2rkvf39.js} +1 -1
  66. package/dist/{codex-login-059nm145.js → codex-login-terbdsqq.js} +2 -2
  67. package/dist/{codex-session-runner-xzwwddp8.js → codex-session-runner-3cgb8y1g.js} +10 -10
  68. package/dist/{commands-98at8mkn.js → commands-mhxam6z2.js} +9 -9
  69. package/dist/{cursor-adapter-dkg6qnkp.js → cursor-adapter-2qda3st7.js} +6 -6
  70. package/dist/{db-01g8wtvw.js → db-sjks418k.js} +11 -9
  71. package/dist/{devin-adapter-qmttt2fz.js → devin-adapter-bffjj5n4.js} +2 -2
  72. package/dist/{dispatcher-nn2k1wxd.js → dispatcher-1es759rn.js} +22 -26
  73. package/dist/{dsh-adapter-w4d4vwdq.js → dsh-adapter-bek4cyw9.js} +6 -6
  74. package/dist/{e2b-9e4g2hvt.js → e2b-nd50eccg.js} +1 -1
  75. package/dist/{event-bus-z7v2t26e.js → event-bus-vnfthafg.js} +2 -2
  76. package/dist/grok-adapter-ar9cw9jg.js +29 -0
  77. package/dist/{handlers-b0kqsbg8.js → handlers-1gkbhktr.js} +25 -29
  78. package/dist/{hook-sap7v1aj.js → hook-ds2zxq9v.js} +16 -18
  79. package/dist/{hook-pz50gqep.js → hook-vxa3tcsy.js} +10 -12
  80. package/dist/{http-ncr8rmd0.js → http-3h21z9md.js} +679 -227
  81. package/dist/{index-c4aqcyz1.js → index-9b65j1m5.js} +11 -11
  82. package/dist/{index-r46s01nq.js → index-bn6cy8ps.js} +22 -26
  83. package/dist/{index-38kqdt3e.js → index-dkfxpkmm.js} +23 -27
  84. package/dist/{index-630kj5vd.js → index-f8xnqrye.js} +55 -43
  85. package/dist/{index-fdk1t4d6.js → index-n0rxa4ca.js} +11962 -9900
  86. package/dist/{index-9nrj7797.js → index-n99fycq7.js} +22 -26
  87. package/dist/{keepalive-31613r2h.js → keepalive-3xspa9jw.js} +14 -11
  88. package/dist/{lead-mx59tr5s.js → lead-6qphc124.js} +43 -43
  89. package/dist/{maintenance-55ff39ca.js → maintenance-0teem1bb.js} +10 -12
  90. package/dist/{node-f0ykebbv.js → node-9sg7ajby.js} +25 -7
  91. package/dist/{oauth-refresh-sweep-anma954w.js → oauth-refresh-sweep-sh9q25xp.js} +13 -10
  92. package/dist/{onboard-b2fy8ftb.js → onboard-54qmnqxe.js} +4 -3
  93. package/dist/{opencode-adapter-p1s92tmy.js → opencode-adapter-wjfa2wvd.js} +6 -6
  94. package/dist/{otel-impl-9brvt7xv.js → otel-impl-ycqzp2a1.js} +2 -2
  95. package/dist/{pi-mono-adapter-kz4rrwvd.js → pi-mono-adapter-e77e9zst.js} +6 -6
  96. package/dist/{pricing-refresh-7w8mbmva.js → pricing-refresh-atnkngqs.js} +10 -10
  97. package/dist/{rbac-roles-ejp7qwvb.js → rbac-roles-9pjphh9q.js} +10 -10
  98. package/dist/{rbac-roles-nhk4r7bc.js → rbac-roles-j78wdksm.js} +10 -10
  99. package/dist/{render-v2-2gzt8skq.js → render-v2-pxjs49fx.js} +9 -10
  100. package/dist/{setup-5et8t412.js → setup-ymej0xtx.js} +2 -2
  101. package/dist/{work-objects-tb0er7qa.js → work-objects-3nh4ymzb.js} +26 -30
  102. package/dist/{worker-62cv075d.js → worker-qa9fkz5s.js} +43 -43
  103. package/dist/{x-vbxb31jr.js → x-ma4gnfgy.js} +2 -2
  104. package/openapi.json +457 -31
  105. package/package.json +14 -6
  106. package/plugin/commands/create-pr.md +7 -1
  107. package/plugin/pi-skills/create-pr/SKILL.md +7 -1
  108. package/src/agentmail/app.ts +5 -4
  109. package/src/be/automation-preflight.ts +11 -9
  110. package/src/be/boot-scrub-sweep.ts +302 -0
  111. package/src/be/connector-codes.ts +81 -0
  112. package/src/be/crypto/key-bootstrap.ts +1 -1
  113. package/src/be/db/approvals.ts +15 -1
  114. package/src/be/db/codex-oauth-device-flows.ts +40 -0
  115. package/src/be/db/config-secrets.ts +31 -0
  116. package/src/be/db/runtime.ts +4 -0
  117. package/src/be/db/script-runs.ts +15 -0
  118. package/src/be/db/tasks/write.ts +1 -1
  119. package/src/be/db-queries/mcp-oauth.ts +5 -0
  120. package/src/be/db-queries/oauth.ts +77 -0
  121. package/src/be/db.ts +162 -59
  122. package/src/be/events.ts +17 -1
  123. package/src/be/memory/index-content.ts +2 -2
  124. package/src/be/memory/providers/sqlite-store.ts +63 -11
  125. package/src/be/memory/types.ts +14 -0
  126. package/src/be/migrations/197_connector_codes.sql +23 -0
  127. package/src/be/migrations/198_permission_audit_guest.sql +33 -0
  128. package/src/be/migrations/200_backfill_auto_memory_expiry.sql +20 -0
  129. package/src/be/migrations/201_workflow_replay_columns.sql +10 -0
  130. package/src/be/migrations/202_codex_oauth_device_flows.sql +24 -0
  131. package/src/be/model-tier-resolution.ts +2 -1
  132. package/src/be/model-validation.ts +1 -1
  133. package/src/be/pricing-normalize.ts +3 -0
  134. package/src/be/rbac-audit.ts +2 -1
  135. package/src/be/scripts/db.ts +28 -4
  136. package/src/be/scripts/source-secrets.ts +37 -0
  137. package/src/be/scripts/typecheck.ts +23 -2
  138. package/src/be/scrub-json.ts +32 -0
  139. package/src/be/sealed-json.ts +59 -0
  140. package/src/be/secret-registry.ts +131 -0
  141. package/src/be/seed-pricing.ts +14 -0
  142. package/src/be/users.ts +7 -1
  143. package/src/be/workflow-replay.ts +77 -0
  144. package/src/cli.tsx +7 -0
  145. package/src/commands/provider-credentials.ts +46 -8
  146. package/src/commands/runner.ts +2 -10
  147. package/src/extensions/contract-types.generated.ts +1 -1
  148. package/src/github/app.ts +4 -4
  149. package/src/heartbeat/heartbeat.ts +20 -8
  150. package/src/hooks/pr-body-guard.ts +1 -1
  151. package/src/http/agents.ts +1 -0
  152. package/src/http/approval-requests.ts +1 -0
  153. package/src/http/apps.ts +3 -0
  154. package/src/http/auth.ts +4 -1
  155. package/src/http/codex-oauth-device.ts +8 -10
  156. package/src/http/codex-oauth-keep-warm.ts +2 -2
  157. package/src/http/config-values.ts +59 -0
  158. package/src/http/config.ts +5 -0
  159. package/src/http/db-query-bounded.ts +3 -0
  160. package/src/http/db-query-shared.ts +107 -15
  161. package/src/http/db-query.ts +10 -2
  162. package/src/http/extensions.ts +1 -0
  163. package/src/http/index.ts +23 -6
  164. package/src/http/integrations.ts +1 -38
  165. package/src/http/ip-rate-limit.ts +82 -0
  166. package/src/http/kv.ts +38 -4
  167. package/src/http/mcp-servers.ts +1 -0
  168. package/src/http/memory.ts +9 -0
  169. package/src/http/models-catalog.ts +2 -0
  170. package/src/http/operator-actor.ts +4 -0
  171. package/src/http/page-proxy.ts +22 -0
  172. package/src/http/pages.ts +4 -2
  173. package/src/http/realtime.ts +1 -1
  174. package/src/http/request-principal.ts +14 -1
  175. package/src/http/script-connections.ts +8 -0
  176. package/src/http/script-runs.ts +12 -4
  177. package/src/http/scripts.ts +15 -0
  178. package/src/http/session-cost-recompute.ts +3 -2
  179. package/src/http/tasks.ts +9 -0
  180. package/src/http/users.ts +226 -0
  181. package/src/http/workflows.ts +3 -2
  182. package/src/http.ts +1 -0
  183. package/src/integrations/kapso/client.ts +3 -1
  184. package/src/jira/app.ts +2 -2
  185. package/src/kv-view.ts +324 -0
  186. package/src/linear/app.ts +2 -2
  187. package/src/oauth/mcp-wrapper.ts +1 -1
  188. package/src/prompts/memories.ts +42 -1
  189. package/src/providers/acp-adapter.ts +85 -24
  190. package/src/providers/acp-target-catalog.ts +54 -1
  191. package/src/providers/acp-targets.ts +358 -4
  192. package/src/providers/amp-adapter.ts +1 -1
  193. package/src/providers/cursor-adapter.ts +1 -1
  194. package/src/providers/grok-adapter.ts +186 -0
  195. package/src/providers/index.ts +5 -1
  196. package/src/providers/pricing-sources.md +6 -0
  197. package/src/providers/reasoning-effort.ts +17 -1
  198. package/src/providers/types.ts +2 -1
  199. package/src/rbac/can.ts +4 -3
  200. package/src/rbac/legacy-policy.ts +3 -3
  201. package/src/rbac/types.ts +6 -0
  202. package/src/script-workflows/supervisor.ts +11 -2
  203. package/src/scripts-runtime/eval-harness.ts +2 -12
  204. package/src/scripts-runtime/executors/quickjs-bundle.ts +152 -0
  205. package/src/scripts-runtime/executors/quickjs-prelude.ts +285 -0
  206. package/src/scripts-runtime/executors/quickjs-runner.ts +580 -0
  207. package/src/scripts-runtime/executors/quickjs-worker.ts +81 -0
  208. package/src/scripts-runtime/executors/quickjs.ts +196 -0
  209. package/src/scripts-runtime/executors/registry.ts +2 -0
  210. package/src/scripts-runtime/executors/sourcemap.ts +79 -0
  211. package/src/scripts-runtime/script-error-hints.ts +14 -0
  212. package/src/scripts-runtime/swarm-sdk.ts +9 -2
  213. package/src/scripts-runtime/types/stdlib.d.ts +27 -8
  214. package/src/scripts-runtime/types/swarm-extension.d.ts +3 -0
  215. package/src/scripts-runtime/types/swarm-sdk.d.ts +27 -8
  216. package/src/server-user.ts +2 -20
  217. package/src/server.ts +3 -1
  218. package/src/slack/app.ts +4 -0
  219. package/src/slack/config.ts +3 -1
  220. package/src/slack/egress-scrub.ts +87 -0
  221. package/src/tests/acp-adapter.test.ts +191 -2
  222. package/src/tests/boot-scrub-sweep.test.ts +390 -0
  223. package/src/tests/budget-user-scope.test.ts +7 -21
  224. package/src/tests/check-pr-body.test.ts +149 -6
  225. package/src/tests/check-text-columns.test.ts +145 -0
  226. package/src/tests/claude-adapter-binary.test.ts +1 -1
  227. package/src/tests/claude-sdk-process.fixture.ts +13 -2
  228. package/src/tests/codex-oauth-device.test.ts +23 -37
  229. package/src/tests/codex-oauth-keep-warm.test.ts +29 -0
  230. package/src/tests/console-scrub.test.ts +141 -0
  231. package/src/tests/credential-check.test.ts +90 -0
  232. package/src/tests/db-query-denylist.test.ts +202 -0
  233. package/src/tests/db-task-read-contract.test.ts +1 -0
  234. package/src/tests/db-task-write-contract.test.ts +1 -0
  235. package/src/tests/docs-index-footprint.test.js +3 -3
  236. package/src/tests/entrypoint-acp-target-binary.test.ts +44 -1
  237. package/src/tests/events-scrub.test.ts +88 -0
  238. package/src/tests/explicit-model-validation.test.ts +81 -0
  239. package/src/tests/extensions-pre-task.test.ts +33 -1
  240. package/src/tests/fixtures/grok/acp-updates-recorded.jsonl +53 -0
  241. package/src/tests/fixtures/grok/prompt-response-meta.json +36 -0
  242. package/src/tests/fixtures/secret-corpus.ts +320 -0
  243. package/src/tests/grok-adapter.test.ts +647 -0
  244. package/src/tests/heartbeat-checklist.test.ts +44 -0
  245. package/src/tests/integration-disable-flags.test.ts +156 -0
  246. package/src/tests/kv-http.test.ts +34 -0
  247. package/src/tests/kv-tool.test.ts +274 -1
  248. package/src/tests/kv-view.test.ts +147 -0
  249. package/src/tests/mcp-user-route.test.ts +257 -15
  250. package/src/tests/memory-injection-outcome.test.ts +143 -0
  251. package/src/tests/model-tier-resolution.test.ts +76 -3
  252. package/src/tests/page-guest-session.test.ts +424 -0
  253. package/src/tests/page-proxy.test.ts +1 -1
  254. package/src/tests/page-viewer-auth.test.ts +22 -1
  255. package/src/tests/pr-body-leaks.test.ts +157 -2
  256. package/src/tests/preload.ts +10 -0
  257. package/src/tests/provider-registration.test.ts +3 -1
  258. package/src/tests/rbac-audit.test.ts +32 -0
  259. package/src/tests/rbac-mcp-admission-e2e.test.ts +4 -62
  260. package/src/tests/script-connections.test.ts +9 -0
  261. package/src/tests/script-executor-conformance.test.ts +3 -0
  262. package/src/tests/script-executor-quickjs.test.ts +442 -0
  263. package/src/tests/script-executor-registry.test.ts +6 -1
  264. package/src/tests/script-runs-http.test.ts +126 -1
  265. package/src/tests/script-runs-scrub.test.ts +275 -0
  266. package/src/tests/scripts-http.test.ts +82 -1
  267. package/src/tests/scripts-mcp-e2e.test.ts +42 -6
  268. package/src/tests/scripts-runtime.test.ts +37 -0
  269. package/src/tests/scrubbed-text-12e.test.ts +289 -0
  270. package/src/tests/sealed-key-guard.test.ts +166 -0
  271. package/src/tests/secret-canary-sweep.test.ts +958 -0
  272. package/src/tests/secret-registry.test.ts +355 -0
  273. package/src/tests/secret-rules.test.ts +230 -0
  274. package/src/tests/secret-scrubber.test.ts +317 -1
  275. package/src/tests/session-costs-golden.test.ts +17 -0
  276. package/src/tests/slack-egress-scrub.test.ts +216 -0
  277. package/src/tests/swarm-tool-result-gate.test.ts +143 -30
  278. package/src/tests/task-tools-ctx.test.ts +3 -0
  279. package/src/tests/user-token-routes.test.ts +342 -3
  280. package/src/tests/workflow-replay.test.ts +518 -0
  281. package/src/tests/workflow-scrub.test.ts +242 -0
  282. package/src/tests/workflow-system-one-decision-providers.test.ts +737 -0
  283. package/src/tools/db-query.ts +1 -1
  284. package/src/tools/inject-learning.ts +1 -1
  285. package/src/tools/kv/kv-get.ts +136 -6
  286. package/src/tools/send-task.ts +61 -17
  287. package/src/tools/utils.ts +186 -43
  288. package/src/tools/workflows/get-workflow-run.ts +3 -2
  289. package/src/types.ts +16 -1
  290. package/src/utils/console-scrub-install.ts +5 -0
  291. package/src/utils/console-scrub.ts +57 -0
  292. package/src/utils/credentials.ts +8 -0
  293. package/src/utils/page-session.ts +6 -2
  294. package/src/utils/pr-body-leaks.ts +149 -10
  295. package/src/utils/request-auth-context.ts +2 -0
  296. package/src/utils/secret-rules.generated.ts +2055 -0
  297. package/src/utils/secret-scrubber.ts +662 -36
  298. package/src/workflows/engine.ts +39 -4
  299. package/src/workflows/executors/notify.ts +6 -1
  300. package/src/workflows/executors/system-one-decision.ts +31 -12
  301. package/src/workflows/executors/system-one-openai-wire.ts +168 -0
  302. package/src/workflows/executors/system-one-providers.ts +112 -11
  303. package/src/workflows/executors/system-one-wire.ts +79 -0
  304. package/src/workflows/recovery.ts +10 -1
  305. package/src/workflows/resume.ts +62 -3
  306. package/src/workflows/retry-poller.ts +3 -0
  307. package/templates/ai-toolbox.manifest.json +2 -2
  308. package/templates/skills/agent-browser/SKILL.md +5 -5
  309. package/templates/skills/agent-browser/config.json +1 -1
  310. package/templates/skills/agent-browser/content.md +4 -4
  311. package/templates/skills/artifacts/content.md +17 -7
  312. package/templates/skills/jira-interaction/config.json +1 -1
  313. package/templates/skills/jira-interaction/content.md +56 -205
  314. package/templates/skills/linear-interaction/config.json +1 -1
  315. package/templates/skills/linear-interaction/content.md +31 -50
  316. package/templates/skills/pages/content.md +8 -4
  317. package/templates/skills/qa/content.md +2 -2
  318. package/templates/skills/workflow-iterate/content.md +4 -4
  319. package/tsconfig.json +3 -3
  320. package/dist/boot-scrub-logs-t8xg7wd7.js +0 -99
  321. package/dist/cli-0a7r5331.js +0 -168
  322. package/dist/cli-93rxw9yd.js +0 -951
  323. package/dist/cli-cvk6yzw5.js +0 -40
  324. package/dist/cli-dgnj0zcc.js +0 -40
  325. package/dist/cli-qt5wzq6v.js +0 -256
  326. package/dist/cli-v7fqehqd.js +0 -9281
  327. package/dist/cli-vbr859rz.js +0 -572
  328. package/src/be/boot-scrub-logs.ts +0 -132
@@ -105,8 +105,10 @@ describe("findPrBodyLeaks: negative controls", () => {
105
105
  test("a fenced code example with a fresh UUID taskId", () => {
106
106
  const body = `## Proof of work\n\n\`\`\`ts\nconst taskId = "${randomUUID()}";\nawait store({ parentTaskId: "${randomUUID()}" });\n\`\`\`\n`;
107
107
  expect(findPrBodyLeaks(body)).toEqual([]);
108
- // The same line outside the fence is still a ref.
109
- expect(findPrBodyLeaks(`taskId = "${randomUUID()}"`)).toEqual(["swarm-task-ref"]);
108
+ // The same line outside the fence is still a ref. The rule needs a letter in
109
+ // the first group, so a random UUID would flake (~2% are all digits there).
110
+ const ref = `${hexWithLetter()}-1111-2222-3333-444444444444`;
111
+ expect(findPrBodyLeaks(`taskId = "${ref}"`)).toEqual(["swarm-task-ref"]);
110
112
  });
111
113
 
112
114
  test("a fence does not hide Slack or dashboard links", () => {
@@ -149,6 +151,159 @@ describe("findPrBodyLeaks: negative controls", () => {
149
151
  });
150
152
  });
151
153
 
154
+ describe("findPrBodyLeaks: Swarm provenance allowlist", () => {
155
+ const provenance = (...lines: string[]) =>
156
+ `${NORMAL_BODY}\n## Swarm provenance <!-- bot -->\n\n${lines.map((l) => `- ${l}`).join("\n")}\n`;
157
+
158
+ test("task, session, agent-fs and Slack permalinks pass inside the section", () => {
159
+ const body = provenance(
160
+ `Task: ${dashboardLink()} · tree: ${dashboardLink().replace("/tasks/", "/sessions/")}`,
161
+ `Ask: ${slackLink()}?thread_ts=${slackTs()}&cid=${slackId("D")}`,
162
+ `Plan: ${agentFsLink()} (durable: ${agentFsPath()})`,
163
+ `Follow-up of task ${hexWithLetter()}.`,
164
+ );
165
+ expect(findPrBodyLeaks(body)).toEqual([]);
166
+ });
167
+
168
+ test("the same links outside the section still fail", () => {
169
+ const body = `${NORMAL_BODY}\nSee ${slackLink()} and ${dashboardLink()}.\n${provenance()}`;
170
+ // The permalink's embedded channel id also counts outside the section.
171
+ expect(findPrBodyLeaks(body)).toEqual(["slack-id", "slack-link", "swarm-dashboard-link"]);
172
+ });
173
+
174
+ test("bare Slack ids, ts values and private-chat quotes stay blocked inside it", () => {
175
+ expect(findPrBodyLeaks(provenance(`Channel ${slackId("C")}`))).toEqual(["slack-id"]);
176
+ expect(findPrBodyLeaks(provenance(`ts ${slackTs()}`))).toEqual(["slack-ts"]);
177
+ expect(
178
+ findPrBodyLeaks(provenance(`Taras in ${"DM"}: "it would be nice to explain the motivation"`)),
179
+ ).toEqual(["private-chat-quote"]);
180
+ });
181
+
182
+ test("the section ends at the next heading, and a fenced heading does not open it", () => {
183
+ const after = `${provenance(`Task: ${dashboardLink()}`)}\n## Notes\n\n${dashboardLink()}\n`;
184
+ expect(findPrBodyLeaks(after)).toEqual(["swarm-dashboard-link"]);
185
+ const fenced = `\`\`\`md\n## Swarm provenance\n\`\`\`\n${slackLink()}\n`;
186
+ expect(findPrBodyLeaks(fenced)).toContain("slack-link");
187
+ });
188
+
189
+ test("a heading inside an HTML comment does not open it", () => {
190
+ const body = `<!--\n## Swarm provenance\n-->\n${dashboardLink()}\n`;
191
+ expect(findPrBodyLeaks(body)).toEqual(["swarm-dashboard-link"]);
192
+ const oneLine = `<!-- note -->\n## Swarm provenance\n\n- ${dashboardLink()}\n`;
193
+ expect(findPrBodyLeaks(oneLine)).toEqual([]);
194
+ });
195
+
196
+ test("a shorter fence does not close a longer one", () => {
197
+ const body = `\`\`\`\`md\n\`\`\`\n## Swarm provenance\n\`\`\`\`\n${dashboardLink()}\n`;
198
+ expect(findPrBodyLeaks(body)).toEqual(["swarm-dashboard-link"]);
199
+ });
200
+
201
+ test("tildes do not close a backtick fence", () => {
202
+ const body = `\`\`\`\n~~~\n## Swarm provenance\n\`\`\`\n${dashboardLink()}\n`;
203
+ expect(findPrBodyLeaks(body)).toEqual(["swarm-dashboard-link"]);
204
+ });
205
+
206
+ test("a fence line with an info string does not close it", () => {
207
+ const body = `\`\`\`\n\`\`\`md\n## Swarm provenance\n\`\`\`\n${dashboardLink()}\n`;
208
+ expect(findPrBodyLeaks(body)).toEqual(["swarm-dashboard-link"]);
209
+ });
210
+
211
+ test("a longer closing fence closes it, so a later section opens", () => {
212
+ const body = `\`\`\`\ncode\n\`\`\`\`\`\n## Swarm provenance\n\n- ${dashboardLink()}\n`;
213
+ expect(findPrBodyLeaks(body)).toEqual([]);
214
+ });
215
+
216
+ // CommonMark 0.31.2 §4.5: a fence line is indented at most 3 columns, and a
217
+ // tab expands to the next multiple of 4, so a deeper "closer" is fence content.
218
+ test("a closer indented 4 spaces does not close the fence", () => {
219
+ const body = `\`\`\`\n \`\`\`\n## Swarm provenance\n\`\`\`\n${dashboardLink()}\n`;
220
+ expect(findPrBodyLeaks(body)).toEqual(["swarm-dashboard-link"]);
221
+ expect(checkPrBodyLeaks(body)).toEqual(["internal identifier in body: swarm-dashboard-link"]);
222
+ });
223
+
224
+ test("a closer with a leading tab does not close the fence", () => {
225
+ const body = `\`\`\`\n\t\`\`\`\n## Swarm provenance\n\`\`\`\n${dashboardLink()}\n`;
226
+ expect(findPrBodyLeaks(body)).toEqual(["swarm-dashboard-link"]);
227
+ expect(checkPrBodyLeaks(body)).toEqual(["internal identifier in body: swarm-dashboard-link"]);
228
+ });
229
+
230
+ // CommonMark §2.1 whitespace is ASCII space and tab only; JS trim() and \s
231
+ // also strip U+00A0, which GitHub renders as text.
232
+ test("a closer followed by a no-break space does not close the fence", () => {
233
+ const body = `\`\`\`\n\`\`\` \n## Swarm provenance\n\`\`\`\n${dashboardLink()}\n`;
234
+ expect(findPrBodyLeaks(body)).toEqual(["swarm-dashboard-link"]);
235
+ expect(checkPrBodyLeaks(body)).toEqual(["internal identifier in body: swarm-dashboard-link"]);
236
+ });
237
+
238
+ test("a closer followed by ASCII spaces or a tab still closes the fence", () => {
239
+ for (const tail of [" ", "\t", " \t "]) {
240
+ const body = `\`\`\`\ncode\n\`\`\`${tail}\n## Swarm provenance\n\n- ${dashboardLink()}\n`;
241
+ expect(findPrBodyLeaks(body)).toEqual([]);
242
+ expect(checkPrBodyLeaks(body)).toEqual([]);
243
+ }
244
+ });
245
+
246
+ test("a heading needs ASCII whitespace after the hashes to open the section", () => {
247
+ const nbsp = `## Swarm provenance\n\n- ${dashboardLink()}\n`;
248
+ expect(findPrBodyLeaks(nbsp)).toEqual(["swarm-dashboard-link"]);
249
+ expect(checkPrBodyLeaks(nbsp)).toEqual(["internal identifier in body: swarm-dashboard-link"]);
250
+ const glued = `## Swarm provenance#\n\n- ${dashboardLink()}\n`;
251
+ expect(findPrBodyLeaks(glued)).toEqual(["swarm-dashboard-link"]);
252
+ const closed = `##\tSwarm provenance ##\t\n\n- ${dashboardLink()}\n`;
253
+ expect(findPrBodyLeaks(closed)).toEqual([]);
254
+ });
255
+
256
+ test("a non-breaking space in the heading text is part of the name", () => {
257
+ for (const heading of ["## Swarm provenance ", "## Swarm provenance"]) {
258
+ const body = `${heading}\n\n- ${dashboardLink()}\n`;
259
+ expect(findPrBodyLeaks(body)).toEqual(["swarm-dashboard-link"]);
260
+ expect(checkPrBodyLeaks(body)).toEqual(["internal identifier in body: swarm-dashboard-link"]);
261
+ }
262
+ const spaced = `## Swarm \t provenance \t\n\n- ${dashboardLink()}\n`;
263
+ expect(findPrBodyLeaks(spaced)).toEqual([]);
264
+ const commented = `## Swarm provenance <!-- bot --> \n\n- ${dashboardLink()}\n`;
265
+ expect(findPrBodyLeaks(commented)).toEqual([]);
266
+ });
267
+
268
+ test("a closer indented 3 spaces still closes the fence", () => {
269
+ const body = `\`\`\`\ncode\n \`\`\`\n## Swarm provenance\n\n- ${dashboardLink()}\n`;
270
+ expect(findPrBodyLeaks(body)).toEqual([]);
271
+ });
272
+
273
+ test("an opener indented 4 spaces is not a fence, for sections or prose", () => {
274
+ const ref = `${hexWithLetter()}-1111-2222-3333-444444444444`;
275
+ expect(findPrBodyLeaks(` \`\`\`\ntaskId = "${ref}"\n\`\`\`\n`)).toEqual(["swarm-task-ref"]);
276
+ const body = ` \`\`\`\n## Swarm provenance\n\n- ${dashboardLink()}\n`;
277
+ expect(findPrBodyLeaks(body)).toEqual([]);
278
+ });
279
+
280
+ test("a fence closed less indented than its opener cannot open the section", () => {
281
+ // In a list item the closer ends the item and opens a new fence, which hides the heading.
282
+ const body = `- y\n \`\`\`\n\`\`\`\n## Swarm provenance\n\`\`\`\n${dashboardLink()}\n`;
283
+ expect(findPrBodyLeaks(body)).toEqual(["swarm-dashboard-link"]);
284
+ });
285
+
286
+ test("an indented heading cannot open the section", () => {
287
+ // A list item fence indented 4 columns hides it on GitHub.
288
+ const body = `- y\n \`\`\`\n ## Swarm provenance\n \`\`\`\n${dashboardLink()}\n`;
289
+ expect(findPrBodyLeaks(body)).toEqual(["swarm-dashboard-link"]);
290
+ });
291
+
292
+ test("an indented, setext, or commented-out heading still ends the section", () => {
293
+ const section = provenance(`Task: ${dashboardLink()}`);
294
+ for (const end of [" ## Notes", "- ## Notes", "\nNotes\n---", " <!--\n## Notes\n-->"]) {
295
+ expect(findPrBodyLeaks(`${section}\n${end}\n\n${dashboardLink()}\n`)).toEqual([
296
+ "swarm-dashboard-link",
297
+ ]);
298
+ }
299
+ });
300
+
301
+ test("the heading matches case-insensitively with a trailing marker", () => {
302
+ const body = `## swarm PROVENANCE <!-- bot -->\n\n- ${dashboardLink()}\n`;
303
+ expect(findPrBodyLeaks(body)).toEqual([]);
304
+ });
305
+ });
306
+
152
307
  describe("check-pr-body leak gating", () => {
153
308
  test("only bot authors, or a local run without an author, get the leak check", () => {
154
309
  expect(shouldCheckLeaks(undefined)).toBe(true);
@@ -30,6 +30,16 @@ startHangWatchdog();
30
30
  // drain set API_DRAIN_MAX_MS for their own server.
31
31
  process.env.API_DRAIN_MAX_MS = "0";
32
32
 
33
+ // Credential and provider-routing tests assume no Anthropic credentials, as
34
+ // on CI. A developer shell (or a repo .env, which Bun loads before this file)
35
+ // often sets them, for example a Claude Code session behind a proxy. Then
36
+ // ~17 tests fail locally and the pre-push hook blocks the push. Remove them
37
+ // here; spawned servers inherit the cleaned env, and suites that need a
38
+ // value set their own.
39
+ for (const key of ["ANTHROPIC_API_KEY", "ANTHROPIC_AUTH_TOKEN", "ANTHROPIC_BASE_URL"]) {
40
+ delete process.env[key];
41
+ }
42
+
33
43
  // @hono/node-server (pulled in transitively by @modelcontextprotocol/sdk's
34
44
  // streamableHttp transport) replaces globalThis.Response/Request with its own
35
45
  // lightweight Node-adapter classes the first time getRequestListener() runs.
@@ -61,6 +61,7 @@ const modelsDevFixture = {
61
61
  anthropic: { models: { "claude-test": { cost: { input: 1, output: 2 } } } },
62
62
  openai: { models: { "gpt-test": { cost: { input: 1, output: 2 } } } },
63
63
  openrouter: { models: { "test/model": { cost: { input: 1, output: 2 } } } },
64
+ xai: { models: { "grok-test": { cost: { input: 1, output: 2 } } } },
64
65
  };
65
66
 
66
67
  interface TouchPoint {
@@ -233,6 +234,7 @@ const BOOTSTRAP: Record<ProviderName, { expect: string } | { exempt: string }> =
233
234
  dsh: { expect: "Warning: dsh provider has no credentials yet" },
234
235
  amp: { expect: "Warning: amp provider has no credentials yet" },
235
236
  cursor: { expect: "Warning: cursor provider has no credentials yet" },
237
+ grok: { expect: "Warning: grok provider has no credentials yet" },
236
238
  pi: { expect: "Warning: pi provider has no credentials yet" },
237
239
  opencode: { expect: "Warning: opencode provider has no credentials yet" },
238
240
  "claude-managed": { expect: "Warning: claude-managed provider missing:" },
@@ -282,7 +284,7 @@ function extractMarkedBlock(name: string): string {
282
284
  /** Resolvable no-op stubs for every binary the verify chain may look up. */
283
285
  function makeBinaryStubDir(): string {
284
286
  const dir = mkdtempSync(join(tmpdir(), "provider-registration-bin-"));
285
- for (const name of ["claude", "codex", "opencode", "dsh", "amp"]) {
287
+ for (const name of ["claude", "codex", "opencode", "dsh", "amp", "grok"]) {
286
288
  const file = join(dir, name);
287
289
  writeFileSync(file, "#!/bin/sh\nexit 0\n");
288
290
  chmodSync(file, 0o755);
@@ -168,6 +168,38 @@ describe("buffer + flush persistence", () => {
168
168
  expect(denyRow?.reason).toBeTruthy();
169
169
  });
170
170
 
171
+ test("persists a mixed batch with a guest denial and an operator decision", async () => {
172
+ setAuditSink(enqueueAuditRow);
173
+
174
+ const operator = can({
175
+ principal: { kind: "operator" },
176
+ verb: "models.catalog.write",
177
+ source: "http",
178
+ });
179
+ const guest = can({
180
+ principal: { kind: "guest" },
181
+ verb: "models.catalog.write",
182
+ source: "http",
183
+ });
184
+ expect(operator.allow).toBe(true);
185
+ expect(guest.allow).toBe(false);
186
+
187
+ await flushAuditBuffer();
188
+
189
+ const rows = await selectAuditRows();
190
+ expect(rows.length).toBe(2);
191
+ expect(rows.find((r) => r.principalType === "operator")).toMatchObject({
192
+ principalId: null,
193
+ verb: "models.catalog.write",
194
+ decision: "allow",
195
+ });
196
+ expect(rows.find((r) => r.principalType === "guest")).toMatchObject({
197
+ principalId: null,
198
+ verb: "models.catalog.write",
199
+ decision: "deny",
200
+ });
201
+ });
202
+
171
203
  test("auto-flushes at the 200-row threshold without the interval writer", async () => {
172
204
  for (let i = 0; i < 200; i++) {
173
205
  enqueueAuditRow(
@@ -155,13 +155,6 @@ async function createPendingTask(base: string, token: string): Promise<string> {
155
155
  return res.body.id as string;
156
156
  }
157
157
 
158
- function taskIdFrom(result: ToolCallResult): string {
159
- expect(result.isError).not.toBe(true);
160
- const task = result.structuredContent?.task as { id?: unknown } | undefined;
161
- expect(typeof task?.id).toBe("string");
162
- return task.id as string;
163
- }
164
-
165
158
  function expectToolSuccess(result: ToolCallResult): void {
166
159
  expect(result.isError).not.toBe(true);
167
160
  expect(result.structuredContent?.success).toBe(true);
@@ -179,24 +172,11 @@ function expectSoftForbidden(result: ToolCallResult, verb: string): void {
179
172
  );
180
173
  }
181
174
 
182
- async function createUnassignedViaMcp(
183
- base: string,
184
- token: string,
185
- sessionId: string,
186
- ): Promise<string> {
187
- const created = await mcpUserCall(base, token, sessionId, "send-task", {
188
- task: `unassigned task ${crypto.randomUUID()}`,
189
- });
190
- expectToolSuccess(created);
191
- return taskIdFrom(created);
192
- }
193
-
194
175
  async function exerciseAllowedUserTools(
195
176
  base: string,
196
177
  token: string,
197
178
  sessionId: string,
198
179
  pendingTaskId: string,
199
- unassignedTaskId: string,
200
180
  ): Promise<void> {
201
181
  const created = await mcpUserCall(base, token, sessionId, "send-task", {
202
182
  task: `allowed send-task ${crypto.randomUUID()}`,
@@ -217,12 +197,6 @@ async function exerciseAllowedUserTools(
217
197
  reason: "rbac mcp-user e2e",
218
198
  });
219
199
  expectToolSuccess(cancelled);
220
-
221
- const moved = await mcpUserCall(base, token, sessionId, "task-action", {
222
- action: "to_backlog",
223
- taskId: unassignedTaskId,
224
- });
225
- expectToolSuccess(moved);
226
200
  }
227
201
 
228
202
  describe("RBAC admission over /mcp-user", () => {
@@ -248,25 +222,13 @@ describe("RBAC admission over /mcp-user", () => {
248
222
  const admin = await createUserToken(server.base, "MCP Admin");
249
223
  const adminSid = await mcpUserInit(server.base, admin.token);
250
224
  const adminPendingTaskId = await createPendingTask(server.base, admin.token);
251
- const adminUnassignedTaskId = await createUnassignedViaMcp(server.base, admin.token, adminSid);
252
225
 
253
- await exerciseAllowedUserTools(
254
- server.base,
255
- admin.token,
256
- adminSid,
257
- adminPendingTaskId,
258
- adminUnassignedTaskId,
259
- );
226
+ await exerciseAllowedUserTools(server.base, admin.token, adminSid, adminPendingTaskId);
260
227
  expect(countAuditRows(enabledDbPath)).toBe(0);
261
228
 
262
229
  const requester = await createUserToken(server.base, "MCP Requester");
263
230
  const requesterSid = await mcpUserInit(server.base, requester.token);
264
231
  const requesterPendingTaskId = await createPendingTask(server.base, requester.token);
265
- const requesterUnassignedTaskId = await createUnassignedViaMcp(
266
- server.base,
267
- requester.token,
268
- requesterSid,
269
- );
270
232
  rewriteUserRoles(enabledDbPath, requester.userId, [REQUESTER_ROLE_ID]);
271
233
 
272
234
  await exerciseAllowedUserTools(
@@ -274,13 +236,11 @@ describe("RBAC admission over /mcp-user", () => {
274
236
  requester.token,
275
237
  requesterSid,
276
238
  requesterPendingTaskId,
277
- requesterUnassignedTaskId,
278
239
  );
279
240
 
280
241
  const empty = await createUserToken(server.base, "MCP Empty Grant");
281
242
  const emptySid = await mcpUserInit(server.base, empty.token);
282
243
  const emptyPendingTaskId = await createPendingTask(server.base, empty.token);
283
- const emptyUnassignedTaskId = await createUnassignedViaMcp(server.base, empty.token, emptySid);
284
244
  rewriteUserRoles(enabledDbPath, empty.userId, []);
285
245
 
286
246
  expectSoftForbidden(
@@ -295,13 +255,6 @@ describe("RBAC admission over /mcp-user", () => {
295
255
  }),
296
256
  "task.cancel.own",
297
257
  );
298
- expectSoftForbidden(
299
- await mcpUserCall(server.base, empty.token, emptySid, "task-action", {
300
- action: "to_backlog",
301
- taskId: emptyUnassignedTaskId,
302
- }),
303
- "task.action.own",
304
- );
305
258
  expectReadableToolResult(
306
259
  await mcpUserCall(server.base, empty.token, emptySid, "get-tasks", { limit: 10 }),
307
260
  );
@@ -323,7 +276,7 @@ describe("RBAC admission over /mcp-user", () => {
323
276
  row.verb === "task.create.own" &&
324
277
  row.decision === "allow",
325
278
  ) &&
326
- ["task.create.own", "task.cancel.own", "task.action.own"].every((verb) =>
279
+ ["task.create.own", "task.cancel.own"].every((verb) =>
327
280
  auditRows.some(
328
281
  (row) =>
329
282
  row.principalId === empty.userId && row.decision === "deny" && row.verb === verb,
@@ -346,7 +299,7 @@ describe("RBAC admission over /mcp-user", () => {
346
299
  mcpRows
347
300
  .filter((row) => row.principalId === empty.userId && row.decision === "deny")
348
301
  .map((row) => row.verb),
349
- ).toEqual(["task.create.own", "task.cancel.own", "task.action.own"]);
302
+ ).toEqual(["task.create.own", "task.cancel.own"]);
350
303
 
351
304
  await server.stop();
352
305
  server = undefined;
@@ -362,19 +315,8 @@ describe("RBAC admission over /mcp-user", () => {
362
315
  const flagOffSid = await mcpUserInit(server.base, flagOff.token);
363
316
  rewriteUserRoles(disabledDbPath, flagOff.userId, []);
364
317
  const flagOffPendingTaskId = await createPendingTask(server.base, flagOff.token);
365
- const flagOffUnassignedTaskId = await createUnassignedViaMcp(
366
- server.base,
367
- flagOff.token,
368
- flagOffSid,
369
- );
370
318
 
371
- await exerciseAllowedUserTools(
372
- server.base,
373
- flagOff.token,
374
- flagOffSid,
375
- flagOffPendingTaskId,
376
- flagOffUnassignedTaskId,
377
- );
319
+ await exerciseAllowedUserTools(server.base, flagOff.token, flagOffSid, flagOffPendingTaskId);
378
320
  expect(countAuditRows(disabledDbPath)).toBe(0);
379
321
  });
380
322
  });
@@ -606,6 +606,15 @@ describe("script connections", () => {
606
606
  markMigrationApplied(database, "195_approval_request_approvals.sql");
607
607
  // 196 alters skills, which this migration-112-only fixture does not create.
608
608
  markMigrationApplied(database, "196_skill_invocations.sql");
609
+ // 198 rebuilds permission_audit, which this migration-112-only fixture does not create.
610
+ markMigrationApplied(database, "198_permission_audit_guest.sql");
611
+ // 200 backfills agent_memory, which this migration-112-only fixture does not create.
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");
609
618
 
610
619
  const id = crypto.randomUUID();
611
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 () => {