@desplega.ai/agent-swarm 1.173.0 → 2.0.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 (432) hide show
  1. package/README.md +1 -1
  2. package/dist/{acp-adapter-ccm9xrep.js → acp-adapter-sj2g0m84.js} +17 -16
  3. package/dist/{actions-sgeyv08k.js → actions-fegk44gr.js} +39 -34
  4. package/dist/{amp-adapter-gpxp3kc9.js → amp-adapter-ad8ft89x.js} +8 -7
  5. package/dist/{app-km9qnv2f.js → app-kfvgbvzy.js} +18 -14
  6. package/dist/{assistant-5r8tcvvd.js → assistant-z7zdy9ja.js} +40 -35
  7. package/dist/{boot-reembed-swwzdgd7.js → boot-reembed-5c2z0mbe.js} +10 -9
  8. package/dist/{boot-reembed-16rv9843.js → boot-reembed-j8xa181e.js} +10 -9
  9. package/dist/{boot-scrub-sweep-zkr6vkqb.js → boot-scrub-sweep-hnkn3rgt.js} +11 -10
  10. package/dist/{boot-seeds-d8sp49t2.js → boot-seeds-kwjq4d2z.js} +14 -13
  11. package/dist/{claude-adapter-yzzzh8jp.js → claude-adapter-9vmvpvgd.js} +19 -18
  12. package/dist/{claude-managed-adapter-xgxhqrt2.js → claude-managed-adapter-k9qae05j.js} +7 -5
  13. package/dist/{claude-managed-setup-bnmjarjc.js → claude-managed-setup-q54zx5q2.js} +6 -4
  14. package/dist/{claude-sdk-session-4whxere4.js → claude-sdk-session-9jye6cxr.js} +11881 -11472
  15. package/dist/{cli-94b48jhk.js → cli-03gedhng.js} +16 -14
  16. package/dist/{cli-1w0vj3gw.js → cli-1e4b6bzh.js} +7 -7
  17. package/dist/{cli-d603qf86.js → cli-29jc5q7w.js} +10 -8
  18. package/dist/cli-2m1ktfzz.js +17795 -0
  19. package/dist/{cli-x119kdba.js → cli-3dj8k4vy.js} +1 -1
  20. package/dist/{cli-srqq3gc5.js → cli-3fbmmawm.js} +5 -5
  21. package/dist/{cli-gmdajfw5.js → cli-4jxm2qtz.js} +4987 -1708
  22. package/dist/{cli-tadfgd0t.js → cli-5hkm7whv.js} +33 -4
  23. package/dist/{cli-42qhyvwx.js → cli-5t4f29ez.js} +3 -3
  24. package/dist/{cli-7a0sde2x.js → cli-6cagzc96.js} +9 -0
  25. package/dist/cli-6cvfxs8r.js +47 -0
  26. package/dist/{cli-0kxf81jj.js → cli-6jyj59nb.js} +2 -2
  27. package/dist/{cli-w1apjecb.js → cli-6razj6t4.js} +2 -2
  28. package/dist/{cli-anzdk0z4.js → cli-74ahz61x.js} +2 -2
  29. package/dist/{cli-m89t9jdf.js → cli-7x0g4sn1.js} +15416 -16024
  30. package/dist/{cli-yatxqynm.js → cli-9ha6hs2d.js} +1 -290
  31. package/dist/{cli-81r1wjyv.js → cli-9nfx44eh.js} +1 -1
  32. package/dist/cli-9traj3dn.js +638 -0
  33. package/dist/{cli-smcrrmhy.js → cli-agzebbd5.js} +9 -9
  34. package/dist/{cli-na2pxe4f.js → cli-apx87jne.js} +2 -2
  35. package/dist/{cli-rfz2ve55.js → cli-bzszjf2p.js} +2855 -862
  36. package/dist/{cli-c5km5znr.js → cli-c8r3kzzb.js} +1 -1
  37. package/dist/{cli-hsem16hb.js → cli-cgtksawx.js} +7 -4
  38. package/dist/{cli-db3m6zdt.js → cli-d2qr7aj4.js} +1 -1
  39. package/dist/{cli-rfs4vddv.js → cli-d5f9ed5c.js} +5 -5
  40. package/dist/cli-e3g5mbkz.js +776 -0
  41. package/dist/{cli-7jykv7ef.js → cli-e6mpjhed.js} +1 -1
  42. package/dist/{cli-7a93cv8c.js → cli-ee9gtz70.js} +98 -23
  43. package/dist/{cli-3nxdmjrc.js → cli-eejsgvm4.js} +4 -4
  44. package/dist/{cli-fmhj1ehj.js → cli-ef1hg1wv.js} +470 -51
  45. package/dist/cli-f527ce74.js +781 -0
  46. package/dist/{cli-ppy4nnnh.js → cli-f7kr7svz.js} +2 -2
  47. package/dist/cli-hjwmhcg1.js +10622 -0
  48. package/dist/{cli-2bw3w47j.js → cli-hnhtk6cj.js} +1 -1
  49. package/dist/{cli-ywm1azg9.js → cli-hnmhtzvh.js} +6 -4
  50. package/dist/{cli-h7k0xtry.js → cli-hvqbtna4.js} +4 -1256
  51. package/dist/{cli-s8z6x4b4.js → cli-j0pfptdp.js} +218 -102
  52. package/dist/cli-jta2eyzc.js +619 -0
  53. package/dist/{cli-5er5gjbm.js → cli-k382tshb.js} +10 -11
  54. package/dist/{cli-v3zhnxsb.js → cli-k3dp5tpf.js} +2 -2
  55. package/dist/{cli-wd0hz6ah.js → cli-kbrt8gqa.js} +9 -91
  56. package/dist/cli-kceah1eh.js +92 -0
  57. package/dist/{cli-ne7smj1b.js → cli-ke896fcf.js} +10 -10
  58. package/dist/{cli-xc7p7s3g.js → cli-kt6zqg0e.js} +6 -6
  59. package/dist/{cli-wrh08zjd.js → cli-m50t4309.js} +5 -18
  60. package/dist/cli-n2wq4j7m.js +6985 -0
  61. package/dist/{cli-bv2etw2y.js → cli-n3hzg89b.js} +46 -15
  62. package/dist/{cli-tmhsxhb8.js → cli-n5vwg842.js} +23 -6
  63. package/dist/{cli-qm1448f9.js → cli-ne236afy.js} +8 -8
  64. package/dist/{cli-d161g69q.js → cli-p1x85d2t.js} +50 -2
  65. package/dist/{cli-7pz3b7x5.js → cli-przpmdgr.js} +25 -0
  66. package/dist/{cli-0a7mm3zp.js → cli-q0ke1yww.js} +25 -21
  67. package/dist/{cli-e3pmyt39.js → cli-q50942rs.js} +1 -1
  68. package/dist/{cli-r44t9wqc.js → cli-r106725k.js} +2 -2
  69. package/dist/{cli-phcqp1b9.js → cli-r2pnvj8g.js} +2 -2
  70. package/dist/{cli-jpc4r224.js → cli-r62a35ea.js} +19 -3
  71. package/dist/{cli-cd0hv9nh.js → cli-ra0xzggj.js} +2 -2
  72. package/dist/cli-rhs9pz33.js +294 -0
  73. package/dist/{cli-r4fqfpwf.js → cli-s2sp957d.js} +50 -27
  74. package/dist/{cli-1x7da0de.js → cli-s4setq91.js} +8 -8
  75. package/dist/{cli-6thk4jz3.js → cli-se8w6wny.js} +6 -6
  76. package/dist/cli-skxvvpap.js +10140 -0
  77. package/dist/{cli-014vns2y.js → cli-stfs0k5s.js} +208 -525
  78. package/dist/{cli-0p3enk35.js → cli-tqaazemc.js} +9 -1
  79. package/dist/{cli-nwrdx840.js → cli-tr4sxp68.js} +3 -3
  80. package/dist/{cli-r0byd61h.js → cli-tsde1hdr.js} +15 -15
  81. package/dist/{cli-b07hr3bt.js → cli-txqb9s38.js} +2 -2
  82. package/dist/{cli-6r2f7edk.js → cli-v1yb0rqb.js} +6 -6
  83. package/dist/{cli-saqyage0.js → cli-vatyhshy.js} +179 -27
  84. package/dist/{cli-5wpw4rab.js → cli-vdach5a9.js} +5 -5
  85. package/dist/{cli-7avy4v8w.js → cli-w1q7h2r2.js} +5 -3
  86. package/dist/{cli-rcp5aqdw.js → cli-w7m81tb7.js} +1 -1
  87. package/dist/{cli-c3rk1v0x.js → cli-wayw93nt.js} +3 -3
  88. package/dist/cli-wmgrkscj.js +23254 -0
  89. package/dist/{cli-9xf7fdtp.js → cli-z5jjpaxw.js} +29 -6
  90. package/dist/{cli-451ks6aw.js → cli-zkpks3r3.js} +1 -1
  91. package/dist/cli.js +29 -45
  92. package/dist/{codex-adapter-r3dn4fp5.js → codex-adapter-y81sssk0.js} +15 -14
  93. package/dist/{codex-login-841kv5fk.js → codex-login-f2rcfpp9.js} +3 -2
  94. package/dist/{codex-session-runner-fwt793h4.js → codex-session-runner-8vx4j6a9.js} +15 -14
  95. package/dist/{commands-z8fq9qna.js → commands-kcpvpcrr.js} +11 -10
  96. package/dist/{controller-hq5ggpcp.js → controller-ywrmt6yd.js} +5 -4
  97. package/dist/{controllers-connect-3mf2btyj.js → controllers-connect-kwpvj3rf.js} +42 -37
  98. package/dist/{cursor-adapter-q8pgv5qa.js → cursor-adapter-cehv1qns.js} +7 -6
  99. package/dist/{db-yyhqqrbf.js → db-k8ankj20.js} +12 -11
  100. package/dist/{db-qfpq4bt7.js → db-ww5d7jq3.js} +28 -9
  101. package/dist/{devin-adapter-xgtj680j.js → devin-adapter-x1022qb1.js} +3 -2
  102. package/dist/{dispatcher-x0x7qfcq.js → dispatcher-e8gq5m68.js} +38 -33
  103. package/dist/{docker-f503k9p0.js → docker-ff6y1190.js} +3 -2
  104. package/dist/{dsh-adapter-34etvb5m.js → dsh-adapter-3epjm3af.js} +7 -6
  105. package/dist/{e2b-66xra4az.js → e2b-8ygwhg0f.js} +4 -3
  106. package/dist/{e2b-xwhyj8cw.js → e2b-swhnpw3n.js} +4 -3
  107. package/dist/{event-bus-vjtv82gm.js → event-bus-6axnngdj.js} +3 -2
  108. package/dist/{external-channel-h1fjecwe.js → external-channel-h3359cwn.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-0d4x7gkx.js} +18 -17
  112. package/dist/{handlers-znb9n7d4.js → handlers-an1gzv21.js} +41 -36
  113. package/dist/{heartbeat-bm4qkn8d.js → heartbeat-6dakeb61.js} +43 -38
  114. package/dist/{hook-254r4mza.js → hook-6v6cv6d5.js} +11 -23
  115. package/dist/{hook-xaa3q80s.js → hook-sa5xye4y.js} +12 -11
  116. package/dist/{http-3dgrgp8y.js → http-r7f3eky0.js} +1499 -251
  117. package/dist/index-52j1aja4.js +3465 -0
  118. package/dist/{index-fx3jqq1e.js → index-7ep4gzdk.js} +38 -33
  119. package/dist/index-9gw58n4k.js +193 -0
  120. package/dist/index-9j87mcm8.js +56305 -0
  121. package/dist/index-bsmcresk.js +79424 -0
  122. package/dist/index-mh85qhec.js +475 -0
  123. package/dist/{index-g2byveez.js → index-rmpw9a8q.js} +38 -33
  124. package/dist/{index-2ybk7khy.js → index-s2wxgzr0.js} +468 -683
  125. package/dist/{index-bdsqa799.js → index-v1v6n7hg.js} +43 -38
  126. package/dist/index-we27kd31.js +12810 -0
  127. package/dist/{index-jd7sejm2.js → index-zk96aq1k.js} +10 -9
  128. package/dist/{keepalive-6ywhpja1.js → keepalive-be9vwy5h.js} +12 -11
  129. package/dist/{lead-cw45n21s.js → lead-0861cwrf.js} +61 -56
  130. package/dist/{maintenance-ss1a1ge9.js → maintenance-mzzgc8bn.js} +10 -9
  131. package/dist/{node-fkajaq96.js → node-mn1zs7an.js} +2 -1
  132. package/dist/{oauth-refresh-sweep-3c6f8vyy.js → oauth-refresh-sweep-cpdnfwdj.js} +11 -10
  133. package/dist/{onboard-8hzdhc1k.js → onboard-fh8qs4r3.js} +11 -3
  134. package/dist/{opencode-adapter-qbchbcfz.js → opencode-adapter-nm42wwj8.js} +18 -13
  135. package/dist/{otel-impl-e6mnvc4v.js → otel-impl-x6c1c9g4.js} +242 -868
  136. package/dist/{pi-mono-adapter-3c9g9snh.js → pi-mono-adapter-9n2ya240.js} +244 -106
  137. package/dist/{pricing-refresh-gedkjdw0.js → pricing-refresh-qb0rv0ap.js} +12 -11
  138. package/dist/{rbac-roles-dt4vh1yy.js → rbac-roles-1gzdwbh1.js} +11 -10
  139. package/dist/{rbac-roles-eszdg6pm.js → rbac-roles-3mraey52.js} +12 -11
  140. package/dist/{render-v2-907cy6sa.js → render-v2-agpa45zz.js} +18 -14
  141. package/dist/{resume-42stcaz0.js → resume-x0ydy9s9.js} +25 -21
  142. package/dist/{runtime-qfry88v2.js → runtime-sa2vr16g.js} +2 -2
  143. package/dist/{setup-adbc1c10.js → setup-as9ga0d6.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-y5g1y7ev.js} +41 -36
  146. package/dist/{worker-bzdra9rf.js → worker-860c3e38.js} +61 -56
  147. package/dist/{x-qfmygqqv.js → x-m477fr0h.js} +3 -2
  148. package/openapi.json +523 -344
  149. package/package.json +10 -11
  150. package/src/be/config-refs.ts +60 -0
  151. package/src/be/db/page-versions.ts +15 -0
  152. package/src/be/db/runtime.ts +35 -8
  153. package/src/be/db/skill-name-resolution.ts +36 -0
  154. package/src/be/db/tasks/read.ts +230 -0
  155. package/src/be/db/tasks/write.ts +7 -0
  156. package/src/be/db.ts +52 -7
  157. package/src/be/migrations/215_swarm_config_export_to_env.sql +34 -0
  158. package/src/be/migrations/216_task_resolved_effort.sql +5 -0
  159. package/src/be/migrations/217_chat_sdk_state.sql +104 -0
  160. package/src/be/model-catalog-store.ts +5 -1
  161. package/src/be/model-tier-resolution.ts +12 -1
  162. package/src/be/model-validation.ts +5 -2
  163. package/src/be/models-catalog.ts +26 -1
  164. package/src/be/modelsdev-cache.json +198 -63
  165. package/src/be/modelsdev-cache.ts +9 -1
  166. package/src/be/script-credential-broker.ts +39 -2
  167. package/src/be/scripts/run-saved.ts +1 -0
  168. package/src/be/scripts/typecheck.ts +10 -8
  169. package/src/be/seed/runner.ts +13 -1
  170. package/src/be/seed/state-db.ts +5 -0
  171. package/src/be/seed/types.ts +6 -0
  172. package/src/be/seed-pricing.ts +19 -2
  173. package/src/be/seed-scripts/catalog/ops-catalog-audit.inline.ts +1 -1
  174. package/src/be/seed-scripts/catalog/ops-catalog-audit.ts +1 -1
  175. package/src/be/seed-skills/bundled-files.generated.json +0 -18
  176. package/src/be/seed-skills/index.ts +104 -3
  177. package/src/be/swarm-config-guard.ts +52 -0
  178. package/src/cli.tsx +0 -16
  179. package/src/commands/onboard/non-interactive.ts +4 -2
  180. package/src/commands/provider-credentials.ts +52 -6
  181. package/src/commands/runner.ts +190 -54
  182. package/src/e2b/env.ts +5 -0
  183. package/src/extensions/contract-types.generated.ts +1 -1
  184. package/src/extensions/contract.ts +45 -0
  185. package/src/extensions/dispatcher.ts +7 -0
  186. package/src/extensions/post-bridge.ts +40 -0
  187. package/src/github/handlers.ts +11 -4
  188. package/src/heartbeat/templates.ts +8 -5
  189. package/src/hooks/hook.ts +0 -26
  190. package/src/http/all-routes.ts +0 -1
  191. package/src/http/config.ts +36 -17
  192. package/src/http/core.ts +4 -0
  193. package/src/http/index.ts +14 -2
  194. package/src/http/integrations.ts +88 -1
  195. package/src/http/mcp.ts +15 -5
  196. package/src/http/page-proxy.ts +2 -4
  197. package/src/http/pages-public.ts +3 -3
  198. package/src/http/pages.ts +3 -13
  199. package/src/http/poll.ts +28 -68
  200. package/src/http/requested-by.ts +54 -0
  201. package/src/http/scripts.ts +1 -0
  202. package/src/http/stats.ts +1 -30
  203. package/src/http/tasks.ts +61 -5
  204. package/src/http/utils.ts +14 -0
  205. package/src/http/webhooks.ts +159 -1
  206. package/src/http/x.ts +1 -0
  207. package/src/integrations/chat-sdk/adapters.ts +129 -0
  208. package/src/integrations/chat-sdk/attachments.ts +232 -0
  209. package/src/integrations/chat-sdk/background.ts +36 -0
  210. package/src/integrations/chat-sdk/commands.ts +194 -0
  211. package/src/integrations/chat-sdk/config.ts +198 -0
  212. package/src/integrations/chat-sdk/discord-api.ts +63 -0
  213. package/src/integrations/chat-sdk/discord-gateway.ts +185 -0
  214. package/src/integrations/chat-sdk/handlers.ts +28 -0
  215. package/src/integrations/chat-sdk/inbound.ts +426 -0
  216. package/src/integrations/chat-sdk/limits.ts +48 -0
  217. package/src/integrations/chat-sdk/logger.ts +81 -0
  218. package/src/integrations/chat-sdk/outbound.ts +123 -0
  219. package/src/integrations/chat-sdk/relay.ts +172 -0
  220. package/src/integrations/chat-sdk/router.ts +169 -0
  221. package/src/integrations/chat-sdk/runtime.ts +351 -0
  222. package/src/integrations/chat-sdk/schema.ts +245 -0
  223. package/src/integrations/chat-sdk/state.ts +243 -0
  224. package/src/integrations/chat-sdk/status.ts +244 -0
  225. package/src/integrations/chat-sdk/steering.ts +58 -0
  226. package/src/integrations/chat-sdk/teams-origin.ts +77 -0
  227. package/src/integrations/chat-sdk/telegram-api.ts +56 -0
  228. package/src/integrations/chat-sdk/templates.ts +16 -0
  229. package/src/integrations/chat-sdk/test-hooks.ts +37 -0
  230. package/src/integrations/chat-sdk/thread-buffer.ts +100 -0
  231. package/src/integrations/chat-sdk/typing.ts +65 -0
  232. package/src/pages/version.ts +4 -5
  233. package/src/prompts/base-prompt.ts +111 -45
  234. package/src/providers/acp-target-catalog.ts +6 -0
  235. package/src/providers/acp-targets.ts +55 -2
  236. package/src/providers/amp-adapter.ts +1 -1
  237. package/src/providers/claude-adapter.ts +1 -1
  238. package/src/providers/claude-flag-settings.ts +17 -5
  239. package/src/providers/claude-managed-adapter.ts +1 -1
  240. package/src/providers/claude-session-events.ts +6 -1
  241. package/src/providers/dsh-adapter.ts +1 -1
  242. package/src/providers/opencode-adapter.ts +243 -26
  243. package/src/providers/opencode-swarm-plugin.ts +31 -0
  244. package/src/providers/pi-mono-adapter.ts +398 -153
  245. package/src/providers/pricing-sources.md +33 -2
  246. package/src/providers/reasoning-effort.ts +27 -0
  247. package/src/providers/types.ts +6 -2
  248. package/src/rbac/legacy-policy.ts +8 -2
  249. package/src/rbac/permissions.ts +21 -0
  250. package/src/scheduler/scheduler.ts +4 -2
  251. package/src/scripts-runtime/loader.ts +19 -7
  252. package/src/scripts-runtime/sdk-allowlist.ts +8 -6
  253. package/src/scripts-runtime/swarm-sdk.ts +0 -11
  254. package/src/scripts-runtime/types/stdlib.d.ts +27 -15
  255. package/src/scripts-runtime/types/swarm-extension.d.ts +63 -1
  256. package/src/scripts-runtime/types/swarm-sdk.d.ts +27 -15
  257. package/src/server.ts +52 -18
  258. package/src/slack/event-dedup.ts +1 -1
  259. package/src/tasks/additive-buffer.ts +4 -2
  260. package/src/tasks/context-key.ts +60 -1
  261. package/src/tasks/model-pin.ts +67 -0
  262. package/src/tasks/worker-follow-up.ts +4 -31
  263. package/src/tests/acp-adapter.test.ts +42 -0
  264. package/src/tests/api-drain-worker-e2e.test.ts +1 -6
  265. package/src/tests/base-prompt.test.ts +58 -54
  266. package/src/tests/chat-sdk-commands.test.ts +191 -0
  267. package/src/tests/chat-sdk-config.test.ts +334 -0
  268. package/src/tests/chat-sdk-discord-api.test.ts +68 -0
  269. package/src/tests/chat-sdk-discord-gateway.test.ts +252 -0
  270. package/src/tests/chat-sdk-discord-webhook.test.ts +126 -0
  271. package/src/tests/chat-sdk-fields-sync.test.ts +50 -0
  272. package/src/tests/chat-sdk-harness.ts +146 -0
  273. package/src/tests/chat-sdk-inbound.test.ts +363 -0
  274. package/src/tests/chat-sdk-relay.test.ts +470 -0
  275. package/src/tests/chat-sdk-reload.test.ts +128 -0
  276. package/src/tests/chat-sdk-shutdown.test.ts +220 -0
  277. package/src/tests/chat-sdk-slack.test.ts +268 -0
  278. package/src/tests/chat-sdk-state.test.ts +163 -0
  279. package/src/tests/chat-sdk-status-route.test.ts +382 -0
  280. package/src/tests/chat-sdk-teams.test.ts +277 -0
  281. package/src/tests/chat-sdk-tools.test.ts +520 -0
  282. package/src/tests/chat-sdk-typing.test.ts +120 -0
  283. package/src/tests/chat-sdk-webhook-telegram.test.ts +200 -0
  284. package/src/tests/claude-adapter-binary.test.ts +9 -4
  285. package/src/tests/claude-flag-settings.test.ts +11 -3
  286. package/src/tests/claude-managed-adapter.test.ts +1 -1
  287. package/src/tests/claude-sdk-lifecycle.test.ts +9 -3
  288. package/src/tests/claude-thinking-tokens.test.ts +57 -0
  289. package/src/tests/claude-worker-parity.test.ts +2 -7
  290. package/src/tests/config-env-export-chat-sdk.test.ts +11 -0
  291. package/src/tests/config-refs.test.ts +66 -0
  292. package/src/tests/context-key.test.ts +19 -0
  293. package/src/tests/cors-allowlist.test.ts +25 -0
  294. package/src/tests/create-page-tool.test.ts +18 -0
  295. package/src/tests/credential-check.test.ts +139 -0
  296. package/src/tests/credentials.test.ts +21 -0
  297. package/src/tests/db-runtime.test.ts +61 -0
  298. package/src/tests/db-task-read-contract.test.ts +20 -0
  299. package/src/tests/db-task-write-contract.test.ts +10 -0
  300. package/src/tests/docs-index-footprint.test.js +3 -3
  301. package/src/tests/dsh-adapter.test.ts +1 -1
  302. package/src/tests/enabled-capabilities.test.ts +20 -0
  303. package/src/tests/entrypoint-api-readiness.test.ts +14 -8
  304. package/src/tests/entrypoint-config-env-export.test.ts +5 -2
  305. package/src/tests/entrypoint-export-to-env.test.ts +78 -0
  306. package/src/tests/explicit-model-validation.test.ts +105 -1
  307. package/src/tests/extensions-example-route-chat-channel.test.ts +154 -0
  308. package/src/tests/extensions-post-bridge.test.ts +51 -0
  309. package/src/tests/fetch-resolved-env.test.ts +128 -0
  310. package/src/tests/fixtures/chat-sdk/telegram-private-text.json +10 -0
  311. package/src/tests/fixtures/extensions/route-chat-channel-to-agent/hooks.ts +27 -0
  312. package/src/tests/fixtures/extensions/route-chat-channel-to-agent/manifest.json +9 -0
  313. package/src/tests/fixtures/openai-compatible-registry-extension.child.ts +146 -0
  314. package/src/tests/fixtures/runner-legacy-resume-prompt.ts +122 -0
  315. package/src/tests/github-handlers.test.ts +35 -0
  316. package/src/tests/harness-provider-resolution.test.ts +12 -0
  317. package/src/tests/heartbeat-checklist.test.ts +3 -2
  318. package/src/tests/hook-runtime-close.test.ts +0 -2
  319. package/src/tests/http-api-integration.test.ts +0 -25
  320. package/src/tests/migration-046-budgets.test.ts +17 -16
  321. package/src/tests/migration-217-chat-sdk-state.test.ts +113 -0
  322. package/src/tests/model-tier-resolution.test.ts +93 -0
  323. package/src/tests/models-catalog.test.ts +42 -1
  324. package/src/tests/openai-compatible-providers.test.ts +134 -0
  325. package/src/tests/openai-compatible-registry-extension.test.ts +42 -0
  326. package/src/tests/opencode-adapter.test.ts +216 -2
  327. package/src/tests/opencode-helmcode-sdk.test.ts +201 -0
  328. package/src/tests/opencode-shell-env.test.ts +145 -0
  329. package/src/tests/openrouter-base-url.test.ts +101 -151
  330. package/src/tests/page-proxy.test.ts +1 -1
  331. package/src/tests/pages-browser-sdk.test.ts +76 -0
  332. package/src/tests/pages-feedback.test.ts +1 -1
  333. package/src/tests/paused-tasks-requested-by.test.ts +102 -0
  334. package/src/tests/pi-custom-provider-fake-server.test.ts +442 -0
  335. package/src/tests/pi-mono-adapter.test.ts +385 -5
  336. package/src/tests/pi-shell-env.test.ts +170 -0
  337. package/src/tests/prompt-template-remaining.test.ts +35 -0
  338. package/src/tests/rbac-engine.test.ts +11 -3
  339. package/src/tests/runner-exit-after-tasks.test.ts +0 -4
  340. package/src/tests/runner-legacy-resume-prompt.test.ts +64 -0
  341. package/src/tests/script-connections.test.ts +2 -0
  342. package/src/tests/scripts-http.test.ts +201 -1
  343. package/src/tests/scripts-only-gating.test.ts +2 -3
  344. package/src/tests/scripts-runtime.test.ts +58 -40
  345. package/src/tests/secret-scrubber.test.ts +86 -0
  346. package/src/tests/seed-skills-bundled-files.test.ts +45 -53
  347. package/src/tests/seed-skills-retire.test.ts +182 -0
  348. package/src/tests/send-task-requester-gate.test.ts +58 -0
  349. package/src/tests/session-cost-recompute-unit.test.ts +46 -0
  350. package/src/tests/skill-get-name-resolution.test.ts +137 -0
  351. package/src/tests/sonnet-5-5-registry.test.ts +9 -1
  352. package/src/tests/swarm-config-export-to-env.test.ts +267 -0
  353. package/src/tests/swarm-config-reserved-keys.test.ts +16 -0
  354. package/src/tests/swarm-diff.test.ts +1 -1
  355. package/src/tests/task-model-effort-rerun.test.ts +164 -0
  356. package/src/tests/task-session-prompts.test.ts +132 -0
  357. package/src/tests/task-tool-manifest.test.ts +14 -0
  358. package/src/tests/task-tools-ctx.test.ts +2 -0
  359. package/src/tests/tool-annotations.test.ts +6 -6
  360. package/src/tests/ui-logs-parser.test.ts +43 -0
  361. package/src/tests/used-by-route.test.ts +6 -0
  362. package/src/tests/vercel-ai-gateway.test.ts +149 -0
  363. package/src/tests/workflow-swarm-script.test.ts +38 -0
  364. package/src/tests/workflow-wait-filter.test.ts +5 -3
  365. package/src/tools/chat-access.ts +41 -0
  366. package/src/tools/chat-open-dm.ts +59 -0
  367. package/src/tools/chat-post.ts +74 -0
  368. package/src/tools/chat-read-thread.ts +100 -0
  369. package/src/tools/chat-reply.ts +75 -0
  370. package/src/tools/create-page.ts +3 -13
  371. package/src/tools/model-catalog/index.ts +2 -1
  372. package/src/tools/register-chat-channel.ts +139 -0
  373. package/src/tools/send-task.ts +24 -0
  374. package/src/tools/skills/skill-get.ts +17 -9
  375. package/src/tools/swarm-config/get-config.ts +2 -1
  376. package/src/tools/swarm-config/list-config.ts +2 -1
  377. package/src/tools/swarm-config/set-config.ts +16 -3
  378. package/src/tools/tool-config.ts +8 -6
  379. package/src/types.ts +12 -2
  380. package/src/utils/config-env-export.ts +74 -0
  381. package/src/utils/credentials.test.ts +92 -0
  382. package/src/utils/credentials.ts +52 -4
  383. package/src/utils/harness-provider.ts +15 -6
  384. package/src/utils/runtime-model-catalog.ts +11 -1
  385. package/src/utils/secret-scrubber.ts +71 -2
  386. package/src/utils/task-tool-manifest.ts +19 -2
  387. package/src/workflows/executors/swarm-script.ts +1 -0
  388. package/templates/skills/agent-browser/SKILL.md +1 -1
  389. package/templates/skills/agent-browser/content.md +1 -1
  390. package/templates/skills/apps/SKILL.md +8 -1
  391. package/templates/skills/apps/content.md +8 -1
  392. package/templates/skills/chat-sdk/config.json +14 -0
  393. package/templates/skills/chat-sdk/content.md +84 -0
  394. package/templates/skills/composio-gmail/content.md +7 -0
  395. package/templates/skills/composio-google-docs/content.md +1 -1
  396. package/templates/skills/email-domain-health/config.json +14 -0
  397. package/templates/skills/email-domain-health/content.md +131 -0
  398. package/templates/skills/kv-storage/content.md +2 -2
  399. package/templates/skills/pages/content.md +8 -10
  400. package/templates/skills/swarm-extensions/content.md +2 -0
  401. package/templates/skills/swarm-scripts/SKILL.md +4 -2
  402. package/templates/skills/swarm-scripts/config.json +1 -1
  403. package/templates/skills/swarm-scripts/content.md +4 -2
  404. package/templates/skills/workflow-iterate/content.md +38 -0
  405. package/dist/artifact-eczsknc3.js +0 -3843
  406. package/dist/cli-bd957dgb.js +0 -153
  407. package/dist/cli-q51yprfq.js +0 -1104
  408. package/dist/cli-t1ze64h3.js +0 -811
  409. package/dist/cli-tcc4wd5d.js +0 -56471
  410. package/dist/index-9ch1n761.js +0 -1102
  411. package/src/artifact-sdk/index.ts +0 -2
  412. package/src/artifact-sdk/localtunnel.d.ts +0 -20
  413. package/src/artifact-sdk/port.ts +0 -12
  414. package/src/artifact-sdk/server.ts +0 -183
  415. package/src/artifact-sdk/tunnel.ts +0 -19
  416. package/src/commands/artifact.ts +0 -249
  417. package/src/http/ecosystem.ts +0 -77
  418. package/src/tests/artifact-commands.test.ts +0 -92
  419. package/src/tests/artifact-sdk.test.ts +0 -793
  420. package/src/tests/register-service-security.test.ts +0 -194
  421. package/src/tools/list-services.ts +0 -107
  422. package/src/tools/register-service.ts +0 -177
  423. package/src/tools/unregister-service.ts +0 -73
  424. package/src/tools/update-service-status.ts +0 -96
  425. package/templates/skills/artifacts/config.json +0 -14
  426. package/templates/skills/artifacts/content.md +0 -344
  427. package/templates/skills/artifacts/files/examples/approval-flow.ts +0 -34
  428. package/templates/skills/artifacts/files/examples/hono-dashboard.ts +0 -31
  429. package/templates/skills/artifacts/files/examples/multi-artifact.ts +0 -20
  430. package/templates/skills/artifacts/files/examples/static-report.sh +0 -17
  431. /package/src/{artifact-sdk → pages}/browser-sdk.ts +0 -0
  432. /package/src/{artifact-sdk → pages}/feedback-overlay.ts +0 -0
