@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
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * Multi-file (bundled) seeded skills.
3
3
  *
4
- * Regression coverage for the collision that shipped `artifacts` twice — once
4
+ * Regression coverage for the collision that shipped the since-retired
5
+ * `artifacts` skill twice — once
5
6
  * baked into the worker image with an `examples/` directory, once seeded from
6
7
  * `templates/skills/` as a simple skill. The DB copy won at runtime, dropped a
7
8
  * `.swarm-managed` marker, and the next reconcile pass deleted the baked
@@ -58,17 +59,9 @@ describe("seeded skills with bundled files", () => {
58
59
  await removeDbFiles(TEST_DB_PATH);
59
60
  });
60
61
 
61
- test("embedded manifest exposes bundled files for artifacts and pages", async () => {
62
+ test("embedded manifest exposes bundled files for pages", async () => {
62
63
  const skills = await loadSeedSkills();
63
64
 
64
- const artifacts = skills.find((skill) => skill.name === "artifacts");
65
- expect(artifacts?.files.map((file) => file.path).sort()).toEqual([
66
- "examples/approval-flow.ts",
67
- "examples/hono-dashboard.ts",
68
- "examples/multi-artifact.ts",
69
- "examples/static-report.sh",
70
- ]);
71
-
72
65
  const pages = skills.find((skill) => skill.name === "pages");
73
66
  expect(pages?.files.map((file) => file.path).sort()).toEqual([
74
67
  "examples/annotated-pr.html",
@@ -84,7 +77,7 @@ describe("seeded skills with bundled files", () => {
84
77
  const embedded = await loadSeedSkills();
85
78
  const fromDisk = await loadSeedSkills(TEMPLATES_DIR);
86
79
 
87
- for (const name of ["artifacts", "pages"]) {
80
+ for (const name of ["pages", "script-builder"]) {
88
81
  const a = embedded.find((skill) => skill.name === name);
89
82
  const b = fromDisk.find((skill) => skill.name === name);
90
83
  expect(a?.files).toEqual(b?.files ?? []);
@@ -107,18 +100,17 @@ describe("seeded skills with bundled files", () => {
107
100
  });
108
101
 
109
102
  test("seeding writes skill_files rows and marks the skill complex", async () => {
110
- const artifacts = await getSkillByName("artifacts", "swarm");
111
- expect(artifacts).toBeTruthy();
112
- if (!artifacts) return;
103
+ const pages = await getSkillByName("pages", "swarm");
104
+ expect(pages).toBeTruthy();
105
+ if (!pages) return;
113
106
 
114
- expect(artifacts.isComplex).toBe(true);
107
+ expect(pages.isComplex).toBe(true);
115
108
 
116
- const files = await getSkillFiles(artifacts.id);
109
+ const files = await getSkillFiles(pages.id);
117
110
  expect(files.map((file) => file.path).sort()).toEqual([
118
- "examples/approval-flow.ts",
119
- "examples/hono-dashboard.ts",
120
- "examples/multi-artifact.ts",
121
- "examples/static-report.sh",
111
+ "examples/annotated-pr.html",
112
+ "examples/multiplayer-board.html",
113
+ "examples/report-page.html",
122
114
  ]);
123
115
  expect(files.every((file) => file.content.length > 0)).toBe(true);
124
116
  });
@@ -131,24 +123,24 @@ describe("seeded skills with bundled files", () => {
131
123
  });
132
124
 
133
125
  test("a bundled-file edit registers as drift and is preserved", async () => {
134
- const item = (await skillsSeeder.items()).find((candidate) => candidate.key === "artifacts");
126
+ const item = (await skillsSeeder.items()).find((candidate) => candidate.key === "pages");
135
127
  expect(item).toBeDefined();
136
128
  if (!item) return;
137
129
 
138
130
  // Freshly seeded and untouched: upstream hashes to exactly the source.
139
131
  expect(await skillsSeeder.upstreamHash(item)).toBe(item.contentHash);
140
132
 
141
- const artifacts = await getSkillByName("artifacts", "swarm");
142
- if (!artifacts) throw new Error("artifacts skill missing");
143
- const original = (await getSkillFiles(artifacts.id)).find(
144
- (file) => file.path === "examples/hono-dashboard.ts",
133
+ const pages = await getSkillByName("pages", "swarm");
134
+ if (!pages) throw new Error("pages skill missing");
135
+ const original = (await getSkillFiles(pages.id)).find(
136
+ (file) => file.path === "examples/report-page.html",
145
137
  );
146
138
  if (!original) throw new Error("bundled example missing");
147
139
 
148
140
  // Simulate a user editing a bundled file through the skills API.
149
- await upsertSkillFile(artifacts.id, {
141
+ await upsertSkillFile(pages.id, {
150
142
  path: original.path,
151
- content: `${original.content}\n// edited by a user`,
143
+ content: `${original.content}\n<!-- edited by a user -->`,
152
144
  });
153
145
 
154
146
  // The edit must move the upstream hash — otherwise the seeder would think
@@ -159,11 +151,11 @@ describe("seeded skills with bundled files", () => {
159
151
  expect(result.failed).toEqual([]);
160
152
  expect(result.skippedUserModified).toBeGreaterThan(0);
161
153
 
162
- const after = (await getSkillFiles(artifacts.id)).find((file) => file.path === original.path);
163
- expect(after?.content).toContain("// edited by a user");
154
+ const after = (await getSkillFiles(pages.id)).find((file) => file.path === original.path);
155
+ expect(after?.content).toContain("<!-- edited by a user -->");
164
156
 
165
157
  // Restore so later tests see a pristine skill.
166
- await upsertSkillFile(artifacts.id, { path: original.path, content: original.content });
158
+ await upsertSkillFile(pages.id, { path: original.path, content: original.content });
167
159
  });
168
160
 
169
161
  test("a userInvocable flip registers as drift and is preserved", async () => {
@@ -195,24 +187,24 @@ describe("seeded skills with bundled files", () => {
195
187
  // format (no bundled-file section). The new seeder must still recognise it
196
188
  // as pristine and update it — not misread it as user-modified and skip it
197
189
  // forever, which would mean the merged content never reaches production.
198
- const artifacts = await getSkillByName("artifacts", "swarm");
199
- if (!artifacts) throw new Error("artifacts skill missing");
190
+ const pages = await getSkillByName("pages", "swarm");
191
+ if (!pages) throw new Error("pages skill missing");
200
192
 
201
- const legacyContent = "---\nname: artifacts\ndescription: old\n---\n\nold body\n";
202
- await updateSkill(artifacts.id, { content: legacyContent, isComplex: false });
203
- for (const file of await getSkillFiles(artifacts.id)) {
204
- await deleteSkillFile(artifacts.id, file.path);
193
+ const legacyContent = "---\nname: pages\ndescription: old\n---\n\nold body\n";
194
+ await updateSkill(pages.id, { content: legacyContent, isComplex: false });
195
+ for (const file of await getSkillFiles(pages.id)) {
196
+ await deleteSkillFile(pages.id, file.path);
205
197
  }
206
198
 
207
199
  // Old-format hash: base only, no file section. The flag mirrors the seeded
208
- // config so the test does not pin artifacts' systemDefault value.
200
+ // config so the test does not pin pages' systemDefault value.
209
201
  const legacyHash = computeContentHash(
210
- `${legacyContent}\n\n# seed:systemDefault=${artifacts.systemDefault ? 1 : 0}\n`,
202
+ `${legacyContent}\n\n# seed:systemDefault=${pages.systemDefault ? 1 : 0}\n`,
211
203
  );
212
- await recordSeedState("skill", "artifacts", legacyHash);
204
+ await recordSeedState("skill", "pages", legacyHash);
213
205
 
214
- const item = (await skillsSeeder.items()).find((candidate) => candidate.key === "artifacts");
215
- if (!item) throw new Error("artifacts seed item missing");
206
+ const item = (await skillsSeeder.items()).find((candidate) => candidate.key === "pages");
207
+ if (!item) throw new Error("pages seed item missing");
216
208
 
217
209
  // Pristine: upstream (file-less) still hashes in the old format.
218
210
  expect(await skillsSeeder.upstreamHash(item)).toBe(legacyHash);
@@ -221,10 +213,10 @@ describe("seeded skills with bundled files", () => {
221
213
  expect(result.failed).toEqual([]);
222
214
  expect(result.skippedUserModified).toBe(0);
223
215
 
224
- const upgraded = await getSkillByName("artifacts", "swarm");
216
+ const upgraded = await getSkillByName("pages", "swarm");
225
217
  expect(upgraded?.isComplex).toBe(true);
226
218
  expect(upgraded?.content).not.toBe(legacyContent);
227
- expect((await getSkillFiles(upgraded?.id ?? "")).length).toBe(4);
219
+ expect((await getSkillFiles(upgraded?.id ?? "")).length).toBe(3);
228
220
 
229
221
  // And it settles: the next run is a clean no-op.
230
222
  const second = await runSeeder(skillsSeeder, { quiet: true });
@@ -273,18 +265,18 @@ describe("seeded skills with bundled files", () => {
273
265
  });
274
266
 
275
267
  test("bundled files survive repeated filesystem syncs (the reconcile bug)", async () => {
276
- const artifacts = await getSkillByName("artifacts", "swarm");
277
- expect(artifacts).toBeTruthy();
278
- if (!artifacts) return;
268
+ const pages = await getSkillByName("pages", "swarm");
269
+ expect(pages).toBeTruthy();
270
+ if (!pages) return;
279
271
 
280
272
  const entry: SkillFsEntry = {
281
- id: artifacts.id,
282
- name: artifacts.name,
283
- content: artifacts.content,
284
- isComplex: artifacts.isComplex,
273
+ id: pages.id,
274
+ name: pages.name,
275
+ content: pages.content,
276
+ isComplex: pages.isComplex,
285
277
  isEnabled: true,
286
278
  isActive: true,
287
- files: (await getSkillFiles(artifacts.id)).map((file) => ({
279
+ files: (await getSkillFiles(pages.id)).map((file) => ({
288
280
  path: file.path,
289
281
  content: file.content,
290
282
  isBinary: file.isBinary,
@@ -297,9 +289,9 @@ describe("seeded skills with bundled files", () => {
297
289
  FAKE_HOME,
298
290
  ".claude",
299
291
  "skills",
300
- "artifacts",
292
+ "pages",
301
293
  "examples",
302
- "hono-dashboard.ts",
294
+ "report-page.html",
303
295
  );
304
296
  expect(existsSync(examplePath)).toBe(true);
305
297
 
@@ -0,0 +1,182 @@
1
+ /**
2
+ * Retiring a seeded skill whose template was removed.
3
+ *
4
+ * Dropping `templates/skills/<name>/` alone leaves the last seeded row live on
5
+ * every existing install. `retireSeedSkills` deletes it, but only while the
6
+ * seeder still owns it: a recorded `seed_state` hash that the live copy
7
+ * matches and every metadata column still holds what the seeder wrote.
8
+ * Unrecorded and user-edited skills stay, including metadata-only edits.
9
+ */
10
+
11
+ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
12
+ import { unlink } from "node:fs/promises";
13
+ import {
14
+ closeDb,
15
+ computeContentHash,
16
+ createAgent,
17
+ createSkill,
18
+ getAgentSkills,
19
+ getSkillByName,
20
+ getSkillFiles,
21
+ initDb,
22
+ installSkill,
23
+ updateSkill,
24
+ upsertSkillFiles,
25
+ } from "../be/db";
26
+ import { getSeedState, recordSeedState, runSeeder } from "../be/seed";
27
+ import { RETIRED_SEED_SKILLS, retireSeedSkills, skillsSeeder } from "../be/seed-skills";
28
+
29
+ const TEST_DB_PATH = `./test-seed-skills-retire-${process.pid}.sqlite`;
30
+
31
+ async function removeDbFiles(path: string): Promise<void> {
32
+ for (const suffix of ["", "-wal", "-shm"]) {
33
+ await unlink(path + suffix).catch(() => {});
34
+ }
35
+ }
36
+
37
+ const BUNDLED_FILE = { path: "examples/serve.ts", content: "pm2 start serve.ts\n" };
38
+
39
+ /**
40
+ * Seed a swarm skill the way the seeder does (frontmatter description equal to
41
+ * the description column, isComplex set from the file count) and record the
42
+ * hash it would have written.
43
+ */
44
+ async function seedLegacySkill(name: string, withFile = false): Promise<string> {
45
+ const content = `---\nname: ${name}\ndescription: old\n---\n\nRun pm2 start.\n`;
46
+ const skill = await createSkill({
47
+ name,
48
+ description: "old",
49
+ content,
50
+ type: "personal",
51
+ scope: "swarm",
52
+ ownerAgentId: undefined,
53
+ systemDefault: false,
54
+ isComplex: withFile,
55
+ });
56
+ let hashed = `${content}\n\n# seed:systemDefault=0\n`;
57
+ if (withFile) {
58
+ await upsertSkillFiles(skill.id, [BUNDLED_FILE]);
59
+ hashed += `# seed:files\n${BUNDLED_FILE.path}\n${BUNDLED_FILE.content}\n`;
60
+ }
61
+ await recordSeedState("skill", name, computeContentHash(hashed));
62
+ return skill.id;
63
+ }
64
+
65
+ describe("retiring seeded skills", () => {
66
+ let agentId: string;
67
+
68
+ beforeAll(async () => {
69
+ await removeDbFiles(TEST_DB_PATH);
70
+ initDb(TEST_DB_PATH);
71
+ agentId = (
72
+ await createAgent({ name: "retire-test", isLead: false, status: "idle", capabilities: [] })
73
+ ).id;
74
+ });
75
+
76
+ afterAll(async () => {
77
+ closeDb();
78
+ await removeDbFiles(TEST_DB_PATH);
79
+ });
80
+
81
+ test("artifacts is retired and no longer seeded", async () => {
82
+ expect(RETIRED_SEED_SKILLS).toContain("artifacts");
83
+ const keys = (await skillsSeeder.items()).map((item) => item.key);
84
+ expect(keys).not.toContain("artifacts");
85
+ });
86
+
87
+ test("the seeder run deletes a pristine retired skill and its seed state", async () => {
88
+ const id = await seedLegacySkill("artifacts", true);
89
+ await installSkill(agentId, id);
90
+
91
+ const result = await runSeeder(skillsSeeder, { quiet: true });
92
+ expect(result.failed).toEqual([]);
93
+ expect(result.retired).toBe(1);
94
+ expect(await getSkillByName("artifacts", "swarm")).toBeNull();
95
+ expect(await getSkillFiles(id)).toEqual([]);
96
+ expect((await getAgentSkills(agentId, false)).map((s) => s.id)).not.toContain(id);
97
+ expect(await getSeedState("skill", "artifacts")).toBeNull();
98
+
99
+ // Settled: nothing left to retire on the next boot.
100
+ expect((await runSeeder(skillsSeeder, { quiet: true })).retired).toBe(0);
101
+ });
102
+
103
+ test("the seeder run keeps a retired skill with metadata-only edits, with its installs and files", async () => {
104
+ const id = await seedLegacySkill("artifacts", true);
105
+ await installSkill(agentId, id);
106
+ await updateSkill(id, {
107
+ allowedTools: "Read",
108
+ model: "user-selected-model",
109
+ description: "my edited description",
110
+ });
111
+
112
+ const result = await runSeeder(skillsSeeder, { quiet: true });
113
+ expect(result.failed).toEqual([]);
114
+ expect(result.retired).toBe(0);
115
+
116
+ const kept = await getSkillByName("artifacts", "swarm");
117
+ expect(kept?.id).toBe(id);
118
+ expect(kept?.model).toBe("user-selected-model");
119
+ expect((await getSkillFiles(id)).map((file) => file.path)).toEqual([BUNDLED_FILE.path]);
120
+ expect((await getAgentSkills(agentId, false)).map((s) => s.id)).toContain(id);
121
+ });
122
+
123
+ // Every column updateSkill can write outside the seed hash. Each one alone
124
+ // marks the row as user-edited.
125
+ const METADATA_EDITS: [string, Parameters<typeof updateSkill>[1]][] = [
126
+ ["description", { description: "mine" }],
127
+ ["allowedTools", { allowedTools: "Read" }],
128
+ ["model", { model: "opus" }],
129
+ ["effort", { effort: "high" }],
130
+ ["context", { context: "fork" }],
131
+ ["agent", { agent: "reviewer" }],
132
+ ["disableModelInvocation", { disableModelInvocation: true }],
133
+ ["isEnabled", { isEnabled: false }],
134
+ ["isComplex", { isComplex: true }],
135
+ ["sourceUrl", { sourceUrl: "https://example.com/SKILL.md" }],
136
+ ["sourceRepo", { sourceRepo: "me/skills" }],
137
+ ["sourcePath", { sourcePath: "skills/artifacts" }],
138
+ ["sourceBranch", { sourceBranch: "dev" }],
139
+ ["sourceHash", { sourceHash: "abc123" }],
140
+ ["lastFetchedAt", { lastFetchedAt: "2026-01-01T00:00:00.000Z" }],
141
+ ];
142
+
143
+ test.each(METADATA_EDITS)("a retired skill with an edited %s is kept", async (field, edit) => {
144
+ const name = `zz-retired-meta-${field.toLowerCase()}`;
145
+ const id = await seedLegacySkill(name);
146
+ await updateSkill(id, edit);
147
+
148
+ expect(await retireSeedSkills([name])).toEqual([]);
149
+ expect(await getSkillByName(name, "swarm")).not.toBeNull();
150
+ });
151
+
152
+ test("a pristine retired skill touched by a no-op update is still retired", async () => {
153
+ const id = await seedLegacySkill("zz-retired-noop");
154
+ await updateSkill(id, {});
155
+
156
+ expect(await retireSeedSkills(["zz-retired-noop"])).toEqual(["zz-retired-noop"]);
157
+ expect(await getSkillByName("zz-retired-noop", "swarm")).toBeNull();
158
+ });
159
+
160
+ test("a retired skill edited since it was seeded is kept", async () => {
161
+ const id = await seedLegacySkill("zz-retired-edited");
162
+ await updateSkill(id, { content: "---\nname: zz-retired-edited\n---\n\nmine\n" });
163
+
164
+ expect(await retireSeedSkills(["zz-retired-edited"])).toEqual([]);
165
+ expect(await getSkillByName("zz-retired-edited", "swarm")).not.toBeNull();
166
+ });
167
+
168
+ test("a same-name skill the seeder never recorded is kept", async () => {
169
+ await createSkill({
170
+ name: "zz-retired-unrecorded",
171
+ description: "user authored",
172
+ content: "---\nname: zz-retired-unrecorded\n---\n\nmine\n",
173
+ type: "personal",
174
+ scope: "swarm",
175
+ ownerAgentId: undefined,
176
+ systemDefault: false,
177
+ });
178
+
179
+ expect(await retireSeedSkills(["zz-retired-unrecorded"])).toEqual([]);
180
+ expect(await getSkillByName("zz-retired-unrecorded", "swarm")).not.toBeNull();
181
+ });
182
+ });
@@ -2,6 +2,7 @@
2
2
  * A task's requester drives attribution, cost reporting and admission. A non-lead agent may omit
3
3
  * `requestedByUserId` or pass the requester of its own current task. Only a lead, the operator or
4
4
  * a user may name any other registered user. Covers the send-task tool and POST /api/tasks.
5
+ * POST /api/tasks also refuses an agent a foreign parent or a chat thread it does not own.
5
6
  */
6
7
  import { afterAll, beforeAll, describe, expect, test } from "bun:test";
7
8
  import { unlink } from "node:fs/promises";
@@ -18,6 +19,7 @@ import {
18
19
  initDb,
19
20
  } from "../be/db";
20
21
  import { handleTasks } from "../http/tasks";
22
+ import { chatSdkContextKey } from "../tasks/context-key";
21
23
  import { registerSendTaskTool } from "../tools/send-task";
22
24
  import { type HttpRequestAuth, setRequestAuth } from "../utils/request-auth-context";
23
25
 
@@ -258,3 +260,59 @@ describe("POST /api/tasks with an X-Agent-ID on the swarm key", () => {
258
260
  expect(lead.body.requestedByUserId).toBe(userBId);
259
261
  });
260
262
  });
263
+
264
+ describe("POST /api/tasks parent and chat-thread binding for an agent caller", () => {
265
+ test("a worker cannot attach a child to a task it neither owns nor created", async () => {
266
+ const foreign = await createTaskExtended("lead chat task (http)", {
267
+ agentId: LEAD_ID,
268
+ source: "chat",
269
+ contextKey: chatSdkContextKey("telegram:552222"),
270
+ });
271
+ const before = await taskCount();
272
+ const result = await postTask(
273
+ { agentId: WORKER_ID },
274
+ { parentTaskId: foreign.id, agentId: WORKER_ID, routingReason: "skill" },
275
+ );
276
+ expect(result.status).toBe(403);
277
+ expect(await taskCount()).toBe(before);
278
+ });
279
+
280
+ test("a worker cannot bind a new task to a chat thread by contextKey", async () => {
281
+ const before = await taskCount();
282
+ const result = await postTask(
283
+ { agentId: WORKER_ID },
284
+ {
285
+ contextKey: chatSdkContextKey("telegram:552222"),
286
+ agentId: WORKER_ID,
287
+ routingReason: "skill",
288
+ },
289
+ );
290
+ expect(result.status).toBe(403);
291
+ expect(await taskCount()).toBe(before);
292
+ });
293
+
294
+ test("a worker may attach under its own task, and the operator and a lead keep access", async () => {
295
+ const own = await createTaskExtended("worker chat task (http)", {
296
+ agentId: WORKER_ID,
297
+ source: "chat",
298
+ contextKey: chatSdkContextKey("telegram:551111"),
299
+ });
300
+ const child = await postTask(
301
+ { agentId: WORKER_ID },
302
+ { parentTaskId: own.id, agentId: OTHER_WORKER_ID, routingReason: "skill" },
303
+ );
304
+ expect(child.status).toBe(201);
305
+
306
+ const foreign = await createTaskExtended("lead chat task (http, open)", {
307
+ agentId: LEAD_ID,
308
+ source: "chat",
309
+ contextKey: chatSdkContextKey("telegram:553333"),
310
+ });
311
+ expect((await postTask({}, { parentTaskId: foreign.id })).status).toBe(201);
312
+ expect((await postTask({ agentId: LEAD_ID }, { parentTaskId: foreign.id })).status).toBe(201);
313
+ expect(
314
+ (await postTask({ agentId: LEAD_ID }, { contextKey: chatSdkContextKey("telegram:554444") }))
315
+ .status,
316
+ ).toBe(201);
317
+ });
318
+ });
@@ -3,6 +3,8 @@
3
3
  // behavior that is uneconomical to express through a server round-trip.
4
4
 
5
5
  import { describe, expect, test } from "bun:test";
6
+ import { loadModelsDevCache } from "../be/modelsdev-cache";
7
+ import { buildModelsDevSeedRows } from "../be/seed-pricing";
6
8
  import { recomputeSessionCost } from "../http/session-cost-recompute";
7
9
  import type { PricingProvider, PricingTokenClass } from "../types";
8
10
 
@@ -215,3 +217,47 @@ describe("recomputeSessionCost — pure branch behavior", () => {
215
217
  expect(result.costSource).toBe("pricing-table");
216
218
  });
217
219
  });
220
+
221
+ describe("nan and helmcode sessions price at $0 from the seeded table", () => {
222
+ // Rows projected from the vendored snapshot, exactly as the boot seeder and
223
+ // the runtime refresh insert them.
224
+ const cache = loadModelsDevCache();
225
+ const rows = cache ? buildModelsDevSeedRows(cache) : [];
226
+ const book: RateBook = Object.fromEntries(
227
+ rows.map((row) => [`${row.provider}:${row.model}:${row.tokenClass}`, row.pricePerMillionUsd]),
228
+ );
229
+
230
+ test("pi on nan/glm5.3-flash and opencode on helmcode/qwen3.6 record pricing-table, total 0", async () => {
231
+ for (const [provider, model] of [
232
+ ["pi", "nan/glm5.3-flash"],
233
+ ["opencode", "helmcode/qwen3.6"],
234
+ ] as const) {
235
+ const result = await recomputeSessionCost(
236
+ {
237
+ ...BASE,
238
+ provider,
239
+ model,
240
+ harnessCostUsd: 0,
241
+ inputTokens: 50_000,
242
+ outputTokens: 2_000,
243
+ cacheReadTokens: 10_000,
244
+ },
245
+ lookupFrom(book),
246
+ );
247
+ expect(result.costSource).toBe("pricing-table");
248
+ expect(result.totalCostUsd).toBe(0);
249
+ }
250
+ });
251
+
252
+ test("nan/qwen3.6 and helmcode/qwen3.6 are separate pricing keys", () => {
253
+ for (const harness of ["pi", "opencode"]) {
254
+ const keys = new Set(
255
+ rows
256
+ .filter((row) => row.provider === harness && row.model.endsWith("/qwen3.6"))
257
+ .map((row) => row.model),
258
+ );
259
+ expect(keys.has("nan/qwen3.6")).toBe(true);
260
+ expect(keys.has("helmcode/qwen3.6")).toBe(true);
261
+ }
262
+ });
263
+ });
@@ -0,0 +1,137 @@
1
+ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
2
+ import { unlink } from "node:fs/promises";
3
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
+ import { closeDb, createAgent, createSkill, initDb } from "../be/db";
5
+ import { skillsSeeder } from "../be/seed-skills";
6
+ import { registerSkillGetTool } from "../tools/skills/skill-get";
7
+
8
+ const TEST_DB_PATH = "./test-skill-get-name-resolution.sqlite";
9
+
10
+ const CALLER_ID = "aaaa0000-0000-4000-8000-000000000031";
11
+ const OTHER_ID = "bbbb0000-0000-4000-8000-000000000032";
12
+ const THIRD_ID = "cccc0000-0000-4000-8000-000000000033";
13
+
14
+ type StructuredContent = {
15
+ success: boolean;
16
+ message: string;
17
+ skill?: { id: string; ownerAgentId: string | null };
18
+ };
19
+
20
+ async function callSkillGet(
21
+ server: McpServer,
22
+ callerAgentId: string | undefined,
23
+ args: Record<string, unknown>,
24
+ ): Promise<StructuredContent> {
25
+ // biome-ignore lint/complexity/noBannedTypes: accessing internal MCP SDK type for test
26
+ const tools = (server as unknown as { _registeredTools: Record<string, { handler: Function }> })
27
+ ._registeredTools;
28
+ const result = await tools["skill-get"].handler(args, {
29
+ sessionId: "test-session",
30
+ requestInfo: { headers: { "x-agent-id": callerAgentId ?? "" } },
31
+ });
32
+ return (result as { structuredContent: StructuredContent }).structuredContent;
33
+ }
34
+
35
+ function swarmSkill(name: string, ownerAgentId: string | null) {
36
+ return createSkill({
37
+ name,
38
+ description: `${name} description`,
39
+ content: `---\nname: ${name}\ndescription: ${name} description\n---\n\nBody.`,
40
+ type: "personal",
41
+ scope: "swarm",
42
+ ownerAgentId: ownerAgentId ?? undefined,
43
+ });
44
+ }
45
+
46
+ describe("skill-get swarm name resolution", () => {
47
+ let server: McpServer;
48
+
49
+ beforeAll(async () => {
50
+ for (const suffix of ["", "-wal", "-shm"]) {
51
+ try {
52
+ await unlink(TEST_DB_PATH + suffix);
53
+ } catch {
54
+ // File doesn't exist
55
+ }
56
+ }
57
+ closeDb();
58
+ initDb(TEST_DB_PATH);
59
+ for (const [id, name] of [
60
+ [CALLER_ID, "Caller"],
61
+ [OTHER_ID, "Other"],
62
+ [THIRD_ID, "Third"],
63
+ ] as const) {
64
+ await createAgent({ id, name, isLead: false, status: "idle" });
65
+ }
66
+ server = new McpServer({ name: "test-skill-get-name-resolution", version: "1.0.0" });
67
+ registerSkillGetTool(server);
68
+ });
69
+
70
+ afterAll(async () => {
71
+ closeDb();
72
+ for (const suffix of ["", "-wal", "-shm"]) {
73
+ try {
74
+ await unlink(TEST_DB_PATH + suffix);
75
+ } catch {
76
+ // ignore
77
+ }
78
+ }
79
+ });
80
+
81
+ test("an owned-only swarm skill resolves by name", async () => {
82
+ const owned = await swarmSkill("owned-only-skill", OTHER_ID);
83
+
84
+ const withCaller = await callSkillGet(server, CALLER_ID, { name: "owned-only-skill" });
85
+ expect(withCaller.success).toBe(true);
86
+ expect(withCaller.skill?.id).toBe(owned.id);
87
+
88
+ const anonymous = await callSkillGet(server, undefined, { name: "owned-only-skill" });
89
+ expect(anonymous.success).toBe(true);
90
+ expect(anonymous.skill?.id).toBe(owned.id);
91
+ });
92
+
93
+ test("the ownerless row beats owned rows, including the caller's own", async () => {
94
+ // Live shape: qa-loom has an ownerless row and an agent-owned twin.
95
+ const owned = await swarmSkill("shadowed-skill", CALLER_ID);
96
+ const ownerless = await swarmSkill("shadowed-skill", null);
97
+ expect(owned.createdAt <= ownerless.createdAt).toBe(true);
98
+
99
+ const result = await callSkillGet(server, CALLER_ID, { name: "shadowed-skill" });
100
+ expect(result.success).toBe(true);
101
+ expect(result.skill?.id).toBe(ownerless.id);
102
+ });
103
+
104
+ test("the caller's own row beats another agent's row", async () => {
105
+ await swarmSkill("two-owner-skill", OTHER_ID);
106
+ const mine = await swarmSkill("two-owner-skill", CALLER_ID);
107
+
108
+ const result = await callSkillGet(server, CALLER_ID, { name: "two-owner-skill" });
109
+ expect(result.success).toBe(true);
110
+ expect(result.skill?.id).toBe(mine.id);
111
+ });
112
+
113
+ test("two foreign-owned rows return an error naming both ids", async () => {
114
+ const first = await swarmSkill("foreign-pair-skill", OTHER_ID);
115
+ const second = await swarmSkill("foreign-pair-skill", THIRD_ID);
116
+
117
+ const result = await callSkillGet(server, CALLER_ID, { name: "foreign-pair-skill" });
118
+ expect(result.success).toBe(false);
119
+ expect(result.message).toContain(first.id);
120
+ expect(result.message).toContain(second.id);
121
+ expect(result.message).toContain("skillId");
122
+
123
+ const byId = await callSkillGet(server, CALLER_ID, { skillId: second.id });
124
+ expect(byId.success).toBe(true);
125
+ expect(byId.skill?.id).toBe(second.id);
126
+ });
127
+
128
+ test("the seed path still matches ownerless rows only", async () => {
129
+ await swarmSkill("seed-owned-only", OTHER_ID);
130
+
131
+ const item = { key: "seed-owned-only", contentHash: "" } as never;
132
+ expect(await skillsSeeder.upstreamHash(item)).toBeNull();
133
+
134
+ await swarmSkill("seed-owned-only", null);
135
+ expect(await skillsSeeder.upstreamHash(item)).not.toBeNull();
136
+ });
137
+ });
@@ -23,10 +23,11 @@ describe("Sonnet 5.5 registry", () => {
23
23
  .filter((row) => row.provider === provider && row.model === model)
24
24
  .map((row) => [row.tokenClass, row.pricePerMillionUsd]),
25
25
  );
26
+ // Sonnet 5.5 cache hits cost 0.05x input, not the usual 0.1x.
26
27
  expect(rates).toEqual({
27
28
  input: 2,
28
29
  output: 10,
29
- cached_input: 0.2,
30
+ cached_input: 0.1,
30
31
  cache_write: 2.5,
31
32
  cache_write_1h: 4,
32
33
  });
@@ -41,4 +42,11 @@ describe("Sonnet 5.5 registry", () => {
41
42
  ).toBe(2);
42
43
  }
43
44
  });
45
+
46
+ test("the shortname fallback seeds the same cache-read rate when the snapshot lacks the model", () => {
47
+ const row = buildModelsDevSeedRows({ anthropic: { models: {} } }).find(
48
+ (r) => r.provider === "claude" && r.model === "sonnet" && r.tokenClass === "cached_input",
49
+ );
50
+ expect(row?.pricePerMillionUsd).toBe(0.1);
51
+ });
44
52
  });