@desplega.ai/agent-swarm 1.173.0 → 2.1.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 (511) hide show
  1. package/README.md +1 -1
  2. package/dist/{acp-adapter-ccm9xrep.js → acp-adapter-kr4abrsk.js} +17 -16
  3. package/dist/{actions-sgeyv08k.js → actions-eh4g6w2r.js} +39 -34
  4. package/dist/{amp-adapter-gpxp3kc9.js → amp-adapter-7h7wdt8q.js} +8 -7
  5. package/dist/{app-km9qnv2f.js → app-7k8s9w5p.js} +18 -14
  6. package/dist/{assistant-5r8tcvvd.js → assistant-wswt2y7p.js} +40 -35
  7. package/dist/{boot-reembed-16rv9843.js → boot-reembed-9apm9qw6.js} +10 -9
  8. package/dist/{boot-reembed-swwzdgd7.js → boot-reembed-whzbdthj.js} +10 -9
  9. package/dist/{boot-scrub-sweep-zkr6vkqb.js → boot-scrub-sweep-ndzmvmgt.js} +11 -10
  10. package/dist/{boot-seeds-d8sp49t2.js → boot-seeds-e9sta0yt.js} +16 -13
  11. package/dist/{claude-adapter-yzzzh8jp.js → claude-adapter-4mqd3r6r.js} +19 -18
  12. package/dist/{claude-managed-adapter-xgxhqrt2.js → claude-managed-adapter-9wswb52m.js} +7 -5
  13. package/dist/{claude-managed-setup-bnmjarjc.js → claude-managed-setup-tpe1effx.js} +6 -4
  14. package/dist/{claude-sdk-session-4whxere4.js → claude-sdk-session-q49ghmh9.js} +12402 -11850
  15. package/dist/{cli-ne7smj1b.js → cli-01xvj1rq.js} +10 -10
  16. package/dist/{cli-nwrdx840.js → cli-035vekcw.js} +3 -3
  17. package/dist/{cli-9xf7fdtp.js → cli-0pd8hzhc.js} +29 -6
  18. package/dist/{cli-tmhsxhb8.js → cli-0veb92a3.js} +28 -6
  19. package/dist/{cli-c3rk1v0x.js → cli-10jesr25.js} +3 -3
  20. package/dist/{cli-6thk4jz3.js → cli-1y7yywmj.js} +6 -6
  21. package/dist/{cli-6r2f7edk.js → cli-20krdhz5.js} +47 -19
  22. package/dist/{cli-rfs4vddv.js → cli-29rja109.js} +39 -20
  23. package/dist/cli-2m1ktfzz.js +17795 -0
  24. package/dist/{cli-na2pxe4f.js → cli-2pwa0q4d.js} +15 -4
  25. package/dist/{cli-7a93cv8c.js → cli-4mengw46.js} +441 -43
  26. package/dist/{cli-94b48jhk.js → cli-5twdke5x.js} +16 -14
  27. package/dist/{cli-7a0sde2x.js → cli-6cagzc96.js} +9 -0
  28. package/dist/cli-6cvfxs8r.js +47 -0
  29. package/dist/{cli-w1apjecb.js → cli-711vwbxn.js} +2 -2
  30. package/dist/{cli-r4fqfpwf.js → cli-78vv7446.js} +50 -27
  31. package/dist/{cli-srqq3gc5.js → cli-7n6s5man.js} +5 -5
  32. package/dist/{cli-m89t9jdf.js → cli-7x0g4sn1.js} +15416 -16024
  33. package/dist/{cli-2bw3w47j.js → cli-8wwxw8a3.js} +1 -1
  34. package/dist/{cli-yatxqynm.js → cli-9ha6hs2d.js} +1 -290
  35. package/dist/{cli-ywm1azg9.js → cli-9jzdj90z.js} +8 -4
  36. package/dist/cli-9traj3dn.js +638 -0
  37. package/dist/{cli-smcrrmhy.js → cli-agzebbd5.js} +9 -9
  38. package/dist/{cli-c5km5znr.js → cli-c8r3kzzb.js} +1 -1
  39. package/dist/cli-cvv9k0hh.js +10143 -0
  40. package/dist/{cli-anzdk0z4.js → cli-d9286m9h.js} +2 -2
  41. package/dist/cli-e3g5mbkz.js +776 -0
  42. package/dist/cli-f527ce74.js +781 -0
  43. package/dist/{cli-gmdajfw5.js → cli-gxzkg1de.js} +5020 -1729
  44. package/dist/{cli-451ks6aw.js → cli-h1arqn31.js} +1 -1
  45. package/dist/cli-hjwmhcg1.js +10622 -0
  46. package/dist/{cli-jpc4r224.js → cli-hqr88ew6.js} +19 -3
  47. package/dist/{cli-h7k0xtry.js → cli-hvqbtna4.js} +4 -1256
  48. package/dist/{cli-cd0hv9nh.js → cli-j67mh2vg.js} +2 -2
  49. package/dist/cli-jta2eyzc.js +619 -0
  50. package/dist/{cli-wd0hz6ah.js → cli-kbrt8gqa.js} +9 -91
  51. package/dist/cli-kceah1eh.js +92 -0
  52. package/dist/{cli-d603qf86.js → cli-ksphgh48.js} +10 -8
  53. package/dist/{cli-3nxdmjrc.js → cli-mgz4nbgg.js} +4 -4
  54. package/dist/{cli-saqyage0.js → cli-mz5as5ha.js} +179 -27
  55. package/dist/cli-n2wq4j7m.js +6985 -0
  56. package/dist/{cli-ppy4nnnh.js → cli-n8ndw8j7.js} +2 -2
  57. package/dist/{cli-1w0vj3gw.js → cli-naqzr17j.js} +7 -7
  58. package/dist/{cli-0a7mm3zp.js → cli-nm1p8jfr.js} +25 -21
  59. package/dist/{cli-wrh08zjd.js → cli-p3zbc1rw.js} +5 -18
  60. package/dist/{cli-hsem16hb.js → cli-pe05f8yk.js} +29 -6
  61. package/dist/{cli-5wpw4rab.js → cli-qmzsbkvy.js} +5 -5
  62. package/dist/{cli-bv2etw2y.js → cli-qnm4e9e4.js} +99 -590
  63. package/dist/{cli-1x7da0de.js → cli-qqy4q48b.js} +8 -8
  64. package/dist/{cli-r44t9wqc.js → cli-r106725k.js} +2 -2
  65. package/dist/{cli-5er5gjbm.js → cli-r35rw5de.js} +15 -15
  66. package/dist/{cli-v3zhnxsb.js → cli-r610p9bs.js} +2 -2
  67. package/dist/{cli-7jykv7ef.js → cli-rc6pf5kv.js} +1 -1
  68. package/dist/{cli-x119kdba.js → cli-rdxm77k3.js} +1 -1
  69. package/dist/cli-rhs9pz33.js +294 -0
  70. package/dist/{cli-0p3enk35.js → cli-s69nfyeh.js} +9 -1
  71. package/dist/{cli-0kxf81jj.js → cli-sawv63e8.js} +2 -2
  72. package/dist/{cli-e3pmyt39.js → cli-sg19gbf0.js} +1 -1
  73. package/dist/{cli-d161g69q.js → cli-shk7r5cs.js} +57 -6
  74. package/dist/{cli-7pz3b7x5.js → cli-sksetb2q.js} +36 -1
  75. package/dist/{cli-014vns2y.js → cli-stfs0k5s.js} +208 -525
  76. package/dist/{cli-db3m6zdt.js → cli-t02z59f2.js} +1 -1
  77. package/dist/{cli-b07hr3bt.js → cli-txqb9s38.js} +2 -2
  78. package/dist/{cli-s8z6x4b4.js → cli-v7g551te.js} +225 -113
  79. package/dist/{cli-rfz2ve55.js → cli-vgqzp4em.js} +3341 -955
  80. package/dist/{cli-7avy4v8w.js → cli-w1q7h2r2.js} +5 -3
  81. package/dist/{cli-rcp5aqdw.js → cli-w7m81tb7.js} +1 -1
  82. package/dist/cli-wfnemr1t.js +86 -0
  83. package/dist/cli-wmgrkscj.js +23254 -0
  84. package/dist/{cli-qm1448f9.js → cli-wms6x9db.js} +8 -8
  85. package/dist/{cli-tadfgd0t.js → cli-x1r545x8.js} +33 -4
  86. package/dist/{cli-xc7p7s3g.js → cli-xczsgshn.js} +6 -6
  87. package/dist/{cli-fmhj1ehj.js → cli-y3167wt1.js} +882 -271
  88. package/dist/{cli-r0byd61h.js → cli-yjmqpxt3.js} +17 -16
  89. package/dist/{cli-81r1wjyv.js → cli-z780ec7c.js} +1 -1
  90. package/dist/{cli-phcqp1b9.js → cli-zm9t2vde.js} +2 -2
  91. package/dist/cli.js +34 -49
  92. package/dist/{codex-adapter-r3dn4fp5.js → codex-adapter-4z16v9n0.js} +15 -14
  93. package/dist/{codex-login-841kv5fk.js → codex-login-pkepy2j1.js} +3 -2
  94. package/dist/{codex-session-runner-fwt793h4.js → codex-session-runner-rvms9y2x.js} +15 -14
  95. package/dist/{commands-z8fq9qna.js → commands-2a9rtyat.js} +11 -10
  96. package/dist/{controller-hq5ggpcp.js → controller-3k3madbn.js} +5 -4
  97. package/dist/{controllers-connect-3mf2btyj.js → controllers-connect-v9yn8zb9.js} +42 -37
  98. package/dist/{cursor-adapter-q8pgv5qa.js → cursor-adapter-98gbhyzn.js} +7 -6
  99. package/dist/{db-qfpq4bt7.js → db-eywwsnjw.js} +30 -9
  100. package/dist/{db-yyhqqrbf.js → db-ncypk9np.js} +12 -11
  101. package/dist/{devin-adapter-xgtj680j.js → devin-adapter-zeq8b9gz.js} +3 -2
  102. package/dist/{dispatcher-x0x7qfcq.js → dispatcher-w94kc575.js} +38 -33
  103. package/dist/{docker-f503k9p0.js → docker-b1e4cqab.js} +3 -2
  104. package/dist/{dsh-adapter-34etvb5m.js → dsh-adapter-47ztej36.js} +7 -6
  105. package/dist/{e2b-66xra4az.js → e2b-bbt8ey45.js} +4 -3
  106. package/dist/{e2b-xwhyj8cw.js → e2b-dp804sen.js} +4 -3
  107. package/dist/{event-bus-vjtv82gm.js → event-bus-j2jdg3qb.js} +3 -2
  108. package/dist/{external-channel-h1fjecwe.js → external-channel-zrkg8ar9.js} +11 -10
  109. package/dist/{google-generative-ai-szdxbpqg.js → google-generative-ai-g8db7x0f.js} +4 -2
  110. package/dist/{google-vertex-qzw3p36d.js → google-vertex-r2692q0r.js} +4 -2
  111. package/dist/{grok-adapter-e7mr287e.js → grok-adapter-s9ncp9vy.js} +18 -17
  112. package/dist/{handlers-znb9n7d4.js → handlers-kqftvjgw.js} +41 -36
  113. package/dist/{heartbeat-bm4qkn8d.js → heartbeat-9q96p8v9.js} +43 -38
  114. package/dist/{hook-254r4mza.js → hook-56qe10dp.js} +11 -23
  115. package/dist/{hook-xaa3q80s.js → hook-yy6rw42k.js} +12 -11
  116. package/dist/{http-3dgrgp8y.js → http-r2dnb8f8.js} +2411 -275
  117. package/dist/{index-fx3jqq1e.js → index-3gtmkhc6.js} +38 -33
  118. package/dist/{index-bdsqa799.js → index-3k5hkz7r.js} +43 -38
  119. package/dist/index-52j1aja4.js +3465 -0
  120. package/dist/{index-g2byveez.js → index-5e101240.js} +38 -33
  121. package/dist/{index-2ybk7khy.js → index-5wyfjc2c.js} +845 -683
  122. package/dist/index-9gw58n4k.js +193 -0
  123. package/dist/index-9j87mcm8.js +56305 -0
  124. package/dist/index-bsmcresk.js +79424 -0
  125. package/dist/index-mh85qhec.js +475 -0
  126. package/dist/{index-jd7sejm2.js → index-vxvhxxbn.js} +10 -9
  127. package/dist/index-we27kd31.js +12810 -0
  128. package/dist/{keepalive-6ywhpja1.js → keepalive-psj6j54c.js} +12 -11
  129. package/dist/{lead-cw45n21s.js → lead-hajz4688.js} +61 -56
  130. package/dist/{maintenance-ss1a1ge9.js → maintenance-ps02sb5m.js} +10 -9
  131. package/dist/{node-fkajaq96.js → node-mwb5r697.js} +2 -1
  132. package/dist/{oauth-refresh-sweep-3c6f8vyy.js → oauth-refresh-sweep-0284h93b.js} +11 -10
  133. package/dist/{onboard-8hzdhc1k.js → onboard-rbf6bg01.js} +11 -3
  134. package/dist/{opencode-adapter-qbchbcfz.js → opencode-adapter-13dcatgq.js} +18 -13
  135. package/dist/{otel-impl-e6mnvc4v.js → otel-impl-zvqcbhp7.js} +242 -868
  136. package/dist/{pi-mono-adapter-3c9g9snh.js → pi-mono-adapter-xxy47q4v.js} +244 -106
  137. package/dist/{pricing-refresh-gedkjdw0.js → pricing-refresh-kefx9v3t.js} +12 -11
  138. package/dist/{rbac-roles-dt4vh1yy.js → rbac-roles-3m36rxm2.js} +11 -10
  139. package/dist/{rbac-roles-eszdg6pm.js → rbac-roles-z76xv0hq.js} +12 -11
  140. package/dist/{render-v2-907cy6sa.js → render-v2-wz73bs25.js} +18 -14
  141. package/dist/{resume-42stcaz0.js → resume-k4wzge8w.js} +25 -21
  142. package/dist/{runtime-qfry88v2.js → runtime-sa2vr16g.js} +2 -2
  143. package/dist/{setup-adbc1c10.js → setup-2fp2pypp.js} +2 -2
  144. package/dist/{virtual-modules-rtmt494q.js → virtual-modules-9gkxqrap.js} +6 -5
  145. package/dist/{work-objects-wpj8ck95.js → work-objects-6k0ywz3m.js} +41 -36
  146. package/dist/{worker-bzdra9rf.js → worker-5v3rxsdh.js} +61 -56
  147. package/dist/{x-qfmygqqv.js → x-4mtdeym1.js} +3 -2
  148. package/openapi.json +1279 -329
  149. package/package.json +15 -15
  150. package/src/agentmail/handlers.ts +21 -11
  151. package/src/apps/definition.ts +16 -0
  152. package/src/azure-devops/api.ts +52 -2
  153. package/src/azure-devops/boards-sync.ts +50 -20
  154. package/src/azure-devops/index.ts +1 -0
  155. package/src/azure-devops/outbound.ts +95 -0
  156. package/src/be/boot-seeds.ts +11 -0
  157. package/src/be/config-refs.ts +60 -0
  158. package/src/be/db/model-profiles.ts +315 -0
  159. package/src/be/db/page-versions.ts +15 -0
  160. package/src/be/db/runtime.ts +35 -8
  161. package/src/be/db/skill-name-resolution.ts +36 -0
  162. package/src/be/db/tasks/read.ts +237 -0
  163. package/src/be/db/tasks/write.ts +7 -0
  164. package/src/be/db.ts +87 -36
  165. package/src/be/migrations/215_swarm_config_export_to_env.sql +34 -0
  166. package/src/be/migrations/216_task_resolved_effort.sql +5 -0
  167. package/src/be/migrations/217_chat_sdk_state.sql +104 -0
  168. package/src/be/migrations/219_relabel_pi_context_compactions.sql +22 -0
  169. package/src/be/migrations/220_model_profiles.sql +137 -0
  170. package/src/be/model-catalog-store.ts +5 -1
  171. package/src/be/model-tier-resolution.ts +155 -10
  172. package/src/be/model-validation.ts +12 -2
  173. package/src/be/models-catalog.ts +26 -1
  174. package/src/be/modelsdev-cache.json +198 -63
  175. package/src/be/modelsdev-cache.ts +9 -1
  176. package/src/be/script-credential-broker.ts +39 -2
  177. package/src/be/scripts/run-saved.ts +1 -0
  178. package/src/be/scripts/typecheck.ts +22 -8
  179. package/src/be/secret-registry.ts +6 -2
  180. package/src/be/seed/runner.ts +13 -1
  181. package/src/be/seed/state-db.ts +5 -0
  182. package/src/be/seed/types.ts +6 -0
  183. package/src/be/seed-model-profiles.ts +41 -0
  184. package/src/be/seed-pricing.ts +19 -2
  185. package/src/be/seed-scripts/catalog/heartbeat-jev-gate.ts +368 -0
  186. package/src/be/seed-scripts/catalog/ops-catalog-audit.inline.ts +1 -1
  187. package/src/be/seed-scripts/catalog/ops-catalog-audit.ts +1 -1
  188. package/src/be/seed-scripts/index.ts +9 -0
  189. package/src/be/seed-skills/bundled-files.generated.json +0 -18
  190. package/src/be/seed-skills/index.ts +104 -3
  191. package/src/be/swarm-config-guard.ts +53 -0
  192. package/src/cli.tsx +0 -16
  193. package/src/commands/onboard/non-interactive.ts +4 -2
  194. package/src/commands/provider-credentials.ts +52 -6
  195. package/src/commands/runner.ts +193 -71
  196. package/src/e2b/env.ts +5 -0
  197. package/src/extensions/catalog.generated.json +17 -0
  198. package/src/extensions/contract-types.generated.ts +1 -1
  199. package/src/extensions/contract.ts +45 -0
  200. package/src/extensions/dispatcher.ts +7 -0
  201. package/src/extensions/loader.ts +10 -5
  202. package/src/extensions/post-bridge.ts +40 -0
  203. package/src/github/handlers.ts +11 -4
  204. package/src/heartbeat/heartbeat.ts +2 -1
  205. package/src/heartbeat/templates.ts +8 -5
  206. package/src/hooks/hook.ts +0 -26
  207. package/src/http/agents.ts +49 -2
  208. package/src/http/all-routes.ts +1 -1
  209. package/src/http/apps.ts +30 -3
  210. package/src/http/config.ts +62 -19
  211. package/src/http/core.ts +8 -0
  212. package/src/http/index.ts +25 -3
  213. package/src/http/integrations.ts +88 -1
  214. package/src/http/mcp.ts +15 -5
  215. package/src/http/model-profiles.ts +303 -0
  216. package/src/http/page-proxy.ts +2 -4
  217. package/src/http/pages-public.ts +13 -7
  218. package/src/http/pages.ts +3 -13
  219. package/src/http/poll.ts +42 -71
  220. package/src/http/requested-by.ts +54 -0
  221. package/src/http/script-runs.ts +1 -1
  222. package/src/http/scripts.ts +26 -2
  223. package/src/http/stats.ts +1 -30
  224. package/src/http/tasks.ts +79 -5
  225. package/src/http/utils.ts +14 -0
  226. package/src/http/webhooks.ts +160 -3
  227. package/src/http/x.ts +1 -0
  228. package/src/integrations/chat-sdk/adapters.ts +129 -0
  229. package/src/integrations/chat-sdk/attachments.ts +232 -0
  230. package/src/integrations/chat-sdk/background.ts +36 -0
  231. package/src/integrations/chat-sdk/commands.ts +194 -0
  232. package/src/integrations/chat-sdk/config.ts +198 -0
  233. package/src/integrations/chat-sdk/discord-api.ts +63 -0
  234. package/src/integrations/chat-sdk/discord-gateway.ts +185 -0
  235. package/src/integrations/chat-sdk/handlers.ts +28 -0
  236. package/src/integrations/chat-sdk/inbound.ts +426 -0
  237. package/src/integrations/chat-sdk/limits.ts +48 -0
  238. package/src/integrations/chat-sdk/logger.ts +81 -0
  239. package/src/integrations/chat-sdk/outbound.ts +123 -0
  240. package/src/integrations/chat-sdk/relay.ts +172 -0
  241. package/src/integrations/chat-sdk/router.ts +169 -0
  242. package/src/integrations/chat-sdk/runtime.ts +351 -0
  243. package/src/integrations/chat-sdk/schema.ts +245 -0
  244. package/src/integrations/chat-sdk/state.ts +243 -0
  245. package/src/integrations/chat-sdk/status.ts +244 -0
  246. package/src/integrations/chat-sdk/steering.ts +58 -0
  247. package/src/integrations/chat-sdk/teams-origin.ts +77 -0
  248. package/src/integrations/chat-sdk/telegram-api.ts +56 -0
  249. package/src/integrations/chat-sdk/templates.ts +16 -0
  250. package/src/integrations/chat-sdk/test-hooks.ts +37 -0
  251. package/src/integrations/chat-sdk/thread-buffer.ts +100 -0
  252. package/src/integrations/chat-sdk/typing.ts +65 -0
  253. package/src/{artifact-sdk → pages}/feedback-overlay.ts +65 -6
  254. package/src/pages/version.ts +4 -5
  255. package/src/prompts/base-prompt.ts +111 -45
  256. package/src/providers/acp-target-catalog.ts +6 -0
  257. package/src/providers/acp-targets.ts +56 -3
  258. package/src/providers/amp-adapter.ts +1 -1
  259. package/src/providers/claude-adapter.ts +1 -1
  260. package/src/providers/claude-flag-settings.ts +17 -5
  261. package/src/providers/claude-managed-adapter.ts +1 -1
  262. package/src/providers/claude-session-events.ts +6 -1
  263. package/src/providers/dsh-adapter.ts +1 -1
  264. package/src/providers/opencode-adapter.ts +243 -26
  265. package/src/providers/opencode-swarm-plugin.ts +31 -0
  266. package/src/providers/pi-mono-adapter.ts +398 -153
  267. package/src/providers/pi-mono-extension.ts +17 -3
  268. package/src/providers/pricing-sources.md +33 -2
  269. package/src/providers/reasoning-effort.ts +37 -0
  270. package/src/providers/types.ts +6 -2
  271. package/src/rbac/legacy-policy.ts +16 -2
  272. package/src/rbac/permissions.ts +26 -0
  273. package/src/scheduler/deferred-task-waits.ts +18 -5
  274. package/src/scheduler/scheduler.ts +11 -3
  275. package/src/scripts-runtime/executors/quickjs-prelude.ts +75 -6
  276. package/src/scripts-runtime/executors/quickjs-runner.ts +85 -8
  277. package/src/scripts-runtime/executors/quickjs-web-shims.ts +1041 -0
  278. package/src/scripts-runtime/extract-args-schema.ts +9 -8
  279. package/src/scripts-runtime/loader.ts +19 -7
  280. package/src/scripts-runtime/quickjs-compat.ts +464 -0
  281. package/src/scripts-runtime/sdk-allowlist.ts +12 -6
  282. package/src/scripts-runtime/swarm-sdk.ts +0 -11
  283. package/src/scripts-runtime/types/stdlib.d.ts +39 -15
  284. package/src/scripts-runtime/types/swarm-extension.d.ts +66 -1
  285. package/src/scripts-runtime/types/swarm-sdk.d.ts +39 -15
  286. package/src/server.ts +62 -19
  287. package/src/slack/blocks.ts +4 -2
  288. package/src/slack/event-dedup.ts +1 -1
  289. package/src/slack/reaction-shortcode.ts +14 -0
  290. package/src/slack/render-v2.ts +12 -7
  291. package/src/slack/responses.ts +5 -4
  292. package/src/slack/thread-buffer.ts +41 -22
  293. package/src/tasks/additive-buffer.ts +4 -2
  294. package/src/tasks/context-key.ts +60 -1
  295. package/src/tasks/model-pin.ts +67 -0
  296. package/src/tasks/worker-follow-up.ts +15 -33
  297. package/src/tests/acp-adapter.test.ts +42 -0
  298. package/src/tests/agentmail-filters.test.ts +47 -0
  299. package/src/tests/api-drain-worker-e2e.test.ts +1 -6
  300. package/src/tests/apps-spike2.test.ts +35 -0
  301. package/src/tests/azure-devops-outbound.test.ts +420 -0
  302. package/src/tests/base-prompt.test.ts +58 -54
  303. package/src/tests/chat-sdk-commands.test.ts +191 -0
  304. package/src/tests/chat-sdk-config.test.ts +334 -0
  305. package/src/tests/chat-sdk-discord-api.test.ts +68 -0
  306. package/src/tests/chat-sdk-discord-gateway.test.ts +252 -0
  307. package/src/tests/chat-sdk-discord-webhook.test.ts +126 -0
  308. package/src/tests/chat-sdk-fields-sync.test.ts +50 -0
  309. package/src/tests/chat-sdk-harness.ts +146 -0
  310. package/src/tests/chat-sdk-inbound.test.ts +363 -0
  311. package/src/tests/chat-sdk-relay.test.ts +470 -0
  312. package/src/tests/chat-sdk-reload.test.ts +128 -0
  313. package/src/tests/chat-sdk-shutdown.test.ts +220 -0
  314. package/src/tests/chat-sdk-slack.test.ts +268 -0
  315. package/src/tests/chat-sdk-state.test.ts +163 -0
  316. package/src/tests/chat-sdk-status-route.test.ts +382 -0
  317. package/src/tests/chat-sdk-teams.test.ts +277 -0
  318. package/src/tests/chat-sdk-tools.test.ts +520 -0
  319. package/src/tests/chat-sdk-typing.test.ts +120 -0
  320. package/src/tests/chat-sdk-webhook-telegram.test.ts +200 -0
  321. package/src/tests/claude-adapter-binary.test.ts +9 -4
  322. package/src/tests/claude-flag-settings.test.ts +11 -3
  323. package/src/tests/claude-managed-adapter.test.ts +1 -1
  324. package/src/tests/claude-sdk-lifecycle.test.ts +9 -3
  325. package/src/tests/claude-thinking-tokens.test.ts +57 -0
  326. package/src/tests/claude-worker-parity.test.ts +2 -7
  327. package/src/tests/config-env-export-chat-sdk.test.ts +11 -0
  328. package/src/tests/config-refs.test.ts +66 -0
  329. package/src/tests/context-key.test.ts +19 -0
  330. package/src/tests/cors-allowlist.test.ts +25 -0
  331. package/src/tests/create-page-tool.test.ts +18 -0
  332. package/src/tests/credential-check.test.ts +139 -0
  333. package/src/tests/credentials.test.ts +21 -0
  334. package/src/tests/db-runtime.test.ts +61 -0
  335. package/src/tests/db-task-read-contract.test.ts +22 -0
  336. package/src/tests/db-task-write-contract.test.ts +11 -0
  337. package/src/tests/deferred-task-wake.test.ts +78 -1
  338. package/src/tests/docs-index-footprint.test.js +3 -3
  339. package/src/tests/dsh-adapter.test.ts +1 -1
  340. package/src/tests/enabled-capabilities.test.ts +20 -0
  341. package/src/tests/entrypoint-api-readiness.test.ts +14 -8
  342. package/src/tests/entrypoint-config-env-export.test.ts +5 -2
  343. package/src/tests/entrypoint-export-to-env.test.ts +78 -0
  344. package/src/tests/env-flag.test.ts +1 -0
  345. package/src/tests/explicit-model-validation.test.ts +105 -1
  346. package/src/tests/extensions-catalog.test.ts +1 -0
  347. package/src/tests/extensions-example-comms-adherence-gate.test.ts +413 -0
  348. package/src/tests/extensions-example-route-chat-channel.test.ts +154 -0
  349. package/src/tests/extensions-loader.test.ts +15 -0
  350. package/src/tests/extensions-post-bridge.test.ts +51 -0
  351. package/src/tests/fetch-resolved-env.test.ts +128 -0
  352. package/src/tests/fixtures/chat-sdk/telegram-private-text.json +10 -0
  353. package/src/tests/fixtures/extensions/route-chat-channel-to-agent/hooks.ts +27 -0
  354. package/src/tests/fixtures/extensions/route-chat-channel-to-agent/manifest.json +9 -0
  355. package/src/tests/fixtures/openai-compatible-registry-extension.child.ts +146 -0
  356. package/src/tests/fixtures/runner-legacy-resume-prompt.ts +122 -0
  357. package/src/tests/github-handlers.test.ts +35 -0
  358. package/src/tests/harness-provider-resolution.test.ts +12 -0
  359. package/src/tests/heartbeat-checklist.test.ts +3 -2
  360. package/src/tests/heartbeat-jev-gate-credentials.test.ts +168 -0
  361. package/src/tests/heartbeat-jev-gate.test.ts +385 -0
  362. package/src/tests/hook-runtime-close.test.ts +0 -2
  363. package/src/tests/http-api-integration.test.ts +0 -25
  364. package/src/tests/migration-046-budgets.test.ts +17 -16
  365. package/src/tests/migration-217-chat-sdk-state.test.ts +113 -0
  366. package/src/tests/model-profile-backcompat.test.ts +327 -0
  367. package/src/tests/model-profile-providers.test.ts +302 -0
  368. package/src/tests/model-profile-runtime-secrets.test.ts +223 -0
  369. package/src/tests/model-profiles-api.test.ts +614 -0
  370. package/src/tests/model-tier-resolution.test.ts +93 -0
  371. package/src/tests/models-catalog.test.ts +42 -1
  372. package/src/tests/openai-compatible-providers.test.ts +134 -0
  373. package/src/tests/openai-compatible-registry-extension.test.ts +42 -0
  374. package/src/tests/opencode-adapter.test.ts +216 -2
  375. package/src/tests/opencode-helmcode-sdk.test.ts +201 -0
  376. package/src/tests/opencode-shell-env.test.ts +145 -0
  377. package/src/tests/openrouter-base-url.test.ts +101 -151
  378. package/src/tests/page-proxy.test.ts +1 -1
  379. package/src/tests/pages-browser-sdk.test.ts +76 -0
  380. package/src/tests/pages-feedback-keys.test.ts +121 -0
  381. package/src/tests/pages-feedback.test.ts +4 -1
  382. package/src/tests/paused-tasks-requested-by.test.ts +102 -0
  383. package/src/tests/pi-context-compaction.test.ts +193 -0
  384. package/src/tests/pi-custom-provider-fake-server.test.ts +442 -0
  385. package/src/tests/pi-mono-adapter.test.ts +385 -5
  386. package/src/tests/pi-shell-env.test.ts +170 -0
  387. package/src/tests/profile-claim.test.ts +269 -0
  388. package/src/tests/prompt-template-remaining.test.ts +35 -0
  389. package/src/tests/quickjs-compat.test.ts +213 -0
  390. package/src/tests/rbac-engine.test.ts +32 -3
  391. package/src/tests/runner-exit-after-tasks.test.ts +0 -4
  392. package/src/tests/runner-legacy-resume-prompt.test.ts +64 -0
  393. package/src/tests/script-connections.test.ts +6 -0
  394. package/src/tests/script-executor-conformance.test.ts +252 -1
  395. package/src/tests/script-executor-quickjs.test.ts +96 -0
  396. package/src/tests/script-runs-http.test.ts +89 -0
  397. package/src/tests/scripts-http.test.ts +217 -1
  398. package/src/tests/scripts-only-gating.test.ts +2 -3
  399. package/src/tests/scripts-runtime.test.ts +58 -40
  400. package/src/tests/secret-scrubber.test.ts +96 -0
  401. package/src/tests/seed-model-profiles.test.ts +174 -0
  402. package/src/tests/seed-scripts.test.ts +2 -2
  403. package/src/tests/seed-skills-bundled-files.test.ts +45 -53
  404. package/src/tests/seed-skills-retire.test.ts +182 -0
  405. package/src/tests/send-task-requester-gate.test.ts +58 -0
  406. package/src/tests/session-cost-recompute-unit.test.ts +46 -0
  407. package/src/tests/skill-get-name-resolution.test.ts +137 -0
  408. package/src/tests/skill-invocations.test.ts +4 -2
  409. package/src/tests/slack-blocks.test.ts +36 -0
  410. package/src/tests/slack-inline-output.test.ts +14 -0
  411. package/src/tests/slack-reaction-config.test.ts +20 -0
  412. package/src/tests/slack-render-v2.test.ts +37 -0
  413. package/src/tests/slack-thread-buffer.test.ts +98 -1
  414. package/src/tests/sonnet-5-5-registry.test.ts +9 -1
  415. package/src/tests/swarm-config-export-to-env.test.ts +267 -0
  416. package/src/tests/swarm-config-reserved-keys.test.ts +16 -0
  417. package/src/tests/swarm-diff.test.ts +1 -1
  418. package/src/tests/task-model-effort-rerun.test.ts +164 -0
  419. package/src/tests/task-session-prompts.test.ts +132 -0
  420. package/src/tests/task-tool-manifest.test.ts +14 -0
  421. package/src/tests/task-tools-ctx.test.ts +2 -0
  422. package/src/tests/tool-annotations.test.ts +6 -6
  423. package/src/tests/ui-logs-parser.test.ts +43 -0
  424. package/src/tests/used-by-route.test.ts +6 -0
  425. package/src/tests/vercel-ai-gateway.test.ts +149 -0
  426. package/src/tests/workflow-llm.test.ts +61 -0
  427. package/src/tests/workflow-swarm-script.test.ts +38 -0
  428. package/src/tests/workflow-wait-filter.test.ts +5 -3
  429. package/src/tools/chat-access.ts +41 -0
  430. package/src/tools/chat-open-dm.ts +59 -0
  431. package/src/tools/chat-post.ts +74 -0
  432. package/src/tools/chat-read-thread.ts +100 -0
  433. package/src/tools/chat-reply.ts +75 -0
  434. package/src/tools/create-page.ts +3 -13
  435. package/src/tools/model-catalog/index.ts +2 -1
  436. package/src/tools/model-profiles/list-model-profiles.ts +54 -0
  437. package/src/tools/register-chat-channel.ts +139 -0
  438. package/src/tools/request-human-input.ts +11 -3
  439. package/src/tools/script-apis.ts +5 -1
  440. package/src/tools/script-upsert.ts +4 -1
  441. package/src/tools/send-task.ts +48 -1
  442. package/src/tools/skills/skill-get.ts +17 -9
  443. package/src/tools/swarm-config/get-config.ts +2 -1
  444. package/src/tools/swarm-config/list-config.ts +2 -1
  445. package/src/tools/swarm-config/set-config.ts +16 -3
  446. package/src/tools/system-one-decide.ts +104 -0
  447. package/src/tools/task-action.ts +22 -1
  448. package/src/tools/tool-config.ts +14 -6
  449. package/src/types.ts +48 -3
  450. package/src/utils/config-env-export.ts +74 -0
  451. package/src/utils/credentials.test.ts +92 -0
  452. package/src/utils/credentials.ts +52 -4
  453. package/src/utils/harness-provider.ts +15 -6
  454. package/src/utils/runtime-model-catalog.ts +11 -1
  455. package/src/utils/secret-scrubber.ts +86 -10
  456. package/src/utils/task-tool-manifest.ts +19 -2
  457. package/src/utils/vcs-repo-match.ts +29 -0
  458. package/src/workflows/executors/raw-llm.ts +8 -3
  459. package/src/workflows/executors/swarm-script.ts +1 -0
  460. package/src/workflows/executors/system-one-decision.ts +21 -11
  461. package/src/workflows/executors/workflow-llm.ts +22 -0
  462. package/src/workflows/index.ts +21 -12
  463. package/templates/extensions/README.md +1 -0
  464. package/templates/extensions/comms-adherence-gate/README.md +88 -0
  465. package/templates/extensions/comms-adherence-gate/hooks.ts +489 -0
  466. package/templates/extensions/comms-adherence-gate/manifest.json +8 -0
  467. package/templates/skills/agent-browser/SKILL.md +1 -1
  468. package/templates/skills/agent-browser/content.md +1 -1
  469. package/templates/skills/apps/SKILL.md +8 -1
  470. package/templates/skills/apps/content.md +8 -1
  471. package/templates/skills/chat-sdk/config.json +14 -0
  472. package/templates/skills/chat-sdk/content.md +84 -0
  473. package/templates/skills/composio-gmail/content.md +7 -0
  474. package/templates/skills/composio-google-docs/content.md +1 -1
  475. package/templates/skills/email-domain-health/config.json +14 -0
  476. package/templates/skills/email-domain-health/content.md +131 -0
  477. package/templates/skills/kv-storage/content.md +2 -2
  478. package/templates/skills/pages/content.md +8 -10
  479. package/templates/skills/swarm-extensions/content.md +2 -0
  480. package/templates/skills/swarm-scripts/SKILL.md +4 -2
  481. package/templates/skills/swarm-scripts/config.json +1 -1
  482. package/templates/skills/swarm-scripts/content.md +4 -2
  483. package/templates/skills/workflow-iterate/content.md +38 -0
  484. package/dist/artifact-eczsknc3.js +0 -3843
  485. package/dist/cli-42qhyvwx.js +0 -33
  486. package/dist/cli-bd957dgb.js +0 -153
  487. package/dist/cli-q51yprfq.js +0 -1104
  488. package/dist/cli-t1ze64h3.js +0 -811
  489. package/dist/cli-tcc4wd5d.js +0 -56471
  490. package/dist/index-9ch1n761.js +0 -1102
  491. package/src/artifact-sdk/index.ts +0 -2
  492. package/src/artifact-sdk/localtunnel.d.ts +0 -20
  493. package/src/artifact-sdk/port.ts +0 -12
  494. package/src/artifact-sdk/server.ts +0 -183
  495. package/src/artifact-sdk/tunnel.ts +0 -19
  496. package/src/commands/artifact.ts +0 -249
  497. package/src/http/ecosystem.ts +0 -77
  498. package/src/tests/artifact-commands.test.ts +0 -92
  499. package/src/tests/artifact-sdk.test.ts +0 -793
  500. package/src/tests/register-service-security.test.ts +0 -194
  501. package/src/tools/list-services.ts +0 -107
  502. package/src/tools/register-service.ts +0 -177
  503. package/src/tools/unregister-service.ts +0 -73
  504. package/src/tools/update-service-status.ts +0 -96
  505. package/templates/skills/artifacts/config.json +0 -14
  506. package/templates/skills/artifacts/content.md +0 -344
  507. package/templates/skills/artifacts/files/examples/approval-flow.ts +0 -34
  508. package/templates/skills/artifacts/files/examples/hono-dashboard.ts +0 -31
  509. package/templates/skills/artifacts/files/examples/multi-artifact.ts +0 -20
  510. package/templates/skills/artifacts/files/examples/static-report.sh +0 -17
  511. /package/src/{artifact-sdk → pages}/browser-sdk.ts +0 -0