@@ -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
 
@@ -0,0 +1,76 @@
1
+ import { describe, expect, mock, test } from "bun:test";
2
+ import { BROWSER_SDK_JS } from "../pages/browser-sdk";
3
+
4
+ // ─── Browser SDK tests ─────────────────────────────────────────────────
5
+
6
+ describe("BROWSER_SDK_JS", () => {
7
+ test("is a non-empty string", () => {
8
+ expect(typeof BROWSER_SDK_JS).toBe("string");
9
+ expect(BROWSER_SDK_JS.length).toBeGreaterThan(100);
10
+ });
11
+
12
+ test("contains SwarmSDK class", () => {
13
+ expect(BROWSER_SDK_JS).toContain("class SwarmSDK");
14
+ });
15
+
16
+ test("is syntactically valid JavaScript", () => {
17
+ expect(() => new Function(BROWSER_SDK_JS)).not.toThrow();
18
+ });
19
+
20
+ test("exposes the eight canonical domains", () => {
21
+ const expectedDomains = [
22
+ "this.tasks",
23
+ "this.agents",
24
+ "this.events",
25
+ "this.memory",
26
+ "this.repos",
27
+ "this.schedules",
28
+ "this.approvalRequests",
29
+ "this.assets",
30
+ ];
31
+ for (const domain of expectedDomains) {
32
+ expect(BROWSER_SDK_JS).toContain(domain);
33
+ }
34
+ });
35
+
36
+ test("removed domains are NOT exposed (messages, services, slack)", () => {
37
+ const removed = ["this.messages", "this.services", "this.slack", "postMessage", "readMessages"];
38
+ for (const r of removed) {
39
+ expect(BROWSER_SDK_JS).not.toContain(r);
40
+ }
41
+ });
42
+
43
+ test("assigns SwarmSDK class + swarmSdk singleton to window", () => {
44
+ expect(BROWSER_SDK_JS).toContain("window.SwarmSDK = SwarmSDK");
45
+ expect(BROWSER_SDK_JS).toContain("window.swarmSdk = new SwarmSDK()");
46
+ });
47
+
48
+ test("routes calls through the /@swarm/api/* proxy", () => {
49
+ expect(BROWSER_SDK_JS).toContain("const base = '/@swarm/api'");
50
+ // Sentinel endpoints — one per domain.
51
+ expect(BROWSER_SDK_JS).toContain("'/tasks'");
52
+ expect(BROWSER_SDK_JS).toContain("'/agents'");
53
+ expect(BROWSER_SDK_JS).toContain("'/events'");
54
+ expect(BROWSER_SDK_JS).toContain("'/memory/search'");
55
+ expect(BROWSER_SDK_JS).toContain("'/repos'");
56
+ expect(BROWSER_SDK_JS).toContain("'/schedules'");
57
+ expect(BROWSER_SDK_JS).toContain("'/approval-requests'");
58
+ expect(BROWSER_SDK_JS).toContain("'/assets'");
59
+ expect(BROWSER_SDK_JS).toContain("'/assets/key-audit'");
60
+ expect(BROWSER_SDK_JS).toContain("'/assets/mappings'");
61
+ });
62
+
63
+ test("does not fetch on SDK load or construction", () => {
64
+ const fetchMock = mock(() => Promise.resolve(Response.json({})));
65
+ const window = {};
66
+ const loadSdk = new Function("window", "fetch", `${BROWSER_SDK_JS}\nreturn window.SwarmSDK;`);
67
+
68
+ const SwarmSDK = loadSdk(window, fetchMock);
69
+ expect(window).toHaveProperty("swarmSdk");
70
+ expect(fetchMock).not.toHaveBeenCalled();
71
+
72
+ new SwarmSDK();
73
+ new SwarmSDK();
74
+ expect(fetchMock).not.toHaveBeenCalled();
75
+ });
76
+ });
@@ -12,11 +12,11 @@ import {
12
12
  type Server,
13
13
  type ServerResponse,
14
14
  } from "node:http";
