@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
@@ -898,6 +898,12 @@ describe("ClaudeManagedAdapter (Phase 4) — repo provisioning + cost data", ()
898
898
  cacheReadPerMillion: 0.25,
899
899
  cacheWritePerMillion: 12.5,
900
900
  });
901
+ expect(CLAUDE_MANAGED_MODEL_PRICING["claude-opus-5-5"]).toEqual({
902
+ inputPerMillion: 4.0,
903
+ outputPerMillion: 20.0,
904
+ cacheReadPerMillion: 0.2,
905
+ cacheWritePerMillion: 5.0,
906
+ });
901
907
  expect(CLAUDE_MANAGED_MODEL_PRICING["claude-opus-5"]).toEqual({
902
908
  inputPerMillion: 5.0,
903
909
  outputPerMillion: 25.0,
@@ -0,0 +1,97 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { modelGroupsForHarness } from "../../apps/ui/src/lib/agent-runtime-models";
3
+ import { buildModelsCatalog } from "../be/models-catalog";
4
+ import cache from "../be/modelsdev-cache.json";
5
+ import { buildModelsDevSeedRows } from "../be/seed-pricing";
6
+ import {
7
+ CODEX_DEFAULT_MODEL,
8
+ CODEX_MODELS,
9
+ computeCodexCostUsd,
10
+ FALLBACK_CODEX_MODEL_PRICING,
11
+ getCodexContextWindow,
12
+ resolveCodexModel,
13
+ } from "../providers/codex-models";
14
+ import { applyReasoningEffort, reasoningCapability } from "../providers/reasoning-effort";
15
+
16
+ const releases = [
17
+ { id: "gpt-6-sol", label: "GPT-6 Sol", input: 2, output: 10, read: 0.2, write: 2.5 },
18
+ { id: "gpt-6-luna", label: "GPT-6 Luna", input: 0.1, output: 0.5, read: 0.01, write: 0.125 },
19
+ ] as const;
20
+
21
+ for (const release of releases) {
22
+ describe(release.id, () => {
23
+ test("uses the exact bare CLI ID with verified context and base costs", () => {
24
+ expect(CODEX_MODELS).toContain(release.id);
25
+ expect(resolveCodexModel(release.id.toUpperCase())).toBe(release.id);
26
+ expect(getCodexContextWindow(release.id)).toBe(1_050_000);
27
+ expect(cache.openai.models[release.id].limit.output).toBe(128_000);
28
+ expect(FALLBACK_CODEX_MODEL_PRICING[release.id]).toEqual({
29
+ inputPerMillion: release.input,
30
+ cachedInputPerMillion: release.read,
31
+ outputPerMillion: release.output,
32
+ });
33
+ expect(computeCodexCostUsd(release.id, 1_000_000, 250_000, 100_000)).toBeCloseTo(
34
+ release.input * 0.75 + release.read * 0.25 + release.output * 0.1,
35
+ 8,
36
+ );
37
+ });
38
+
39
+ test("exposes the full reasoning range and transports none/max correctly", () => {
40
+ expect(reasoningCapability("codex", release.id)).toEqual({
41
+ supported: true,
42
+ levels: ["off", "low", "medium", "high", "xhigh", "max"],
43
+ default: "medium",
44
+ });
45
+ for (const [effort, value] of [
46
+ ["off", "none"],
47
+ ["max", "max"],
48
+ ] as const) {
49
+ expect(applyReasoningEffort("codex", release.id, effort)).toEqual({
50
+ kind: "codex-config",
51
+ config: { model_reasoning_effort: value },
52
+ });
53
+ }
54
+ });
55
+
56
+ test("seeds all four token classes under canonical Codex IDs", () => {
57
+ const rows = buildModelsDevSeedRows(cache).filter(
58
+ (row) => row.provider === "codex" && row.model === release.id,
59
+ );
60
+ expect(
61
+ Object.fromEntries(rows.map((row) => [row.tokenClass, row.pricePerMillionUsd])),
62
+ ).toEqual({
63
+ input: release.input,
64
+ output: release.output,
65
+ cached_input: release.read,
66
+ cache_write: release.write,
67
+ });
68
+ expect(cache.openai.models[release.id].cost.tiers).toEqual([
69
+ {
70
+ input: release.input * 2,
71
+ output: release.output * 1.5,
72
+ cache_read: release.read * 2,
73
+ cache_write: release.write * 2,
74
+ tier: { type: "context", size: 272000 },
75
+ },
76
+ ]);
77
+ });
78
+
79
+ test("is available through the catalog and direct UI selector", () => {
80
+ const model = buildModelsCatalog(cache).openai?.models[release.id];
81
+ expect(model?.limit?.context).toBe(1_050_000);
82
+ expect(model?.cost).toEqual({ input: release.input, output: release.output });
83
+ const option = modelGroupsForHarness("codex", undefined, undefined)
84
+ .flatMap((group) => group.models)
85
+ .find((entry) => entry.id === release.id);
86
+ expect(option?.label).toBe(release.label);
87
+ expect(option?.reasoningLevels).toEqual(["off", "low", "medium", "high", "xhigh", "max"]);
88
+ });
89
+ });
90
+ }
91
+
92
+ test("release additions preserve rollout-safe defaults and aliases", () => {
93
+ expect(CODEX_DEFAULT_MODEL).toBe("gpt-5.6-terra");
94
+ expect(resolveCodexModel("sonnet")).toBe("gpt-5.6-terra");
95
+ expect(resolveCodexModel("haiku")).toBe("gpt-5.6-luna");
96
+ expect(resolveCodexModel("opus")).toBe("gpt-5.6-sol");
97
+ });
@@ -50,6 +50,12 @@ beforeAll(async () => {
50
50
  value: BOOTSTRAP_VALUE,
51
51
  isSecret: true,
52
52
  });
53
+ await upsertSwarmConfig({
54
+ scope: "global",
55
+ key: "SLACK_SIGNING_SECRET",
56
+ value: "synthetic-api-only-signing-secret",
57
+ isSecret: true,
58
+ });
53
59
  await upsertSwarmConfig({
54
60
  scope: "global",
55
61
  key: "SOME_WORKER_SECRET",
@@ -86,6 +92,7 @@ describe("API-only config keys are never served over HTTP", () => {
86
92
  test("GET /api/config/resolved WITHOUT agentId strips the bootstrap key", async () => {
87
93
  const { configs } = await getJson("/api/config/resolved?includeSecrets=true");
88
94
  expect(configs.some((c) => c.key === BOOTSTRAP_KEY)).toBe(false);
95
+ expect(configs.some((c) => c.key === "SLACK_SIGNING_SECRET")).toBe(false);
89
96
  // A legitimate worker secret still comes through.
90
97
  expect(configs.some((c) => c.key === "SOME_WORKER_SECRET")).toBe(true);
91
98
  });
@@ -95,11 +102,13 @@ describe("API-only config keys are never served over HTTP", () => {
95
102
  `/api/config/resolved?includeSecrets=true&agentId=${agentId}`,
96
103
  );
97
104
  expect(configs.some((c) => c.key === BOOTSTRAP_KEY)).toBe(false);
105
+ expect(configs.some((c) => c.key === "SLACK_SIGNING_SECRET")).toBe(false);
98
106
  });
99
107
 
100
108
  test("GET /api/config?scope=global strips the bootstrap key", async () => {
101
109
  const { configs } = await getJson("/api/config?scope=global&includeSecrets=true");
102
110
  expect(configs.some((c) => c.key === BOOTSTRAP_KEY)).toBe(false);
111
+ expect(configs.some((c) => c.key === "SLACK_SIGNING_SECRET")).toBe(false);
103
112
  expect(configs.some((c) => c.key === "SOME_WORKER_SECRET")).toBe(true);
104
113
  });
105
114
  });
@@ -18,6 +18,7 @@ describe("getContextWindowSize", () => {
18
18
  });
19
19
 
20
20
  test("returns 1M for opus models", () => {
21
+ expect(getContextWindowSize("claude-opus-5-5")).toBe(1_000_000);
21
22
  expect(getContextWindowSize("claude-opus-5")).toBe(1_000_000);
22
23
  expect(getContextWindowSize("claude-opus-4-8")).toBe(1_000_000);
23
24
  expect(getContextWindowSize("claude-opus-4-7")).toBe(1_000_000);
@@ -0,0 +1,276 @@
1
+ import { afterEach, describe, expect, test } from "bun:test";
2
+ import { chmod, mkdtemp, rename, rm } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { checkProviderCredentials } from "../commands/provider-credentials";
6
+ import { createProviderAdapter } from "../providers";
7
+ import { checkDshCredentials, DshAdapter } from "../providers/dsh-adapter";
8
+ import type { ProviderEvent, ProviderSessionConfig } from "../providers/types";
9
+ import { DEFAULT_MODEL_TIER_MAP } from "../types";
10
+ import { getModelAwareCredentialVars } from "../utils/credentials";
11
+ import { resolveHarnessProvider } from "../utils/harness-provider";
12
+ import { DEFAULT_OPENROUTER_BASE_URL } from "../utils/openrouter-base-url";
13
+
14
+ const directories: string[] = [];
15
+ afterEach(async () => {
16
+ await Promise.all(
17
+ directories.splice(0).map((path) => rm(path, { recursive: true, force: true })),
18
+ );
19
+ });
20
+
21
+ async function fixture(mode = "success"): Promise<ProviderSessionConfig> {
22
+ const cwd = await mkdtemp(join(tmpdir(), "dsh-adapter-test-"));
23
+ directories.push(cwd);
24
+ const binary = join(cwd, "dsh");
25
+ await Bun.write(
26
+ binary,
27
+ `#!${process.execPath}
28
+ const args = process.argv.slice(2);
29
+ const patchPath = args[args.indexOf("--patch") + 1];
30
+ const patch = await Bun.file(patchPath).json();
31
+ const prompt = await Bun.stdin.text();
32
+ await Bun.write("invocation.json", JSON.stringify({ args, patchPath, patch, prompt, cwd: process.cwd() }));
33
+ const emit = (event) => console.log(JSON.stringify(event));
34
+ emit({ type: "session", sessionId: "dsh-test-session" });
35
+ const mode = process.env.DSH_TEST_MODE;
36
+ if (mode === "abort") { setInterval(() => {}, 1000); }
37
+ else {
38
+ if (mode === "malformed") console.log("bad json");
39
+ emit({ type: "status", phase: "turn_end", reason: { kind: mode === "failure" ? "error" : "completed" } });
40
+ if (mode !== "missing-final") {
41
+ // A split UTF-8 character plus a final line without a newline exercises both decoders.
42
+ const line = Buffer.from(JSON.stringify({ type: "final", text: "Done ✓" }));
43
+ const split = line.indexOf(Buffer.from("✓")) + 1;
44
+ process.stdout.write(line.subarray(0, split));
45
+ await Bun.sleep(10);
46
+ process.stdout.write(line.subarray(split));
47
+ }
48
+ if (mode === "failure") { console.error(process.env.DEEPSEEK_API_KEY); process.exitCode = 7; }
49
+ }
50
+ `,
51
+ );
52
+ await chmod(binary, 0o700);
53
+ return {
54
+ prompt: "--not-a-flag\nKeep $() and `quotes` literal.",
55
+ systemPrompt: "System instructions:\n!!js must remain text",
56
+ model: "deepseek-v4-pro",
57
+ role: "worker",
58
+ agentId: "agent-test",
59
+ taskId: "task-test",
60
+ apiUrl: "http://unused.invalid",
61
+ apiKey: "unused",
62
+ cwd,
63
+ logFile: join(cwd, "log.jsonl"),
64
+ env: {
65
+ DSH_BINARY: binary,
66
+ DEEPSEEK_API_KEY: "dsh-test-credential-value",
67
+ OPENROUTER_API_KEY: "",
68
+ OPENROUTER_BASE_URL: "",
69
+ DSH_TEST_MODE: mode,
70
+ },
71
+ };
72
+ }
73
+
74
+ describe("dsh harness", () => {
75
+ test("registry, model defaults, and credential dispatch select dsh", async () => {
76
+ expect(await createProviderAdapter("dsh")).toBeInstanceOf(DshAdapter);
77
+ expect(resolveHarnessProvider({ HARNESS_PROVIDER: "dsh" }, {})).toBe("dsh");
78
+ expect(DEFAULT_MODEL_TIER_MAP.dsh).toEqual(DEFAULT_MODEL_TIER_MAP.pi);
79
+ expect(checkDshCredentials({ OPENAI_API_KEY: "unrelated" }).ready).toBe(false);
80
+ expect(checkDshCredentials({ DEEPSEEK_API_KEY: " " }).ready).toBe(false);
81
+ expect(checkDshCredentials({}).missing).toEqual(["DEEPSEEK_API_KEY", "OPENROUTER_API_KEY"]);
82
+ expect((await checkProviderCredentials("dsh", { OPENROUTER_API_KEY: "present" })).ready).toBe(
83
+ true,
84
+ );
85
+ expect(getModelAwareCredentialVars("dsh", DEFAULT_MODEL_TIER_MAP.dsh.ultra)).toEqual([
86
+ "OPENROUTER_API_KEY",
87
+ ]);
88
+ expect(getModelAwareCredentialVars("dsh", "deepseek-v4-pro")).toEqual(["DEEPSEEK_API_KEY"]);
89
+ expect((await checkProviderCredentials("dsh", { DEEPSEEK_API_KEY: "present" })).ready).toBe(
90
+ true,
91
+ );
92
+ });
93
+
94
+ test("passes prompt on stdin, patches model/system, preserves cwd and cleans up", async () => {
95
+ const config = await fixture();
96
+ const session = await new DshAdapter().createSession(config);
97
+ const events: ProviderEvent[] = [];
98
+ session.onEvent((event) => events.push(event));
99
+ expect(await session.waitForCompletion()).toMatchObject({
100
+ exitCode: 0,
101
+ isError: false,
102
+ output: "Done ✓",
103
+ sessionId: "dsh-test-session",
104
+ });
105
+ const invocation = await Bun.file(join(config.cwd, "invocation.json")).json();
106
+ expect(invocation.prompt).toBe(config.prompt);
107
+ expect(invocation.cwd).toBe(config.cwd);
108
+ expect(invocation.args).toEqual([
109
+ "--profile",
110
+ "headless",
111
+ "--patch",
112
+ invocation.patchPath,
113
+ "--json",
114
+ "-",
115
+ ]);
116
+ expect(invocation.patch).toContainEqual({
117
+ id: "agent-default-model",
118
+ config: { provider: "deepseek-official", model: config.model },
119
+ });
120
+ expect(invocation.patch).toContainEqual({
121
+ id: "system-prompt",
122
+ config: { personaPrefix: config.systemPrompt },
123
+ });
124
+ expect(await Bun.file(invocation.patchPath).exists()).toBe(false);
125
+ expect(events).toContainEqual({
126
+ type: "session_init",
127
+ sessionId: "dsh-test-session",
128
+ provider: "dsh",
129
+ });
130
+ });
131
+
132
+ for (const model of [
133
+ DEFAULT_MODEL_TIER_MAP.dsh.regular,
134
+ DEFAULT_MODEL_TIER_MAP.dsh.ultra,
135
+ "openrouter/vendor/new-model",
136
+ ]) {
137
+ test(`routes ${model} through OpenRouter with only its key`, async () => {
138
+ const config = await fixture();
139
+ config.model = model;
140
+ config.env = {
141
+ ...config.env,
142
+ DEEPSEEK_API_KEY: "",
143
+ OPENROUTER_API_KEY: "openrouter-test-key",
144
+ };
145
+ const session = await new DshAdapter().createSession(config);
146
+ expect((await session.waitForCompletion()).isError).toBe(false);
147
+ const { patch } = await Bun.file(join(config.cwd, "invocation.json")).json();
148
+ const modelId = model.slice("openrouter/".length);
149
+ expect(patch).toContainEqual({
150
+ id: "agent-default-model",
151
+ config: { provider: "openrouter", model: modelId },
152
+ });
153
+ expect(patch).toContainEqual({
154
+ id: "llm-pi-ai",
155
+ config: {
156
+ providers: {
157
+ openrouter: {
158
+ apiKeyEnv: "OPENROUTER_API_KEY",
159
+ baseURL: DEFAULT_OPENROUTER_BASE_URL,
160
+ api: "openai-completions",
161
+ models: [{ id: modelId }],
162
+ },
163
+ },
164
+ },
165
+ });
166
+ expect(JSON.stringify(patch)).not.toContain("openrouter-test-key");
167
+ });
168
+ }
169
+
170
+ test("defaults to OpenRouter and honors its base URL when both keys exist", async () => {
171
+ const config = await fixture();
172
+ config.model = undefined;
173
+ config.env = {
174
+ ...config.env,
175
+ OPENROUTER_API_KEY: "openrouter-test-key",
176
+ OPENROUTER_BASE_URL: " https://gateway.example/proxy/v1/// ",
177
+ };
178
+ const session = await new DshAdapter().createSession(config);
179
+ expect((await session.waitForCompletion()).isError).toBe(false);
180
+ const { patch } = await Bun.file(join(config.cwd, "invocation.json")).json();
181
+ expect(patch[0].config).toEqual({
182
+ provider: "openrouter",
183
+ model: "deepseek/deepseek-v4.1-flash",
184
+ });
185
+ expect(patch[2].config.providers.openrouter.baseURL).toBe("https://gateway.example/proxy/v1");
186
+ });
187
+
188
+ test("bare models keep direct DeepSeek when both keys exist", async () => {
189
+ const config = await fixture();
190
+ config.env = { ...config.env, OPENROUTER_API_KEY: "openrouter-test-key" };
191
+ const session = await new DshAdapter().createSession(config);
192
+ expect((await session.waitForCompletion()).isError).toBe(false);
193
+ const { patch } = await Bun.file(join(config.cwd, "invocation.json")).json();
194
+ expect(patch[0].config.provider).toBe("deepseek-official");
195
+ expect(patch[2]).toEqual({ id: "llm-deepseek", config: { apiKeyEnv: "DEEPSEEK_API_KEY" } });
196
+ });
197
+
198
+ test("fails closed for absent or mismatched credentials and empty model IDs", async () => {
199
+ const config = await fixture();
200
+ config.env = { ...config.env, DEEPSEEK_API_KEY: "", OPENROUTER_API_KEY: "" };
201
+ await expect(new DshAdapter().createSession(config)).rejects.toThrow(
202
+ "DEEPSEEK_API_KEY or OPENROUTER_API_KEY",
203
+ );
204
+ config.env.DEEPSEEK_API_KEY = "direct-test-key";
205
+ config.model = DEFAULT_MODEL_TIER_MAP.dsh.regular;
206
+ await expect(new DshAdapter().createSession(config)).rejects.toThrow(
207
+ "requires OPENROUTER_API_KEY",
208
+ );
209
+ config.env = { ...config.env, DEEPSEEK_API_KEY: "", OPENROUTER_API_KEY: "openrouter-test-key" };
210
+ config.model = "deepseek-v4-pro";
211
+ await expect(new DshAdapter().createSession(config)).rejects.toThrow(
212
+ "requires DEEPSEEK_API_KEY",
213
+ );
214
+ config.model = "openrouter/";
215
+ await expect(new DshAdapter().createSession(config)).rejects.toThrow("requires a model ID");
216
+ expect(await Bun.file(join(config.cwd, "invocation.json")).exists()).toBe(false);
217
+ });
218
+
219
+ test("fails closed when only npx is installed", async () => {
220
+ const config = await fixture();
221
+ await rename(join(config.cwd, "dsh"), join(config.cwd, "npx"));
222
+ config.env = { ...config.env, DSH_BINARY: "", PATH: config.cwd };
223
+ await expect(new DshAdapter().createSession(config)).rejects.toThrow(
224
+ "dsh CLI not found. Install @deepseek-ai/dsh@0.1.7-alpha.2",
225
+ );
226
+ expect(await Bun.file(join(config.cwd, "invocation.json")).exists()).toBe(false);
227
+ });
228
+
229
+ test("uses a preinstalled dsh from PATH without DSH_BINARY", async () => {
230
+ const config = await fixture();
231
+ config.env = { ...config.env, DSH_BINARY: "", PATH: config.cwd };
232
+ const session = await new DshAdapter().createSession(config);
233
+ expect((await session.waitForCompletion()).isError).toBe(false);
234
+ const invocation = await Bun.file(join(config.cwd, "invocation.json")).json();
235
+ expect(invocation.args[0]).toBe("--profile");
236
+ });
237
+
238
+ test("does not fall back when an explicit DSH_BINARY is missing", async () => {
239
+ const config = await fixture();
240
+ config.env = { ...config.env, DSH_BINARY: join(config.cwd, "missing"), PATH: config.cwd };
241
+ await expect(new DshAdapter().createSession(config)).rejects.toThrow();
242
+ expect(await Bun.file(join(config.cwd, "invocation.json")).exists()).toBe(false);
243
+ });
244
+
245
+ for (const mode of ["failure", "missing-final", "malformed"]) {
246
+ test(`reports ${mode} as failure even with output or exit zero`, async () => {
247
+ const config = await fixture(mode);
248
+ const session = await new DshAdapter().createSession(config);
249
+ const events: ProviderEvent[] = [];
250
+ session.onEvent((event) => events.push(event));
251
+ const result = await session.waitForCompletion();
252
+ expect(result.isError).toBe(true);
253
+ expect(result.exitCode).toBe(mode === "failure" ? 7 : 1);
254
+ expect(JSON.stringify({ result, events })).not.toContain(config.env!.DEEPSEEK_API_KEY);
255
+ const invocation = await Bun.file(join(config.cwd, "invocation.json")).json();
256
+ expect(await Bun.file(invocation.patchPath).exists()).toBe(false);
257
+ });
258
+ }
259
+
260
+ test("abort terminates a running process and removes its patch", async () => {
261
+ const config = await fixture("abort");
262
+ const session = await new DshAdapter().createSession(config);
263
+ await new Promise<void>((resolve) =>
264
+ session.onEvent((event) => {
265
+ if (event.type === "session_init") resolve();
266
+ }),
267
+ );
268
+ await session.abort();
269
+ expect(await session.waitForCompletion()).toMatchObject({
270
+ isError: true,
271
+ failureReason: "dsh session aborted",
272
+ });
273
+ const invocation = await Bun.file(join(config.cwd, "invocation.json")).json();
274
+ expect(await Bun.file(invocation.patchPath).exists()).toBe(false);
275
+ });
276
+ });
@@ -85,11 +85,11 @@ describe("nightly report", () => {
85
85
  });
86
86
  expect(report.ok).toBe(true);
87
87
  expect(report.missingLegs).toEqual([]);
88
- expect(report.totalCostUsd).toBeCloseTo(0.0615, 6);
88
+ expect(report.totalCostUsd).toBeCloseTo(0.0738, 6);
89
89
  expect(report.warnings).toEqual([]);
90
90
  const markdown = nightlyMarkdown(report, [], DEFAULT_PROVIDERS);
91
91
  expect(markdown).toContain("## Nightly E2E: PASS");
92
- expect(markdown).toContain("5/5 harness legs passed");
92
+ expect(markdown).toContain("6/6 harness legs passed");
93
93
  expect(markdown).toContain("| claude | claude-model | PASS | 1 |");
94
94
  expect(markdown).not.toContain("### Warnings");
95
95
  });
@@ -159,7 +159,7 @@ describe("nightly report", () => {
159
159
  results: [contractResult(), harnessResult([leg("claude")]), harnessResult([failed])],
160
160
  });
161
161
  expect(report.ok).toBe(false);
162
- expect(report.missingLegs).toEqual(["claude-sdk", "pi", "opencode"]);
162
+ expect(report.missingLegs).toEqual(["claude-sdk", "pi", "opencode", "dsh"]);
163
163
  expect(report.warnings).toContain("pi: no result file from the leg.");
164
164
  expect(report.warnings).toContain("codex: needed 2 attempts.");
165
165
  const markdown = nightlyMarkdown(report, [], DEFAULT_PROVIDERS);
@@ -43,6 +43,7 @@ describe("e2e redaction", () => {
43
43
  const saved = { ...process.env };
44
44
  try {
45
45
  process.env.OPENROUTER_API_KEY = "example-sk-or-v1-known-value-1234";
46
+ process.env.DEEPSEEK_API_KEY = "deepseek-known-value-1234";
46
47
  process.env.CODEX_OAUTH = JSON.stringify({
47
48
  access: "access-token-value-1234",
48
49
  refresh: "refresh-token-value-1234",
@@ -51,6 +52,7 @@ describe("e2e redaction", () => {
51
52
  });
52
53
  const known = knownSecrets(["extra-secret-value"]);
53
54
  expect(known).toContain("example-sk-or-v1-known-value-1234");
55
+ expect(known).toContain("deepseek-known-value-1234");
54
56
  expect(known).toContain("access-token-value-1234");
55
57
  expect(known).toContain("refresh-token-value-1234");
56
58
  expect(known).toContain("extra-secret-value");
@@ -62,6 +62,12 @@ describe("swarm-config-guard: Configuration-page value validation", () => {
62
62
  expect(validateConfigValue("SLACK_THREAD_STEERING_MODE", "now")).toContain("must be one of");
63
63
  });
64
64
 
65
+ test("Slack transport is constrained", () => {
66
+ expect(validateConfigValue("SLACK_MODE", "socket")).toBeNull();
67
+ expect(validateConfigValue("SLACK_MODE", "http")).toBeNull();
68
+ expect(validateConfigValue("SLACK_MODE", "webhook")).toContain("must be one of");
69
+ });
70
+
65
71
  test("Slack reaction shortcode keys accept bare and colon-wrapped names", () => {
66
72
  const keys = [
67
73
  "SLACK_REACTION_ACCEPTED",
@@ -2,6 +2,7 @@ import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:tes
2
2
  import { closeDb, getDbClient, initDb } from "../be/db";
3
3
  import {
4
4
  deleteExtension,
5
+ ExtensionOwnershipError,
5
6
  getExtensionById,
6
7
  getExtensionFiles,
7
8
  getExtensionVersion,
@@ -67,6 +68,46 @@ describe("extension DB helpers", () => {
67
68
  ).toBe(1);
68
69
  });
69
70
 
71
+ test("concurrent worker installs cannot take ownership of the same name", async () => {
72
+ const bundle = await loadBundleFixture("minimal");
73
+ const results = await Promise.allSettled(
74
+ ["worker-a", "worker-b"].map((agentId) =>
75
+ installExtension({ ...bundle, agentId, ownerOnly: agentId }),
76
+ ),
77
+ );
78
+ expect(results.filter((result) => result.status === "fulfilled")).toHaveLength(1);
79
+ const rejected = results.find((result) => result.status === "rejected");
80
+ expect(rejected?.status === "rejected" && rejected.reason).toBeInstanceOf(
81
+ ExtensionOwnershipError,
82
+ );
83
+ const winner = results.find((result) => result.status === "fulfilled");
84
+ if (winner?.status !== "fulfilled") throw new Error("missing winning install");
85
+ expect(await listExtensionVersions(winner.value.extension.id)).toHaveLength(1);
86
+ });
87
+
88
+ test("worker metadata writes and deletion recheck disabled ownership at the database write", async () => {
89
+ const installed = await installExtension({
90
+ ...(await loadBundleFixture("minimal")),
91
+ agentId: "owner",
92
+ });
93
+ const id = installed.extension.id;
94
+ await expect(
95
+ updateExtensionMeta(id, { priority: 7, ownerOnly: "other" }),
96
+ ).rejects.toBeInstanceOf(ExtensionOwnershipError);
97
+ await expect(deleteExtension(id, "other")).rejects.toBeInstanceOf(ExtensionOwnershipError);
98
+ await setExtensionState(id, { enabled: true, status: "enabled" });
99
+ await expect(
100
+ updateExtensionMeta(id, { priority: 7, ownerOnly: "owner" }),
101
+ ).rejects.toBeInstanceOf(ExtensionOwnershipError);
102
+ await expect(deleteExtension(id, "owner")).rejects.toBeInstanceOf(ExtensionOwnershipError);
103
+ expect(await getExtensionById(id)).toMatchObject({ enabled: true, priority: 100 });
104
+ await setExtensionState(id, { enabled: false, status: "disabled" });
105
+ expect(await updateExtensionMeta(id, { priority: 7, ownerOnly: "owner" })).toMatchObject({
106
+ priority: 7,
107
+ });
108
+ expect(await deleteExtension(id, "owner")).toBe(true);
109
+ });
110
+
70
111
  test("matching bundles dedupe and changed hooks create a complete snapshot", async () => {
71
112
  const bundle = await loadBundleFixture("minimal");
72
113
  const first = await installExtension(bundle);