@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
package/src/server.ts CHANGED
@@ -6,6 +6,7 @@ import { startPricingRefreshLoop } from "./be/pricing-refresh";
6
6
  import { isSteeringEnabled } from "./be/steering";
7
7
  import { registerGithubTaskReactions } from "./github/task-reactions";
8
8
  import { loadGlobalConfigsIntoEnv } from "./http/core";
9
+ import { isChatSdkEnabled } from "./integrations/chat-sdk/config";
9
10
  import { resolveTemplate } from "./prompts/resolver";
10
11
  import { isRbacEnabled } from "./rbac";
11
12
  import { getSlackConfiguration } from "./slack/config";
@@ -21,6 +22,10 @@ import { registerAppSyncTool } from "./tools/app-sync";
21
22
  import { registerAppUpsertTool } from "./tools/app-upsert";
22
23
  import { registerCancelApprovalRequestTool } from "./tools/cancel-approval-request";
23
24
  import { registerCancelTaskTool } from "./tools/cancel-task";
25
+ import { registerChatOpenDmTool } from "./tools/chat-open-dm";
26
+ import { registerChatPostTool } from "./tools/chat-post";
27
+ import { registerChatReadThreadTool } from "./tools/chat-read-thread";
28
+ import { registerChatReplyTool } from "./tools/chat-reply";
24
29
  import { registerContextDiffTool } from "./tools/context-diff";
25
30
  import { registerContextHistoryTool } from "./tools/context-history";
26
31
  import { registerCreateChannelTool } from "./tools/create-channel";
@@ -54,7 +59,6 @@ import {
54
59
  } from "./tools/kv";
55
60
  // Messaging capability
56
61
  import { registerListChannelsTool } from "./tools/list-channels";
57
- import { registerListServicesTool } from "./tools/list-services";
58
62
  import { registerManageUserTool } from "./tools/manage-user";
59
63
  // MCP Servers capability
