@desplega.ai/agent-swarm 1.152.2 → 1.154.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 (284) hide show
  1. package/README.md +2 -2
  2. package/dist/{acp-adapter-pfeje2v5.js → acp-adapter-eb2aj8xy.js} +4 -4
  3. package/dist/{actions-0sttc7f5.js → actions-kpnwp1kz.js} +31 -27
  4. package/dist/{anthropic-messages-2y8nktar.js → anthropic-messages-9vqsbznt.js} +112 -84
  5. package/dist/{app-p8teva95.js → app-zk1apkf6.js} +10 -7
  6. package/dist/{assistant-r45jnsrt.js → assistant-kwq4y50q.js} +32 -28
  7. package/dist/{azure-openai-responses-f38x2avq.js → azure-openai-responses-04qwdwfq.js} +27 -13
  8. package/dist/{boot-reembed-je26h5y7.js → boot-reembed-22hd3jjn.js} +9 -7
  9. package/dist/{boot-reembed-1qmybsd4.js → boot-reembed-qe168424.js} +10 -8
  10. package/dist/{boot-scrub-logs-gte3h1mm.js → boot-scrub-logs-pkdpsrjd.js} +7 -5
  11. package/dist/{claude-adapter-csc1mzka.js → claude-adapter-3fx90nww.js} +18 -16
  12. package/dist/{claude-managed-adapter-dsmkcrjy.js → claude-managed-adapter-ea2aga6w.js} +3 -3
  13. package/dist/{claude-sdk-session-fcd6f078.js → claude-sdk-session-z7fahf1k.js} +18 -16
  14. package/dist/{cli-r2fpe7cx.js → cli-00w2c66p.js} +61 -55
  15. package/dist/{cli-wbyk465z.js → cli-09y3tjxz.js} +1 -1
  16. package/dist/{cli-ht5rs63n.js → cli-0e31nm4n.js} +16 -7
  17. package/dist/cli-0x5qzvkg.js +4279 -0
  18. package/dist/{cli-d3p82erb.js → cli-18amys3y.js} +9 -9
  19. package/dist/{cli-4ahpr4sw.js → cli-1jpjafw4.js} +5 -5
  20. package/dist/{cli-jbmc0r7r.js → cli-243tzws8.js} +1 -1
  21. package/dist/{cli-ch38q5z4.js → cli-24k806sc.js} +6789 -2542
  22. package/dist/cli-3bkz2p7j.js +2828 -0
  23. package/dist/cli-3er6gcsn.js +1849 -0
  24. package/dist/cli-4k7jazkq.js +3339 -0
  25. package/dist/{cli-c4eeb5q2.js → cli-5b6b62tz.js} +1 -1
  26. package/dist/{cli-qd65dvr6.js → cli-5vs0nh43.js} +65 -44
  27. package/dist/{cli-z3wkng35.js → cli-6r3cgpk2.js} +224 -156
  28. package/dist/{cli-pgj4a5fb.js → cli-71cj99hx.js} +4 -3
  29. package/dist/cli-8sea27q4.js +14075 -0
  30. package/dist/{cli-jvy9qgjs.js → cli-913yakhv.js} +22 -7
  31. package/dist/{cli-pf9q3696.js → cli-927erqqt.js} +5 -2
  32. package/dist/cli-95jnpnqg.js +230 -0
  33. package/dist/{cli-wpv5rgg2.js → cli-abp3g4ax.js} +15 -2
  34. package/dist/{cli-ckz5t2hb.js → cli-acy6bfhr.js} +10 -7
  35. package/dist/{cli-4yc5d8fk.js → cli-avkd6mqv.js} +50 -0
  36. package/dist/{cli-8wrympd7.js → cli-bzr0ftht.js} +26 -7
  37. package/dist/{cli-qn042eh7.js → cli-c1v10xb2.js} +194 -1913
  38. package/dist/cli-cgj4hc12.js +18902 -0
  39. package/dist/{cli-2nnd56zt.js → cli-e0pg4zeh.js} +3 -2
  40. package/dist/{cli-r84ctdxn.js → cli-e31ehqwy.js} +2 -2
  41. package/dist/{cli-gphgnvpf.js → cli-e3229na9.js} +3 -3
  42. package/dist/{cli-gpqcs935.js → cli-e3cyf59d.js} +2 -2
  43. package/dist/{cli-s25avqg9.js → cli-e781c6f9.js} +1 -0
  44. package/dist/{cli-s2qjdd6z.js → cli-eetrvv91.js} +1 -1
  45. package/dist/{cli-j4nv2wqy.js → cli-fdzg6jhf.js} +10 -4
  46. package/dist/{cli-fm4ytsdk.js → cli-g2h29qar.js} +2 -2
  47. package/dist/{cli-zvgj5zg8.js → cli-ge67p080.js} +302 -3497
  48. package/dist/cli-h38x0qhg.js +6912 -0
  49. package/dist/{cli-9xvdm3bx.js → cli-h60qfasa.js} +742 -278
  50. package/dist/{cli-jew3jda5.js → cli-hws0vn86.js} +2 -2
  51. package/dist/{cli-j8dggbcr.js → cli-k0g1sjzm.js} +23 -31
  52. package/dist/{cli-xw7vak96.js → cli-kp3qs53m.js} +5 -5
  53. package/dist/{cli-rgvp6r2x.js → cli-mcnhgnn1.js} +193 -4
  54. package/dist/{cli-zvkq5csn.js → cli-myaxtgh8.js} +7 -6
  55. package/dist/{cli-56h18ng2.js → cli-n62atwh9.js} +1 -1
  56. package/dist/cli-n7h1kqpf.js +29 -0
  57. package/dist/{cli-fmy65jdn.js → cli-pf1v8xhb.js} +7 -5
  58. package/dist/{cli-xy1cchaf.js → cli-q85qhckh.js} +238 -11
  59. package/dist/{cli-3bpzj8me.js → cli-r1d3dzt6.js} +2 -2
  60. package/dist/{pi-mono-adapter-jcqt71cg.js → cli-rxkqvngf.js} +67343 -101083
  61. package/dist/{cli-kj76p1ez.js → cli-scwq51gn.js} +13 -7
  62. package/dist/{cli-b9dd0h3k.js → cli-stwsmd6k.js} +3 -2
  63. package/dist/{cli-c2mk1kgf.js → cli-v4en75c5.js} +8 -1
  64. package/dist/{cli-z61ykvam.js → cli-wg04s9kr.js} +4 -3
  65. package/dist/cli-yn0a417a.js +4069 -0
  66. package/dist/{cli-zfzznn7c.js → cli-ytxs7mn1.js} +26 -2
  67. package/dist/{cli-4j8k1m4w.js → cli-zt1227k1.js} +1162 -192
  68. package/dist/cli.js +28 -35
  69. package/dist/{codex-adapter-fxmpsabw.js → codex-adapter-ttdm995v.js} +12 -11
  70. package/dist/{codex-hook-8jexc34x.js → codex-hook-8gx7bcz0.js} +3 -3
  71. package/dist/{codex-session-runner-rr1e47ea.js → codex-session-runner-3v8nh4c8.js} +12 -11
  72. package/dist/{commands-0f5rxz51.js → commands-1cxwbhhq.js} +7 -5
  73. package/dist/{db-r3jxf3wy.js → db-y9zwsr79.js} +25 -5
  74. package/dist/{dispatcher-06vph6q4.js → dispatcher-9m9zss36.js} +30 -26
  75. package/dist/dsh-adapter-gsb2qqf9.js +19 -0
  76. package/dist/{e2b-413b8t4v.js → e2b-93x1k0j5.js} +6 -7
  77. package/dist/{google-generative-ai-ndjmj5fa.js → google-generative-ai-gwwrnzqn.js} +33 -73
  78. package/dist/{google-vertex-fr665tqs.js → google-vertex-5wfawpwz.js} +38 -68
  79. package/dist/{handlers-0hgtc910.js → handlers-36fm1bwn.js} +33 -29
  80. package/dist/{hook-9kvrv1er.js → hook-pntw4r8h.js} +12 -10
  81. package/dist/{hook-fbfhkat3.js → hook-vwq8y26p.js} +13 -11
  82. package/dist/{http-xbyx8k5j.js → http-20jesakk.js} +357 -261
  83. package/dist/{index-jsw3aynb.js → index-6artrehg.js} +30 -26
  84. package/dist/{cli-pdz9qde4.js → index-9ch1n761.js} +10 -11
  85. package/dist/{index-rnyahfn5.js → index-9n5z2492.js} +30 -26
  86. package/dist/{index-66xz5e9j.js → index-h5gs261z.js} +521 -9
  87. package/dist/{index-tfsdxyv6.js → index-j5wrf1yw.js} +40 -25
  88. package/dist/{index-2sp0384z.js → index-kkeftv5h.js} +738 -21
  89. package/dist/{index-sgk50mbg.js → index-m2xqpz5r.js} +31 -27
  90. package/dist/{index-gpfn1741.js → index-m9ks8a7t.js} +9 -7
  91. package/dist/index-w097eg0x.js +5 -0
  92. package/dist/jiti-loader-b4837mtm.js +34 -0
  93. package/dist/jiti-static-loader-q2g5ds0y.js +31267 -0
  94. package/dist/{keepalive-s3xpr7sw.js → keepalive-pwxvxd2c.js} +9 -7
  95. package/dist/{lead-rq4n5r16.js → lead-m789wqa3.js} +46 -41
  96. package/dist/{maintenance-f0atj1ab.js → maintenance-bvg6w7e2.js} +10 -8
  97. package/dist/{mistral-conversations-qhr84ph3.js → mistral-conversations-zx0kn554.js} +24 -18
  98. package/dist/{node-x1g4v0pq.js → node-5qtycf6a.js} +16 -11
  99. package/dist/{oauth-refresh-sweep-q0t3bxre.js → oauth-refresh-sweep-6v4bd737.js} +8 -6
  100. package/dist/{onboard-br5j1kch.js → onboard-snckpcj1.js} +115 -35
  101. package/dist/{openai-codex-responses-wy6a43wd.js → openai-codex-responses-4vjqzvjp.js} +33 -27
  102. package/dist/{openai-completions-32rdwmze.js → openai-completions-pxsmym7g.js} +52 -64
  103. package/dist/{openai-responses-6xwf7bzg.js → openai-responses-tmn0rqp0.js} +23 -24
  104. package/dist/{opencode-adapter-28axqa09.js → opencode-adapter-qbmv4tpy.js} +6 -6
  105. package/dist/{otel-impl-vdqge2nt.js → otel-impl-e0ytens5.js} +4 -4
  106. package/dist/{pi-messages-b1mbr6gp.js → pi-messages-txpjj39q.js} +3 -3
  107. package/dist/pi-mono-adapter-c4ggemhz.js +1316 -0
  108. package/dist/{pricing-refresh-hb3691fd.js → pricing-refresh-52g2ywrw.js} +9 -7
  109. package/dist/{rbac-roles-t78xt39b.js → rbac-roles-5455kfdn.js} +8 -6
  110. package/dist/{rbac-roles-jh280k9g.js → rbac-roles-hbf0xa52.js} +9 -7
  111. package/dist/{render-v2-7p8r2ymg.js → render-v2-npm8xdzh.js} +16 -9
  112. package/dist/{seed-pricing-hf889enh.js → seed-pricing-650wbr1n.js} +8 -6
  113. package/dist/{setup-w6mb03jv.js → setup-k1mew5rf.js} +4 -5
  114. package/dist/virtual-modules-2q5b2pk4.js +54 -0
  115. package/dist/{work-objects-nt8gy1kj.js → work-objects-2ea3kvwg.js} +33 -29
  116. package/dist/{worker-dwy617t0.js → worker-ec6hvc4m.js} +46 -41
  117. package/openapi.json +109 -16
  118. package/package.json +7 -6
  119. package/plugin/opencode-plugins/lib/memory-raters.ts +7 -0
  120. package/src/be/automation-preflight.ts +6 -4
  121. package/src/be/db/agents.ts +75 -2
  122. package/src/be/db/tasks/read.ts +2 -0
  123. package/src/be/db/tasks/write.ts +7 -1
  124. package/src/be/db.ts +140 -18
  125. package/src/be/extensions/db.ts +35 -4
  126. package/src/be/memory/raters/llm.ts +2 -6
  127. package/src/be/memory/raters/noop.ts +2 -3
  128. package/src/be/memory/raters/registry.ts +4 -10
  129. package/src/be/migrations/157_defer_provenance_and_marker.sql +27 -0
  130. package/src/be/models-catalog.ts +2 -0
  131. package/src/be/modelsdev-cache.json +213 -0
  132. package/src/be/rbac-audit.ts +2 -0
  133. package/src/be/seed/agent-fs-provision.ts +23 -0
  134. package/src/be/seed-pricing.ts +3 -1
  135. package/src/be/swarm-config-guard.ts +1 -0
  136. package/src/commands/onboard/compose-generator.ts +16 -1
  137. package/src/commands/onboard/env-generator.ts +14 -1
  138. package/src/commands/onboard/manifest.ts +1 -0
  139. package/src/commands/onboard/steps/integration-slack.tsx +56 -16
  140. package/src/commands/onboard/steps/review.tsx +5 -0
  141. package/src/commands/onboard/types.ts +6 -0
  142. package/src/commands/onboard.tsx +5 -1
  143. package/src/commands/provider-credentials.ts +14 -2
  144. package/src/commands/runner.ts +18 -2
  145. package/src/extensions/identity.ts +15 -7
  146. package/src/extensions/lifecycle.ts +15 -21
  147. package/src/hooks/codex-hook.ts +7 -1
  148. package/src/http/agents.ts +12 -4
  149. package/src/http/config.ts +1 -1
  150. package/src/http/core.ts +5 -3
  151. package/src/http/extensions.ts +108 -34
  152. package/src/http/poll.ts +7 -0
  153. package/src/http/schedules.ts +10 -0
  154. package/src/http/script-runs.ts +35 -26
  155. package/src/http/status.ts +24 -11
  156. package/src/http/tasks.ts +6 -1
  157. package/src/prompts/base-prompt.ts +15 -7
  158. package/src/prompts/memories.ts +5 -7
  159. package/src/prompts/session-templates.ts +3 -2
  160. package/src/prompts/steering-delivery.ts +2 -0
  161. package/src/providers/claude-managed-models.ts +8 -0
  162. package/src/providers/codex-adapter.ts +3 -6
  163. package/src/providers/codex-models.ts +15 -1
  164. package/src/providers/dsh-adapter.ts +248 -0
  165. package/src/providers/index.ts +5 -1
  166. package/src/providers/modelsdev-reasoning.json +50 -0
  167. package/src/providers/pi-mono-extension.ts +2 -5
  168. package/src/rbac/legacy-policy.ts +15 -1
  169. package/src/rbac/types.ts +2 -0
  170. package/src/server.ts +7 -1
  171. package/src/slack/ack.ts +43 -8
  172. package/src/slack/app.ts +32 -10
  173. package/src/slack/config.ts +29 -0
  174. package/src/slack/handlers.ts +7 -1
  175. package/src/slack/render-v2.ts +186 -17
  176. package/src/slack/responses.ts +1 -3
  177. package/src/slack/task-output.ts +66 -8
  178. package/src/slack/thread-buffer.ts +27 -3
  179. package/src/telemetry.ts +2 -3
  180. package/src/tests/agent-fs-provision-seeder.test.ts +56 -2
  181. package/src/tests/agents-harness-provider.test.ts +19 -0
  182. package/src/tests/agents-list-model-display.test.ts +60 -1
  183. package/src/tests/base-prompt.test.ts +25 -0
  184. package/src/tests/claude-managed-adapter.test.ts +6 -0
  185. package/src/tests/codex-gpt6-release.test.ts +97 -0
  186. package/src/tests/codex-steering-hook.test.ts +2 -2
  187. package/src/tests/config-api-only-keys.test.ts +9 -0
  188. package/src/tests/context-window.test.ts +1 -0
  189. package/src/tests/db-task-read-contract.test.ts +9 -0
  190. package/src/tests/db-task-write-contract.test.ts +9 -0
  191. package/src/tests/defer-task.test.ts +115 -16
  192. package/src/tests/dsh-adapter.test.ts +276 -0
  193. package/src/tests/e2e-nightly-report.test.ts +3 -3
  194. package/src/tests/e2e-redact.test.ts +2 -0
  195. package/src/tests/env-flag.test.ts +6 -0
  196. package/src/tests/extension-agent-visibility.test.ts +474 -0
  197. package/src/tests/extensions-db.test.ts +41 -0
  198. package/src/tests/extensions-http.test.ts +148 -4
  199. package/src/tests/extensions-lifecycle.test.ts +21 -1
  200. package/src/tests/extensions-mcp-tools.test.ts +9 -5
  201. package/src/tests/fetch-resolved-env.test.ts +43 -0
  202. package/src/tests/internal-ai/complete-structured.test.ts +32 -9
  203. package/src/tests/internal-ai/credentials.test.ts +4 -4
  204. package/src/tests/internal-ai/models.test.ts +20 -0
  205. package/src/tests/memory-rate-tool.test.ts +7 -10
  206. package/src/tests/memory-rater-defaults.test.ts +23 -0
  207. package/src/tests/memory-rater-e2e.test.ts +2 -3
  208. package/src/tests/migration-046-budgets.test.ts +2 -0
  209. package/src/tests/model-control.test.ts +2 -2
  210. package/src/tests/onboard-compose.test.ts +14 -0
  211. package/src/tests/onboard-env.test.ts +17 -0
  212. package/src/tests/onboard-manifest.test.ts +12 -0
  213. package/src/tests/opus-5-5-registry.test.ts +48 -0
  214. package/src/tests/pricing-refresh.test.ts +1 -1
  215. package/src/tests/pricing-routes.test.ts +15 -3
  216. package/src/tests/provider-registration.test.ts +5 -2
  217. package/src/tests/rbac-admission-e2e.test.ts +54 -0
  218. package/src/tests/rbac-e2e-helpers.ts +2 -0
  219. package/src/tests/rbac-engine.test.ts +32 -0
  220. package/src/tests/rbac-wire-e2e.test.ts +33 -0
  221. package/src/tests/reasoning-effort.test.ts +8 -0
  222. package/src/tests/reload-config.test.ts +33 -0
  223. package/src/tests/script-connections.test.ts +3 -0
  224. package/src/tests/session-costs-model-key-normalize.test.ts +1 -0
  225. package/src/tests/slack-config.test.ts +136 -0
  226. package/src/tests/slack-mode-lifecycle.test.ts +69 -0
  227. package/src/tests/slack-render-v2.test.ts +446 -16
  228. package/src/tests/slack-steering.test.ts +69 -1
  229. package/src/tests/slack-task-output.test.ts +33 -12
  230. package/src/tests/status.test.ts +22 -2
  231. package/src/tests/steering-core.test.ts +35 -0
  232. package/src/tests/steering-delivery.test.ts +39 -0
  233. package/src/tests/steering-transport.test.ts +2 -2
  234. package/src/tests/task-tool-preload-mcp.test.ts +3 -1
  235. package/src/tests/telemetry-init.test.ts +18 -0
  236. package/src/tests/workflow-live-recovery.test.ts +126 -0
  237. package/src/tests/workflow-llm.test.ts +3 -3
  238. package/src/tools/defer-task.ts +64 -26
  239. package/src/tools/extension-common.ts +4 -0
  240. package/src/tools/extension-install.ts +1 -1
  241. package/src/tools/extension-list.ts +4 -3
  242. package/src/tools/join-swarm.ts +1 -1
  243. package/src/tools/schedules/create-schedule.ts +11 -1
  244. package/src/tools/schedules/patch-schedule.ts +5 -0
  245. package/src/tools/schedules/update-schedule.ts +5 -0
  246. package/src/tools/send-task.ts +6 -0
  247. package/src/tools/utils.ts +1 -1
  248. package/src/types.ts +30 -8
  249. package/src/utils/context-window.ts +1 -0
  250. package/src/utils/credentials.ts +8 -0
  251. package/src/utils/internal-ai/complete-structured.ts +8 -0
  252. package/src/utils/internal-ai/models.ts +5 -5
  253. package/src/utils/memory-raters.ts +3 -0
  254. package/src/workflows/engine.ts +28 -0
  255. package/src/workflows/recovery.ts +7 -4
  256. package/templates/schedules/daily-status-report/content.md +25 -12
  257. package/templates/schedules/daily-workflow-health-audit/content.md +1 -1
  258. package/templates/skills/swarm-extensions/content.md +9 -5
  259. package/dist/cli-4wppfkcm.js +0 -16384
  260. package/dist/cli-55qrx3q8.js +0 -6041
  261. package/dist/cli-cg4vnyh4.js +0 -305
  262. package/dist/cli-jyetr64j.js +0 -52
  263. package/dist/cli-pp9ax9pa.js +0 -520
  264. package/dist/cli-qa66qaf0.js +0 -6076
  265. package/dist/cli-qghpvca2.js +0 -590
  266. package/dist/cli-rembwgt8.js +0 -861
  267. package/dist/cli-tcvj7j2r.js +0 -1971
  268. package/dist/cli-vfcth11k.js +0 -36
  269. package/dist/cli-w1d1bgkk.js +0 -1422
  270. package/dist/cli-y6x0gqtg.js +0 -5977
  271. package/dist/es5-tj3tn4jj.js +0 -844
  272. package/dist/index-1kq5hw2p.js +0 -467
  273. package/dist/index-1p8wdstt.js +0 -7
  274. package/dist/index-8fdjzb2f.js +0 -100
  275. package/dist/index-bxmwzbgw.js +0 -365
  276. package/dist/index-nzerxd5z.js +0 -7
  277. package/dist/index-pyymnzat.js +0 -1168
  278. package/dist/index-qv6atbab.js +0 -619
  279. package/dist/index-rja32qa7.js +0 -801
  280. package/dist/index-tzg5srfp.js +0 -211
  281. package/dist/index-vhncw983.js +0 -90
  282. package/dist/index-xk036zsk.js +0 -859
  283. package/dist/index-zyazbzcb.js +0 -6
  284. package/dist/{claude-managed-setup-48dwg9bv.js → claude-managed-setup-f5pmsyrp.js} +3 -3
