@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
@@ -0,0 +1,201 @@
1
+ /**
2
+ * The provider key splice and the `helmcode` block, run through the real
3
+ * `@opencode/sdk` embedded host against a fake OpenAI-compatible server.
4
+ *
5
+ * `applyProviderKeySplice` builds the provider entry; the test only repoints
6
+ * `options.baseURL` at a local `Bun.serve({ port: 0 })` that answers
7
+ * `/v1/chat/completions`, then checks the request OpenCode sent: the picked
8
+ * pool member as the bearer (not the comma pool sitting in `process.env`) and
9
+ * the bare model id. The model catalog is bundled in the SDK, and no request
10
+ * leaves the machine. The control test pins why `env: []` is needed: without
11
+ * it, the pool in `process.env` beats `options.apiKey`.
12
+ */
13
+
14
+ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
15
+ import { mkdtempSync, rmSync } from "node:fs";
16
+ import { tmpdir } from "node:os";
17
+ import { join } from "node:path";
18
+ import { OpenCode, type OpenCodeEvent } from "@opencode/sdk";
19
+ import { applyProviderKeySplice, type OpencodeConfig } from "../providers/opencode-adapter";
20
+
21
+ const TEST_TIMEOUT_MS = 60_000;
22
+
23
+ const PICKED = "sk-helmcode-picked-live-0123456789";
24
+ const POOL_IN_PROCESS_ENV = "sk-helmcode-wrong-aaaaaaaaaa,sk-helmcode-wrong-bbbbbbbbbb";
25
+
26
+ type SeenRequest = { path: string; authorization: string | null; model?: string };
27
+
28
+ let server: ReturnType<typeof Bun.serve>;
29
+ let directory: string;
30
+ const seen: SeenRequest[] = [];
31
+ let prevHelmcodeKey: string | undefined;
32
+
33
+ /** One streamed (or plain) assistant turn that says "pong". */
34
+ function completion(model: string, stream: boolean): Response {
35
+ const base = { id: "chatcmpl-test", created: 0, model };
36
+ const usage = { prompt_tokens: 5, completion_tokens: 1, total_tokens: 6 };
37
+ if (!stream) {
38
+ return Response.json({
39
+ ...base,
40
+ object: "chat.completion",
41
+ choices: [
42
+ {
43
+ index: 0,
44
+ message: { role: "assistant", content: "pong" },
45
+ finish_reason: "stop",
46
+ },
47
+ ],
48
+ usage,
49
+ });
50
+ }
51
+ const chunk = (body: Record<string, unknown>) =>
52
+ `data: ${JSON.stringify({ ...base, object: "chat.completion.chunk", ...body })}\n\n`;
53
+ const sse =
54
+ chunk({
55
+ choices: [{ index: 0, delta: { role: "assistant", content: "pong" }, finish_reason: null }],
56
+ }) +
57
+ chunk({ choices: [{ index: 0, delta: {}, finish_reason: "stop" }], usage }) +
58
+ "data: [DONE]\n\n";
59
+ return new Response(sse, { headers: { "content-type": "text/event-stream" } });
60
+ }
61
+
62
+ beforeAll(() => {
63
+ directory = mkdtempSync(join(tmpdir(), "opencode-helmcode-sdk-"));
64
+ server = Bun.serve({
65
+ port: 0,
66
+ async fetch(req) {
67
+ const url = new URL(req.url);
68
+ const body = req.method === "POST" ? ((await req.json().catch(() => ({}))) as any) : {};
69
+ seen.push({
70
+ path: url.pathname,
71
+ authorization: req.headers.get("authorization"),
72
+ model: body?.model,
73
+ });
74
+ if (url.pathname.endsWith("/chat/completions")) {
75
+ return completion(String(body?.model ?? ""), body?.stream === true);
76
+ }
77
+ return new Response("not found", { status: 404 });
78
+ },
79
+ });
80
+ // The pool a production entrypoint exports raw; the splice must beat it.
81
+ prevHelmcodeKey = process.env.HELMCODE_API_KEY;
82
+ process.env.HELMCODE_API_KEY = POOL_IN_PROCESS_ENV;
83
+ });
84
+
85
+ afterAll(() => {
86
+ server.stop(true);
87
+ rmSync(directory, { recursive: true, force: true });
88
+ if (prevHelmcodeKey === undefined) delete process.env.HELMCODE_API_KEY;
89
+ else process.env.HELMCODE_API_KEY = prevHelmcodeKey;
90
+ });
91
+
92
+ async function runOnePrompt(config: OpencodeConfig): Promise<string> {
93
+ const host = await OpenCode.create({
94
+ config: { content: JSON.stringify(config) },
95
+ plugins: [],
96
+ log: { level: "error", emit: () => {} },
97
+ });
98
+ try {
99
+ const session = await host.sessions.create({ location: { directory } });
100
+ const done = (async () => {
101
+ for await (const event of host.events.subscribe() as AsyncIterable<OpenCodeEvent>) {
102
+ const data = (event as { data?: { sessionID?: string; error?: unknown } }).data;
103
+ if (data?.sessionID !== session.id) continue;
104
+ if (event.type === "session.execution.succeeded") return "succeeded";
105
+ if (event.type === "session.execution.failed") {
106
+ return `failed: ${JSON.stringify(data.error)}`;
107
+ }
108
+ }
109
+ return "stream ended";
110
+ })();
111
+ await host.sessions.prompt({ sessionID: session.id, text: "say pong", delivery: "queue" });
112
+ return await done;
113
+ } finally {
114
+ await host.close();
115
+ }
116
+ }
117
+
118
+ describe("provider key splice through the real OpenCode host", () => {
119
+ test(
120
+ "sends the picked key and the bare model id to the provider's baseURL",
121
+ async () => {
122
+ seen.length = 0;
123
+ const model = "helmcode/glm5.3";
124
+ const config: OpencodeConfig = { model };
125
+ const spliced = applyProviderKeySplice(config, model, { HELMCODE_API_KEY: PICKED });
126
+ expect(spliced?.name).toBe("HELMCODE_API_KEY");
127
+ const helmcode = config.provider?.helmcode;
128
+ if (!helmcode?.options) throw new Error("helmcode block missing");
129
+ helmcode.options.baseURL = `http://127.0.0.1:${server.port}/v1`;
130
+
131
+ const outcome = await runOnePrompt(config);
132
+
133
+ const chats = seen.filter((r) => r.path === "/v1/chat/completions");
134
+ expect(chats.length).toBeGreaterThan(0);
135
+ for (const chat of chats) {
136
+ expect(chat.authorization).toBe(`Bearer ${PICKED}`);
137
+ expect(chat.model).toBe("glm5.3");
138
+ }
139
+ expect(outcome).toBe("succeeded");
140
+ },
141
+ TEST_TIMEOUT_MS,
142
+ );
143
+
144
+ test(
145
+ "nan (built-in provider): env [] plus the picked key beats a pool in process.env",
146
+ async () => {
147
+ const prevNan = process.env.NAN_API_KEY;
148
+ process.env.NAN_API_KEY = POOL_IN_PROCESS_ENV;
149
+ seen.length = 0;
150
+ try {
151
+ const model = "nan/glm5.3-flash";
152
+ const config: OpencodeConfig = { model };
153
+ const spliced = applyProviderKeySplice(config, model, { NAN_API_KEY: PICKED });
154
+ expect(spliced?.name).toBe("NAN_API_KEY");
155
+ const nan = config.provider?.nan;
156
+ if (!nan?.options) throw new Error("nan block missing");
157
+ nan.options.baseURL = `http://127.0.0.1:${server.port}/v1`;
158
+
159
+ const outcome = await runOnePrompt(config);
160
+
161
+ const chats = seen.filter((r) => r.path === "/v1/chat/completions");
162
+ expect(chats.length).toBeGreaterThan(0);
163
+ for (const chat of chats) {
164
+ expect(chat.authorization).toBe(`Bearer ${PICKED}`);
165
+ expect(chat.model).toBe("glm5.3-flash");
166
+ }
167
+ expect(outcome).toBe("succeeded");
168
+ } finally {
169
+ if (prevNan === undefined) delete process.env.NAN_API_KEY;
170
+ else process.env.NAN_API_KEY = prevNan;
171
+ }
172
+ },
173
+ TEST_TIMEOUT_MS,
174
+ );
175
+
176
+ test(
177
+ "nan control: without the splice the pool in process.env reaches the provider",
178
+ async () => {
179
+ const prevNan = process.env.NAN_API_KEY;
180
+ process.env.NAN_API_KEY = POOL_IN_PROCESS_ENV;
181
+ seen.length = 0;
182
+ try {
183
+ const model = "nan/glm5.3-flash";
184
+ const config: OpencodeConfig = {
185
+ model,
186
+ provider: {
187
+ nan: { options: { apiKey: PICKED, baseURL: `http://127.0.0.1:${server.port}/v1` } },
188
+ },
189
+ };
190
+ await runOnePrompt(config);
191
+ const chats = seen.filter((r) => r.path === "/v1/chat/completions");
192
+ expect(chats.length).toBeGreaterThan(0);
193
+ expect(chats[0]?.authorization).not.toBe(`Bearer ${PICKED}`);
194
+ } finally {
195
+ if (prevNan === undefined) delete process.env.NAN_API_KEY;
196
+ else process.env.NAN_API_KEY = prevNan;
197
+ }
198
+ },
199
+ TEST_TIMEOUT_MS,
200
+ );
201
+ });
@@ -0,0 +1,145 @@
1
+ import {
2
+ afterAll,
3
+ afterEach,
4
+ beforeAll,
5
+ beforeEach,
6
+ describe,
7
+ expect,
8
+ spyOn,
9
+ test,
10
+ } from "bun:test";
11
+ import { mkdtempSync, rmSync } from "node:fs";
12
+ import { tmpdir } from "node:os";
13
+ import { join } from "node:path";
14
+ import { OpenCode } from "@opencode/sdk";
15
+ import { fetchResolvedEnv } from "../commands/runner";
16
+ import { createOpencodeSwarmPlugin } from "../providers/opencode-swarm-plugin";
17
+
18
+ /**
19
+ * OpenCode runs in-process, so its shell tool would spawn with the worker's
20
+ * process.env. The swarm plugin swaps in the env the runner resolved for the
21
+ * task: a row admitted after boot (exportToEnv flipped on) must reach the
22
+ * shell, and a row revoked after boot (flipped off) must leave it, even though
23
+ * process.env still holds the boot value.
24
+ */
25
+
26
+ type ConfigRow = { key: string; value: string; isSecret: boolean; exportToEnv: boolean };
27
+
28
+ const SHELL_TIMEOUT_MS = 15_000;
29
+
30
+ let server: ReturnType<typeof Bun.serve>;
31
+ let apiUrl = "";
32
+ let rows: ConfigRow[] = [];
33
+ let directory = "";
34
+
35
+ beforeAll(() => {
36
+ server = Bun.serve({
37
+ port: 0,
38
+ fetch: () => Response.json({ configs: rows }),
39
+ });
40
+ apiUrl = `http://localhost:${server.port}`;
41
+ directory = mkdtempSync(join(tmpdir(), "opencode-shell-env-"));
42
+ });
43
+
44
+ afterAll(() => {
45
+ server.stop(true);
46
+ rmSync(directory, { recursive: true, force: true });
47
+ });
48
+
49
+ type Host = Awaited<ReturnType<typeof OpenCode.create>>;
50
+
51
+ async function hostWithEnv(env: Record<string, string | undefined>): Promise<Host> {
52
+ const plugin = createOpencodeSwarmPlugin({
53
+ apiUrl: "http://127.0.0.1:9",
54
+ apiKey: "",
55
+ agentId: "agent-1",
56
+ taskId: "",
57
+ isLead: false,
58
+ env,
59
+ });
60
+ return OpenCode.create({ plugins: [plugin], log: { level: "error", emit: () => {} } });
61
+ }
62
+
63
+ /**
64
+ * Runs one command as a session shell call, the path the agent's shell tool
65
+ * takes (`host.shell.create` skips plugin hooks), and returns its output.
66
+ */
67
+ async function shell(host: Host, command: string): Promise<string> {
68
+ const session = await host.sessions.create({ location: { directory } });
69
+ const outFile = join(directory, `out-${crypto.randomUUID()}.txt`);
70
+ await host.sessions.shell({
71
+ sessionID: session.id,
72
+ command: `{ ${command}; } > ${outFile}.tmp 2>&1; mv ${outFile}.tmp ${outFile}`,
73
+ });
74
+ const deadline = Date.now() + SHELL_TIMEOUT_MS;
75
+ while (!(await Bun.file(outFile).exists())) {
76
+ if (Date.now() > deadline) throw new Error("shell did not exit");
77
+ await Bun.sleep(25);
78
+ }
79
+ return (await Bun.file(outFile).text()).trim();
80
+ }
81
+
82
+ describe("OpenCode shell env (real OpenCode host)", () => {
83
+ const envKeys = ["SHELL_REVOKED_CANARY", "SHELL_ADMITTED_CANARY"] as const;
84
+ const saved: Record<string, string | undefined> = {};
85
+ let log: ReturnType<typeof spyOn>;
86
+
87
+ beforeEach(() => {
88
+ for (const key of envKeys) saved[key] = process.env[key];
89
+ log = spyOn(console, "log").mockImplementation(() => {});
90
+ });
91
+ afterEach(() => {
92
+ log.mockRestore();
93
+ for (const key of envKeys) {
94
+ if (saved[key] === undefined) delete process.env[key];
95
+ else process.env[key] = saved[key];
96
+ }
97
+ });
98
+
99
+ test(
100
+ "admission and revocation after boot reach the shell",
101
+ async () => {
102
+ process.env.SHELL_REVOKED_CANARY = "boot-revoked";
103
+ delete process.env.SHELL_ADMITTED_CANARY;
104
+ rows = [
105
+ { key: "SHELL_REVOKED_CANARY", value: "row-revoked", isSecret: true, exportToEnv: false },
106
+ { key: "SHELL_ADMITTED_CANARY", value: "row-admitted", isSecret: true, exportToEnv: true },
107
+ ];
108
+ const { env } = await fetchResolvedEnv(apiUrl, "key", "agent-1", process.env);
109
+ const host = await hostWithEnv(env);
110
+ try {
111
+ const out = await shell(
112
+ host,
113
+ 'echo "admitted=$SHELL_ADMITTED_CANARY revoked=$SHELL_REVOKED_CANARY term=$OPENCODE_TERMINAL."; tr "\\0" "\\n" < /proc/$$/environ | grep -c "^SHELL_REVOKED_CANARY=" || true',
114
+ );
115
+ expect(out).toContain("admitted=row-admitted revoked= term=1.");
116
+ expect(out.split("\n").at(-1)).toBe("0");
117
+ } finally {
118
+ await host.close();
119
+ }
120
+ },
121
+ SHELL_TIMEOUT_MS * 2,
122
+ );
123
+
124
+ test(
125
+ "concurrent hosts keep their own env",
126
+ async () => {
127
+ const [first, second] = await Promise.all([
128
+ hostWithEnv({ ...process.env, SHELL_ADMITTED_CANARY: "first" }),
129
+ hostWithEnv({ ...process.env, SHELL_ADMITTED_CANARY: undefined }),
130
+ ]);
131
+ try {
132
+ const [a, b] = await Promise.all([
133
+ shell(first, 'echo "v=$SHELL_ADMITTED_CANARY"'),
134
+ shell(second, 'echo "v=$SHELL_ADMITTED_CANARY"'),
135
+ ]);
136
+ expect(a).toBe("v=first");
137
+ expect(b).toBe("v=");
138
+ expect(process.env.SHELL_ADMITTED_CANARY).toBeUndefined();
139
+ } finally {
140
+ await Promise.all([first.close(), second.close()]);
141
+ }
142
+ },
143
+ SHELL_TIMEOUT_MS * 2,
144
+ );
145
+ });
@@ -2,16 +2,17 @@
2
2
  * OPENROUTER_BASE_URL gateway routing — unit tests.
