@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
@@ -1,13 +1,26 @@
1
1
  import { afterAll, afterEach, beforeAll, describe, expect, spyOn, test } from "bun:test";
2
- import { existsSync, mkdirSync, readFileSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
2
+ import {
3
+ existsSync,
4
+ mkdirSync,
5
+ mkdtempSync,
6
+ readFileSync,
7
+ rmSync,
8
+ symlinkSync,
9
+ writeFileSync,
10
+ } from "node:fs";
11
+ import { tmpdir } from "node:os";
3
12
  import { join } from "node:path";
13
+ import { OPENAI_COMPATIBLE_PROVIDERS } from "@desplega/model-catalog";
4
14
  import * as piCodingAgent from "@earendil-works/pi-coding-agent";
5
15
  import { configureHttpResolver, resetHttpResolver } from "../prompts/resolver";
6
16
  import {
17
+ checkPiMonoCredentials,
7
18
  createPiRuntimeAuth,
8
19
  extractPiAssistantText,
9
20
  PiMonoAdapter,
10
21
  resolveModel,
22
+ type SessionModelsJson,
23
+ writeSessionModelsJson,
11
24
  } from "../providers/pi-mono-adapter";
12
25
  import type { ProviderSessionConfig } from "../providers/types";
13
26
 
@@ -151,8 +164,8 @@ describe("PiMonoAdapter.createSession — OPENROUTER_BASE_URL gateway", () => {
151
164
  const model = capturedOptions?.model as { baseUrl?: string; id?: string } | undefined;
152
165
  expect(model?.id).toBe("google/gemini-3-flash-preview");
153
166
  expect(model?.baseUrl).toBe(GATEWAY);
154
- // The override was materialized for the runtime to load.
155
- expect(existsSync(join(agentDir, "models.json"))).toBe(true);
167
+ // The override lives in the session's own copy, never the shared file.
168
+ expect(existsSync(join(agentDir, "models.json"))).toBe(false);
156
169
  });
157
170
 
158
171
  test("session model keeps the default openrouter.ai baseUrl when env is unset", async () => {
@@ -310,7 +323,7 @@ describe("PiMonoAdapter.createSession — MCP setup", () => {
310
323
  const adapter = new PiMonoAdapter();
311
324
  await adapter.createSession(makeMcpConfig(`http://localhost:${server.port}`));
312
325
  const options = createAgentSessionSpy.mock.calls[0]?.[0];
313
- expect(options?.customTools?.map((tool) => tool.name)).toEqual(["get-tasks"]);
326
+ expect(options?.customTools?.map((tool) => tool.name)).toEqual(["get-tasks", "bash"]);
314
327
  expect(initializeSessions).toEqual([null, null]);
315
328
  expect(new Set(seen)).toEqual(new Set(["pi-retry-runtime"]));
316
329
  } finally {
@@ -330,7 +343,9 @@ describe("PiMonoAdapter.createSession — MCP setup", () => {
330
343
  await adapter.createSession(makeMcpConfig(`http://localhost:${server.port}`));
331
344
  expect(initializeSessions).toEqual([null, null, null]);
332
345
  expect(failures).toEqual(["initialize"]);
333
- expect(createAgentSessionSpy.mock.calls[0]?.[0]?.customTools).toEqual([]);
346
+ expect(
347
+ createAgentSessionSpy.mock.calls[0]?.[0]?.customTools?.map((tool) => tool.name),
348
+ ).toEqual(["bash"]);
334
349
  } finally {
335
350
  server.stop(true);
336
351
  }
@@ -542,6 +557,371 @@ describe("createPiRuntimeAuth", () => {
542
557
  });
543
558
  });
544
559
 
560
+ // ─── NaN / Helmcode custom providers (models.json, runtime keys, resolve) ───
561
+
562
+ const NAN_ENV = { NAN_API_KEY: "example-nan-picked" };
563
+ const HELMCODE_ENV = { HELMCODE_API_KEY: "example-helmcode-picked" };
564
+ async function readModelsJson(path: string | undefined): Promise<Record<string, any>> {
565
+ if (!path) throw new Error("expected a per-session models.json");
566
+ return JSON.parse(await Bun.file(path).text());
567
+ }
568
+
569
+ function withTempDir(prefix: string, fn: (dir: string) => Promise<void>): () => Promise<void> {
570
+ return async () => {
571
+ const dir = mkdtempSync(join(tmpdir(), prefix));
572
+ try {
573
+ await fn(dir);
574
+ } finally {
575
+ rmSync(dir, { recursive: true, force: true });
576
+ }
577
+ };
578
+ }
579
+
580
+ /** Compose a session's models.json and always remove it afterwards. */
581
+ async function withSessionModels(
582
+ dir: string,
583
+ env: Record<string, string | undefined>,
584
+ model: string | undefined,
585
+ fn: (session: SessionModelsJson) => Promise<void>,
586
+ ): Promise<void> {
587
+ const session = await writeSessionModelsJson(dir, env, model);
588
+ try {
589
+ await fn(session);
590
+ } finally {
591
+ await session.dispose();
592
+ }
593
+ }
594
+
595
+ describe("writeSessionModelsJson (pi)", () => {
596
+ test(
597
+ "writes both blocks with $VAR apiKeys to a private file, never the key value or the shared file",
598
+ withTempDir("pi-custom-fresh-", async (dir) => {
599
+ await withSessionModels(dir, { ...NAN_ENV, ...HELMCODE_ENV }, undefined, async (s) => {
600
+ expect(s.modelsPath).toBeDefined();
601
+ expect(s.modelsPath?.startsWith(dir)).toBe(false);
602
+ expect(existsSync(join(dir, "models.json"))).toBe(false);
603
+ const raw = await Bun.file(s.modelsPath as string).text();
604
+ expect(raw).not.toContain("example-nan-picked");
605
+ expect(raw).not.toContain("example-helmcode-picked");
606
+ const written = JSON.parse(raw);
607
+ expect(written.providers.nan).toMatchObject({
608
+ baseUrl: OPENAI_COMPATIBLE_PROVIDERS.nan.baseUrl,
609
+ api: "openai-completions",
610
+ apiKey: "$NAN_API_KEY",
611
+ });
612
+ expect(written.providers.helmcode).toMatchObject({
613
+ baseUrl: OPENAI_COMPATIBLE_PROVIDERS.helmcode.baseUrl,
614
+ api: "openai-completions",
615
+ apiKey: "$HELMCODE_API_KEY",
616
+ });
617
+ expect(written.providers.helmcode.models.map((m: { id: string }) => m.id)).toEqual(
618
+ OPENAI_COMPATIBLE_PROVIDERS.helmcode.models.map((m) => m.id),
619
+ );
620
+ const glm = written.providers.nan.models.find((m: { id: string }) => m.id === "glm5.3");
621
+ expect(glm).toMatchObject({
622
+ contextWindow: 1000000,
623
+ maxTokens: 131072,
624
+ reasoning: true,
625
+ compat: { supportsDeveloperRole: true },
626
+ });
627
+ });
628
+ }),
629
+ );
630
+
631
+ test(
632
+ "dispose removes the private file",
633
+ withTempDir("pi-custom-dispose-", async (dir) => {
634
+ const s = await writeSessionModelsJson(dir, NAN_ENV);
635
+ expect(existsSync(s.modelsPath as string)).toBe(true);
636
+ await s.dispose();
637
+ await s.dispose();
638
+ expect(existsSync(s.modelsPath as string)).toBe(false);
639
+ }),
640
+ );
641
+
642
+ test(
643
+ "only the HELMCODE_API_NAN alias set: nan block still uses $NAN_API_KEY",
644
+ withTempDir("pi-custom-alias-", async (dir) => {
645
+ await withSessionModels(dir, { HELMCODE_API_NAN: "example-alias" }, undefined, async (s) => {
646
+ const written = await readModelsJson(s.modelsPath);
647
+ expect(written.providers.nan.apiKey).toBe("$NAN_API_KEY");
648
+ expect(written.providers.helmcode).toBeUndefined();
649
+ expect(JSON.stringify(written)).not.toContain("example-alias");
650
+ });
651
+ }),
652
+ );
653
+
654
+ test(
655
+ "no key, default gateway, no marker: the session loads the shared file",
656
+ withTempDir("pi-custom-noop-", async (dir) => {
657
+ const userConfig = { providers: { nan: { baseUrl: "https://mine.example/v1" } } };
658
+ await Bun.write(join(dir, "models.json"), JSON.stringify(userConfig));
659
+ const s = await writeSessionModelsJson(dir, {});
660
+ expect(s.modelsPath).toBeUndefined();
661
+ expect(JSON.parse(readFileSync(join(dir, "models.json"), "utf-8"))).toEqual(userConfig);
662
+ }),
663
+ );
664
+
665
+ test(
666
+ "a user-authored providers.nan in the shared file wins; helmcode is still added",
667
+ withTempDir("pi-custom-user-", async (dir) => {
668
+ const userNan = { baseUrl: "https://mine.example/v1", apiKey: "$MY_VAR", models: [] };
669
+ const shared = JSON.stringify({ providers: { nan: userNan } });
670
+ await Bun.write(join(dir, "models.json"), shared);
671
+ await withSessionModels(dir, { ...NAN_ENV, ...HELMCODE_ENV }, undefined, async (s) => {
672
+ const written = await readModelsJson(s.modelsPath);
673
+ expect(written.providers.nan).toEqual(userNan);
674
+ expect(written.providers.helmcode.apiKey).toBe("$HELMCODE_API_KEY");
675
+ });
676
+ expect(readFileSync(join(dir, "models.json"), "utf-8")).toBe(shared);
677
+ }),
678
+ );
679
+
680
+ test(
681
+ "keeps other providers and top-level keys; the gateway comes from this session's env",
682
+ withTempDir("pi-custom-merge-", async (dir) => {
683
+ const gateway = "https://control-plane.example/proxy/v1";
684
+ await Bun.write(
685
+ join(dir, "models.json"),
686
+ JSON.stringify({
687
+ $schema: "https://pi.dev/schemas/models.schema.json",
688
+ providers: {
689
+ custom: { baseUrl: "https://custom.example/v1", api: "openai-completions" },
690
+ openrouter: { baseUrl: gateway },
691
+ },
692
+ }),
693
+ );
694
+ // An earlier version wrote that gateway into the shared file.
695
+ await Bun.write(
696
+ join(dir, ".agent-swarm-openrouter-override.json"),
697
+ JSON.stringify({ baseUrl: gateway }),
698
+ );
699
+ const sharedBefore = readFileSync(join(dir, "models.json"), "utf-8");
700
+ await withSessionModels(dir, NAN_ENV, undefined, async (s) => {
701
+ // This session uses the default gateway: the leftover override
702
+ // recorded in the legacy marker is reverted in its copy.
703
+ const written = await readModelsJson(s.modelsPath);
704
+ expect(written.$schema).toBe("https://pi.dev/schemas/models.schema.json");
705
+ expect(written.providers.openrouter).toBeUndefined();
706
+ expect(written.providers.custom.baseUrl).toBe("https://custom.example/v1");
707
+ expect(written.providers.nan.apiKey).toBe("$NAN_API_KEY");
708
+ });
709
+ const other = "https://other-gateway.example/v1";
710
+ await withSessionModels(dir, { OPENROUTER_BASE_URL: other }, undefined, async (s) => {
711
+ expect((await readModelsJson(s.modelsPath)).providers.openrouter).toEqual({
712
+ baseUrl: other,
713
+ });
714
+ });
715
+ // The shared file is never written by the composer.
716
+ expect(readFileSync(join(dir, "models.json"), "utf-8")).toBe(sharedBefore);
717
+ }),
718
+ );
719
+
720
+ test(
721
+ "sibling sessions get their own endpoints, providers and model ids",
722
+ withTempDir("pi-custom-siblings-", async (dir) => {
723
+ const a = await writeSessionModelsJson(
724
+ dir,
725
+ { HELMCODE_API_KEY: "a", HELMCODE_BASE_URL: "http://127.0.0.1:1/v1", ...NAN_ENV },
726
+ "nan/glm5.2",
727
+ );
728
+ const b = await writeSessionModelsJson(
729
+ dir,
730
+ { HELMCODE_API_KEY: "b", HELMCODE_BASE_URL: "http://127.0.0.1:2/v1" },
731
+ "helmcode/glm5.3",
732
+ );
733
+ try {
734
+ expect(a.modelsPath).not.toBe(b.modelsPath);
735
+ const first = await readModelsJson(a.modelsPath);
736
+ const second = await readModelsJson(b.modelsPath);
737
+ expect(first.providers.helmcode.baseUrl).toBe("http://127.0.0.1:1/v1");
738
+ expect(second.providers.helmcode.baseUrl).toBe("http://127.0.0.1:2/v1");
739
+ // B has no NaN key: no nan block, even while A's session holds one.
740
+ expect(first.providers.nan.models.map((m: { id: string }) => m.id)).toContain("glm5.2");
741
+ expect(second.providers.nan).toBeUndefined();
742
+ } finally {
743
+ await a.dispose();
744
+ await b.dispose();
745
+ }
746
+ }),
747
+ );
748
+
749
+ test(
750
+ "corrupt shared models.json: the copy holds our blocks only, the shared file is untouched",
751
+ withTempDir("pi-custom-corrupt-", async (dir) => {
752
+ await Bun.write(join(dir, "models.json"), "{not json");
753
+ const warn = spyOn(console, "warn").mockImplementation(() => {});
754
+ try {
755
+ await withSessionModels(dir, NAN_ENV, undefined, async (s) => {
756
+ expect((await readModelsJson(s.modelsPath)).providers.nan.apiKey).toBe("$NAN_API_KEY");
757
+ });
758
+ } finally {
759
+ warn.mockRestore();
760
+ }
761
+ expect(readFileSync(join(dir, "models.json"), "utf-8")).toBe("{not json");
762
+ }),
763
+ );
764
+
765
+ test(
766
+ "ModelRuntime resolves the written blocks as nan/<id> and helmcode/<id>",
767
+ withTempDir("pi-custom-runtime-", async (dir) => {
768
+ await withSessionModels(dir, { ...NAN_ENV, ...HELMCODE_ENV }, undefined, async (s) => {
769
+ const runtime = await piCodingAgent.ModelRuntime.create({
770
+ authPath: join(dir, "auth.json"),
771
+ modelsPath: s.modelsPath,
772
+ });
773
+ expect(runtime.getError()).toBeUndefined();
774
+ expect(runtime.getModel("nan", "qwen3.8-flash")?.baseUrl).toBe(
775
+ OPENAI_COMPATIBLE_PROVIDERS.nan.baseUrl,
776
+ );
777
+ expect(runtime.getModel("helmcode", "glm5.3")?.contextWindow).toBe(1048576);
778
+ });
779
+ }),
780
+ );
781
+ });
782
+
783
+ describe("createPiRuntimeAuth — NaN and Helmcode", () => {
784
+ test(
785
+ "HELMCODE_API_NAN alone sets the nan runtime key; NAN_API_KEY wins when both are set",
786
+ withTempDir("pi-custom-auth-", async (dir) => {
787
+ const models = await writeSessionModelsJson(dir, { NAN_API_KEY: "x", HELMCODE_API_KEY: "y" });
788
+ // pi's own agent-dir env var points auth.json at the temp dir.
789
+ const origAgentDir = process.env.PI_CODING_AGENT_DIR;
790
+ process.env.PI_CODING_AGENT_DIR = dir;
791
+ try {
792
+ const path = models.modelsPath;
793
+ const aliasOnly = await createPiRuntimeAuth({ HELMCODE_API_NAN: "a" }, path);
794
+ await expect(aliasOnly.getAuth("nan")).resolves.toMatchObject({ auth: { apiKey: "a" } });
795
+
796
+ const both = await createPiRuntimeAuth({ HELMCODE_API_NAN: "a", NAN_API_KEY: "b" }, path);
797
+ await expect(both.getAuth("nan")).resolves.toMatchObject({ auth: { apiKey: "b" } });
798
+
799
+ const helm = await createPiRuntimeAuth({ HELMCODE_API_KEY: "h" }, path);
800
+ await expect(helm.getAuth("helmcode")).resolves.toMatchObject({ auth: { apiKey: "h" } });
801
+ } finally {
802
+ await models.dispose();
803
+ if (origAgentDir === undefined) delete process.env.PI_CODING_AGENT_DIR;
804
+ else process.env.PI_CODING_AGENT_DIR = origAgentDir;
805
+ }
806
+
807
+ // Runtime keys never land in auth.json.
808
+ const auth = (await Bun.file(join(dir, "auth.json")).exists())
809
+ ? await Bun.file(join(dir, "auth.json")).text()
810
+ : "";
811
+ expect(auth).not.toContain('"a"');
812
+ expect(auth).not.toContain('"b"');
813
+ expect(auth).not.toContain('"h"');
814
+ }),
815
+ );
816
+ });
817
+
818
+ describe("resolveModel — models.json custom providers", () => {
819
+ test(
820
+ "falls back to the runtime for nan/qwen3.8-flash and helmcode/glm5.3",
821
+ withTempDir("pi-custom-resolve-", async (dir) => {
822
+ const models = await writeSessionModelsJson(dir, { ...NAN_ENV, ...HELMCODE_ENV });
823
+ const runtime = await piCodingAgent.ModelRuntime.create({
824
+ authPath: join(dir, "auth.json"),
825
+ modelsPath: models.modelsPath,
826
+ });
827
+ await models.dispose();
828
+ const nan = resolveModel("nan/qwen3.8-flash", NAN_ENV, runtime);
829
+ expect(nan?.provider).toBe("nan");
830
+ expect(nan?.id).toBe("qwen3.8-flash");
831
+ const helm = resolveModel("helmcode/glm5.3", HELMCODE_ENV, runtime);
832
+ expect(helm?.provider).toBe("helmcode");
833
+ expect(helm?.id).toBe("glm5.3");
834
+ expect(helm?.baseUrl).toBe(OPENAI_COMPATIBLE_PROVIDERS.helmcode.baseUrl);
835
+ }),
836
+ );
837
+
838
+ test(
839
+ "a nan/<id> outside the table (nan/glm5.2) gets a bare entry and resolves",
840
+ withTempDir("pi-custom-bare-", async (dir) => {
841
+ await withSessionModels(dir, NAN_ENV, "nan/glm5.2", async (s) => {
842
+ const written = await readModelsJson(s.modelsPath);
843
+ const ids = written.providers.nan.models.map((m: { id: string }) => m.id);
844
+ expect(ids).toContain("glm5.2");
845
+ expect(ids).toContain("glm5.3");
846
+ const runtime = await piCodingAgent.ModelRuntime.create({
847
+ authPath: join(dir, "auth.json"),
848
+ modelsPath: s.modelsPath,
849
+ });
850
+ const model = resolveModel("nan/glm5.2", NAN_ENV, runtime);
851
+ expect(model?.provider).toBe("nan");
852
+ expect(model?.id).toBe("glm5.2");
853
+ expect(model?.baseUrl).toBe(OPENAI_COMPATIBLE_PROVIDERS.nan.baseUrl);
854
+ });
855
+
856
+ // The bare entry belongs to that session: the next task's model never sees it.
857
+ await withSessionModels(dir, NAN_ENV, "nan/glm5.3", async (s) => {
858
+ const next = await readModelsJson(s.modelsPath);
859
+ expect(next.providers.nan.models.map((m: { id: string }) => m.id)).not.toContain("glm5.2");
860
+ });
861
+ }),
862
+ );
863
+
864
+ test("builtin hits are unchanged when a runtime is passed", async () => {
865
+ const runtime = { getModel: () => undefined };
866
+ const model = resolveModel("anthropic/claude-sonnet-5", {}, runtime);
867
+ expect(model?.provider).toBe("anthropic");
868
+ });
869
+
870
+ test("an unknown provider returns undefined and logs a scrubbed warning", () => {
871
+ const warn = spyOn(console, "warn").mockImplementation(() => {});
872
+ try {
873
+ const runtime = { getModel: () => undefined };
874
+ expect(resolveModel("nope/model-x", {}, runtime)).toBeUndefined();
875
+ expect(warn).toHaveBeenCalledTimes(1);
876
+ expect(String(warn.mock.calls[0]?.[0])).toContain('"nope/model-x"');
877
+ expect(String(warn.mock.calls[0]?.[0])).toContain("default model");
878
+ } finally {
879
+ warn.mockRestore();
880
+ }
881
+ });
882
+
883
+ test("without a runtime, nan/<id> does not resolve (pi-ai has no builtin nan)", () => {
884
+ const warn = spyOn(console, "warn").mockImplementation(() => {});
885
+ try {
886
+ expect(resolveModel("nan/glm5.3", NAN_ENV)).toBeUndefined();
887
+ } finally {
888
+ warn.mockRestore();
889
+ }
890
+ });
891
+ });
892
+
893
+ describe("checkPiMonoCredentials — NaN and Helmcode", () => {
894
+ const opts = { homeDir: "/home/worker", fs: { existsSync: () => false } };
895
+
896
+ test("MODEL_OVERRIDE=nan/* accepts NAN_API_KEY or the HELMCODE_API_NAN alias", async () => {
897
+ const env = { MODEL_OVERRIDE: "nan/glm5.3" };
898
+ expect((await checkPiMonoCredentials(env, opts)).ready).toBe(false);
899
+ expect((await checkPiMonoCredentials({ ...env, NAN_API_KEY: "x" }, opts)).ready).toBe(true);
900
+ expect((await checkPiMonoCredentials({ ...env, HELMCODE_API_NAN: "x" }, opts)).ready).toBe(
901
+ true,
902
+ );
903
+ // An OpenRouter key does not satisfy a nan/* model.
904
+ expect((await checkPiMonoCredentials({ ...env, OPENROUTER_API_KEY: "x" }, opts)).ready).toBe(
905
+ false,
906
+ );
907
+ });
908
+
909
+ test("MODEL_OVERRIDE=helmcode/* requires HELMCODE_API_KEY", async () => {
910
+ const env = { MODEL_OVERRIDE: "helmcode/glm5.3" };
911
+ const missing = await checkPiMonoCredentials({ ...env, NAN_API_KEY: "x" }, opts);
912
+ expect(missing.ready).toBe(false);
913
+ expect(missing.missing).toContain("HELMCODE_API_KEY");
914
+ expect((await checkPiMonoCredentials({ ...env, HELMCODE_API_KEY: "x" }, opts)).ready).toBe(
915
+ true,
916
+ );
917
+ });
918
+
919
+ test("permissive case accepts a NaN-only or Helmcode-only worker", async () => {
920
+ expect((await checkPiMonoCredentials({ HELMCODE_API_NAN: "x" }, opts)).ready).toBe(true);
921
+ expect((await checkPiMonoCredentials({ HELMCODE_API_KEY: "x" }, opts)).ready).toBe(true);
922
+ });
923
+ });
924
+
545
925
  describe("Pi-mono event normalization", () => {
546
926
  test("extractPiAssistantText ignores user messages", () => {
547
927
  const text = extractPiAssistantText({
@@ -0,0 +1,170 @@
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 type { AgentSession } from "@earendil-works/pi-coding-agent";
15
+ import { fetchResolvedEnv } from "../commands/runner";
16
+ import { buildPiShellEnv, PiMonoAdapter } from "../providers/pi-mono-adapter";
17
+
18
+ /**
19
+ * pi's Bash tool must run with the env the runner resolved for this task,
20
+ * not the worker's process.env: a row admitted after boot (exportToEnv
21
+ * flipped on) must reach the shell, and a row revoked after boot (flipped off)
22
+ * must leave it, even though process.env still holds the boot value.
23
+ */
24
+
25
+ type ConfigRow = { key: string; value: string; isSecret: boolean; exportToEnv: boolean };
26
+
27
+ let server: ReturnType<typeof Bun.serve>;
28
+ let apiUrl = "";
29
+ let rows: ConfigRow[] = [];
30
+
31
+ beforeAll(() => {
32
+ server = Bun.serve({
33
+ port: 0,
34
+ async fetch(req) {
35
+ const url = new URL(req.url);
36
+ if (url.pathname === "/api/config/resolved") return Response.json({ configs: rows });
37
+ if (url.pathname !== "/mcp") return Response.json({ servers: [] });
38
+ const body = (await req.json()) as { id?: number; method?: string };
39
+ const result =
40
+ body.method === "tools/list"
41
+ ? { tools: [] }
42
+ : { protocolVersion: "2025-03-26", capabilities: {} };
43
+ return Response.json({ jsonrpc: "2.0", id: body.id ?? 1, result });
44
+ },
45
+ });
46
+ apiUrl = `http://localhost:${server.port}`;
47
+ });
48
+
49
+ afterAll(() => {
50
+ server.stop(true);
51
+ });
52
+
53
+ describe("pi Bash tool env", () => {
54
+ const envKeys = [
55
+ "PI_CODING_AGENT_DIR",
56
+ "OPENROUTER_API_KEY",
57
+ "OPENROUTER_BASE_URL",
58
+ "SHELL_REVOKED_CANARY",
59
+ "SHELL_ADMITTED_CANARY",
60
+ ] as const;
61
+ const saved: Record<string, string | undefined> = {};
62
+ let agentDir = "";
63
+ let log: ReturnType<typeof spyOn>;
64
+
65
+ beforeEach(() => {
66
+ for (const key of envKeys) saved[key] = process.env[key];
67
+ agentDir = mkdtempSync(join(tmpdir(), "pi-shell-env-"));
68
+ process.env.PI_CODING_AGENT_DIR = agentDir;
69
+ process.env.OPENROUTER_API_KEY = "example-test-key";
70
+ process.env.OPENROUTER_BASE_URL = "http://127.0.0.1:9/api/v1";
71
+ log = spyOn(console, "log").mockImplementation(() => {});
72
+ });
73
+ afterEach(() => {
74
+ log.mockRestore();
75
+ for (const key of envKeys) {
76
+ if (saved[key] === undefined) delete process.env[key];
77
+ else process.env[key] = saved[key];
78
+ }
79
+ rmSync(agentDir, { recursive: true, force: true });
80
+ });
81
+
82
+ async function sessionWithEnv(env: Record<string, string | undefined>): Promise<AgentSession> {
83
+ const session = await new PiMonoAdapter().createSession({
84
+ prompt: "hello",
85
+ systemPrompt: "test",
86
+ model: "openrouter/google/gemini-3-flash-preview",
87
+ role: "worker",
88
+ agentId: "test-agent",
89
+ taskId: "test-task",
90
+ apiUrl,
91
+ apiKey: "example-test-key",
92
+ cwd: agentDir,
93
+ logFile: join(agentDir, `session-${crypto.randomUUID()}.log`),
94
+ env,
95
+ });
96
+ return (session as unknown as { agentSession: AgentSession }).agentSession;
97
+ }
98
+
99
+ async function shell(session: AgentSession, command: string): Promise<string> {
100
+ const bash = session.getToolDefinition("bash");
101
+ if (!bash) throw new Error("bash tool missing");
102
+ const result = await bash.execute(
103
+ "call-1",
104
+ { command },
105
+ undefined,
106
+ undefined,
107
+ undefined as never,
108
+ );
109
+ return result.content
110
+ .map((part) => ("text" in part ? part.text : ""))
111
+ .join("")
112
+ .trim();
113
+ }
114
+
115
+ test("admission and revocation after boot reach the shell", async () => {
116
+ // Boot exported SHELL_REVOKED_CANARY and withheld SHELL_ADMITTED_CANARY; the
117
+ // operator then flipped both rows.
118
+ process.env.SHELL_REVOKED_CANARY = "boot-revoked";
119
+ delete process.env.SHELL_ADMITTED_CANARY;
120
+ rows = [
121
+ { key: "SHELL_REVOKED_CANARY", value: "row-revoked", isSecret: true, exportToEnv: false },
122
+ { key: "SHELL_ADMITTED_CANARY", value: "row-admitted", isSecret: true, exportToEnv: true },
123
+ ];
124
+ const { env } = await fetchResolvedEnv(apiUrl, "key", "agent-1", process.env);
125
+ const session = await sessionWithEnv(env);
126
+ try {
127
+ const out = await shell(
128
+ session,
129
+ // The shell's own environment: /proc on Linux, `env` on macOS (no procfs).
130
+ 'echo "admitted=$SHELL_ADMITTED_CANARY revoked=$SHELL_REVOKED_CANARY."; { if [ -r /proc/$$/environ ]; then tr "\\0" "\\n" < /proc/$$/environ; else env; fi; } | grep -c "^SHELL_REVOKED_CANARY=" || true',
131
+ );
132
+ expect(out).toContain("admitted=row-admitted revoked=.");
133
+ expect(out.split("\n").at(-1)).toBe("0");
134
+ } finally {
135
+ session.dispose();
136
+ }
137
+ });
138
+
139
+ test("concurrent sessions keep their own env", async () => {
140
+ const first = await sessionWithEnv({ ...process.env, SHELL_ADMITTED_CANARY: "first" });
141
+ const second = await sessionWithEnv({ ...process.env, SHELL_ADMITTED_CANARY: undefined });
142
+ try {
143
+ const [a, b] = await Promise.all([
144
+ shell(first, 'echo "v=$SHELL_ADMITTED_CANARY"'),
145
+ shell(second, 'echo "v=$SHELL_ADMITTED_CANARY"'),
146
+ ]);
147
+ expect(a).toBe("v=first");
148
+ expect(b).toBe("v=");
149
+ expect(process.env.SHELL_ADMITTED_CANARY).toBeUndefined();
150
+ } finally {
151
+ first.dispose();
152
+ second.dispose();
153
+ }
154
+ });
155
+ });
156
+
157
+ describe("buildPiShellEnv", () => {
158
+ test("keeps the session env and takes only PATH and pi's session keys from its spawn env", () => {
159
+ const env = buildPiShellEnv(
160
+ { HOME: "/home/worker", PATH: "/usr/bin", TASK_ONLY: "yes" },
161
+ { PATH: "/pi/bin:/usr/bin", PI_SESSION_ID: "s1", PI_OTHER: "leak", PROCESS_ONLY: "leak" },
162
+ );
163
+ expect(env).toEqual({
164
+ HOME: "/home/worker",
165
+ PATH: "/pi/bin:/usr/bin",
166
+ TASK_ONLY: "yes",
167
+ PI_SESSION_ID: "s1",
168
+ });
169
+ });
170
+ });