15
- import { feedbackOverlayScripts, isPageFeedbackRequested } from "../artifact-sdk/feedback-overlay";
16
15
  import { closeDb, createAgent, getTaskById, initDb } from "../be/db";
17
16
  import { handlePages } from "../http/pages";
18
17
  import { handlePagesPublic } from "../http/pages-public";
19
18
  import { getPathSegments, parseQueryParams } from "../http/utils";
19
+ import { feedbackOverlayScripts, isPageFeedbackRequested } from "../pages/feedback-overlay";
20
20
  import { listenOnFreePort } from "./test-net";
21
21
 
22
22
  const TEST_DB_PATH = "./test-pages-feedback.sqlite";
@@ -0,0 +1,102 @@
1
+ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
2
+ import { unlink } from "node:fs/promises";
3
+ import {
4
+ closeDb,
5
+ createAgent,
6
+ createTaskExtended,
7
+ createUser,
8
+ initDb,
9
+ pauseTask,
10
+ startTask,
11
+ } from "../be/db";
12
+ import { handleTasks } from "../http/tasks";
13
+
14
+ const TEST_DB_PATH = "./test-paused-tasks-requested-by.sqlite";
15
+ const AGENT_ID = "50000000-0000-4000-8000-000000000001";
16
+
17
+ async function removeDbFiles(path: string): Promise<void> {
18
+ for (const suffix of ["", "-wal", "-shm"]) {
19
+ try {
20
+ await unlink(path + suffix);
21
+ } catch (error) {
22
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
23
+ }
24
+ }
25
+ }
26
+
27
+ async function getPausedTasks(): Promise<{
28
+ status: number;
29
+ body: { tasks: Array<{ id: string; requestedBy?: Record<string, unknown> }> };
30
+ }> {
31
+ let status = 200;
32
+ let bodyStr = "";
33
+ const req = {
34
+ method: "GET",
35
+ url: "/api/paused-tasks",
36
+ headers: { "x-agent-id": AGENT_ID },
37
+ } as unknown as Parameters<typeof handleTasks>[0];
38
+ const res = {
39
+ setHeader() {},
40
+ writeHead(code: number) {
41
+ status = code;
42
+ },
43
+ end(body?: string) {
44
+ bodyStr = body ?? "";
45
+ },
46
+ } as unknown as Parameters<typeof handleTasks>[1];
47
+ const handled = await handleTasks(
48
+ req,
49
+ res,
50
+ ["api", "paused-tasks"],
51
+ new URLSearchParams(),
52
+ AGENT_ID,
53
+ );
54
+ if (!handled) throw new Error("handleTasks did not handle the request");
55
+ return { status, body: JSON.parse(bodyStr) };
56
+ }
57
+
58
+ beforeAll(async () => {
59
+ await removeDbFiles(TEST_DB_PATH);
60
+ initDb(TEST_DB_PATH);
61
+ await createAgent({ id: AGENT_ID, name: "Paused Worker", isLead: false, status: "idle" });
62
+ });
63
+
64
+ afterAll(async () => {
65
+ closeDb();
66
+ await removeDbFiles(TEST_DB_PATH);
67
+ });
68
+
69
+ describe("GET /api/paused-tasks requester projection", () => {
70
+ test("carries the same requestedBy a task_assigned trigger carries", async () => {
71
+ const user = await createUser({
72
+ name: "Paused Requester",
73
+ email: "paused@example.com",
74
+ role: "CTO",
75
+ notes: "Paused requester notes",
76
+ });
77
+ const withUser = await createTaskExtended("Paused task with requester", {
78
+ agentId: AGENT_ID,
79
+ requestedByUserId: user.id,
80
+ });
81
+ const withoutUser = await createTaskExtended("Paused task without requester", {
82
+ agentId: AGENT_ID,
83
+ });
84
+ for (const task of [withUser, withoutUser]) {
85
+ await startTask(task.id);
86
+ expect(await pauseTask(task.id)).not.toBeNull();
87
+ }
88
+
89
+ const { status, body } = await getPausedTasks();
90
+ expect(status).toBe(200);
91
+ const byId = new Map(body.tasks.map((task) => [task.id, task]));
92
+ expect(byId.get(withUser.id)?.requestedBy).toMatchObject({
93
+ id: user.id,
94
+ name: "Paused Requester",
95
+ email: "paused@example.com",
96
+ role: "CTO",
97
+ notes: "Paused requester notes",
98
+ });
99
+ expect(byId.get(withoutUser.id)).toBeDefined();
100
+ expect(byId.get(withoutUser.id)?.requestedBy).toBeUndefined();
101
+ });
102
+ });