@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
@@ -1,15 +1,8 @@
1
1
  import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from "bun:test";
2
2
  import { createServer, type Server } from "node:http";
3
3
  import { decryptSecret, encryptSecret, getEncryptionKey } from "../be/crypto";
4
- import {
5
- closeDb,
6
- getDbClient,
7
- getKv,
8
- getSwarmConfigs,
9
- initDb,
10
- upsertKv,
11
- upsertSwarmConfig,
12
- } from "../be/db";
4
+ import { closeDb, getDbClient, getSwarmConfigs, initDb, upsertSwarmConfig } from "../be/db";
5
+ import { getCodexDeviceFlow, upsertCodexDeviceFlow } from "../be/db/codex-oauth-device-flows";
13
6
  import type { OnboardingState } from "../be/onboarding";
14
7
  import { handleCodexOAuthDevice } from "../http/codex-oauth-device";
15
8
  import { handleCore } from "../http/core";
@@ -97,7 +90,7 @@ afterAll(async () => {
97
90
  });
98
91
 
99
92
  beforeEach(async () => {
100
- await getDbClient().run("DELETE FROM kv_entries");
93
+ await getDbClient().run("DELETE FROM codex_oauth_device_flows");
101
94
  await getDbClient().run("DELETE FROM swarm_config");
102
95
  fetchImpl = async () => {
103
96
  throw new Error("Unexpected fetch");
@@ -221,8 +214,8 @@ describe("Codex device HTTP routes", () => {
221
214
 
222
215
  const started = await startFlow();
223
216
  expect(Number.isNaN(Date.parse(started.expiresAt))).toBe(false);
224
- const encrypted = await getKv("codex-oauth-device", started.flowId);
225
- expect(encrypted?.value).not.toContain("device-auth-full");
217
+ const encrypted = await getCodexDeviceFlow(started.flowId);
218
+ expect(encrypted?.state).not.toContain("device-auth-full");
226
219
 
227
220
  const poll = await request(`/api/codex-oauth/device/${started.flowId}/poll`);
228
221
  expect(await poll.json()).toEqual({ status: "complete", slot: 1 });
@@ -318,17 +311,15 @@ describe("Codex device HTTP routes", () => {
318
311
  const secondResponse = await request(path);
319
312
  const secondBody = await secondResponse.json();
320
313
  const callsBeforeRelease = { pollCalls, exchangeCalls };
321
- const entry = await getKv("codex-oauth-device", started.flowId);
322
- const state = JSON.parse(decryptSecret(String(entry!.value), getEncryptionKey())) as Record<
314
+ const entry = await getCodexDeviceFlow(started.flowId);
315
+ const state = JSON.parse(decryptSecret(entry!.state, getEncryptionKey())) as Record<
323
316
  string,
324
317
  unknown
325
318
  >;
326
319
  state.pollLeaseExpiresAt = Date.now() - 1;
327
- await upsertKv({
328
- namespace: "codex-oauth-device",
329
- key: started.flowId,
330
- value: encryptSecret(JSON.stringify(state), getEncryptionKey()),
331
- valueType: "string",
320
+ await upsertCodexDeviceFlow({
321
+ flowId: started.flowId,
322
+ state: encryptSecret(JSON.stringify(state), getEncryptionKey()),
332
323
  expiresAt: Number(state.expiresAt),
333
324
  });
334
325
  releaseExchange();
@@ -359,19 +350,17 @@ describe("Codex device HTTP routes", () => {
359
350
  }) as typeof fetch;
360
351
 
361
352
  const started = await startFlow();
362
- const entry = await getKv("codex-oauth-device", started.flowId);
363
- const state = JSON.parse(decryptSecret(String(entry!.value), getEncryptionKey())) as Record<
353
+ const entry = await getCodexDeviceFlow(started.flowId);
354
+ const state = JSON.parse(decryptSecret(entry!.state, getEncryptionKey())) as Record<
364
355
  string,
365
356
  unknown
366
357
  >;
367
358
  state.lastPolledAt = Date.now() - 10_000;
368
359
  state.pollLeaseId = crypto.randomUUID();
369
360
  state.pollLeaseExpiresAt = Date.now() - 1;
370
- await upsertKv({
371
- namespace: "codex-oauth-device",
372
- key: started.flowId,
373
- value: encryptSecret(JSON.stringify(state), getEncryptionKey()),
374
- valueType: "string",
361
+ await upsertCodexDeviceFlow({
362
+ flowId: started.flowId,
363
+ state: encryptSecret(JSON.stringify(state), getEncryptionKey()),
375
364
  expiresAt: Number(state.expiresAt),
376
365
  });
377
366
 
@@ -380,9 +369,9 @@ describe("Codex device HTTP routes", () => {
380
369
  });
381
370
  expect(pollCalls).toBe(1);
382
371
 
383
- const recovered = await getKv("codex-oauth-device", started.flowId);
372
+ const recovered = await getCodexDeviceFlow(started.flowId);
384
373
  const recoveredState = JSON.parse(
385
- decryptSecret(String(recovered!.value), getEncryptionKey()),
374
+ decryptSecret(recovered!.state, getEncryptionKey()),
386
375
  ) as Record<string, unknown>;
387
376
  expect(recoveredState.pollLeaseId).toBeNull();
388
377
  expect(recoveredState.pollLeaseExpiresAt).toBeNull();
@@ -403,17 +392,15 @@ describe("Codex device HTTP routes", () => {
403
392
  }) as typeof fetch;
404
393
 
405
394
  const started = await startFlow();
406
- const entry = await getKv("codex-oauth-device", started.flowId);
407
- const state = JSON.parse(decryptSecret(String(entry!.value), getEncryptionKey())) as Record<
395
+ const entry = await getCodexDeviceFlow(started.flowId);
396
+ const state = JSON.parse(decryptSecret(entry!.state, getEncryptionKey())) as Record<
408
397
  string,
409
398
  unknown
410
399
  >;
411
400
  state.expiresAt = Date.now() - 1;
412
- await upsertKv({
413
- namespace: "codex-oauth-device",
414
- key: started.flowId,
415
- value: encryptSecret(JSON.stringify(state), getEncryptionKey()),
416
- valueType: "string",
401
+ await upsertCodexDeviceFlow({
402
+ flowId: started.flowId,
403
+ state: encryptSecret(JSON.stringify(state), getEncryptionKey()),
417
404
  expiresAt: Number(state.expiresAt),
418
405
  });
419
406
 
@@ -482,8 +469,7 @@ describe("Codex device HTTP routes", () => {
482
469
  }) as typeof fetch;
483
470
  await getDbClient().run(
484
471
  `CREATE TRIGGER fail_device_flow_write
485
- BEFORE INSERT ON kv_entries
486
- WHEN NEW.namespace = 'codex-oauth-device'
472
+ BEFORE INSERT ON codex_oauth_device_flows
487
473
  BEGIN
488
474
  SELECT RAISE(ABORT, 'forced local write failure');
489
475
  END`,
@@ -355,6 +355,7 @@ describe("credentialsToAuthJson", () => {
355
355
  accountId: "c724a178-abc",
356
356
  };
357
357
 
358
+ const before = Date.now();
358
359
  const authJson = credentialsToAuthJson(creds);
359
360
  expect(authJson.auth_mode).toBe("chatgpt");
360
361
  expect(authJson.OPENAI_API_KEY).toBeNull();
@@ -362,7 +363,9 @@ describe("credentialsToAuthJson", () => {
362
363
  expect(authJson.tokens.refresh_token).toBe(creds.refresh);
363
364
  expect(authJson.tokens.account_id).toBe(creds.accountId);
364
365
  expect(authJson.tokens.id_token).toBe(creds.access);
365
- expect(authJson.last_refresh).toBe(new Date(creds.expires).toISOString());
366
+ expect(Date.parse(authJson.last_refresh)).toBeGreaterThanOrEqual(before);
367
+ expect(Date.parse(authJson.last_refresh)).toBeLessThanOrEqual(Date.now());
368
+ expect(authJson.expires).toBe(creds.expires);
366
369
  });
367
370
  });
368
371
 
@@ -32,9 +32,9 @@ describe("getContextWindowSize", () => {
32
32
  expect(getContextWindowSize("sonnet")).toBe(1_000_000);
33
33
  });
34
34
 
35
- test("returns 200K for haiku models", () => {
35
+ test("returns 200K for Haiku 4.5 and 1M for the haiku shortname (Haiku 5.5)", () => {
36
36
  expect(getContextWindowSize("claude-haiku-4-5")).toBe(200_000);
37
- expect(getContextWindowSize("haiku")).toBe(200_000);
37
+ expect(getContextWindowSize("haiku")).toBe(1_000_000);
38
38
  });
39
39
 
40
40
  test("reads OpenAI windows from the catalog", () => {
@@ -1,6 +1,7 @@
1
1
  import { describe, expect, mock, test } from "bun:test";
2
2
  import {
3
3
  buildCredStatusReport,
4
+ CREDENTIAL_PROVIDER_CHECKERS,
4
5
  checkProviderCredentials,
5
6
  isBedrockMode,
6
7
  isCredCheckDisabled,
@@ -8,6 +9,7 @@ import {
8
9
  shouldRefreshBedrockStatus,
9
10
  validateProviderCredentials,
10
11
  } from "../commands/provider-credentials";
12
+ import { ACP_TARGET_IDS } from "../providers/acp-target-catalog";
11
13
  import { checkClaudeCredentials } from "../providers/claude-adapter";
12
14
  import { checkClaudeManagedCredentials } from "../providers/claude-managed-adapter";
13
15
  import { checkCodexCredentials } from "../providers/codex-adapter";
@@ -660,6 +662,54 @@ describe("checkOpencodeCredentials", () => {
660
662
  });
661
663
  });
662
664
 
665
+ describe("ACP readiness", () => {
666
+ test.each(["typo", "", " \t "])("rejects unsupported target %j", async (target) => {
667
+ const status = await checkProviderCredentials("acp", {
668
+ ACP_TARGET: target,
669
+ ACP_TARGET_COMMAND: "custom-acp --stdio",
670
+ });
671
+ expect(status.ready).toBe(false);
672
+ expect(status.missing).toEqual(["ACP_TARGET"]);
673
+ expect(status.hint).toContain("ACP_TARGET");
674
+ for (const supported of ACP_TARGET_IDS) expect(status.hint).toContain(supported);
675
+ });
676
+
677
+ test.each([undefined, "custom"])("requires a command for target %s", async (target) => {
678
+ const status = await CREDENTIAL_PROVIDER_CHECKERS.acp({ ACP_TARGET: target });
679
+ expect(status.ready).toBe(false);
680
+ expect(status.missing).toEqual(["ACP_TARGET_COMMAND"]);
681
+ expect(status.hint).toContain("ACP_TARGET_COMMAND");
682
+ expect(status.hint).toContain("ACP_COMMAND");
683
+ });
684
+
685
+ test.each(["", " \t "])("rejects a blank custom command %j", async (command) => {
686
+ const status = await checkProviderCredentials("acp", {
687
+ ACP_TARGET: "custom",
688
+ ACP_TARGET_COMMAND: command,
689
+ ACP_COMMAND: "legacy-acp",
690
+ });
691
+ expect(status.ready).toBe(false);
692
+ expect(status.missing).toEqual(["ACP_TARGET_COMMAND"]);
693
+ });
694
+
695
+ test.each(["ACP_TARGET_COMMAND", "ACP_COMMAND"])("accepts a command from %s", async (key) => {
696
+ for (const target of [undefined, "custom"]) {
697
+ const status = await checkProviderCredentials("acp", {
698
+ ACP_TARGET: target,
699
+ [key]: "custom-acp --stdio",
700
+ });
701
+ expect(status).toEqual({ ready: true, missing: [], satisfiedBy: "sdk-delegated" });
702
+ }
703
+ });
704
+
705
+ test.each(
706
+ ACP_TARGET_IDS.filter((target) => target !== "custom"),
707
+ )("keeps preset %s ready without a custom command", async (target) => {
708
+ const status = await checkProviderCredentials("acp", { ACP_TARGET: target });
709
+ expect(status).toEqual({ ready: true, missing: [], satisfiedBy: "sdk-delegated" });
710
+ });
711
+ });
712
+
663
713
  // ─── dispatcher ──────────────────────────────────────────────────────────────
664
714
 
665
715
  describe("checkProviderCredentials dispatcher", () => {
@@ -720,7 +770,7 @@ describe("checkProviderCredentials dispatcher", () => {
720
770
  ).ready,
721
771
  ).toBe(true);
722
772
 
723
- const acpStatus = await checkProviderCredentials("acp", {});
773
+ const acpStatus = await checkProviderCredentials("acp", { ACP_TARGET: "opencode" });
724
774
  expect(acpStatus.ready).toBe(true);
725
775
  expect(acpStatus.satisfiedBy).toBe("sdk-delegated");
726
776
  });
@@ -0,0 +1,202 @@
1
+ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
2
+ import { createServer as createHttpServer, type Server } from "node:http";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { closeDb, getDb, initDb, upsertKv } from "../be/db";
6
+ import { upsertCodexDeviceFlow } from "../be/db/codex-oauth-device-flows";
7
+ import {
8
+ DbQueryDeniedTableError,
9
+ executeReadOnlyQueryGated,
10
+ handleDbQuery,
11
+ } from "../http/db-query";
12
+ import { executeReadOnlyQuery } from "../http/db-query-shared";
13
+ import { getPathSegments, parseQueryParams } from "../http/utils";
14
+ import { listenOnFreePort } from "./test-net";
15
+
16
+ // A real on-disk database: the bounded path spawns a child process that opens
17
+ // its own connection, so an in-memory test template would be invisible to it.
18
+ const DB_PATH = join(tmpdir(), `db-query-denylist-${process.pid}-${Date.now()}.sqlite`);
19
+
20
+ const testGlobals = globalThis as typeof globalThis & {
21
+ __testMigrationTemplate?: Uint8Array;
22
+ };
23
+ let savedTemplate: Uint8Array | undefined;
24
+
25
+ // Built at runtime so no secret-shaped literal lands in the repo.
26
+ const syntheticToken = () => `gh${"p"}_${"K".repeat(36)}`;
27
+
28
+ async function removeDb(): Promise<void> {
29
+ for (const suffix of ["", "-wal", "-shm"]) {
30
+ try {
31
+ await Bun.file(`${DB_PATH}${suffix}`).delete();
32
+ } catch {}
33
+ }
34
+ }
35
+
36
+ beforeAll(async () => {
37
+ savedTemplate = testGlobals.__testMigrationTemplate;
38
+ testGlobals.__testMigrationTemplate = undefined;
39
+ closeDb();
40
+ await removeDb();
41
+ initDb(DB_PATH);
42
+ const db = getDb();
43
+ db.run("CREATE VIEW denylist_test_view AS SELECT * FROM oauth_authorizations");
44
+ db.run("CREATE TABLE denylist_test_notes (id INTEGER PRIMARY KEY, body TEXT)");
45
+ db.run("INSERT INTO denylist_test_notes (body) VALUES (?)", [
46
+ `deploy used ${syntheticToken()} from the env dump`,
47
+ ]);
48
+ });
49
+
50
+ afterAll(async () => {
51
+ closeDb();
52
+ testGlobals.__testMigrationTemplate = savedTemplate;
53
+ await removeDb();
54
+ });
55
+
56
+ const DENIED_QUERIES: Array<[string, string, string]> = [
57
+ ["direct read", "SELECT * FROM oauth_authorizations", "oauth_authorizations"],
58
+ ["alias", "SELECT a.accessToken FROM oauth_authorizations AS a", "oauth_authorizations"],
59
+ ["CTE", "WITH t AS (SELECT * FROM oauth_authorizations) SELECT * FROM t", "oauth_authorizations"],
60
+ [
61
+ "join",
62
+ "SELECT n.body, p.codeVerifier FROM denylist_test_notes n JOIN oauth_pending p ON 1 = 1",
63
+ "oauth_pending",
64
+ ],
65
+ ["subquery", "SELECT * FROM (SELECT * FROM user_tokens)", "user_tokens"],
66
+ ["view", "SELECT * FROM denylist_test_view", "oauth_authorizations"],
67
+ ["scalar subquery", "SELECT (SELECT clientSecret FROM oauth_apps LIMIT 1) AS s", "oauth_apps"],
68
+ ["count only", "SELECT COUNT(*) FROM session_tokens", "session_tokens"],
69
+ ["mixed case", "select * from Script_Apis", "script_apis"],
70
+ ];
71
+
72
+ describe("db-query credential-table denylist", () => {
73
+ for (const [label, sql, table] of DENIED_QUERIES) {
74
+ test(`in-process path rejects a ${label}`, () => {
75
+ expect(() => executeReadOnlyQuery(sql)).toThrow(DbQueryDeniedTableError);
76
+ expect(() => executeReadOnlyQuery(sql)).toThrow(table);
77
+ });
78
+ }
79
+
80
+ test("bounded path rejects before spawning a child", async () => {
81
+ for (const [, sql, table] of DENIED_QUERIES) {
82
+ await expect(executeReadOnlyQueryGated(sql, [], 5_000, 10)).rejects.toThrow(table);
83
+ }
84
+ });
85
+
86
+ test("allows swarm_config, EXPLAIN and a table name in a string literal", async () => {
87
+ const config = await executeReadOnlyQueryGated("SELECT key FROM swarm_config", [], 5_000, 10);
88
+ expect(config.columns).toEqual(["key"]);
89
+
90
+ const explained = executeReadOnlyQuery("EXPLAIN QUERY PLAN SELECT * FROM oauth_authorizations");
91
+ expect(explained.columns.length).toBeGreaterThan(0);
92
+
93
+ const literal = executeReadOnlyQuery("SELECT 'oauth_authorizations' AS name");
94
+ expect(literal.rows).toEqual([["oauth_authorizations"]]);
95
+ });
96
+ });
97
+
98
+ describe("POST /api/db-query", () => {
99
+ let server: Server;
100
+ let baseUrl = "";
101
+
102
+ beforeAll(async () => {
103
+ server = createHttpServer(async (req, res) => {
104
+ const handled = await handleDbQuery(
105
+ req,
106
+ res,
107
+ getPathSegments(req.url || ""),
108
+ parseQueryParams(req.url || ""),
109
+ );
110
+ if (!handled) {
111
+ res.writeHead(404);
112
+ res.end();
113
+ }
114
+ });
115
+ baseUrl = `http://localhost:${await listenOnFreePort(server)}`;
116
+ });
117
+
118
+ afterAll(async () => {
119
+ await new Promise<void>((resolve) => server.close(() => resolve()));
120
+ });
121
+
122
+ async function post(sql: string) {
123
+ const res = await fetch(`${baseUrl}/api/db-query`, {
124
+ method: "POST",
125
+ headers: { "Content-Type": "application/json" },
126
+ body: JSON.stringify({ sql }),
127
+ });
128
+ return { status: res.status, body: (await res.json()) as Record<string, unknown> };
129
+ }
130
+
131
+ test("rejects a credential table with a clear 400", async () => {
132
+ const { status, body } = await post("SELECT * FROM oauth_authorizations");
133
+ expect(status).toBe(400);
134
+ expect(String(body.error)).toContain("oauth_authorizations");
135
+ expect(String(body.error)).toContain("credentials");
136
+ });
137
+
138
+ test("redacts a secret stored in an allowed table and keeps the context", async () => {
139
+ const { status, body } = await post("SELECT body FROM denylist_test_notes");
140
+ expect(status).toBe(200);
141
+ const serialized = JSON.stringify(body.rows);
142
+ expect(serialized).not.toContain(syntheticToken());
143
+ expect(serialized).toContain("[REDACTED:");
144
+ expect(serialized).toContain("from the env dump");
145
+ });
146
+
147
+ test("denies Codex device-login state and keeps general KV readable", async () => {
148
+ const marker = `device-state-${crypto.randomUUID()}`;
149
+ await upsertCodexDeviceFlow({
150
+ flowId: crypto.randomUUID(),
151
+ state: marker,
152
+ expiresAt: Date.now() + 60_000,
153
+ });
154
+ await upsertKv({
155
+ namespace: "denylist-test",
156
+ key: "note",
157
+ value: "plain kv value",
158
+ valueType: "string",
159
+ });
160
+
161
+ for (const sql of [
162
+ "SELECT * FROM codex_oauth_device_flows",
163
+ "SELECT f.state FROM codex_oauth_device_flows f JOIN kv_entries k ON 1",
164
+ ]) {
165
+ const { status, body } = await post(sql);
166
+ expect(status).toBe(400);
167
+ expect(String(body.error)).toContain("codex_oauth_device_flows");
168
+ expect(JSON.stringify(body)).not.toContain(marker);
169
+ }
170
+
171
+ const kv = await post("SELECT namespace, value FROM kv_entries");
172
+ expect(kv.status).toBe(200);
173
+ const rows = JSON.stringify(kv.body.rows);
174
+ expect(rows).toContain("plain kv value");
175
+ expect(rows).not.toContain("codex-oauth-device");
176
+ expect(rows).not.toContain(marker);
177
+ });
178
+
179
+ test("migration 202 moves legacy kv device flows out of kv_entries", async () => {
180
+ const flowId = crypto.randomUUID();
181
+ const marker = `legacy-state-${crypto.randomUUID()}`;
182
+ await upsertKv({
183
+ namespace: "codex-oauth-device",
184
+ key: flowId,
185
+ value: marker,
186
+ valueType: "string",
187
+ expiresAt: Date.now() + 60_000,
188
+ });
189
+ const migration = await Bun.file(
190
+ join(import.meta.dir, "../be/migrations/202_codex_oauth_device_flows.sql"),
191
+ ).text();
192
+ getDb().exec(migration);
193
+
194
+ const kv = await post("SELECT key FROM kv_entries WHERE namespace = 'codex-oauth-device'");
195
+ expect(kv.status).toBe(200);
196
+ expect(kv.body.rows).toEqual([]);
197
+ const moved = getDb()
198
+ .query("SELECT state FROM codex_oauth_device_flows WHERE flow_id = ?")
199
+ .get(flowId) as { state: string } | null;
200
+ expect(moved?.state).toBe(marker);
201
+ });
202
+ });
@@ -357,6 +357,7 @@ value:getScheduledTaskByName
357
357
  value:getScheduledTasks
358
358
  value:getScriptRun
359
359
  value:getScriptRunByIdempotencyKey
360
+ value:getScriptRunExecutionArgs
360
361
  value:getScriptRunJournalStep
361
362
  value:getServiceByAgentAndName
362
363
  value:getServiceById
@@ -380,6 +380,7 @@ value:getScheduledTaskByName
380
380
  value:getScheduledTasks
381
381
  value:getScriptRun
382
382
  value:getScriptRunByIdempotencyKey
383
+ value:getScriptRunExecutionArgs
383
384
  value:getScriptRunJournalStep
384
385
  value:getServiceByAgentAndName
385
386
  value:getServiceById
@@ -9,7 +9,10 @@ import type { ProviderEvent, ProviderSessionConfig } from "../providers/types";
9
9
  import { DEFAULT_MODEL_TIER_MAP } from "../types";
10
10
  import { getModelAwareCredentialVars } from "../utils/credentials";
11
11
  import { resolveHarnessProvider } from "../utils/harness-provider";
12
- import { DEFAULT_OPENROUTER_BASE_URL } from "../utils/openrouter-base-url";
12
+ import {
13
+ DEFAULT_OPENROUTER_BASE_URL,
14
+ OPENROUTER_APP_ATTRIBUTION_HEADERS,
15
+ } from "../utils/openrouter-base-url";
13
16
 
14
17
  const directories: string[] = [];
15
18
  afterEach(async () => {
@@ -166,6 +169,7 @@ describe("dsh harness", () => {
166
169
  openrouter: {
167
170
  apiKeyEnv: "OPENROUTER_API_KEY",
168
171
  baseURL: DEFAULT_OPENROUTER_BASE_URL,
172
+ headers: OPENROUTER_APP_ATTRIBUTION_HEADERS,
169
173
  api: "openai-completions",
170
174
  models: [{ id: modelId }],
171
175
  },
@@ -316,6 +320,24 @@ describe("dsh harness", () => {
316
320
  model: "deepseek/deepseek-v4.1-flash",
317
321
  });
318
322
  expect(patch[2].config.providers.openrouter.baseURL).toBe("https://gateway.example/proxy/v1");
323
+ // A gateway gets no OpenRouter app attribution.
324
+ expect(patch[2].config.providers.openrouter).not.toHaveProperty("headers");
325
+ });
326
+
327
+ test("OPENROUTER_APP_ATTRIBUTION=false drops the attribution headers", async () => {
328
+ const config = await fixture();
329
+ config.model = "openrouter/vendor/new-model";
330
+ config.env = {
331
+ ...config.env,
332
+ DEEPSEEK_API_KEY: "",
333
+ OPENROUTER_API_KEY: "openrouter-test-key",
334
+ OPENROUTER_APP_ATTRIBUTION: "false",
335
+ };
336
+ const session = await new DshAdapter().createSession(config);
337
+ expect((await session.waitForCompletion()).isError).toBe(false);
338
+ const { patch } = await Bun.file(join(config.cwd, "invocation.json")).json();
339
+ expect(patch[2].config.providers.openrouter.baseURL).toBe(DEFAULT_OPENROUTER_BASE_URL);
340
+ expect(patch[2].config.providers.openrouter).not.toHaveProperty("headers");
319
341
  });
320
342
 
321
343
  test("bare models keep direct DeepSeek when both keys exist", async () => {