@desplega.ai/agent-swarm 1.152.2 → 1.153.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 (224) hide show
  1. package/README.md +1 -1
  2. package/dist/{acp-adapter-pfeje2v5.js → acp-adapter-ahmjxhek.js} +4 -4
  3. package/dist/{actions-0sttc7f5.js → actions-jzrfpkmn.js} +30 -26
  4. package/dist/{anthropic-messages-2y8nktar.js → anthropic-messages-9vqsbznt.js} +112 -84
  5. package/dist/{app-p8teva95.js → app-03na2vwb.js} +9 -6
  6. package/dist/{assistant-r45jnsrt.js → assistant-hfkxvhgt.js} +31 -27
  7. package/dist/{azure-openai-responses-f38x2avq.js → azure-openai-responses-04qwdwfq.js} +27 -13
  8. package/dist/{boot-reembed-je26h5y7.js → boot-reembed-c98xrad3.js} +8 -6
  9. package/dist/{boot-reembed-1qmybsd4.js → boot-reembed-zt3qm9xn.js} +9 -7
  10. package/dist/{boot-scrub-logs-gte3h1mm.js → boot-scrub-logs-jcqd8ksj.js} +6 -4
  11. package/dist/{claude-adapter-csc1mzka.js → claude-adapter-mgryg8g3.js} +17 -15
  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-jjf4zss1.js} +17 -15
  14. package/dist/{cli-r2fpe7cx.js → cli-00w2c66p.js} +61 -55
  15. package/dist/{cli-fm4ytsdk.js → cli-0d9nbqxf.js} +1 -1
  16. package/dist/cli-0x5qzvkg.js +4279 -0
  17. package/dist/{cli-d3p82erb.js → cli-18amys3y.js} +9 -9
  18. package/dist/{cli-wbyk465z.js → cli-1hwnb4hc.js} +1 -1
  19. package/dist/{cli-xw7vak96.js → cli-3bcmp015.js} +5 -5
  20. package/dist/cli-3er6gcsn.js +1849 -0
  21. package/dist/{cli-c4eeb5q2.js → cli-5b6b62tz.js} +1 -1
  22. package/dist/{cli-4ahpr4sw.js → cli-5mbmvxnn.js} +5 -5
  23. package/dist/{cli-xy1cchaf.js → cli-5taqtpze.js} +235 -8
  24. package/dist/{cli-fmy65jdn.js → cli-6ks9xf1n.js} +7 -5
  25. package/dist/cli-7g9s78jf.js +2827 -0
  26. package/dist/{cli-jew3jda5.js → cli-7pnz2v6s.js} +2 -2
  27. package/dist/{cli-r84ctdxn.js → cli-83wkg402.js} +2 -2
  28. package/dist/{cli-jvy9qgjs.js → cli-a9scg8sv.js} +4 -4
  29. package/dist/{cli-4yc5d8fk.js → cli-avkd6mqv.js} +50 -0
  30. package/dist/{cli-ht5rs63n.js → cli-b2g6mzjc.js} +15 -6
  31. package/dist/{cli-jbmc0r7r.js → cli-bw0dfwkr.js} +1 -1
  32. package/dist/{cli-8wrympd7.js → cli-bzr0ftht.js} +26 -7
  33. package/dist/{cli-wpv5rgg2.js → cli-cdq08v3r.js} +2 -2
  34. package/dist/cli-cgj4hc12.js +18902 -0
  35. package/dist/cli-d6hk10xx.js +14075 -0
  36. package/dist/{cli-zvgj5zg8.js → cli-djvxhnc4.js} +31 -3324
  37. package/dist/{cli-s25avqg9.js → cli-e781c6f9.js} +1 -0
  38. package/dist/{cli-3bpzj8me.js → cli-eazstkad.js} +2 -2
  39. package/dist/{cli-s2qjdd6z.js → cli-eetrvv91.js} +1 -1
  40. package/dist/cli-h38x0qhg.js +6912 -0
  41. package/dist/{cli-9xvdm3bx.js → cli-h60qfasa.js} +742 -278
  42. package/dist/{pi-mono-adapter-jcqt71cg.js → cli-ja7mrygn.js} +67343 -101083
  43. package/dist/{cli-j8dggbcr.js → cli-k0g1sjzm.js} +23 -31
  44. package/dist/{cli-gphgnvpf.js → cli-m04pt18h.js} +2 -2
  45. package/dist/{cli-rgvp6r2x.js → cli-mcnhgnn1.js} +193 -4
  46. package/dist/{cli-j4nv2wqy.js → cli-mzgyhxbe.js} +10 -4
  47. package/dist/{cli-qn042eh7.js → cli-n0q9b7j4.js} +46 -1870
  48. package/dist/cli-n7h1kqpf.js +29 -0
  49. package/dist/{cli-ckz5t2hb.js → cli-nq0kgyxe.js} +5 -5
  50. package/dist/{cli-z3wkng35.js → cli-qrqhc31j.js} +151 -125
  51. package/dist/{cli-56h18ng2.js → cli-r8tdds6s.js} +1 -1
  52. package/dist/{cli-ch38q5z4.js → cli-rj7de4b2.js} +6788 -2541
  53. package/dist/{cli-qd65dvr6.js → cli-s5s1f2yn.js} +56 -40
  54. package/dist/{cli-kj76p1ez.js → cli-scwq51gn.js} +13 -7
  55. package/dist/{cli-2nnd56zt.js → cli-tdh4hjr2.js} +3 -2
  56. package/dist/{cli-c2mk1kgf.js → cli-v4en75c5.js} +8 -1
  57. package/dist/{cli-b9dd0h3k.js → cli-vw03hg5t.js} +3 -2
  58. package/dist/{cli-zvkq5csn.js → cli-ybpp1srm.js} +6 -6
  59. package/dist/cli-yn0a417a.js +4069 -0
  60. package/dist/{cli-gpqcs935.js → cli-ysc1kbet.js} +2 -2
  61. package/dist/{cli-zfzznn7c.js → cli-ytxs7mn1.js} +26 -2
  62. package/dist/cli-zcsyfw2z.js +3335 -0
  63. package/dist/{cli-4j8k1m4w.js → cli-zt1227k1.js} +1162 -192
  64. package/dist/cli-zy5w9gpt.js +230 -0
  65. package/dist/cli.js +26 -34
  66. package/dist/{codex-adapter-fxmpsabw.js → codex-adapter-8548daap.js} +12 -11
  67. package/dist/{codex-session-runner-rr1e47ea.js → codex-session-runner-2dad8p4x.js} +12 -11
  68. package/dist/{commands-0f5rxz51.js → commands-355dbpa8.js} +6 -4
  69. package/dist/{db-r3jxf3wy.js → db-4jjvwz1s.js} +6 -4
  70. package/dist/{dispatcher-06vph6q4.js → dispatcher-ca590nkz.js} +29 -25
  71. package/dist/dsh-adapter-rgvj6ykp.js +19 -0
  72. package/dist/{e2b-413b8t4v.js → e2b-93x1k0j5.js} +6 -7
  73. package/dist/{google-generative-ai-ndjmj5fa.js → google-generative-ai-xtw7tggr.js} +33 -73
  74. package/dist/{google-vertex-fr665tqs.js → google-vertex-zdqpms6n.js} +38 -68
  75. package/dist/{handlers-0hgtc910.js → handlers-2y6dqpvf.js} +32 -28
  76. package/dist/{hook-9kvrv1er.js → hook-0s8knepr.js} +11 -9
  77. package/dist/{hook-fbfhkat3.js → hook-k0gec254.js} +12 -10
  78. package/dist/{http-xbyx8k5j.js → http-jp2nb4zw.js} +300 -237
  79. package/dist/{index-tfsdxyv6.js → index-0wzg3sp3.js} +39 -24
  80. package/dist/{index-rnyahfn5.js → index-3mq6ce93.js} +29 -25
  81. package/dist/{index-jsw3aynb.js → index-42s7eezq.js} +29 -25
  82. package/dist/{cli-pdz9qde4.js → index-9ch1n761.js} +10 -11
  83. package/dist/{index-gpfn1741.js → index-b56xkbc7.js} +8 -6
  84. package/dist/{index-66xz5e9j.js → index-h5gs261z.js} +521 -9
  85. package/dist/{index-2sp0384z.js → index-kkeftv5h.js} +738 -21
  86. package/dist/{index-sgk50mbg.js → index-s37dbevy.js} +30 -26
  87. package/dist/index-w097eg0x.js +5 -0
  88. package/dist/jiti-loader-b4837mtm.js +34 -0
  89. package/dist/jiti-static-loader-q2g5ds0y.js +31267 -0
  90. package/dist/{keepalive-s3xpr7sw.js → keepalive-9pfw7t02.js} +8 -6
  91. package/dist/{lead-rq4n5r16.js → lead-7vcmcdq4.js} +44 -39
  92. package/dist/{maintenance-f0atj1ab.js → maintenance-x277jbpd.js} +9 -7
  93. package/dist/{mistral-conversations-qhr84ph3.js → mistral-conversations-zx0kn554.js} +24 -18
  94. package/dist/{node-x1g4v0pq.js → node-5qtycf6a.js} +16 -11
  95. package/dist/{oauth-refresh-sweep-q0t3bxre.js → oauth-refresh-sweep-5fwa7th2.js} +7 -5
  96. package/dist/{onboard-br5j1kch.js → onboard-17sv0f51.js} +103 -35
  97. package/dist/{openai-codex-responses-wy6a43wd.js → openai-codex-responses-4vjqzvjp.js} +33 -27
  98. package/dist/{openai-completions-32rdwmze.js → openai-completions-pxsmym7g.js} +52 -64
  99. package/dist/{openai-responses-6xwf7bzg.js → openai-responses-tmn0rqp0.js} +23 -24
  100. package/dist/{opencode-adapter-28axqa09.js → opencode-adapter-qbmv4tpy.js} +6 -6
  101. package/dist/{otel-impl-vdqge2nt.js → otel-impl-srjy4be4.js} +4 -4
  102. package/dist/{pi-messages-b1mbr6gp.js → pi-messages-txpjj39q.js} +3 -3
  103. package/dist/pi-mono-adapter-946a0sdd.js +1316 -0
  104. package/dist/{pricing-refresh-hb3691fd.js → pricing-refresh-vetcm563.js} +8 -6
  105. package/dist/{rbac-roles-jh280k9g.js → rbac-roles-cfzgr6r1.js} +8 -6
  106. package/dist/{rbac-roles-t78xt39b.js → rbac-roles-cmxaddmy.js} +7 -5
  107. package/dist/{render-v2-7p8r2ymg.js → render-v2-f9hzff72.js} +9 -6
  108. package/dist/{seed-pricing-hf889enh.js → seed-pricing-jggk6rsy.js} +7 -5
  109. package/dist/{setup-w6mb03jv.js → setup-8mfceb9t.js} +4 -5
  110. package/dist/virtual-modules-ax661xp0.js +54 -0
  111. package/dist/{work-objects-nt8gy1kj.js → work-objects-vdsdcczj.js} +32 -28
  112. package/dist/{worker-dwy617t0.js → worker-cd0nhyv1.js} +44 -39
  113. package/openapi.json +36 -14
  114. package/package.json +6 -6
  115. package/src/be/automation-preflight.ts +6 -4
  116. package/src/be/extensions/db.ts +35 -4
  117. package/src/be/models-catalog.ts +2 -0
  118. package/src/be/modelsdev-cache.json +213 -0
  119. package/src/be/rbac-audit.ts +2 -0
  120. package/src/be/seed-pricing.ts +3 -1
  121. package/src/be/swarm-config-guard.ts +1 -0
  122. package/src/commands/onboard/compose-generator.ts +6 -1
  123. package/src/commands/onboard/env-generator.ts +6 -1
  124. package/src/commands/onboard/manifest.ts +1 -0
  125. package/src/commands/onboard/steps/integration-slack.tsx +56 -16
  126. package/src/commands/onboard/steps/review.tsx +5 -0
  127. package/src/commands/onboard/types.ts +6 -0
  128. package/src/commands/onboard.tsx +5 -1
  129. package/src/commands/provider-credentials.ts +14 -2
  130. package/src/extensions/lifecycle.ts +15 -21
  131. package/src/http/agents.ts +2 -2
  132. package/src/http/config.ts +1 -1
  133. package/src/http/core.ts +5 -3
  134. package/src/http/extensions.ts +108 -34
  135. package/src/http/status.ts +22 -10
  136. package/src/prompts/base-prompt.ts +15 -7
  137. package/src/providers/claude-managed-models.ts +8 -0
  138. package/src/providers/codex-models.ts +15 -1
  139. package/src/providers/dsh-adapter.ts +248 -0
  140. package/src/providers/index.ts +5 -1
  141. package/src/providers/modelsdev-reasoning.json +50 -0
  142. package/src/rbac/legacy-policy.ts +15 -1
  143. package/src/rbac/types.ts +2 -0
  144. package/src/server.ts +7 -1
  145. package/src/slack/app.ts +32 -10
  146. package/src/slack/config.ts +29 -0
  147. package/src/telemetry.ts +2 -3
  148. package/src/tests/agents-harness-provider.test.ts +19 -0
  149. package/src/tests/agents-list-model-display.test.ts +60 -1
  150. package/src/tests/base-prompt.test.ts +25 -0
  151. package/src/tests/claude-managed-adapter.test.ts +6 -0
  152. package/src/tests/codex-gpt6-release.test.ts +97 -0
  153. package/src/tests/config-api-only-keys.test.ts +9 -0
  154. package/src/tests/context-window.test.ts +1 -0
  155. package/src/tests/dsh-adapter.test.ts +276 -0
  156. package/src/tests/e2e-nightly-report.test.ts +3 -3
  157. package/src/tests/e2e-redact.test.ts +2 -0
  158. package/src/tests/env-flag.test.ts +6 -0
  159. package/src/tests/extensions-db.test.ts +41 -0
  160. package/src/tests/extensions-http.test.ts +148 -4
  161. package/src/tests/extensions-lifecycle.test.ts +18 -0
  162. package/src/tests/extensions-mcp-tools.test.ts +9 -5
  163. package/src/tests/internal-ai/complete-structured.test.ts +32 -9
  164. package/src/tests/internal-ai/credentials.test.ts +4 -4
  165. package/src/tests/internal-ai/models.test.ts +20 -0
  166. package/src/tests/migration-046-budgets.test.ts +2 -0
  167. package/src/tests/model-control.test.ts +2 -2
  168. package/src/tests/onboard-compose.test.ts +14 -0
  169. package/src/tests/onboard-env.test.ts +17 -0
  170. package/src/tests/onboard-manifest.test.ts +12 -0
  171. package/src/tests/opus-5-5-registry.test.ts +48 -0
  172. package/src/tests/pricing-refresh.test.ts +1 -1
  173. package/src/tests/pricing-routes.test.ts +15 -3
  174. package/src/tests/provider-registration.test.ts +5 -2
  175. package/src/tests/rbac-admission-e2e.test.ts +54 -0
  176. package/src/tests/rbac-e2e-helpers.ts +2 -0
  177. package/src/tests/rbac-engine.test.ts +32 -0
  178. package/src/tests/rbac-wire-e2e.test.ts +33 -0
  179. package/src/tests/reasoning-effort.test.ts +8 -0
  180. package/src/tests/reload-config.test.ts +33 -0
  181. package/src/tests/session-costs-model-key-normalize.test.ts +1 -0
  182. package/src/tests/slack-config.test.ts +136 -0
  183. package/src/tests/slack-mode-lifecycle.test.ts +69 -0
  184. package/src/tests/status.test.ts +22 -2
  185. package/src/tests/task-tool-preload-mcp.test.ts +3 -1
  186. package/src/tests/telemetry-init.test.ts +18 -0
  187. package/src/tests/workflow-llm.test.ts +3 -3
  188. package/src/tools/extension-common.ts +4 -0
  189. package/src/tools/extension-install.ts +1 -1
  190. package/src/tools/extension-list.ts +4 -3
  191. package/src/types.ts +17 -8
  192. package/src/utils/context-window.ts +1 -0
  193. package/src/utils/credentials.ts +8 -0
  194. package/src/utils/internal-ai/complete-structured.ts +8 -0
  195. package/src/utils/internal-ai/models.ts +5 -5
  196. package/templates/schedules/daily-status-report/content.md +25 -12
  197. package/templates/schedules/daily-workflow-health-audit/content.md +1 -1
  198. package/templates/skills/swarm-extensions/content.md +9 -5
  199. package/dist/cli-4wppfkcm.js +0 -16384
  200. package/dist/cli-55qrx3q8.js +0 -6041
  201. package/dist/cli-cg4vnyh4.js +0 -305
  202. package/dist/cli-jyetr64j.js +0 -52
  203. package/dist/cli-pp9ax9pa.js +0 -520
  204. package/dist/cli-qa66qaf0.js +0 -6076
  205. package/dist/cli-qghpvca2.js +0 -590
  206. package/dist/cli-rembwgt8.js +0 -861
  207. package/dist/cli-tcvj7j2r.js +0 -1971
  208. package/dist/cli-vfcth11k.js +0 -36
  209. package/dist/cli-w1d1bgkk.js +0 -1422
  210. package/dist/cli-y6x0gqtg.js +0 -5977
  211. package/dist/es5-tj3tn4jj.js +0 -844
  212. package/dist/index-1kq5hw2p.js +0 -467
  213. package/dist/index-1p8wdstt.js +0 -7
  214. package/dist/index-8fdjzb2f.js +0 -100
  215. package/dist/index-bxmwzbgw.js +0 -365
  216. package/dist/index-nzerxd5z.js +0 -7
  217. package/dist/index-pyymnzat.js +0 -1168
  218. package/dist/index-qv6atbab.js +0 -619
  219. package/dist/index-rja32qa7.js +0 -801
  220. package/dist/index-tzg5srfp.js +0 -211
  221. package/dist/index-vhncw983.js +0 -90
  222. package/dist/index-xk036zsk.js +0 -859
  223. package/dist/index-zyazbzcb.js +0 -6
  224. package/dist/{claude-managed-setup-48dwg9bv.js → claude-managed-setup-f5pmsyrp.js} +3 -3