3
3
  *
4
4
  * Covers the shared resolver (`src/utils/openrouter-base-url.ts`), the pi
5
- * models.json override writer (`ensureOpenRouterModelsOverride`) including
6
- * its composition through pi-coding-agent's ModelRuntime, and attribution host gating.
5
+ * per-session models.json composer (`writeSessionModelsJson`) for every shared
6
+ * starting state, its composition through pi-coding-agent's ModelRuntime, and
7
+ * attribution host gating.
7
8
  */
8
9
 
9
- import { afterAll, beforeAll, describe, expect, test } from "bun:test";
10
- import { mkdtempSync, rmSync } from "node:fs";
10
+ import { describe, expect, test } from "bun:test";
11
+ import { mkdtempSync, readdirSync, readFileSync, rmSync } from "node:fs";
11
12
  import { tmpdir } from "node:os";
12
13
  import { join } from "node:path";
13
14
  import { ModelRuntime } from "@earendil-works/pi-coding-agent";
14
- import { ensureOpenRouterModelsOverride } from "../providers/pi-mono-adapter";
15
+ import { writeSessionModelsJson } from "../providers/pi-mono-adapter";
15
16
  import {
16
17
  DEFAULT_OPENROUTER_BASE_URL,
17
18
  getOpenRouterAttributionHeaders,
@@ -47,162 +48,111 @@ describe("getOpenRouterBaseUrl", () => {
47
48
  });
48
49
  });