@@ -267,6 +267,99 @@ describe("claim-time resolution via /api/poll", () => {
267
267
  });
268
268
  });
269
269
 
270
+ // A row that already ran carries that run's model: the session report wrote it
271
+ // into `model`, and the claim wrote `resolvedModel`/`modelSource`. Re-claiming
272
+ // it must resolve from the config in force NOW, not replay the previous run.
273
+ describe("re-claim after a previous run", () => {
274
+ async function markRan(
275
+ taskId: string,
276
+ fields: { model: string; resolvedModel?: string; modelSource?: string },
277
+ ): Promise<void> {
278
+ await getDbClient().run(
279
+ `UPDATE agent_tasks SET model = ?, resolvedModel = ?, modelSource = ?,
280
+ claudeSessionId = 'previous-session', status = 'pending' WHERE id = ?`,
281
+ [fields.model, fields.resolvedModel ?? null, fields.modelSource ?? null, taskId],
282
+ );
283
+ }
284
+
285
+ test("an unpinned task drops the previous run's model, so the agent's current config applies", async () => {
286
+ const worker = await createAgent({
287
+ name: "rerun-unpinned",
288
+ isLead: false,
289
+ status: "idle",
290
+ maxTasks: 1,
291
+ harnessProvider: "claude",
292
+ });
293
+ const task = await createTaskExtended("plain work", { agentId: worker.id });
294
+ // First run: no pin, MODEL_OVERRIDE was opus, the session reported it.
295
+ await markRan(task.id, { model: "claude-opus-5-5" });
296
+
297
+ const trigger = await callPoll(worker.id);
298
+
299
+ expect(trigger?.type).toBe("task_assigned");
300
+ // No `model` in the trigger: the worker falls through to MODEL_OVERRIDE.
301
+ expect(trigger?.task.model).toBeUndefined();
302
+ expect(trigger?.task.resolvedModel).toBeUndefined();
303
+ expect(await taskRow(task.id)).toEqual({
304
+ resolvedModel: null,
305
+ modelSource: null,
306
+ modelAlias: null,
307
+ });
308
+ });
309
+
310
+ test("a tier task re-resolves the tier after the tier config changed", async () => {
311
+ const worker = await createAgent({
312
+ name: "rerun-tier",
313
+ isLead: false,
314
+ status: "idle",
315
+ maxTasks: 1,
316
+ harnessProvider: "claude",
317
+ });
318
+ const task = await createTaskExtended("smart work", { agentId: worker.id, modelTier: "smart" });
319
+ await markRan(task.id, {
320
+ model: "claude-opus-5-5",
321
+ resolvedModel: "claude-opus-5-5",
322
+ modelSource: "tier-config",
323
+ });
324
+ process.env.MODEL_TIER_CLAUDE_SMART = "claude-sonnet-5-5";
325
+
326
+ const trigger = await callPoll(worker.id, header({}));
327
+
328
+ expect(trigger?.task.model).toBeUndefined();
329
+ expect(trigger?.task.resolvedModel).toBe("claude-sonnet-5-5");
330
+ expect(await taskRow(task.id)).toEqual({
331
+ resolvedModel: "claude-sonnet-5-5",
332
+ modelSource: "tier-config",
333
+ modelAlias: null,
334
+ });
335
+ });
336
+
337
+ test("a task-pinned model stays pinned on re-claim", async () => {
338
+ const worker = await createAgent({
339
+ name: "rerun-pinned",
340
+ isLead: false,
341
+ status: "idle",
342
+ maxTasks: 1,
343
+ harnessProvider: "claude",
344
+ });
345
+ const task = await createTaskExtended("pinned work", {
346
+ agentId: worker.id,
347
+ model: "claude-sonnet-5-5",
348
+ });
349
+ await markRan(task.id, {
350
+ model: "claude-sonnet-5-5",
351
+ resolvedModel: "claude-sonnet-5-5",
352
+ modelSource: "model",
353
+ });
354
+
355
+ const trigger = await callPoll(worker.id);
356
+
357
+ expect(trigger?.task.model).toBe("claude-sonnet-5-5");
358
+ expect(trigger?.task.resolvedModel).toBe("claude-sonnet-5-5");
359
+ expect((await taskRow(task.id))?.modelSource).toBe("model");
360
+ });
361
+ });
362
+
270
363
  describe("claim-time harness guard", () => {
271
364
  test("a directed task pinning an Anthropic model on a Codex worker fails fast with a tagged reason", async () => {
272
365
  const worker = await createAgent({
@@ -11,14 +11,17 @@
11
11
  */
12
12
 
13
13
  import { afterEach, describe, expect, test } from "bun:test";
14
+ import { reasoningLevelsForModel } from "@desplega/model-catalog";
14
15
  import {
15
16
  buildModelsCatalog,
17
+ type CatalogProviderId,
16
18
  getModelsCatalog,
17
19
  PINNED_MODELSDEV_ENTRIES,
18
20
  resetModelsCatalogForTests,
19
21
  updateLiveModelsCatalog,
20
22
  } from "../be/models-catalog";
21
23
  import type { ModelsDevCache } from "../be/modelsdev-cache";
24
+ import { getContextWindowSize } from "../utils/context-window";
22
25
 
23
26
  afterEach(() => {
24
27
  resetModelsCatalogForTests();
@@ -82,6 +85,14 @@ describe("getModelsCatalog", () => {
82
85
  expect(Object.keys(result.providers.openrouter?.models ?? {}).length).toBeGreaterThan(0);
83
86
  expect(Object.keys(result.providers.anthropic?.models ?? {}).length).toBeGreaterThan(0);
84
87
  expect(Object.keys(result.providers.opencode?.models ?? {}).length).toBeGreaterThan(0);
88
+ expect(result.providers.nan?.models["glm5.3-flash"]).toBeDefined();
89
+ expect(Object.keys(result.providers.helmcode?.models ?? {}).sort()).toEqual([
90
+ "deepseek-v4-flash",
91
+ "gemma4",
92
+ "glm5.3",
93
+ "glm5.3-flash",
94
+ "qwen3.6",
95
+ ]);
85
96
  });
86
97
 
87
98
  test("prefers live data after an update", () => {
@@ -99,7 +110,7 @@ describe("getModelsCatalog", () => {
99
110
  const result = getModelsCatalog();
100
111
  for (const entryPath of PINNED_MODELSDEV_ENTRIES) {
101
112
  const slashIndex = entryPath.indexOf("/");
102
- const providerId = entryPath.slice(0, slashIndex) as "anthropic" | "amazon-bedrock";
113
+ const providerId = entryPath.slice(0, slashIndex) as CatalogProviderId;
103
114
  const modelId = entryPath.slice(slashIndex + 1);
104
115
  expect(result.providers[providerId]?.models[modelId]).toBeDefined();
105
116
  }
@@ -118,3 +129,33 @@ describe("getModelsCatalog", () => {
118
129
  expect(result.providers.anthropic?.models["claude-mythos-5"]?.name).toBe("Live Mythos");
119
130
  });
120
131
  });
132
+
133
+ describe("nan and helmcode sections", () => {
134
+ test("the registry-synthesized helmcode section survives a live payload that lacks it", () => {
135
+ // LIVE_CACHE has no helmcode provider: the registry section is refilled.
136
+ updateLiveModelsCatalog(LIVE_CACHE);
137
+ const helmcode = getModelsCatalog().providers.helmcode;
138
+ expect(helmcode?.id).toBe("helmcode");
139
+ expect(helmcode?.name).toBe("Helmcode");
140
+ expect(helmcode?.models["glm5.3"]?.limit?.context).toBe(1_048_576);
141
+ expect(helmcode?.models["qwen3.6"]?.limit?.context).toBe(262_144);
142
+ });
143
+
144
+ test("reasoning levels resolve for helmcode and nan ids on pi and opencode", () => {
145
+ const catalog = getModelsCatalog().providers;
146
+ for (const harness of ["pi", "opencode"]) {
147
+ expect(
148
+ reasoningLevelsForModel(harness, "helmcode/glm5.3-flash", catalog).length,
149
+ ).toBeGreaterThan(0);
150
+ expect(reasoningLevelsForModel(harness, "nan/glm5.3-flash", catalog).length).toBeGreaterThan(
151
+ 0,
152
+ );
153
+ }
154
+ });
155
+
156
+ test("context windows come from the catalog sections", () => {
157
+ expect(getContextWindowSize("helmcode/glm5.3")).toBe(1_048_576);
158
+ expect(getContextWindowSize("helmcode/gemma4")).toBe(262_144);
159
+ expect(getContextWindowSize("nan/qwen3.6")).toBe(262_144);
160
+ });
161
+ });
@@ -0,0 +1,134 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import {
3
+ baseUrlEnvVar,
4
+ OPENAI_COMPATIBLE_PROVIDERS,
5
+ openAICompatibleProviderIds,
6
+ providerBaseUrl,
7
+ providerForKeyVar,
8
+ providerForModel,
9
+ providerKeyFromEnv,
10
+ withRegistryCatalogSections,
11
+ } from "@desplega/model-catalog";
12
+ import { loadModelsDevCache } from "../be/modelsdev-cache";
13
+ import snapshot from "../be/modelsdev-cache.json";
14
+ import { normalizeProviderKeyAliases } from "../utils/credentials";
15
+
16
+ type SnapshotModel = {
17
+ name: string;
18
+ reasoning?: boolean;
19
+ tool_call?: boolean;
20
+ modalities?: { input?: string[] };
21
+ limit?: { context?: number; output?: number };
22
+ };
23
+ type SnapshotProvider = {
24
+ api?: string;
25
+ env?: string[];
26
+ npm?: string;
27
+ models: Record<string, SnapshotModel>;
28
+ };
29
+
30
+ describe("openai-compatible provider table", () => {
31
+ test("providerForModel routes by exact prefix only", () => {
32
+ expect(providerForModel("nan/glm5.3-flash")?.id).toBe("nan");
33
+ expect(providerForModel("helmcode/gemma4")?.id).toBe("helmcode");
34
+ expect(providerForModel("openrouter/nan/glm5.3")).toBeUndefined();
35
+ expect(providerForModel("glm5.3")).toBeUndefined();
36
+ expect(providerForModel("nanx/glm5.3")).toBeUndefined();
37
+ expect(providerForModel("toString/x")).toBeUndefined();
38
+ expect(providerForModel(undefined)).toBeUndefined();
39
+ });
40
+
41
+ test("providerForKeyVar finds canonical and alias key vars", () => {
42
+ expect(providerForKeyVar("NAN_API_KEY")?.id).toBe("nan");
43
+ expect(providerForKeyVar("HELMCODE_API_NAN")?.id).toBe("nan");
44
+ expect(providerForKeyVar("HELMCODE_API_KEY")?.id).toBe("helmcode");
45
+ expect(providerForKeyVar("OPENROUTER_API_KEY")).toBeUndefined();
46
+ });
47
+
48
+ test("<ID>_BASE_URL overrides the default base URL", () => {
49
+ const nan = OPENAI_COMPATIBLE_PROVIDERS.nan;
50
+ const helmcode = OPENAI_COMPATIBLE_PROVIDERS.helmcode;
51
+ expect(baseUrlEnvVar(nan)).toBe("NAN_BASE_URL");
52
+ expect(baseUrlEnvVar(helmcode)).toBe("HELMCODE_BASE_URL");
53
+ expect(providerBaseUrl(nan, {})).toBe("https://api.nan.builders/v1");
54
+ expect(providerBaseUrl(nan, { NAN_BASE_URL: " " })).toBe("https://api.nan.builders/v1");
55
+ expect(providerBaseUrl(helmcode, { HELMCODE_BASE_URL: "http://127.0.0.1:9/v1/" })).toBe(
56
+ "http://127.0.0.1:9/v1",
57
+ );
58
+ });
59
+
60
+ test("providerKeyFromEnv prefers the canonical var, then the alias", () => {
61
+ const nan = OPENAI_COMPATIBLE_PROVIDERS.nan;
62
+ expect(providerKeyFromEnv(nan, { HELMCODE_API_NAN: "alias" })).toBe("alias");
63
+ expect(providerKeyFromEnv(nan, { NAN_API_KEY: "canon", HELMCODE_API_NAN: "alias" })).toBe(
64
+ "canon",
65
+ );
66
+ expect(providerKeyFromEnv(nan, { NAN_API_KEY: " " })).toBeUndefined();
67
+ expect(providerKeyFromEnv(OPENAI_COMPATIBLE_PROVIDERS.helmcode, { NAN_API_KEY: "k" })).toBe(
68
+ undefined,
69
+ );
70
+ });
71
+
72
+ test("normalizeProviderKeyAliases copies an alias only into an empty canonical var", () => {
73
+ const aliasOnly: Record<string, string | undefined> = { HELMCODE_API_NAN: "alias" };
74
+ normalizeProviderKeyAliases(aliasOnly);
75
+ expect(aliasOnly.NAN_API_KEY).toBe("alias");
76
+ const both: Record<string, string | undefined> = {
77
+ NAN_API_KEY: "canon",
78
+ HELMCODE_API_NAN: "alias",
79
+ };
80
+ normalizeProviderKeyAliases(both);
81
+ expect(both.NAN_API_KEY).toBe("canon");
82
+ });
83
+
84
+ test("registry-sourced sections are synthesized, never hand-written in the snapshot", () => {
85
+ const raw = snapshot as unknown as Record<string, unknown>;
86
+ for (const id of openAICompatibleProviderIds()) {
87
+ const entry = OPENAI_COMPATIBLE_PROVIDERS[id];
88
+ if (entry.catalog === "registry") expect(raw[id]).toBeUndefined();
89
+ else expect(raw[id]).toBeDefined();
90
+ }
91
+ // The server loader carries the synthesized sections.
92
+ expect(loadModelsDevCache()?.helmcode?.models?.["glm5.3"]?.limit?.context).toBe(1048576);
93
+ });
94
+
95
+ // The registry duplicates the models.dev sections of `catalog: "models.dev"`
96
+ // entries (the pi/opencode blocks need limits the worker cannot read from
97
+ // the catalog store); keep the two from drifting. Registry-sourced sections
98
+ // are built from the registry, so they agree by construction.
99
+ const merged = withRegistryCatalogSections(
100
+ snapshot as unknown as Record<string, SnapshotProvider | undefined>,
101
+ );
102
+ for (const id of openAICompatibleProviderIds()) {
103
+ test(`${id} matches its catalog section`, () => {
104
+ const entry = OPENAI_COMPATIBLE_PROVIDERS[id];
105
+ const section = merged[id];
106
+ expect(section).toBeDefined();
107
+ if (!section) return;
108
+ // OpenCode's built-in AI Gateway SDK has no OpenAI-compatible `api` URL.
109
+ if (entry.npm === "@ai-sdk/gateway") expect(section.api).toBeUndefined();
110
+ else expect(section.api).toBe(entry.baseUrl);
111
+ expect(section.env).toEqual([entry.envVar]);
112
+ expect(section.npm).toBe(entry.npm);
113
+ for (const model of entry.models) {
114
+ const snap = section.models[model.id];
115
+ expect(snap, `${id}/${model.id} missing from snapshot`).toBeDefined();
116
+ if (!snap) continue;
117
+ expect({
118
+ name: model.name,
119
+ contextWindow: model.contextWindow,
120
+ maxOutputTokens: model.maxOutputTokens,
121
+ reasoning: model.reasoning,
122
+ input: [...model.input],
123
+ }).toEqual({
124
+ name: snap.name,
125
+ contextWindow: snap.limit?.context,
126
+ maxOutputTokens: snap.limit?.output,
127
+ reasoning: snap.reasoning,
128
+ input: (snap.modalities?.input ?? []).filter((m) => m === "text" || m === "image"),
129
+ });
130
+ expect(snap.tool_call).toBe(true);
131
+ }
132
+ });
133
+ }
134
+ });
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Proof that adding an OpenAI-compatible provider takes one registry entry:
3
+ * a fake `testgw` entry must reach every derived surface
4
+ * (`fixtures/openai-compatible-registry-extension.child.ts`).
5
+ *
6
+ * The proof mocks the registry module, and its consumers cache derivations of
7
+ * the table at import (integration cards, picker constants) that no mock
8
+ * restore can undo. It runs in its own `bun test` child so this process, and
9
+ * every file sharing it in a single-process run, keeps the real table.
10
+ */
11
+ import { expect, test } from "bun:test";
12
+ import { join } from "node:path";
13
+ import { modelGroupsForHarness } from "../../apps/ui/src/lib/agent-runtime-models";
14
+ import { INTEGRATIONS } from "../../apps/ui/src/lib/integrations-catalog";
15
+ import { CHILD_PROCESS_TEST_BUDGET_MS, expectChildOk, runChild } from "./test-proc";
16
+
17
+ const REPO_ROOT = join(import.meta.dir, "../..");
18
+ const CHILD = "./src/tests/fixtures/openai-compatible-registry-extension.child.ts";
19
+
20
+ test(
21
+ "a testgw registry entry reaches every derived surface (child process)",
22
+ async () => {
23
+ const result = expectChildOk(
24
+ await runChild([process.execPath, "test", CHILD], {
25
+ cwd: REPO_ROOT,
26
+ env: { ...process.env },
27
+ }),
28
+ "registry extension proof",
29
+ );
30
+ // bun test reports to stderr; 0 fail and every case ran.
31
+ const report = `${result.stdout}\n${result.stderr}`;
32
+ expect(report).toMatch(/\b6 pass\b/);
33
+ expect(report).toMatch(/\b0 fail\b/);
34
+ },
35
+ CHILD_PROCESS_TEST_BUDGET_MS,
36
+ );
37
+
38
+ test("this process still sees the real registry only", () => {
39
+ expect(INTEGRATIONS.some((def) => def.id === "testgw")).toBe(false);
40
+ const providers = modelGroupsForHarness("pi", [], {}).map((g) => g.provider);
41
+ expect(providers).not.toContain("TestGW");
42
+ });
@@ -7,18 +7,20 @@
7
7
  * config wiring, steering, abort, and stream-death handling.
8
8
  */
9
9
 
10
- import { afterAll, afterEach, beforeAll, describe, expect, test } from "bun:test";
10
+ import { afterAll, afterEach, beforeAll, describe, expect, spyOn, test } from "bun:test";
11
11
  import type { OpenCodeEvent } from "@opencode/sdk";
12
12
  import { normalizeSessionLogs, type SessionLogRecord } from "../../apps/ui/src/logs-parser";
13
13
  import {
14
14
  _setOpencodeSdkLoaderForTests,
15
15
  _setOpencodeSessionHooksForTests,
16
16
  applyOpenRouterBaseUrlOverride,
17
+ applyProviderKeySplice,
17
18
  OpencodeAdapter,
18
19
  type OpencodeSession,
19
20
  resolveOpencodeSdkSpecifier,
20
21
  } from "../providers/opencode-adapter";
21
22
  import type { ProviderEvent, ProviderSessionConfig } from "../providers/types";
23
+ import { clearVolatileSecretsForTesting } from "../utils/secret-scrubber";
22
24
 
23
25
  const SESSION_ID = "ses_test";
24
26
 
@@ -128,13 +130,14 @@ function makeFakeHost(events: OpenCodeEvent[], opts: FakeHostOptions = {}) {
128
130
  return host;
129
131
  }
130
132
 
131
- function installHost(host: FakeHost) {
133
+ function installHost(host: FakeHost, onCreate?: (options: Record<string, unknown>) => void) {
132
134
  _setOpencodeSdkLoaderForTests(async () => ({
133
135
  version: "2.0.28",
134
136
  sdk: {
135
137
  OpenCode: {
136
138
  create: async (options: Record<string, unknown>) => {
137
139
  host.createOptions = options;
140
+ onCreate?.(options);
138
141
  // Set the plugins up the way OpenCode does, with a stub context.
139
142
  const plugins = options.plugins as Array<{ setup: (ctx: unknown) => Promise<void> }>;
140
143
  for (const plugin of plugins) {
@@ -143,6 +146,8 @@ function installHost(host: FakeHost) {
143
146
  hook: async () => {},
144
147
  list: async () => host.tools.map((id) => ({ id })),
145
148
  },
149
+ // Registered when the session config carries a per-task env.
150
+ shell: { hook: async () => {} },
146
151
  session: {
147
152
  hook: async (
148
153
  name: string,
@@ -841,3 +846,212 @@ describe("OpenRouter attribution config", () => {
841
846
  expect(config).toEqual({});
842
847
  });
843
848
  });
849
+
850
+ describe("provider key splice (one picked key per task)", () => {
851
+ const PICKED = "sk-picked-member-0123456789";
852
+ const POOL = "sk-pool-member-aaaaaaaaaa,sk-pool-member-bbbbbbbbbb";
853
+ const SPLICE_VARS = [
854
+ "NAN_API_KEY",
855
+ "HELMCODE_API_NAN",
856
+ "HELMCODE_API_KEY",
857
+ "OPENROUTER_API_KEY",
858
+ "OPENROUTER_BASE_URL",
859
+ ];
860
+ const saved: Record<string, string | undefined> = {};
861
+ beforeAll(() => {
862
+ for (const key of SPLICE_VARS) saved[key] = process.env[key];
863
+ });
864
+ afterEach(() => {
865
+ for (const key of SPLICE_VARS) {
866
+ if (saved[key] === undefined) delete process.env[key];
867
+ else process.env[key] = saved[key];
868
+ }
869
+ clearVolatileSecretsForTesting();
870
+ });
871
+
872
+ test("nan: env [] and the per-task key, even with a pool in process.env", async () => {
873
+ process.env.NAN_API_KEY = POOL;
874
+ const { host } = await runSession(
875
+ [succeeded],
876
+ testConfig({ model: "nan/glm5.3-flash", env: { NAN_API_KEY: PICKED } }),
877
+ );
878
+ const nan = configOf(host).provider.nan;
879
+ expect(nan.env).toEqual([]);
880
+ expect(nan.options.apiKey).toBe(PICKED);
881
+ });
882
+
883
+ test("nan: the HELMCODE_API_NAN alias is read when NAN_API_KEY is empty", () => {
884
+ const config = {};
885
+ const picked = applyProviderKeySplice(config, "nan/qwen3.6", { HELMCODE_API_NAN: PICKED });
886
+ expect(picked).toEqual({ key: PICKED, name: "HELMCODE_API_NAN" });
887
+ expect(config).toEqual({ provider: { nan: { env: [], options: { apiKey: PICKED } } } });
888
+ });
889
+
890
+ test("a pool that reaches the splice unpicked yields its first member", () => {
891
+ const config = {};
892
+ applyProviderKeySplice(config, "nan/qwen3.6", {
893
+ NAN_API_KEY: ` ${PICKED} , sk-other-0000000000`,
894
+ });
895
+ expect(config).toEqual({ provider: { nan: { env: [], options: { apiKey: PICKED } } } });
896
+ });
897
+
898
+ test("helmcode: full custom block with limits, reasoning options merged in", async () => {
899
+ const { host } = await runSession(
900
+ [succeeded],
901
+ testConfig({
902
+ model: "helmcode/glm5.3",
903
+ reasoningEffort: "high",
904
+ env: { HELMCODE_API_KEY: PICKED },
905
+ }),
906
+ );
907
+ const helmcode = configOf(host).provider.helmcode;
908
+ expect(helmcode).toMatchObject({
909
+ npm: "@ai-sdk/openai-compatible",
910
+ name: "Helmcode",
911
+ env: [],
912
+ options: { baseURL: "https://api.helmcode.com/v1", apiKey: PICKED },
913
+ });
914
+ expect(helmcode.models["glm5.3"]).toEqual({
915
+ name: "GLM-5.3",
916
+ tool_call: true,
917
+ reasoning: true,
918
+ limit: { context: 1048576, output: 32768 },
919
+ options: { reasoningEffort: "high" },
920
+ });
921
+ expect(Object.keys(helmcode.models)).toEqual([
922
+ "deepseek-v4-flash",
923
+ "glm5.3",
924
+ "glm5.3-flash",
925
+ "qwen3.6",
926
+ "gemma4",
927
+ ]);
928
+ });
929
+
930
+ test("helmcode: a model missing from the table still gets an entry", () => {
931
+ const config: Record<string, any> = {};
932
+ applyProviderKeySplice(config, "helmcode/brand-new", { HELMCODE_API_KEY: PICKED });
933
+ expect(config.provider.helmcode.models["brand-new"]).toEqual({
934
+ name: "brand-new",
935
+ tool_call: true,
936
+ });
937
+ });
938
+
939
+ test("openrouter gateway: baseURL kept next to env [] and the picked key", async () => {
940
+ process.env.OPENROUTER_API_KEY = POOL;
941
+ const { host } = await runSession(
942
+ [succeeded],
943
+ testConfig({
944
+ model: "openrouter/deepseek/deepseek-v4-flash",
945
+ env: { OPENROUTER_API_KEY: PICKED, OPENROUTER_BASE_URL: "http://127.0.0.1:1234/v1" },
946
+ }),
947
+ );
948
+ expect(configOf(host).provider.openrouter).toEqual({
949
+ env: [],
950
+ options: { apiKey: PICKED, baseURL: "http://127.0.0.1:1234/v1" },
951
+ });
952
+ });
953
+
954
+ test("openrouter host: attribution headers kept next to env [] and the picked key", async () => {
955
+ const { host } = await runSession(
956
+ [succeeded],
957
+ testConfig({
958
+ model: "openrouter/deepseek/deepseek-v4-flash",
959
+ env: { OPENROUTER_API_KEY: PICKED },
960
+ }),
961
+ );
962
+ const openrouter = configOf(host).provider.openrouter;
963
+ expect(openrouter.env).toEqual([]);
964
+ expect(openrouter.options.apiKey).toBe(PICKED);
965
+ expect(openrouter.options.headers).toMatchObject({ "X-Title": "Agent Swarm" });
966
+ });
967
+
968
+ test("no splice without a key, or for providers outside the splice", () => {
969
+ for (const model of ["nan/qwen3.6", "helmcode/glm5.3", "openrouter/x/y"]) {
970
+ const config = {};
971
+ expect(applyProviderKeySplice(config, model, {})).toBeUndefined();
972
+ expect(config).toEqual({});
973
+ }
974
+ for (const model of ["anthropic/claude-sonnet-4-5", "openai/gpt-5", "glm5.3", "", undefined]) {
975
+ const config = {};
976
+ applyProviderKeySplice(config, model, {
977
+ ANTHROPIC_API_KEY: PICKED,
978
+ OPENAI_API_KEY: PICKED,
979
+ NAN_API_KEY: PICKED,
980
+ });
981
+ expect(config).toEqual({});
982
+ }
983
+ });
984
+ });
985
+
986
+ describe("spliced key never reaches logs, events, or errors", () => {
987
+ // Not in process.env: the key a dashboard save delivers to the per-task env only.
988
+ const SENTINEL = "sk-nan-sentinel-do-not-log-4f9c2e";
989
+ let captured: string[] = [];
990
+ let spies: Array<ReturnType<typeof spyOn>> = [];
991
+ beforeAll(() => {
992
+ delete process.env.NAN_API_KEY;
993
+ });
994
+ afterEach(() => {
995
+ for (const spy of spies) spy.mockRestore();
996
+ spies = [];
997
+ captured = [];
998
+ clearVolatileSecretsForTesting();
999
+ });
1000
+ function captureConsole() {
1001
+ for (const method of ["log", "info", "warn", "error", "debug"] as const) {
1002
+ spies.push(
1003
+ spyOn(console, method).mockImplementation((...args: unknown[]) => {
1004
+ captured.push(args.map((a) => (typeof a === "string" ? a : JSON.stringify(a))).join(" "));
1005
+ }),
1006
+ );
1007
+ }
1008
+ }
1009
+
1010
+ test("SDK log lines and a provider failure that echo the config are scrubbed", async () => {
1011
+ captureConsole();
1012
+ const host = makeFakeHost([
1013
+ ev("session.execution.failed", {
1014
+ sessionID: SESSION_ID,
1015
+ error: { message: `401 Invalid API key ${SENTINEL}` },
1016
+ }),
1017
+ ]);
1018
+ installHost(host, (options) => {
1019
+ const content = (options.config as { content: string }).content;
1020
+ const log = options.log as { emit: (entry: { message: string }) => void };
1021
+ log.emit({ message: `config: ${content}` });
1022
+ });
1023
+ const session = (await new OpencodeAdapter().createSession(
1024
+ testConfig({ model: "nan/glm5.3-flash", env: { NAN_API_KEY: SENTINEL } }),
1025
+ )) as OpencodeSession;
1026
+ const emitted: ProviderEvent[] = [];
1027
+ session.onEvent((e) => emitted.push(e));
1028
+ const result = await session.waitForCompletion();
1029
+
1030
+ // Positive control: the key did reach the SDK config.
1031
+ expect(configOf(host).provider.nan.options.apiKey).toBe(SENTINEL);
1032
+ expect(captured.some((line) => line.includes("[opencode] config:"))).toBe(true);
1033
+ expect(result.isError).toBe(true);
1034
+
1035
+ for (const line of captured) expect(line).not.toContain(SENTINEL);
1036
+ expect(JSON.stringify(emitted)).not.toContain(SENTINEL);
1037
+ expect(result.failureReason ?? "").not.toContain(SENTINEL);
1038
+ });
1039
+
1040
+ test("a host start failure that echoes the config is scrubbed before it propagates", async () => {
1041
+ captureConsole();
1042
+ const host = makeFakeHost([]);
1043
+ installHost(host, (options) => {
1044
+ const content = (options.config as { content: string }).content;
1045
+ throw new Error(`invalid config ${content}`);
1046
+ });
1047
+ const error = await new OpencodeAdapter()
1048
+ .createSession(testConfig({ model: "nan/glm5.3-flash", env: { NAN_API_KEY: SENTINEL } }))
1049
+ .then(
1050
+ () => undefined,
1051
+ (err: unknown) => err as Error,
1052
+ );
1053
+ expect(error?.message).toStartWith("opencode host failed to start: invalid config");
1054
+ expect(error?.message).not.toContain(SENTINEL);
1055
+ for (const line of captured) expect(line).not.toContain(SENTINEL);
1056
+ });
1057
+ });