@@ -6,6 +6,7 @@ import {
6
6
  getExtensionByName,
7
7
  insertExtensionRun,
8
8
  listExtensionRuns,
9
+ listExtensionVersions,
9
10
  setExtensionState,
10
11
  } from "../be/extensions/db";
11
12
  import { enqueueAuditRow, flushAuditBuffer } from "../be/rbac-audit";
@@ -259,16 +260,159 @@ describe("/api/extensions HTTP", () => {
259
260
  expect((await response.json()).runs).toEqual([second, first]);
260
261
  });
261
262
 
262
- test("lead agents may install, workers may not, and agent installs stay disabled", async () => {
263
- expect((await install("minimal", workerId)).status).toBe(403);
264
- const response = await install("minimal", leadId);
263
+ test("worker installs are owned, disabled, and cannot activate their own extension", async () => {
264
+ const response = await install("minimal", workerId);
265
265
  expect(response.status).toBe(200);
266
266
  const extension = (await response.json()).extension as {
267
267
  enabled: boolean;
268
268
  createdByAgentId: string | null;
269
269
  };
270
270
  expect(extension.enabled).toBe(false);
271
- expect(extension.createdByAgentId).toBe(leadId);
271
+ expect(extension.createdByAgentId).toBe(workerId);
272
+ const stored = (await getExtensionByName("minimal"))!;
273
+ expect(stored.status).toBe("disabled");
274
+ expect(stored.agentId).toBeNull();
275
+ for (const operation of ["enable", "disable", "activate-version"]) {
276
+ expect(
277
+ (
278
+ await dispatch(`/api/extensions/${stored.id}/${operation}`, {
279
+ method: "POST",
280
+ agentId: workerId,
281
+ body: JSON.stringify({ version: 1 }),
282
+ })
283
+ ).status,
284
+ ).toBe(403);
285
+ }
286
+ expect(await listExtensionRuns(stored.id)).toHaveLength(0);
287
+ expect((await dispatch(`/api/extensions/${stored.id}`)).text).toContain(workerId);
288
+ expect((await dispatch("/api/extensions")).text).toContain(workerId);
289
+ const changed = await loadBundleFixture("minimal");
290
+ changed.files["hooks.ts"] += "\n// worker draft v2\n";
291
+ expect(
292
+ (
293
+ await dispatch("/api/extensions/install", {
294
+ method: "POST",
295
+ agentId: workerId,
296
+ body: JSON.stringify(changed),
297
+ })
298
+ ).status,
299
+ ).toBe(200);
300
+ expect(await getExtensionByName("minimal")).toMatchObject({
301
+ version: 2,
302
+ activeVersion: 1,
303
+ enabled: false,
304
+ status: "disabled",
305
+ createdByAgentId: workerId,
306
+ });
307
+ const versions = await listExtensionVersions(stored.id);
308
+ expect(versions).toHaveLength(2);
309
+ expect(versions.every((version) => version.changedByAgentId === workerId)).toBe(true);
310
+ });
311
+
312
+ test("install never evaluates worker bundle code", async () => {
313
+ const bundle = await loadBundleFixture("minimal");
314
+ bundle.files["hooks.ts"] += '\nthrow new Error("must only run on activation");\n';
315
+ const response = await dispatch("/api/extensions/install", {
316
+ method: "POST",
317
+ agentId: workerId,
318
+ body: JSON.stringify(bundle),
319
+ });
320
+ expect(response.status).toBe(200);
321
+ const stored = (await getExtensionByName("minimal"))!;
322
+ expect(stored).toMatchObject({ enabled: false, status: "disabled", agentId: null });
323
+ expect(await listExtensionRuns(stored.id)).toHaveLength(0);
324
+ const activation = await dispatch(`/api/extensions/${stored.id}/enable`, { method: "POST" });
325
+ expect(activation.status).toBe(500);
326
+ expect(activation.text).toContain("must only run on activation");
327
+ });
328
+
329
+ test("workers cannot overwrite, patch, or delete another owner's extension", async () => {
330
+ await install("minimal", leadId);
331
+ const stored = (await getExtensionByName("minimal"))!;
332
+ expect((await install("minimal", workerId)).status).toBe(403);
333
+ for (const method of ["PATCH", "DELETE"]) {
334
+ expect(
335
+ (
336
+ await dispatch(`/api/extensions/${stored.id}`, {
337
+ method,
338
+ agentId: workerId,
339
+ ...(method === "PATCH" ? { body: JSON.stringify({ priority: 7 }) } : {}),
340
+ })
341
+ ).status,
342
+ ).toBe(403);
343
+ }
344
+ expect(await getExtensionByName("minimal")).toMatchObject({
345
+ createdByAgentId: leadId,
346
+ priority: 100,
347
+ version: 1,
348
+ });
349
+ });
350
+
351
+ test("worker owners edit and delete disabled drafts, but cannot reload live code", async () => {
352
+ await install("minimal", workerId);
353
+ const stored = (await getExtensionByName("minimal"))!;
354
+ const path = `/api/extensions/${stored.id}`;
355
+ expect(
356
+ (
357
+ await dispatch(path, {
358
+ method: "PATCH",
359
+ agentId: workerId,
360
+ body: JSON.stringify({ priority: 7 }),
361
+ })
362
+ ).status,
363
+ ).toBe(200);
364
+ expect((await dispatch(`${path}/enable`, { method: "POST", agentId: leadId })).status).toBe(
365
+ 200,
366
+ );
367
+ expect(
368
+ (
369
+ await dispatch(path, {
370
+ method: "PATCH",
371
+ agentId: workerId,
372
+ body: JSON.stringify({ config: { live: true } }),
373
+ })
374
+ ).status,
375
+ ).toBe(403);
376
+ const changed = await loadBundleFixture("minimal");
377
+ changed.files["hooks.ts"] += '\nthrow new Error("inactive worker version executed");\n';
378
+ expect(
379
+ (
380
+ await dispatch("/api/extensions/install", {
381
+ method: "POST",
382
+ agentId: workerId,
383
+ body: JSON.stringify({ ...changed, config: { live: true } }),
384
+ })
385
+ ).status,
386
+ ).toBe(403);
387
+ expect(
388
+ (
389
+ await dispatch("/api/extensions/install", {
390
+ method: "POST",
391
+ agentId: workerId,
392
+ body: JSON.stringify(changed),
393
+ })
394
+ ).status,
395
+ ).toBe(200);
396
+ // A permitted reload must keep using version 1, never evaluate the worker draft.
397
+ expect(
398
+ (
399
+ await dispatch(path, {
400
+ method: "PATCH",
401
+ agentId: leadId,
402
+ body: JSON.stringify({ priority: 7 }),
403
+ })
404
+ ).status,
405
+ ).toBe(200);
406
+ expect(await getExtensionByName("minimal")).toMatchObject({
407
+ version: 2,
408
+ activeVersion: 1,
409
+ enabled: true,
410
+ configJson: "{}",
411
+ });
412
+ expect((await dispatch(path, { method: "DELETE", agentId: workerId })).status).toBe(409);
413
+ expect((await dispatch(`${path}/disable`, { method: "POST" })).status).toBe(200);
414
+ expect((await dispatch(path, { method: "DELETE", agentId: workerId })).status).toBe(200);
415
+ expect(await getExtensionByName("minimal")).toBeNull();
272
416
  });
273
417
 
274
418
  test("operator reinstall activates a new version when the extension is enabled", async () => {
@@ -81,6 +81,24 @@ describe("extension lifecycle", () => {
81
81
  expect(listRegistered()).toHaveLength(0);
82
82
  });
83
83
 
84
+ test("activation loads its immutable snapshot, not a concurrently advanced file projection", async () => {
85
+ const extension = await install(await loadBundleFixture("minimal"));
86
+ // Simulate a worker draft replacing the mutable files after activation read its row.
87
+ await getDbClient().run("UPDATE extension_files SET content = ? WHERE extensionId = ?", [
88
+ 'throw new Error("unapproved draft executed");',
89
+ extension.id,
90
+ ]);
91
+ const enabled = await enableExtension(extension.id);
92
+ expect(enabled).toMatchObject({ enabled: true, activeVersion: 1 });
93
+ expect(
94
+ await dispatchPre("pre.task.create", {
95
+ options: {},
96
+ description: "snapshot check",
97
+ origin: "rest",
98
+ }),
99
+ ).toEqual({ action: "continue" });
100
+ });
101
+
84
102
  test("activate-version swaps source and reloads an enabled extension", async () => {
85
103
  const bundle = await loadBundleFixture("priority-a");
86
104
  const extension = await install(bundle);
@@ -200,16 +200,20 @@ describe("extension MCP HTTP proxy tools", () => {
200
200
  expect(result.structuredContent.message).toBeTruthy();
201
201
  });
202
202
 
203
- test("worker installation returns the REST permission error in the shared tool envelope", async () => {
203
+ test("worker installation returns ownership and a disabled draft in the shared tool envelope", async () => {
204
204
  const tools = buildToolServer();
205
205
  const result = (await tools.install.handler(
206
206
  await loadBundleFixture("minimal"),
207
207
  meta(workerId),
208
208
  )) as ToolResult;
209
209
 
210
- expect(result.isError).toBe(true);
211
- expect(result.structuredContent).toMatchObject({ success: false });
212
- expect(String(result.structuredContent.message)).toContain("Forbidden");
210
+ expect(result.isError).toBe(false);
211
+ expect(result.structuredContent).toMatchObject({
212
+ success: true,
213
+ createdByAgentId: workerId,
214
+ enabled: false,
215
+ status: "disabled",
216
+ });
213
217
  });
214
218
 
215
219
  test("lead reinstall stores an inactive version of an enabled extension", async () => {
@@ -254,7 +258,7 @@ describe("extension MCP HTTP proxy tools", () => {
254
258
  const all = (await tools.list.handler({}, meta(leadId))) as ToolResult;
255
259
  expect(all.isError).toBe(false);
256
260
  expect(all.content[0]?.text).toContain(
257
- "| Name | Version | Active | Enabled | Status | Priority | Failures |",
261
+ "| Name | Owner agent | Version | Active | Enabled | Status | Priority | Failures |",
258
262
  );
259
263
  expect(all.structuredContent).toMatchObject({ success: true });
260
264
  expect((all.structuredContent.extensions as unknown[]).length).toBe(1);
@@ -51,7 +51,7 @@ describe("completeStructured", () => {
51
51
  _credentialOverride: {
52
52
  kind: "openrouter",
53
53
  apiKey: "test",
54
- modelDefault: "openrouter/google/gemini-3-flash-preview",
54
+ modelDefault: "openrouter/deepseek/deepseek-v4.1-flash",
55
55
  },
56
56
  _complete: async () => {
57
57
  invocations++;
@@ -98,7 +98,7 @@ describe("completeStructured", () => {
98
98
  _credentialOverride: {
99
99
  kind: "openrouter",
100
100
  apiKey: "test",
101
- modelDefault: "openrouter/google/gemini-3-flash-preview",
101
+ modelDefault: "openrouter/deepseek/deepseek-v4.1-flash",
102
102
  },
103
103
  _complete: async () => {
104
104
  invocations++;
@@ -111,18 +111,41 @@ describe("completeStructured", () => {
111
111
  });
112
112
  }
113
113
 
114
+ test("unknown catalog model returns null without calling complete", async () => {
115
+ let invocations = 0;
116
+ const result = await completeStructured({
117
+ zodSchema: ResultZodSchema,
118
+ toolSchema: ResultToolSchema,
119
+ toolName: "record_result",
120
+ toolDescription: "Record the result.",
121
+ systemPrompt: "sys",
122
+ userPrompt: "user",
123
+ _credentialOverride: {
124
+ kind: "openai-codex",
125
+ apiKey: "test",
126
+ modelDefault: "openai-codex/nonexistent-model",
127
+ },
128
+ _complete: async () => {
129
+ invocations++;
130
+ return makeMsg([]);
131
+ },
132
+ });
133
+ expect(result).toBeNull();
134
+ expect(invocations).toBe(0);
135
+ });
136
+
114
137
  test("passes a provider-compatible forced tool choice", async () => {
115
138
  const credentials: ResolvedCredential[] = [
116
139
  {
117
140
  kind: "openrouter",
118
141
  apiKey: "test",
119
- modelDefault: "openrouter/google/gemini-3-flash-preview",
142
+ modelDefault: "openrouter/deepseek/deepseek-v4.1-flash",
120
143
  },
121
- { kind: "openai", apiKey: "test", modelDefault: "openai/gpt-5.4-mini" },
144
+ { kind: "openai", apiKey: "test", modelDefault: "openai/gpt-6-luna" },
122
145
  {
123
146
  kind: "openai-codex",
124
147
  apiKey: "test",
125
- modelDefault: "openai-codex/gpt-5.4-mini",
148
+ modelDefault: "openai-codex/gpt-6-luna",
126
149
  },
127
150
  {
128
151
  kind: "anthropic",
@@ -172,7 +195,7 @@ describe("completeStructured", () => {
172
195
  _credentialOverride: {
173
196
  kind: "openrouter",
174
197
  apiKey: "test",
175
- modelDefault: "openrouter/google/gemini-3-flash-preview",
198
+ modelDefault: "openrouter/deepseek/deepseek-v4.1-flash",
176
199
  },
177
200
  _complete: async () => {
178
201
  invocations++;
@@ -210,7 +233,7 @@ describe("completeStructured", () => {
210
233
  _credentialOverride: {
211
234
  kind: "openrouter",
212
235
  apiKey: "test",
213
- modelDefault: "openrouter/google/gemini-3-flash-preview",
236
+ modelDefault: "openrouter/deepseek/deepseek-v4.1-flash",
214
237
  },
215
238
  _complete: async () => {
216
239
  invocations++;
@@ -241,7 +264,7 @@ describe("completeStructured", () => {
241
264
  _credentialOverride: {
242
265
  kind: "openrouter",
243
266
  apiKey: "test",
244
- modelDefault: "openrouter/google/gemini-3-flash-preview",
267
+ modelDefault: "openrouter/deepseek/deepseek-v4.1-flash",
245
268
  },
246
269
  _complete: async () => {
247
270
  invocations++;
@@ -418,7 +441,7 @@ describe("completeStructured", () => {
418
441
  _credentialOverride: {
419
442
  kind: "openrouter",
420
443
  apiKey: "test",
421
- modelDefault: "openrouter/google/gemini-3-flash-preview",
444
+ modelDefault: "openrouter/deepseek/deepseek-v4.1-flash",
422
445
  },
423
446
  _complete: async () =>
424
447
  makeMsg([
@@ -30,7 +30,7 @@ describe("resolveCredential", () => {
30
30
  expect(cred?.kind).toBe("openrouter");
31
31
  if (cred?.kind === "openrouter") {
32
32
  expect(cred.apiKey).toBe("or-1");
33
- expect(cred.modelDefault).toBe("openrouter/google/gemini-3-flash-preview");
33
+ expect(cred.modelDefault).toBe("openrouter/deepseek/deepseek-v4.1-flash");
34
34
  }
35
35
  });
36
36
 
@@ -50,7 +50,7 @@ describe("resolveCredential", () => {
50
50
  expect(cred?.kind).toBe("openai");
51
51
  if (cred?.kind === "openai") {
52
52
  expect(cred.apiKey).toBe("sk-o-1");
53
- expect(cred.modelDefault).toBe("openai/gpt-5.4-mini");
53
+ expect(cred.modelDefault).toBe("openai/gpt-6-luna");
54
54
  }
55
55
  });
56
56
 
@@ -79,7 +79,7 @@ describe("resolveCredential", () => {
79
79
  expect(cred?.kind).toBe("openai-codex");
80
80
  if (cred?.kind === "openai-codex") {
81
81
  expect(cred.apiKey).toBe("example-codex-api-key-derived");
82
- expect(cred.modelDefault).toBe("openai-codex/gpt-5.4-mini");
82
+ expect(cred.modelDefault).toBe("openai-codex/gpt-6-luna");
83
83
  }
84
84
  });
85
85
 
@@ -101,7 +101,7 @@ describe("resolveCredential", () => {
101
101
  expect(cred).toEqual({
102
102
  kind: "openai-codex",
103
103
  apiKey: "example-at_provider_runtime",
104
- modelDefault: "openai-codex/gpt-5.4-mini",
104
+ modelDefault: "openai-codex/gpt-6-luna",
105
105
  });
106
106
  });
107
107
 
@@ -0,0 +1,20 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { getBuiltinModel } from "@earendil-works/pi-ai/providers/all";
3
+ import { DEFAULT_MODEL, parseModelStr } from "../../utils/internal-ai/models.js";
4
+
5
+ describe("internal-ai default models", () => {
6
+ for (const [kind, modelString] of Object.entries(DEFAULT_MODEL)) {
7
+ if (kind === "claude-cli") continue;
8
+
9
+ test(`${kind} resolves in the pinned pi-ai catalog`, () => {
10
+ const [provider, modelId] = parseModelStr(modelString);
11
+ const model = getBuiltinModel(
12
+ provider as Parameters<typeof getBuiltinModel>[0],
13
+ modelId as never,
14
+ );
15
+ expect(model).toBeDefined();
16
+ expect(model.id).toBe(modelId);
17
+ expect(model.provider).toBe(provider);
18
+ });
19
+ }
20
+ });
@@ -21,6 +21,8 @@ async function removeDbFiles(path: string): Promise<void> {
21
21
 
22
22
  beforeAll(() => {
23
23
  initDb(TEST_DB_PATH);
24
+ // Mirror API boot: new releases are snapshot-seeded, not new migrations.
25
+ seedPricingFromModelsDev({ quiet: true });
24
26
  });
25
27
 
26
28
  afterAll(async () => {
@@ -505,10 +505,10 @@ describe("Model Control - Priority Resolution Logic", () => {
505
505
  expect(resolveModelTier({ tier: "smart", harnessProvider: "codex" })).toBe("gpt-5.6-sol");
506
506
  expect(resolveModelTier({ tier: "ultra", harnessProvider: "codex" })).toBe("gpt-5.6-sol");
507
507
  expect(resolveModelTier({ tier: "smart", harnessProvider: "opencode" })).toBe(
508
- "openrouter/deepseek/deepseek-v4-pro",
508
+ "openrouter/deepseek/deepseek-v4-pro-0813",
509
509
  );
510
510
  expect(resolveModelTier({ tier: "ultra", harnessProvider: "pi" })).toBe(
511
- "openrouter/anthropic/claude-opus-4.8",
511
+ "openrouter/anthropic/claude-opus-5.5",
512
512
  );
513
513
  });
514
514
 
@@ -8,6 +8,11 @@ function makeState(overrides: Partial<OnboardState>): OnboardState {
8
8
  return { ...INITIAL_STATE, ...overrides };
9
9
  }
10
10
 
11
+ // biome-ignore lint/suspicious/noTemplateCurlyInString: Docker Compose env var syntax
12
+ const COMPOSE_SLACK_MODE = "SLACK_MODE=${SLACK_MODE:-socket}";
13
+ // biome-ignore lint/suspicious/noTemplateCurlyInString: Docker Compose env var syntax
14
+ const COMPOSE_SLACK_SIGNING_SECRET = "SLACK_SIGNING_SECRET=${SLACK_SIGNING_SECRET}";
15
+
11
16
  describe("generateCompose", () => {
12
17
  test("uses the full preset by default and includes every official template once", () => {
13
18
  const full = getPresetById("full");
@@ -286,6 +291,8 @@ describe("generateCompose", () => {
286
291
  // Slack vars on API service
287
292
  expect(yaml).toContain("SLACK_BOT_TOKEN");
288
293
  expect(yaml).toContain("SLACK_APP_TOKEN");
294
+ expect(yaml).toContain(COMPOSE_SLACK_MODE);
295
+ expect(yaml).not.toContain("SLACK_SIGNING_SECRET");
289
296
  // GitLab vars on agent services
290
297
  expect(yaml).toContain("GITLAB_TOKEN");
291
298
  expect(yaml).toContain("GITLAB_EMAIL");
@@ -297,6 +304,13 @@ describe("generateCompose", () => {
297
304
  expect(yaml).toContain("SLACK_DISABLE=false");
298
305
  });
299
306
 
307
+ test("HTTP Slack compose configuration uses the signing secret and no app token", () => {
308
+ const yaml = generateCompose({ ...allIntegrationsState, slackMode: "http" });
309
+ expect(yaml).toContain(COMPOSE_SLACK_MODE);
310
+ expect(yaml).toContain(COMPOSE_SLACK_SIGNING_SECRET);
311
+ expect(yaml).not.toContain("SLACK_APP_TOKEN");
312
+ });
313
+
300
314
  // ── No integrations ──
301
315
 
302
316
  test("no integrations omits integration env vars from agent services", () => {
@@ -99,9 +99,26 @@ describe("generateEnv", () => {
99
99
  const env = generateEnv(state);
100
100
  expect(env).toContain("SLACK_BOT_TOKEN=example-xoxb-slack-bot");
101
101
  expect(env).toContain("SLACK_APP_TOKEN=example-xapp-slack-app");
102
+ expect(env).toContain("SLACK_MODE=socket");
103
+ expect(env).not.toContain("SLACK_SIGNING_SECRET=");
102
104
  expect(env).toContain("SLACK_DISABLE=false");
103
105
  });
104
106
 
107
+ test("Slack HTTP mode emits only its mode-specific credential", () => {
108
+ const env = generateEnv(
109
+ makeState({
110
+ integrations: { github: false, slack: true, gitlab: false, sentry: false },
111
+ slackMode: "http",
112
+ slackBotToken: "example-xoxb-slack-bot",
113
+ slackSigningSecret: "synthetic-signing-secret",
114
+ }),
115
+ );
116
+ expect(env).toContain("SLACK_MODE=http");
117
+ expect(env).toContain("SLACK_BOT_TOKEN=example-xoxb-slack-bot");
118
+ expect(env).toContain("SLACK_SIGNING_SECRET=synthetic-signing-secret");
119
+ expect(env).not.toContain("SLACK_APP_TOKEN=");
120
+ });
121
+
105
122
  // ── No integrations: only core section ──
106
123
 
107
124
  test("no integrations outputs disable flags and no integration credentials", () => {
@@ -36,6 +36,7 @@ describe("generateManifest", () => {
36
36
  expect(manifest).toHaveProperty("harness");
37
37
  expect(manifest).toHaveProperty("services");
38
38
  expect(manifest).toHaveProperty("integrations");
39
+ expect(manifest).toHaveProperty("slackMode");
39
40
  expect(manifest).toHaveProperty("composePath");
40
41
  expect(manifest).toHaveProperty("envPath");
41
42
  expect(manifest).toHaveProperty("apiUrl");
@@ -112,6 +113,17 @@ describe("generateManifest", () => {
112
113
  expect(integrations.slack).toBe(false);
113
114
  expect(integrations.gitlab).toBe(false);
114
115
  expect(integrations.sentry).toBe(true);
116
+ expect(manifest.slackMode).toBe("socket");
117
+ });
118
+
119
+ test("records HTTP transport without persisting its signing secret", () => {
120
+ const manifest = generateManifest({
121
+ ...devState,
122
+ slackMode: "http",
123
+ slackSigningSecret: "synthetic-signing-secret",
124
+ }) as Record<string, unknown>;
125
+ expect(manifest.slackMode).toBe("http");
126
+ expect(JSON.stringify(manifest)).not.toContain("synthetic-signing-secret");
115
127
  });
116
128
 
117
129
  test("all integrations false when none enabled", () => {
@@ -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
  );