49
50
 
50
- describe("ensureOpenRouterModelsOverride (pi)", () => {
51
- let agentDir: string;
52
-
53
- beforeAll(() => {
54
- agentDir = mkdtempSync(join(tmpdir(), "pi-or-override-"));
55
- });
56
-
57
- afterAll(() => {
58
- rmSync(agentDir, { recursive: true, force: true });
59
- });
60
-
61
- test("no-op when OPENROUTER_BASE_URL is unset", async () => {
62
- const dir = mkdtempSync(join(tmpdir(), "pi-or-noop-"));
63
- try {
64
- await ensureOpenRouterModelsOverride(dir, {});
65
- expect(await Bun.file(join(dir, "models.json")).exists()).toBe(false);
66
- } finally {
67
- rmSync(dir, { recursive: true, force: true });
68
- }
69
- });
70
-
71
- test("writes providers.openrouter.baseUrl when the env is set", async () => {
72
- await ensureOpenRouterModelsOverride(agentDir, { OPENROUTER_BASE_URL: GATEWAY });
73
- const written = JSON.parse(await Bun.file(join(agentDir, "models.json")).text());
74
- expect(written).toEqual({ providers: { openrouter: { baseUrl: GATEWAY } } });
75
- });
76
-
77
- test("reverts a written override when the env returns to default (file we created is removed)", async () => {
78
- const dir = mkdtempSync(join(tmpdir(), "pi-or-revert-"));
79
- try {
80
- await ensureOpenRouterModelsOverride(dir, { OPENROUTER_BASE_URL: GATEWAY });
81
- expect(await Bun.file(join(dir, "models.json")).exists()).toBe(true);
82
- await ensureOpenRouterModelsOverride(dir, {});
83
- // The override was ALL the file contained — both it and the marker go.
84
- expect(await Bun.file(join(dir, "models.json")).exists()).toBe(false);
85
- expect(await Bun.file(join(dir, ".agent-swarm-openrouter-override.json")).exists()).toBe(
86
- false,
87
- );
88
- } finally {
89
- rmSync(dir, { recursive: true, force: true });
90
- }
91
- });
92
-
93
- test("revert restores a displaced user baseUrl and keeps sibling keys", async () => {
94
- const dir = mkdtempSync(join(tmpdir(), "pi-or-restore-"));
95
- try {
96
- await Bun.write(
97
- join(dir, "models.json"),
98
- JSON.stringify({
99
- providers: {
100
- openrouter: { baseUrl: "https://user.example/v1", compat: { supportsTools: true } },
101
- custom: { baseUrl: "https://custom.example/v1", api: "openai-completions" },
102
- },
103
- }),
104
- );
105
- await ensureOpenRouterModelsOverride(dir, { OPENROUTER_BASE_URL: GATEWAY });
106
- // Re-run with the gateway still set — displaced value must survive.
107
- await ensureOpenRouterModelsOverride(dir, { OPENROUTER_BASE_URL: GATEWAY });
108
- await ensureOpenRouterModelsOverride(dir, {});
109
- const restored = JSON.parse(await Bun.file(join(dir, "models.json")).text());
110
- expect(restored.providers.openrouter).toEqual({
111
- baseUrl: "https://user.example/v1",
112
- compat: { supportsTools: true },
113
- });
114
- expect(restored.providers.custom).toEqual({
115
- baseUrl: "https://custom.example/v1",
116
- api: "openai-completions",
51
+ describe("writeSessionModelsJson — OpenRouter gateway (pi)", () => {
52
+ const MARKER = ".agent-swarm-openrouter-override.json";
53
+ const LEGACY = "https://legacy-gateway.example/v1";
54
+ const USER = "https://user.example/v1";
55
+ const userOpenRouter = { baseUrl: USER, compat: { supportsTools: true } };
56
+ const custom = { baseUrl: "https://custom.example/v1", api: "openai-completions" };
57
+
58
+ /** Starting states of the shared agent dir; the marker ones are left by earlier versions. */
59
+ const states: Record<string, Record<string, string>> = {
60
+ "no models.json": {},
61
+ "hand-authored baseUrl": {
62
+ "models.json": JSON.stringify({ providers: { openrouter: userOpenRouter, custom } }),
63
+ },
64
+ "legacy override over a user baseUrl": {
65
+ "models.json": JSON.stringify({
66
+ providers: { openrouter: { ...userOpenRouter, baseUrl: LEGACY }, custom },
67
+ }),
68
+ [MARKER]: JSON.stringify({ baseUrl: LEGACY, previous: USER }),
69
+ },
70
+ "legacy override alone": {
71
+ "models.json": JSON.stringify({ providers: { openrouter: { baseUrl: LEGACY } } }),
72
+ [MARKER]: JSON.stringify({ baseUrl: LEGACY }),
73
+ },
74
+ "legacy marker, baseUrl edited by hand since": {
75
+ "models.json": JSON.stringify({ providers: { openrouter: { baseUrl: USER } } }),
76
+ [MARKER]: JSON.stringify({ baseUrl: LEGACY }),
77
+ },
78
+ "corrupt models.json": { "models.json": "{not json" },
79
+ };
80
+
81
+ // The openrouter block the session's runtime loads, per state, for the
82
+ // default gateway and for OPENROUTER_BASE_URL=GATEWAY. `null`: no copy, and
83
+ // the session loads the shared file as it is.
84
+ const expected: Record<string, [defaultGateway: unknown, gateway: unknown]> = {
85
+ "no models.json": [null, { baseUrl: GATEWAY }],
86
+ "hand-authored baseUrl": [null, { ...userOpenRouter, baseUrl: GATEWAY }],
87
+ "legacy override over a user baseUrl": [
88
+ userOpenRouter,
89
+ { ...userOpenRouter, baseUrl: GATEWAY },
90
+ ],
91
+ "legacy override alone": [undefined, { baseUrl: GATEWAY }],
92
+ "legacy marker, baseUrl edited by hand since": [{ baseUrl: USER }, { baseUrl: GATEWAY }],
93
+ "corrupt models.json": [null, { baseUrl: GATEWAY }],
94
+ };
95
+
96
+ function snapshot(dir: string): Record<string, string> {
97
+ return Object.fromEntries(
98
+ readdirSync(dir)
99
+ .sort()
100
+ .map((name) => [name, readFileSync(join(dir, name), "utf-8")]),
101
+ );
102
+ }
103
+
104
+ for (const [state, files] of Object.entries(states)) {
105
+ for (const [i, env] of [{}, { OPENROUTER_BASE_URL: GATEWAY }].entries()) {
106
+ const label = i === 0 ? "default gateway" : "session gateway";
107
+ test(`${state}, ${label}: composed privately, shared dir untouched`, async () => {
108
+ const dir = mkdtempSync(join(tmpdir(), "pi-or-session-"));
109
+ try {
110
+ for (const [name, text] of Object.entries(files)) await Bun.write(join(dir, name), text);
111
+ const before = snapshot(dir);
112
+ const session = await writeSessionModelsJson(dir, env);
113
+ try {
114
+ const want = expected[state]?.[i];
115
+ if (want === null) {
116
+ expect(session.modelsPath).toBeUndefined();
117
+ } else {
118
+ expect(session.modelsPath).toBeDefined();
119
+ expect(session.modelsPath?.startsWith(dir)).toBe(false);
120
+ const copy = JSON.parse(readFileSync(session.modelsPath as string, "utf-8"));
121
+ expect(copy.providers?.openrouter).toEqual(want);
122
+ if (files["models.json"]?.includes("custom.example")) {
123
+ expect(copy.providers.custom).toEqual(custom);
124
+ }
125
+ }
126
+ } finally {
127
+ await session.dispose();
128
+ }
129
+ expect(snapshot(dir)).toEqual(before);
130
+ } finally {
131
+ rmSync(dir, { recursive: true, force: true });
132
+ }
117
133
  });
118
- } finally {
119
- rmSync(dir, { recursive: true, force: true });
120
- }
121
- });
122
-
123
- test("revert leaves a user-edited baseUrl alone (marker mismatch) and drops the marker", async () => {
124
- const dir = mkdtempSync(join(tmpdir(), "pi-or-user-edit-"));
125
- try {
126
- await ensureOpenRouterModelsOverride(dir, { OPENROUTER_BASE_URL: GATEWAY });
127
- // User hand-edits the file after we wrote it.
128
- await Bun.write(
129
- join(dir, "models.json"),
130
- JSON.stringify({ providers: { openrouter: { baseUrl: "https://mine.example/v1" } } }),
131
- );
132
- await ensureOpenRouterModelsOverride(dir, {});
133
- const kept = JSON.parse(await Bun.file(join(dir, "models.json")).text());
134
- expect(kept.providers.openrouter.baseUrl).toBe("https://mine.example/v1");
135
- expect(await Bun.file(join(dir, ".agent-swarm-openrouter-override.json")).exists()).toBe(
136
- false,
137
- );
138
- } finally {
139
- rmSync(dir, { recursive: true, force: true });
140
- }
141
- });
142
-
143
- test("default env never touches a hand-authored models.json (no marker)", async () => {
144
- const dir = mkdtempSync(join(tmpdir(), "pi-or-hands-off-"));
145
- try {
146
- const userConfig = {
147
- providers: { openrouter: { baseUrl: "https://mine.example/v1" } },
148
- };
149
- await Bun.write(join(dir, "models.json"), JSON.stringify(userConfig));
150
- await ensureOpenRouterModelsOverride(dir, {});
151
- expect(JSON.parse(await Bun.file(join(dir, "models.json")).text())).toEqual(userConfig);
152
- } finally {
153
- rmSync(dir, { recursive: true, force: true });
154
134
  }
155
- });
135
+ }
156
136
 
157
- test("merge-preserves existing providers and openrouter keys", async () => {
158
- const dir = mkdtempSync(join(tmpdir(), "pi-or-merge-"));
137
+ test("ModelRuntime composes the override onto built-in OpenRouter models", async () => {
138
+ // The whole point of the lever: built-in model IDs resolved through the
139
+ // runtime's composed store carry the gateway baseUrl.
140
+ const dir = mkdtempSync(join(tmpdir(), "pi-or-runtime-"));
141
+ const session = await writeSessionModelsJson(dir, { OPENROUTER_BASE_URL: GATEWAY });
159
142
  try {
160
- await Bun.write(
161
- join(dir, "models.json"),
162
- JSON.stringify({
163
- providers: {
164
- openrouter: { baseUrl: "https://old.example/v1", compat: { supportsTools: true } },
165
- custom: { baseUrl: "https://custom.example/v1", api: "openai-completions" },
166
- },
167
- }),
168
- );
169
- await ensureOpenRouterModelsOverride(dir, { OPENROUTER_BASE_URL: GATEWAY });
170
- const written = JSON.parse(await Bun.file(join(dir, "models.json")).text());
171
- expect(written.providers.openrouter.baseUrl).toBe(GATEWAY);
172
- expect(written.providers.openrouter.compat).toEqual({ supportsTools: true });
173
- expect(written.providers.custom).toEqual({
174
- baseUrl: "https://custom.example/v1",
175
- api: "openai-completions",
143
+ const runtime = await ModelRuntime.create({
144
+ authPath: join(dir, "auth.json"),
145
+ modelsPath: session.modelsPath,
176
146
  });
147
+ expect(runtime.getError()).toBeUndefined();
148
+ const model = runtime.getModel("openrouter", "anthropic/claude-sonnet-5");
149
+ expect(model?.baseUrl).toBe(GATEWAY);
177
150
  } finally {
151
+ await session.dispose();
178
152
  rmSync(dir, { recursive: true, force: true });
179
153
  }
180
154
  });
181
155
 
182
- test("overwrites a corrupt models.json with just the override", async () => {
183
- const dir = mkdtempSync(join(tmpdir(), "pi-or-corrupt-"));
184
- try {
185
- await Bun.write(join(dir, "models.json"), "{not json");
186
- await ensureOpenRouterModelsOverride(dir, { OPENROUTER_BASE_URL: GATEWAY });
187
- const written = JSON.parse(await Bun.file(join(dir, "models.json")).text());
188
- expect(written).toEqual({ providers: { openrouter: { baseUrl: GATEWAY } } });
189
- } finally {
190
- rmSync(dir, { recursive: true, force: true });
191
- }
192
- });
193
-
194
- test("ModelRuntime composes the override onto built-in OpenRouter models", async () => {
195
- // The whole point of the lever: built-in model IDs resolved through the
196
- // runtime's composed store carry the gateway baseUrl.
197
- const runtime = await ModelRuntime.create({
198
- authPath: join(agentDir, "auth.json"),
199
- modelsPath: join(agentDir, "models.json"),
200
- });
201
- expect(runtime.getError()).toBeUndefined();
202
- const model = runtime.getModel("openrouter", "anthropic/claude-sonnet-5");
203
- expect(model?.baseUrl).toBe(GATEWAY);
204
- });
205
-
206
156
  test("ModelRuntime keeps the default baseUrl when no models.json exists", async () => {
207
157
  const dir = mkdtempSync(join(tmpdir(), "pi-or-default-"));
208
158
  try {
@@ -15,7 +15,7 @@ import { unlink } from "node:fs/promises";
15
15
  import net from "node:net";
16
16
  import { runInNewContext } from "node:vm";
17
17
  import type { Subprocess } from "bun";
18
- import { BROWSER_SDK_JS } from "../artifact-sdk/browser-sdk";
18
+ import { BROWSER_SDK_JS } from "../pages/browser-sdk";
19
19
  import { signPageSession, verifyPageSession } from "../utils/page-session";
20
20
  import { getFreePort, SERVER_BOOT_HOOK_TIMEOUT_MS, waitForServer } from "./test-net";
21
21