@@ -0,0 +1,48 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import {
3
+ findKnownModel,
4
+ pickDefaultModelForHarness,
5
+ } from "../../apps/ui/src/lib/agent-runtime-models";
6
+ import { loadModelsDevCache } from "../be/modelsdev-cache";
7
+ import { buildModelsDevSeedRows } from "../be/seed-pricing";
8
+
9
+ describe("Opus 5.5 registry", () => {
10
+ test("defaults to Opus 5.5 and keeps Opus 5 selectable", () => {
11
+ expect(pickDefaultModelForHarness("claude", [])).toBe("claude-opus-5-5");
12
+ expect(findKnownModel("claude-opus-5-5")).toMatchObject({
13
+ id: "claude-opus-5-5",
14
+ reasoningLevels: ["low", "medium", "high", "xhigh"],
15
+ });
16
+ expect(findKnownModel("claude-opus-5")?.id).toBe("claude-opus-5");
17
+ });
18
+
19
+ test("seeds canonical and alias rates without changing Opus 5", () => {
20
+ const cache = loadModelsDevCache();
21
+ expect(cache).not.toBeNull();
22
+ const rows = buildModelsDevSeedRows(cache!);
23
+ for (const provider of ["claude", "claude-managed"] as const) {
24
+ for (const model of ["claude-opus-5-5", "opus"]) {
25
+ const rates = Object.fromEntries(
26
+ rows
27
+ .filter((row) => row.provider === provider && row.model === model)
28
+ .map((row) => [row.tokenClass, row.pricePerMillionUsd]),
29
+ );
30
+ expect(rates).toEqual({
31
+ input: 4,
32
+ output: 20,
33
+ cached_input: 0.2,
34
+ cache_write: 5,
35
+ cache_write_1h: 8,
36
+ });
37
+ }
38
+ expect(
39
+ rows.find(
40
+ (row) =>
41
+ row.provider === provider &&
42
+ row.model === "claude-opus-5" &&
43
+ row.tokenClass === "input",
44
+ )?.pricePerMillionUsd,
45
+ ).toBe(5);
46
+ }
47
+ });
48
+ });
@@ -87,7 +87,7 @@ describe("models.dev runtime pricing refresh", () => {
87
87
  ).toBe(10);
88
88
  expect(
89
89
  (await getActivePricingRow("pi", "opus", "cache_write_1h", 500))?.pricePerMillionUsd,
90
- ).toBe(10);
90
+ ).toBe(8);
91
91
  expect(
92
92
  (await getActivePricingRow("claude", "claude-opus-5", "cache_write", 500))
93
93
  ?.pricePerMillionUsd,
@@ -13,6 +13,7 @@ import {
13
13
  type ServerResponse,
14
14
  } from "node:http";
15
15
  import { closeDb, getDbClient, getLogsByEventType, initDb } from "../be/db";
16
+ import { seedPricingFromModelsDev } from "../be/seed-pricing";
16
17
  import { handleCore } from "../http/core";
17
18
  import { handlePricing } from "../http/pricing";
18
19
  import { getPathSegments, parseQueryParams } from "../http/utils";
@@ -53,6 +54,7 @@ let port: number;
53
54
  beforeAll(async () => {
54
55
  await removeDbFiles(TEST_DB_PATH);
55
56
  initDb(TEST_DB_PATH);
57
+ seedPricingFromModelsDev({ quiet: true });
56
58
  server = createTestServer(API_KEY);
57
59
  port = await listenOnFreePort(server);
58
60
  });
@@ -103,13 +105,23 @@ describe("Phase 6 — /api/pricing REST surface", () => {
103
105
  expect(res.status).toBe(200);
104
106
  const body = await res.json();
105
107
  expect(body.rows).toBeInstanceOf(Array);
106
- // Codex seed rows include the migration 046 baseline plus later model
107
- // backfills. They should all be present here.
108
+ // Boot seeding adds current snapshot models alongside historical rows.
109
+ // Check each current model rather than a fixed three-row total: some
110
+ // models also publish cache-write prices.
108
111
  const seedRows = body.rows.filter(
109
112
  (r: { provider: string; effectiveFrom: number }) =>
110
113
  r.provider === "codex" && r.effectiveFrom === 0,
111
114
  );
112
- expect(seedRows.length).toBe(Object.keys(CODEX_MODEL_PRICING).length * 3);
115
+ for (const model of Object.keys(CODEX_MODEL_PRICING)) {
116
+ for (const tokenClass of ["input", "cached_input", "output"]) {
117
+ expect(
118
+ seedRows.some(
119
+ (row: { model: string; tokenClass: string }) =>
120
+ row.model === model && row.tokenClass === tokenClass,
121
+ ),
122
+ ).toBe(true);
123
+ }
124
+ }
113
125
  });
114
126
 
115
127
  test("GET /api/pricing/{provider}/{model}/{tokenClass} returns rows latest-first", async () => {
@@ -31,6 +31,7 @@ const modelsDevFixture = {
31
31
 
32
32
  const ENTRYPOINT_EXEMPTIONS = new Set(["claude", "acp"]);
33
33
  const ENTRYPOINT_OUTPUT: Record<string, string> = {
34
+ dsh: "Warning: dsh provider has no credentials yet",
34
35
  pi: "Warning: pi provider has no credentials yet",
35
36
  opencode: "Warning: opencode provider has no credentials yet",
36
37
  "claude-managed": "Warning: claude-managed provider missing:",
@@ -71,9 +72,11 @@ describe("provider registration synchronization", () => {
71
72
  );
72
73
  });
73
74
 
74
- test("pricing seeder emits rows for every provider except ACP", () => {
75
+ test("pricing seeder emits rows for providers with cost reporting", () => {
75
76
  const registered = new Set(buildPricingSeedRows(modelsDevFixture).map((row) => row.provider));
76
- const expected = ProviderNameSchema.options.filter((provider) => provider !== "acp");
77
+ const expected = ProviderNameSchema.options.filter(
78
+ (provider) => provider !== "acp" && provider !== "dsh",
79
+ );
77
80
  expect([...registered].filter((provider) => expected.includes(provider)).sort()).toEqual(
78
81
  [...expected].sort(),
79
82
  );
@@ -10,6 +10,7 @@
10
10
  import { Database } from "bun:sqlite";
11
11
  import { afterAll, describe, expect, setDefaultTimeout, test } from "bun:test";
12
12
  import { join } from "node:path";
13
+ import { loadBundleFixture } from "./fixtures/extensions/load";
13
14
  import {
14
15
  api,
15
16
  makeScratchDir,
@@ -483,3 +484,56 @@ describe("RBAC admission over real HTTP", () => {
483
484
  );
484
485
  });
485
486
  });
487
+
488
+ test.each(["true", "false"])("worker extension ownership with RBAC_ENABLED=%s", async (enabled) => {
489
+ const scratch = await makeScratchDir();
490
+ const instance = await spawnSwarmServer({
491
+ dbPath: join(scratch, "extension-admission.sqlite"),
492
+ logPath: join(scratch, "extension-admission.log"),
493
+ env: { RBAC_ENABLED: enabled },
494
+ });
495
+ try {
496
+ await registerAgent(instance.base, WORKER_A, "extension-owner", false);
497
+ const bundle = await loadBundleFixture("minimal");
498
+ const installed = await api(instance.base, "POST", "/api/extensions/install", {
499
+ agentId: WORKER_A,
500
+ body: bundle,
501
+ });
502
+ expect(installed.status).toBe(200);
503
+ expect(installed.body.extension).toMatchObject({
504
+ createdByAgentId: WORKER_A,
505
+ enabled: false,
506
+ status: "disabled",
507
+ });
508
+ const path = `/api/extensions/${installed.body.extension.id}`;
509
+ for (const operation of ["enable", "disable", "activate-version"]) {
510
+ expect(
511
+ (
512
+ await api(instance.base, "POST", `${path}/${operation}`, {
513
+ agentId: WORKER_A,
514
+ body: { version: 1 },
515
+ })
516
+ ).status,
517
+ ).toBe(403);
518
+ }
519
+ const operatorBundle = { ...bundle, manifest: { ...bundle.manifest, name: "operator-owned" } };
520
+ const operator = await api(instance.base, "POST", "/api/extensions/install", {
521
+ body: operatorBundle,
522
+ });
523
+ expect(operator.status).toBe(200);
524
+ expect(operator.body.extension.createdByAgentId).toBeNull();
525
+ for (const method of ["PATCH", "DELETE"]) {
526
+ expect(
527
+ (
528
+ await api(instance.base, method, `/api/extensions/${operator.body.extension.id}`, {
529
+ agentId: WORKER_A,
530
+ ...(method === "PATCH" ? { body: { priority: 1 } } : {}),
531
+ })
532
+ ).status,
533
+ ).toBe(403);
534
+ }
535
+ } finally {
536
+ await instance.stop();
537
+ await removeScratchDir(scratch);
538
+ }
539
+ });
@@ -78,6 +78,8 @@ export async function spawnSwarmServer(opts: {
78
78
  API_KEY: E2E_API_KEY,
79
79
  AGENT_SWARM_API_KEY: E2E_API_KEY,
80
80
  PORT: String(port),
81
+ // MCP proxy tools must call this scratch server, never an inherited live URL.
82
+ MCP_BASE_URL: `http://localhost:${port}`,
81
83
  SLACK_DISABLE: "true",
82
84
  GITHUB_DISABLE: "true",
83
85
  JIRA_DISABLE: "true",
@@ -255,6 +255,38 @@ describe("lead-or-operator-or-user verbs", () => {
255
255
  }
256
256
  });
257
257
 
258
+ describe("extension ownership", () => {
259
+ test("workers create extensions and write only their own existing resource", () => {
260
+ const worker = { kind: "agent", agentId: "extension-owner", isLead: false } as const;
261
+ for (const resource of [
262
+ { kind: "extension" },
263
+ { kind: "extension", extensionId: "ext-1", createdByAgentId: worker.agentId },
264
+ ] as const) {
265
+ expect(
266
+ can({ principal: worker, verb: "extension.write", resource, source: "http" }).allow,
267
+ ).toBe(true);
268
+ }
269
+ for (const createdByAgentId of [null, "other-agent"]) {
270
+ expect(
271
+ can({
272
+ principal: worker,
273
+ verb: "extension.write",
274
+ resource: { kind: "extension", extensionId: "ext-1", createdByAgentId },
275
+ source: "http",
276
+ }).allow,
277
+ ).toBe(false);
278
+ }
279
+ expect(
280
+ can({
281
+ principal: { ...worker, agentId: "" },
282
+ verb: "extension.write",
283
+ resource: { kind: "extension" },
284
+ source: "http",
285
+ }).allow,
286
+ ).toBe(false);
287
+ });
288
+ });
289
+
258
290
  describe("lead-or-resource-owner verbs", () => {
259
291
  const expected: Expected = {
260
292
  lead: true,
@@ -18,6 +18,7 @@
18
18
  import { Database } from "bun:sqlite";
19
19
  import { afterAll, beforeAll, describe, expect, setDefaultTimeout, test } from "bun:test";
20
20
  import { join } from "node:path";
21
+ import { loadBundleFixture } from "./fixtures/extensions/load";
21
22
  import {
22
23
  api,
23
24
  LEAD,
@@ -119,6 +120,38 @@ afterAll(async () => {
119
120
  });
120
121
 
121
122
  describe("MCP gate matrix", () => {
123
+ test("worker MCP installs retain ownership and lifecycle calls remain lead-only", async () => {
124
+ const bundle = await loadBundleFixture("minimal");
125
+ bundle.manifest.name = "worker-owned-wire";
126
+ const installed = await mcpCall(base, WORKER_A, sidA, "extension-install", bundle);
127
+ expect(installed.structuredContent).toMatchObject({ success: true });
128
+ expect(installed.structuredContent).toMatchObject({
129
+ createdByAgentId: WORKER_A,
130
+ enabled: false,
131
+ status: "disabled",
132
+ });
133
+ expectRow({ type: "agent", id: WORKER_A }, "extension.write", "allow", null, "http");
134
+ const id = installed.structuredContent.id;
135
+ const listed = await mcpCall(base, WORKER_A, sidA, "extension-list", {});
136
+ expect(listed.structuredContent.extensions).toContainEqual(
137
+ expect.objectContaining({ id, createdByAgentId: WORKER_A }),
138
+ );
139
+ for (const tool of ["extension-enable", "extension-disable", "extension-activate-version"]) {
140
+ const denied = await mcpCall(base, WORKER_A, sidA, tool, {
141
+ id,
142
+ ...(tool === "extension-activate-version" ? { version: 1 } : {}),
143
+ });
144
+ expect(denied.structuredContent.success).toBe(false);
145
+ expectRow(
146
+ { type: "agent", id: WORKER_A },
147
+ "extension.activate",
148
+ "deny",
149
+ "requires operator/user authentication, or a lead with EXTENSION_ALLOW_LEAD_ACTIVATION enabled",
150
+ "http",
151
+ );
152
+ }
153
+ });
154
+
122
155
  test("config: set/delete lead-gated; includeSecrets masks for non-lead (DES-445 follow-up)", async () => {
123
156
  // set-config (non-credential key): worker denied, lead allowed.
124
157
  const setWorker = await mcpCall(base, WORKER_A, sidA, "set-config", {
@@ -22,6 +22,14 @@ describe("REASONING_EFFORT_LEVELS", () => {
22
22
  });
23
23
 
24
24
  describe("reasoningCapability — cache-sourced levels", () => {
25
+ test("Claude Opus 5.5 exposes supported CLI effort levels", () => {
26
+ expect(reasoningCapability("claude", "claude-opus-5-5").levels).toEqual([
27
+ "low",
28
+ "medium",
29
+ "high",
30
+ "xhigh",
31
+ ]);
32
+ });
25
33
  test("claude claude-opus-4-8: levels come from reasoning_options.effort, not the fallback", () => {
26
34
  const cap = reasoningCapability("claude", "claude-opus-4-8");
27
35
  expect(cap.supported).toBe(true);
@@ -323,6 +323,39 @@ describe("reload-config", () => {
323
323
  }
324
324
  });
325
325
 
326
+ test("reload applies HTTP mode without reporting a Slack socket as reinitialized", async () => {
327
+ const keys = [
328
+ "SLACK_MODE",
329
+ "SLACK_DISABLE",
330
+ "SLACK_BOT_TOKEN",
331
+ "SLACK_SIGNING_SECRET",
332
+ ] as const;
333
+ const originals = new Map(keys.map((key) => [key, process.env[key]]));
334
+ process.env.SLACK_DISABLE = "false";
335
+ process.env.SLACK_BOT_TOKEN = "xoxb-test";
336
+ process.env.SLACK_SIGNING_SECRET = "synthetic-signing-secret";
337
+ const config = await upsertSwarmConfig({
338
+ scope: "global",
339
+ key: "SLACK_MODE",
340
+ value: "http",
341
+ });
342
+
343
+ try {
344
+ const result = await reloadGlobalConfigsAndIntegrations();
345
+ expect(result.keysUpdated).toContain("SLACK_MODE");
346
+ expect(result.integrationsReinitialized).not.toContain("slack");
347
+ expect(process.env.SLACK_MODE).toBe("http");
348
+ } finally {
349
+ await deleteSwarmConfig(config.id);
350
+ await loadGlobalConfigsIntoEnv(true);
351
+ for (const key of keys) {
352
+ const value = originals.get(key);
353
+ if (value === undefined) delete process.env[key];
354
+ else process.env[key] = value;
355
+ }
356
+ }
357
+ });
358
+
326
359
  // Regression: injection used to be one-way. Deleting a global row left the
327
360
  // previously-injected value live in process.env until the process restarted,
328
361
  // so "reset to default" in the dashboard silently did nothing.
@@ -572,6 +572,9 @@ describe("script connections", () => {
572
572
  markMigrationApplied(database, "152_routing_decisions.sql");
573
573
  // 156 alters inbox_item_state, which this migration-112-only fixture does not create.
574
574
  markMigrationApplied(database, "156_inbox_item_state_read_at.sql");
575
+ // 157 alters scheduled_tasks and slack_messages, neither of which this
576
+ // migration-112-only fixture creates.
577
+ markMigrationApplied(database, "157_defer_provenance_and_marker.sql");
575
578
 
576
579
  const id = crypto.randomUUID();
577
580
  const now = new Date().toISOString();
@@ -133,6 +133,7 @@ describe("normalizeModelKey()", () => {
133
133
  expect(normalizeModelKey("claude", "claude-opus-4-7")).toBe("claude-opus-4-7");
134
134
  expect(normalizeModelKey("claude", "claude-fable-5-1")).toBe("claude-fable-5-1");
135
135
  expect(normalizeModelKey("claude", "claude-mythos-5-1")).toBe("claude-mythos-5-1");
136
+ expect(normalizeModelKey("claude", "claude-opus-5-5")).toBe("claude-opus-5-5");
136
137
  expect(normalizeModelKey("claude", "claude-opus-5")).toBe("claude-opus-5");
137
138
  expect(normalizeModelKey("claude", "claude-fable-5")).toBe("claude-fable-5");
138
139
  expect(normalizeModelKey("claude", "claude-mythos-5")).toBe("claude-mythos-5");
@@ -0,0 +1,136 @@
1
+ import { afterEach, beforeEach, describe, expect, spyOn, test } from "bun:test";
2
+ import { startSlackMock, stopSlackMock } from "../../scripts/e2e/slack";
3
+ import { closeDb, initDb } from "../be/db";
4
+ import { getEnabledCapabilities } from "../server";
5
+ import { getSlackApp, startSlackApp, stopSlackApp } from "../slack/app";
6
+ import { getSlackConfiguration, isSlackConfigured } from "../slack/config";
7
+
8
+ const SLACK_ENV_KEYS = [
9
+ "NODE_ENV",
10
+ "SLACK_ALLOW_DEV_SOCKET_MODE",
11
+ "CAPABILITIES",
12
+ "SLACK_MODE",
13
+ "SLACK_DISABLE",
14
+ "SLACK_BOT_TOKEN",
15
+ "SLACK_APP_TOKEN",
16
+ "SLACK_SIGNING_SECRET",
17
+ "SLACK_API_URL",
18
+ ] as const;
19
+ const originalEnv = new Map(SLACK_ENV_KEYS.map((key) => [key, process.env[key]]));
20
+
21
+ beforeEach(() => {
22
+ for (const key of SLACK_ENV_KEYS) delete process.env[key];
23
+ process.env.NODE_ENV = "test";
24
+ process.env.SLACK_DISABLE = "false";
25
+ });
26
+
27
+ afterEach(async () => {
28
+ await stopSlackApp();
29
+ for (const key of SLACK_ENV_KEYS) {
30
+ const value = originalEnv.get(key);
31
+ if (value === undefined) delete process.env[key];
32
+ else process.env[key] = value;
33
+ }
34
+ });
35
+
36
+ describe("Slack transport configuration", () => {
37
+ test("defaults to Socket Mode and requires the existing token pair", () => {
38
+ expect(getSlackConfiguration({})).toEqual({
39
+ disabled: false,
40
+ mode: "socket",
41
+ missingCredentials: ["SLACK_BOT_TOKEN", "SLACK_APP_TOKEN"],
42
+ });
43
+ expect(getEnabledCapabilities()).toContain("slack");
44
+ expect(isSlackConfigured({ SLACK_BOT_TOKEN: "xoxb-test", SLACK_APP_TOKEN: "xapp-test" })).toBe(
45
+ true,
46
+ );
47
+ });
48
+
49
+ test("HTTP mode requires bot and signing credentials without an app token", () => {
50
+ const env = {
51
+ SLACK_MODE: "http",
52
+ SLACK_BOT_TOKEN: "xoxb-test",
53
+ SLACK_SIGNING_SECRET: "synthetic-signing-secret",
54
+ };
55
+ expect(getSlackConfiguration(env)).toEqual({
56
+ disabled: false,
57
+ mode: "http",
58
+ missingCredentials: [],
59
+ });
60
+ expect(isSlackConfigured(env)).toBe(true);
61
+ });
62
+
63
+ test("disabled and invalid configurations fail closed", () => {
64
+ expect(
65
+ isSlackConfigured({
66
+ SLACK_DISABLE: "true",
67
+ SLACK_BOT_TOKEN: "xoxb-test",
68
+ SLACK_APP_TOKEN: "xapp-test",
69
+ }),
70
+ ).toBe(false);
71
+ expect(
72
+ getSlackConfiguration({
73
+ SLACK_MODE: "webhook",
74
+ SLACK_BOT_TOKEN: "xoxb-test",
75
+ SLACK_APP_TOKEN: "xapp-test",
76
+ }),
77
+ ).toMatchObject({ mode: null });
78
+ });
79
+
80
+ test("HTTP selection remains unavailable and never creates a socket app", async () => {
81
+ process.env.SLACK_MODE = "http";
82
+ expect(getEnabledCapabilities()).not.toContain("slack");
83
+ process.env.SLACK_BOT_TOKEN = "xoxb-test";
84
+ process.env.SLACK_SIGNING_SECRET = "synthetic-signing-secret";
85
+ delete process.env.SLACK_APP_TOKEN;
86
+
87
+ const error = spyOn(console, "error").mockImplementation(() => {});
88
+ try {
89
+ expect(await startSlackApp()).toBe(false);
90
+ expect(getSlackApp()).toBeNull();
91
+ expect(error).toHaveBeenCalledWith(
92
+ "[Slack] HTTP mode is configured but unavailable until the HTTP receiver is installed",
93
+ );
94
+ } finally {
95
+ error.mockRestore();
96
+ }
97
+ });
98
+ });
99
+
100
+ test.each([
101
+ "disabled",
102
+ "invalid mode",
103
+ "missing credentials",
104
+ "HTTP unavailable",
105
+ "socket blocked",
106
+ ])("failed init is retryable without stop: %s", async (reason) => {
107
+ const slack = await startSlackMock(false);
108
+ initDb(":memory:");
109
+ try {
110
+ Object.assign(process.env, slack.mock.env);
111
+ process.env.SLACK_SIGNING_SECRET = "synthetic-signing-secret";
112
+ if (reason === "disabled") process.env.SLACK_DISABLE = "true";
113
+ if (reason === "invalid mode") process.env.SLACK_MODE = "invalid";
114
+ if (reason === "missing credentials") delete process.env.SLACK_APP_TOKEN;
115
+ if (reason === "HTTP unavailable") process.env.SLACK_MODE = "http";
116
+ if (reason === "socket blocked") process.env.NODE_ENV = "development";
117
+
118
+ expect(await startSlackApp()).toBe(false);
119
+ expect(getSlackApp()).toBeNull();
120
+ expect(slack.mock.apiCalls("apps.connections.open")).toHaveLength(0);
121
+
122
+ Object.assign(process.env, slack.mock.env);
123
+ process.env.SLACK_DISABLE = "false";
124
+ process.env.SLACK_MODE = "socket";
125
+ process.env.NODE_ENV = "test";
126
+ // Deliberately retry without stopSlackApp: failed initialization must not latch.
127
+ expect(await startSlackApp()).toBe(true);
128
+ await slack.mock.waitForConnection(10_000);
129
+ expect(slack.mock.connectionCount).toBe(1);
130
+ expect(slack.mock.apiCalls("apps.connections.open")).toHaveLength(1);
131
+ } finally {
132
+ await stopSlackApp();
133
+ await stopSlackMock(slack, false);
134
+ closeDb();
135
+ }
136
+ }, 20_000);
@@ -0,0 +1,69 @@
1
+ import { expect, test } from "bun:test";
2
+ import { createApiClient, pollUntil } from "../../scripts/e2e/http";
3
+ import { startSlackMock, stopSlackMock } from "../../scripts/e2e/slack";
4
+ import { type Sut, startSut, stopSut } from "../../scripts/e2e/sut";
5
+
6
+ test("config reload disconnects a live socket for HTTP and can return to Socket Mode", async () => {
7
+ const slack = await startSlackMock(false);
8
+ let sut: Sut | undefined;
9
+ try {
10
+ sut = await startSut(false, slack.mock.env, {
11
+ SLACK_SIGNING_SECRET: "synthetic-signing-secret",
12
+ HEARTBEAT_DISABLE: "true",
13
+ });
14
+ await slack.mock.waitForConnection(10_000);
15
+ expect(slack.mock.connectionCount).toBe(1);
16
+ const opened = slack.mock.apiCalls("apps.connections.open").length;
17
+ const api = createApiClient(sut.baseUrl, sut.apiKey);
18
+ const http = await api("PUT", "/api/config", {
19
+ body: { scope: "global", key: "SLACK_MODE", value: "http" },
20
+ });
21
+ expect(http.status).toBe(200);
22
+ expect(await pollUntil(() => slack.mock.connectionCount === 0, 10_000, 50)).toBe(true);
23
+ const reload = await api("POST", "/api/config/reload");
24
+ expect(reload.status).toBe(200);
25
+ expect(reload.json).toMatchObject({ success: true });
26
+ expect(
27
+ (reload.json as { integrationsReinitialized: string[] }).integrationsReinitialized,
28
+ ).not.toContain("slack");
29
+ expect(slack.mock.apiCalls("apps.connections.open").length).toBe(opened);
30
+ const socket = await api("PUT", "/api/config", {
31
+ body: { scope: "global", key: "SLACK_MODE", value: "socket" },
32
+ });
33
+ expect(socket.status).toBe(200);
34
+ expect(await pollUntil(() => slack.mock.connectionCount === 1, 10_000, 50)).toBe(true);
35
+ expect(slack.mock.apiCalls("apps.connections.open").length).toBe(opened + 1);
36
+ } finally {
37
+ if (sut) await stopSut(sut, false);
38
+ await stopSlackMock(slack, false);
39
+ }
40
+ }, 30_000);
41
+
42
+ test("config reload recovers from an initial unavailable HTTP transport", async () => {
43
+ const slack = await startSlackMock(false);
44
+ let sut: Sut | undefined;
45
+ try {
46
+ sut = await startSut(false, slack.mock.env, {
47
+ SLACK_MODE: "http",
48
+ SLACK_SIGNING_SECRET: "synthetic-signing-secret",
49
+ HEARTBEAT_DISABLE: "true",
50
+ });
51
+ const api = createApiClient(sut.baseUrl, sut.apiKey);
52
+ const initialReload = await api("POST", "/api/config/reload");
53
+ expect(initialReload.status).toBe(200);
54
+ expect(
55
+ (initialReload.json as { integrationsReinitialized: string[] }).integrationsReinitialized,
56
+ ).not.toContain("slack");
57
+ expect(slack.mock.apiCalls("apps.connections.open")).toHaveLength(0);
58
+
59
+ const socket = await api("PUT", "/api/config", {
60
+ body: { scope: "global", key: "SLACK_MODE", value: "socket" },
61
+ });
62
+ expect(socket.status).toBe(200);
63
+ expect(await pollUntil(() => slack.mock.connectionCount === 1, 10_000, 50)).toBe(true);
64
+ expect(slack.mock.apiCalls("apps.connections.open")).toHaveLength(1);
65
+ } finally {
66
+ if (sut) await stopSut(sut, false);
67
+ await stopSlackMock(slack, false);
68
+ }
69
+ }, 30_000);