60
64
  import {
@@ -91,12 +95,15 @@ import {
91
95
  } from "./tools/prompt-templates";
92
96
  import { registerReadMessagesTool } from "./tools/read-messages";
93
97
  import { registerRegisterAgentmailInboxTool } from "./tools/register-agentmail-inbox";
98
+ import {
99
+ registerRegisterChatChannelTool,
100
+ registerUnregisterChatChannelTool,
101
+ } from "./tools/register-chat-channel";
94
102
  import {
95
103
  registerRegisterKapsoNumberTool,
96
104
  registerUnregisterKapsoNumberTool,
97
105
  } from "./tools/register-kapso-number";
98
106
  // Services capability
99
- import { registerRegisterServiceTool } from "./tools/register-service";
100
107
  // Repo management tools
101
108
  import { registerGetReposTool, registerUpdateRepoTool } from "./tools/repos";
102
109
  import { registerRequestHumanInputTool } from "./tools/request-human-input";
@@ -178,10 +185,8 @@ import {
178
185
  registerTrackerSyncStatusTool,
179
186
  registerTrackerUnlinkTool,
180
187
  } from "./tools/tracker";
181
- import { registerUnregisterServiceTool } from "./tools/unregister-service";
182
188
  // Profiles capability
183
189
  import { registerUpdateProfileTool } from "./tools/update-profile";
184
- import { registerUpdateServiceStatusTool } from "./tools/update-service-status";
185
190
  import { setPreloadedTools } from "./tools/utils";
186
191
  import {
187
192
  registerReplyWhatsappMessageTool,
@@ -216,7 +221,6 @@ export const ALL_CAPABILITIES = [
216
221
  "prompt-templates",
217
222
  "mcp",
218
223
  "profiles",
219
- "services",
220
224
  "scheduling",
221
225
  "memory",
222
226
  "workflows",
@@ -230,6 +234,7 @@ export const ALL_CAPABILITIES = [
230
234
  "repo",
231
235
  "agentmail",
232
236
  "kapso",
237
+ "chat-sdk",
233
238
  "swarm-x",
234
239
  ] as const;
235
240
 
@@ -256,12 +261,12 @@ const DEFAULT_CAPABILITIES: string = [
256
261
  //
257
262
  // Disabled by default
258
263
  //
259
- // "services",
260
264
  // "prompt-templates",
261
265
  // "messaging",
262
266
  // "swarm-x",
263
267
  // "agentmail",
264
268
  // "kapso",
269
+ // "chat-sdk", (added by getEnabledCapabilities when CHAT_SDK_ENABLED is true)
265
270
  ].join(",");
266
271
 
267
272
  // Note: unknown names are kept (they never match hasCapability); workers
@@ -280,11 +285,22 @@ export function hasCapability(cap: CAPABILITIES_T): boolean {
280
285
  return getCapabilities().has(cap);
281
286
  }
282
287
 
288
+ const KNOWN_CAPABILITIES: ReadonlySet<string> = new Set(ALL_CAPABILITIES);
289
+
283
290
  export function getEnabledCapabilities(): CAPABILITIES_T[] {
284
- const capabilities = Array.from(getCapabilities());
291
+ // Only known flags: the register response validates against ALL_CAPABILITIES,
292
+ // so a retired name still listed in CAPABILITIES (e.g. `services`) would
293
+ // otherwise turn every agent registration into a 500.
294
+ const capabilities: CAPABILITIES_T[] = Array.from(getCapabilities()).filter(
295
+ (capability) => KNOWN_CAPABILITIES.has(capability) && capability !== "chat-sdk",
296
+ );
297
+ // The Chat SDK channel follows CHAT_SDK_ENABLED, not the CAPABILITIES list.
298
+ if (isChatSdkEnabled()) capabilities.push("chat-sdk");
285
299
  // Phase 1 has no HTTP receiver. Do not advertise usable Slack tools to
286
- // workers when HTTP (or an invalid transport) was selected.
287
- return getSlackConfiguration().mode === "socket"
300
+ // workers when HTTP (or an invalid transport) was selected, or when the
301
+ // built-in Slack app is off (SLACK_DISABLE, required by the Chat SDK Slack adapter).
302
+ const slack = getSlackConfiguration();
303
+ return slack.mode === "socket" && !slack.disabled
288
304
  ? capabilities
289
305
  : capabilities.filter((capability) => capability !== "slack");
290
306
  }
@@ -344,6 +360,13 @@ function applyToolAllowlist(server: McpServer, allowlist: readonly string[]): vo
344
360
  }
345
361
  }
346
362
 
363
+ function removeTools(server: McpServer, names: readonly string[]): void {
364
+ // oxlint-disable-next-line anti-slop/no-chained-type-assertions -- the MCP SDK keeps its tool registry private.
365
+ const tools = (server as unknown as { _registeredTools: Record<string, { remove(): void }> })
366
+ ._registeredTools;
367
+ for (const name of names) tools[name]?.remove();
368
+ }
369
+
347
370
  export async function createServer(
348
371
  opts: {
349
372
  scriptsOnly?: boolean;
@@ -351,6 +374,8 @@ export async function createServer(
351
374
  preloadedTools?: readonly string[];
352
375
  /** Raw SWARM_ENABLED_TOOLS value for this session; defaults to the env. */
353
376
  enabledTools?: string;
377
+ /** Tool names removed from this session after registration (task-context filters). */
378
+ excludedTools?: readonly string[];
354
379
  } = {},
355
380
  ) {
356
381
  // Reload env
@@ -368,8 +393,12 @@ export async function createServer(
368
393
  // fullSurface to register every tool group regardless of CAPABILITIES.
369
394
  // An allowlist also registers every group, then prunes to the listed names.
370
395
  // This shadows the module-level hasCapability for the registrations below.
396
+ // `chat-sdk` follows CHAT_SDK_ENABLED (as in getEnabledCapabilities), not
397
+ // the CAPABILITIES list.
371
398
  const hasCapability = (cap: CAPABILITIES_T): boolean =>
372
- opts.fullSurface === true || toolAllowlist !== undefined || getCapabilities().has(cap);
399
+ opts.fullSurface === true ||
400
+ toolAllowlist !== undefined ||
401
+ (cap === "chat-sdk" ? isChatSdkEnabled() : getCapabilities().has(cap));
373
402
 
374
403
  // Initialize database with WAL mode
375
404
  // Uses DATABASE_PATH env var for Docker volume compatibility (WAL needs .sqlite, .sqlite-wal, .sqlite-shm on same filesystem)
@@ -677,6 +706,16 @@ export async function createServer(
677
706
  registerReplyWhatsappMessageTool(server);
678
707
  }
679
708
 
709
+ // Chat SDK capability - Telegram/Slack/Discord/Teams through the Chat SDK channel (reply, post, DM, read, channel routes)
710
+ if (hasCapability("chat-sdk")) {
711
+ registerChatReplyTool(server);
712
+ registerChatPostTool(server);
713
+ registerChatOpenDmTool(server);
714
+ registerChatReadThreadTool(server);
715
+ registerRegisterChatChannelTool(server);
716
+ registerUnregisterChatChannelTool(server);
717
+ }
718
+
680
719
  // Swarm-x capability - external command routes mirroring the `agent-swarm x ...` CLI surface
681
720
  if (hasCapability("swarm-x")) {
682
721
  registerSwarmXTool(server);
@@ -693,15 +732,10 @@ export async function createServer(
693
732
  registerDeleteChannelTool(server);
694
733
  }
695
734
 
696
- // Services capability - PM2/background service registry
697
- if (hasCapability("services")) {
698
- registerRegisterServiceTool(server);
699
- registerUnregisterServiceTool(server);
700
- registerListServicesTool(server);
701
- registerUpdateServiceStatusTool(server);
702
- }
703
-
704
735
  if (toolAllowlist) applyToolAllowlist(server, toolAllowlist);
736
+ if (opts.fullSurface !== true && opts.excludedTools?.length) {
737
+ removeTools(server, opts.excludedTools);
738
+ }
705
739
 
706
740
  return server;
707
741
  }
@@ -18,7 +18,7 @@
18
18
  * safe-but-tight bound.
19
19
  *
20
20
  * Single-pod-only. The API server (which owns the Slack socket) runs as a
21
- * single PM2 process; if that ever changes, swap this for a DB-backed table.
21
+ * single API process; if that ever changes, swap this for a DB-backed table.
22
22
  */
23
23
 
24
24
  const DEFAULT_TTL_MS = 300_000; // 5 minutes
@@ -22,8 +22,10 @@
22
22
  * the underlying context becomes irrelevant — e.g. user cancels).
23
23
  *
24
24
  * Concurrency: single-process, single-event-loop. No cross-instance locking.
25
- * Flush callbacks run sequentially per key (the buffer is cleared BEFORE the
26
- * callback fires, so re-enqueues during `onFlush` create a fresh buffer).
25
+ * The buffer is cleared BEFORE the callback fires, so re-enqueues during
26
+ * `onFlush` create a fresh buffer, and its flush can start while the earlier
27
+ * `onFlush` for the same key still runs. A caller that needs per-key order
28
+ * must serialize inside `onFlush`.
27
29
  */
28
30
 
29
31
  export type BufferFlushReason = "timer" | "manual";
@@ -14,6 +14,9 @@
14
14
  * Key schema:
15
15
  * task:slack:{channelId}:{threadTs}
16
16
  * task:agentmail:{threadId}
17
+ * task:chat-sdk:{adapter}:{encodeURIComponent(platformThreadId)}
18
+ * (Chat SDK thread ids carry `:`, so the
19
+ * part after the adapter is URI-encoded)
17
20
  * task:trackers:github:{owner}:{repo}:{issue|pr}:{number}
18
21
  * task:trackers:gitlab:{projectId}:{mr|issue}:{iid}
19
22
  * task:trackers:azure-devops:{repositoryId}:pr:{pullRequestId}
@@ -39,13 +42,20 @@
39
42
 
40
43
  const SEPARATOR = ":";
41
44
 
42
- export type ContextKeyFamily = "slack" | "agentmail" | "trackers" | "schedule" | "workflow";
45
+ export type ContextKeyFamily =
46
+ | "slack"
47
+ | "agentmail"
48
+ | "chat-sdk"
49
+ | "trackers"
50
+ | "schedule"
51
+ | "workflow";
43
52
 
44
53
  export type TrackerProvider = "github" | "gitlab" | "azure-devops" | "linear" | "jira";
45
54
 
46
55
  export type ParsedContextKey =
47
56
  | { family: "slack"; parts: { channelId: string; threadTs: string } }
48
57
  | { family: "agentmail"; parts: { threadId: string } }
58
+ | { family: "chat-sdk"; parts: { adapter: string; threadId: string } }
49
59
  | {
50
60
  family: "trackers";
51
61
  subFamily: "github";
@@ -101,6 +111,42 @@ export function slackContextKey(input: { channelId: string; threadTs: string }):
101
111
  return ["task", "slack", channelId, threadTs].join(SEPARATOR);
102
112
  }
103
113
 
114
+ /**
115
+ * Chat SDK thread ids are `<adapter>:<platform id>` and the platform id itself
116
+ * may contain `:` (Telegram `telegram:<chatId>:<topicId>`, Slack
117
+ * `slack:<channel>:<ts>`). The adapter stays a plain key part; the rest is
118
+ * URI-encoded so the key keeps a fixed part count. `parseContextKey` returns
119
+ * the original thread id in `parts.threadId`.
120
+ */
121
+ export function chatSdkContextKey(threadId: string): string {
122
+ const raw = typeof threadId === "string" ? threadId : "";
123
+ const separatorIndex = raw.indexOf(SEPARATOR);
124
+ if (separatorIndex <= 0 || separatorIndex === raw.length - 1) {
125
+ throw new Error(
126
+ `context-key: chat-sdk thread id must be "<adapter>:<id>" (got ${JSON.stringify(threadId)})`,
127
+ );
128
+ }
129
+ const adapter = assertSafePart(raw.slice(0, separatorIndex), "adapter");
130
+ const rest = encodeURIComponent(raw.slice(separatorIndex + 1));
131
+ return ["task", "chat-sdk", adapter, rest].join(SEPARATOR);
132
+ }
133
+
134
+ /**
135
+ * The Chat SDK thread a contextKey points at, or null when the key is missing,
136
+ * malformed, or of another family. Never throws.
137
+ */
138
+ export function chatThreadOfContextKey(
139
+ key: string | null | undefined,
140
+ ): { adapter: string; threadId: string } | null {
141
+ if (!key) return null;
142
+ try {
143
+ const parsed = parseContextKey(key);
144
+ return parsed.family === "chat-sdk" ? parsed.parts : null;
145
+ } catch {
146
+ return null;
147
+ }
148
+ }
149
+
104
150
  export function agentmailContextKey(input: { threadId: string }): string {
105
151
  const threadId = assertSafePart(input.threadId, "threadId");
106
152
  return ["task", "agentmail", threadId].join(SEPARATOR);
@@ -255,6 +301,19 @@ export function parseContextKey(key: string): ParsedContextKey {
255
301
  }
256
302
  return { family: "agentmail", parts: { threadId: parts[2] as string } };
257
303
  }
304
+ case "chat-sdk": {
305
+ if (parts.length !== 4 || !parts[2] || !parts[3]) {
306
+ throw new Error(`context-key: malformed chat-sdk key: ${JSON.stringify(key)}`);
307
+ }
308
+ const adapter = parts[2];
309
+ let rest: string;
310
+ try {
311
+ rest = decodeURIComponent(parts[3]);
312
+ } catch {
313
+ throw new Error(`context-key: malformed chat-sdk key: ${JSON.stringify(key)}`);
314
+ }
315
+ return { family: "chat-sdk", parts: { adapter, threadId: `${adapter}:${rest}` } };
316
+ }
258
317
  case "schedule": {
259
318
  if (parts.length !== 3) {
260
319
  throw new Error(`context-key: malformed schedule key: ${JSON.stringify(key)}`);
@@ -0,0 +1,67 @@
1
+ import type { AgentTask } from "../types";
2
+
3
+ /** Missing fields read as unset, so a bare `{ model }` is its own pin. */
4
+ export type TaskModelPinFields = {
5
+ [K in
6
+ | "model"
7
+ | "modelSource"
8
+ | "modelAlias"
9
+ | "modelTier"
10
+ | "resolvedModel"
11
+ | "claudeSessionId"]?: AgentTask[K] | null;
12
+ };
13
+
14
+ /**
15
+ * The task's own `model` pin, or undefined when the task has none.
16
+ *
17
+ * `agent_tasks.model` is not always the creator's pin: the worker reports the
18
+ * model it actually ran on `PUT /api/tasks/{id}/session`, which overwrites the
19
+ * column. So the pin is read from the claim-time record instead:
20
+ * - `modelSource = "model"`: the task's own `model` won at claim. A `latest:`
21
+ * alias survives only in `modelAlias` (the session report replaced `model`
22
+ * with the concrete id), so the alias is the pin.
23
+ * - `modelSource = "fallback:cli-unsupported:model"`: the task's own
24
+ * `latest:` alias won, then the claiming CLI rejected what it resolved to.
25
+ * The alias in `modelAlias` is the pin, with or without a `modelTier`.
26
+ * - `modelSource = "fallback:cli-unsupported"` (a tier layer's alias fell
27
+ * back) is not a pin. Rows written before the `:model` variant existed are
28
+ * ambiguous; one with a `modelAlias` and no `modelTier` can only have come
29
+ * from a `model` alias, so its alias is the pin. With a `modelTier` it stays
30
+ * ambiguous and only the tier counts.
31
+ * - no claim record and no session: `model` is still the creator's value.
32
+ * Anything else (a tier layer won, or `model` is just the session report) is
33
+ * not a pin: re-running the row must resolve the model again, from the agent's
34
+ * current config, instead of replaying the model of the previous run.
35
+ */
36
+ export function taskModelPin(task: TaskModelPinFields): string | undefined {
37
+ if (task.modelSource === "model") return task.modelAlias ?? task.model ?? undefined;
38
+ if (task.modelSource === "fallback:cli-unsupported:model" && task.modelAlias) {
39
+ return task.modelAlias;
40
+ }
41
+ if (task.modelSource === "fallback:cli-unsupported" && task.modelAlias && !task.modelTier) {
42
+ return task.modelAlias;
43
+ }
44
+ if (!task.modelSource && !task.resolvedModel && !task.claudeSessionId) {
45
+ return task.model ?? undefined;
46
+ }
47
+ return undefined;
48
+ }
49
+
50
+ /**
51
+ * The model fields a re-run of the SAME task row (paused-task resume) passes to
52
+ * the spawn: the pin and the claim record that resolved it, or nothing, so the
53
+ * worker resolves the model from the agent's current config.
54
+ */
55
+ export function resumedTaskModelSelection(task: TaskModelPinFields): {
56
+ model: string | undefined;
57
+ resolvedModel: string | undefined;
58
+ modelSource: string | undefined;
59
+ } {
60
+ const pin = taskModelPin(task);
61
+ if (!pin) return { model: undefined, resolvedModel: undefined, modelSource: undefined };
62
+ return {
63
+ model: pin,
64
+ resolvedModel: task.resolvedModel ?? undefined,
65
+ modelSource: task.modelSource ?? undefined,
66
+ };
67
+ }
@@ -32,6 +32,7 @@ import {
32
32
  } from "../types";
33
33
  import { isEnvFlagEnabled } from "../utils/env-flag";
34
34
  import { taskAttachmentDisplayUrl } from "../utils/task-attachment-links";
35
+ import { taskModelPin } from "./model-pin";
35
36
  import { createTaskWithSiblingAwareness } from "./sibling-awareness";
36
37
  // Side-effect import: registers task lifecycle templates in the in-memory registry.
37
38
  import "../tools/templates";
@@ -165,23 +166,8 @@ export function getNextResumeGeneration(parent: Pick<AgentTask, "tags">): number
165
166
  * reboot retry) must carry: the parent's portable `modelTier`, plus its
166
167
  * concrete `model` only when that `model` was a task-level pin.
167
168
  *
168
- * `agent_tasks.model` is not always the creator's pin: the worker reports the
169
- * model it actually ran on `POST /api/tasks/{id}/session`, which overwrites the
170
- * column. So the pin is read from the claim-time record instead:
171
- * - `modelSource = "model"`: the task's own `model` won at claim. A `latest:`
172
- * alias survives only in `modelAlias` (the session report replaced `model`
173
- * with the concrete id), so the alias is what travels.
174
- * - `modelSource = "fallback:cli-unsupported:model"`: the task's own
175
- * `latest:` alias won, then the claiming CLI rejected what it resolved to.
176
- * The alias in `modelAlias` travels, with or without a `modelTier`.
177
- * - `modelSource = "fallback:cli-unsupported"` (a tier layer's alias fell
178
- * back) is not a pin. Rows written before the `:model` variant existed are
179
- * ambiguous; one with a `modelAlias` and no `modelTier` can only have come
180
- * from a `model` alias, so its alias travels. With a `modelTier` it stays
181
- * ambiguous and only the tier travels.
182
- * - no claim record and no session: `model` is still the creator's value.
183
- * Anything else (a tier layer won, or `model` is just the session report) is
184
- * not a pin and is left off, so the resume resolves on its claiming worker.
169
+ * The pin rules live in `taskModelPin` (`./model-pin`); anything that is not
170
+ * a pin is left off, so the resume resolves on its claiming worker.
185
171
  *
186
172
  * `resolvedModel`, `modelSource` and `modelAlias` themselves are NOT copied:
187
173
  * claim time writes all three together for the task being claimed, and the
@@ -196,20 +182,7 @@ export function inheritedModelSelection(
196
182
  "model" | "modelTier" | "modelSource" | "modelAlias" | "resolvedModel" | "claudeSessionId"
197
183
  >,
198
184
  ): Pick<CreateTaskOptions, "model" | "modelTier"> {
199
- let model: string | undefined;
200
- if (parent.modelSource === "model") {
201
- model = parent.modelAlias ?? parent.model;
202
- } else if (parent.modelSource === "fallback:cli-unsupported:model" && parent.modelAlias) {
203
- model = parent.modelAlias;
204
- } else if (
205
- parent.modelSource === "fallback:cli-unsupported" &&
206
- parent.modelAlias &&
207
- !parent.modelTier
208
- ) {
209
- model = parent.modelAlias;
210
- } else if (!parent.modelSource && !parent.resolvedModel && !parent.claudeSessionId) {
211
- model = parent.model;
212
- }
185
+ const model = taskModelPin(parent);
213
186
  return {
214
187
  ...(model ? { model } : {}),
215
188
  ...(parent.modelTier ? { modelTier: parent.modelTier } : {}),
@@ -667,6 +667,48 @@ new AgentSideConnection((connection) => new FakeAgent(connection), stream);
667
667
  expect(resolveAcpTarget(config).env(config).OPENCODE_CONFIG_CONTENT).toBe(content);
668
668
  });
669
669
 
670
+ test("OpenCode preset adds the helmcode block with an {env:} key reference, never the key", () => {
671
+ const sentinel = "sk-helmcode-acp-sentinel-0123456789";
672
+ const config = baseConfig({
673
+ model: "helmcode/glm5.3",
674
+ env: {
675
+ ACP_TARGET: "opencode",
676
+ HELMCODE_API_KEY: sentinel,
677
+ OPENCODE_CONFIG_CONTENT: JSON.stringify({
678
+ provider: { helmcode: { options: { timeout: 1000 } } },
679
+ }),
680
+ },
681
+ });
682
+ const env = resolveAcpTarget(config).env(config);
683
+ expect(env.HELMCODE_API_KEY).toBe(sentinel);
684
+ expect(env.OPENCODE_CONFIG_CONTENT).not.toContain(sentinel);
685
+ const helmcode = JSON.parse(env.OPENCODE_CONFIG_CONTENT!).provider.helmcode;
686
+ expect(helmcode).toMatchObject({
687
+ npm: "@ai-sdk/openai-compatible",
688
+ name: "Helmcode",
689
+ env: [],
690
+ options: {
691
+ baseURL: "https://api.helmcode.com/v1",
692
+ apiKey: "{env:HELMCODE_API_KEY}",
693
+ timeout: 1000,
694
+ },
695
+ });
696
+ expect(helmcode.models["glm5.3"]).toMatchObject({
697
+ tool_call: true,
698
+ limit: { context: 1048576, output: 32768 },
699
+ });
700
+ });
701
+
702
+ test("OpenCode preset adds no custom block for a nan model", () => {
703
+ const config = baseConfig({
704
+ model: "nan/glm5.3-flash",
705
+ env: { ACP_TARGET: "opencode", NAN_API_KEY: "example-test-key" },
706
+ });
707
+ const env = resolveAcpTarget(config).env(config);
708
+ expect(env.NAN_API_KEY).toBe("example-test-key");
709
+ expect(JSON.parse(env.OPENCODE_CONFIG_CONTENT!).provider?.helmcode).toBeUndefined();
710
+ });
711
+
670
712
  test("OpenCode preset injects gateway even without a model fallback", () => {
671
713
  const config = baseConfig({
672
714
  model: "",
@@ -28,7 +28,6 @@ let sut: Sut;
28
28
  let api: ApiClient;
29
29
  let fixtureDir: string;
30
30
  let fixturePath: string;
31
- let pm2StubPath: string;
32
31
 
33
32
  /** A `claude` that initializes, accepts the prompt, and then never answers. */
34
33
  const hangingFixtureSource = `#!/usr/bin/env bun
@@ -87,12 +86,8 @@ describe("API drain with a real worker", () => {
87
86
  beforeAll(async () => {
88
87
  fixtureDir = await mkdtemp("/tmp/api-drain-worker-");
89
88
  fixturePath = join(fixtureDir, "claude-fixture");
90
- pm2StubPath = join(fixtureDir, "bin");
91
- await Bun.$`mkdir -p ${pm2StubPath}`.quiet();
92
89
  await Bun.write(fixturePath, hangingFixtureSource);
93
- await Bun.write(join(pm2StubPath, "pm2"), "#!/bin/sh\nexit 0\n");
94
90
  await Bun.$`chmod 755 ${fixturePath}`.quiet();
95
- await Bun.$`chmod 755 ${join(pm2StubPath, "pm2")}`.quiet();
96
91
  sut = await startSut(
97
92
  false,
98
93
  {},
@@ -138,7 +133,7 @@ describe("API drain with a real worker", () => {
138
133
  cwd: repoRoot,
139
134
  env: {
140
135
  ...minimalEnv(),
141
- PATH: `${pm2StubPath}:${process.env.PATH ?? "/usr/bin:/bin"}`,
136
+ PATH: process.env.PATH ?? "/usr/bin:/bin",
142
137
  HOME: home,
143
138
  AGENT_SWARM_API_KEY: sut.apiKey,
144
139
  API_KEY: sut.apiKey,