@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
@@ -0,0 +1,248 @@
1
+ import { mkdtemp, rm, writeFile } from "node:fs/promises";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { DEFAULT_MODEL_TIER_MAP } from "../types";
5
+ import { getOpenRouterBaseUrl } from "../utils/openrouter-base-url";
6
+ import {
7
+ detachedProcessGroup,
8
+ registerProcessGroup,
9
+ terminateProcessGroup,
10
+ } from "../utils/process-group";
11
+ import { registerVolatileSecret, scrubSecrets } from "../utils/secret-scrubber";
12
+ import { resolveSlashSkillPrompt } from "./codex-skill-resolver";
13
+ import type {
14
+ CredStatus,
15
+ ProviderAdapter,
16
+ ProviderEvent,
17
+ ProviderResult,
18
+ ProviderSession,
19
+ ProviderSessionConfig,
20
+ ProviderTraits,
21
+ } from "./types";
22
+
23
+ // npm's `latest` is older and lacks the JSON/stdin contract used here.
24
+ export const DSH_PACKAGE = "@deepseek-ai/dsh@0.1.7-alpha.2";
25
+
26
+ export function checkDshCredentials(env: Record<string, string | undefined>): CredStatus {
27
+ return env.DEEPSEEK_API_KEY?.trim() || env.OPENROUTER_API_KEY?.trim()
28
+ ? { ready: true, missing: [], satisfiedBy: "env" }
29
+ : {
30
+ ready: false,
31
+ missing: ["DEEPSEEK_API_KEY", "OPENROUTER_API_KEY"],
32
+ hint: "Set DEEPSEEK_API_KEY or OPENROUTER_API_KEY for dsh.",
33
+ };
34
+ }
35
+
36
+ class DshSession implements ProviderSession {
37
+ sessionId: string | undefined;
38
+ private listeners: ((event: ProviderEvent) => void)[] = [];
39
+ private pending: ProviderEvent[] = [];
40
+ private output: string | undefined;
41
+ private failure: string | undefined;
42
+ private stderr = "";
43
+ private aborted = false;
44
+ private completion: Promise<ProviderResult>;
45
+
46
+ constructor(
47
+ private proc: Bun.Subprocess<"pipe", "pipe", "pipe">,
48
+ private directory: string,
49
+ prompt: string,
50
+ ) {
51
+ this.completion = this.run(prompt);
52
+ }
53
+
54
+ onEvent(listener: (event: ProviderEvent) => void): void {
55
+ this.listeners.push(listener);
56
+ for (const event of this.pending.splice(0)) listener(event);
57
+ }
58
+
59
+ private emit(event: ProviderEvent): void {
60
+ if (this.listeners.length === 0) this.pending.push(event);
61
+ else for (const listener of this.listeners) listener(event);
62
+ }
63
+
64
+ private consumeLine(line: string): void {
65
+ if (!line.trim()) return;
66
+ const clean = scrubSecrets(line);
67
+ this.emit({ type: "raw_log", content: clean });
68
+ try {
69
+ const event = JSON.parse(clean);
70
+ if (event.type === "session" && typeof event.sessionId === "string") {
71
+ this.sessionId = event.sessionId;
72
+ this.emit({ type: "session_init", sessionId: event.sessionId, provider: "dsh" });
73
+ } else if (event.type === "text" && typeof event.text === "string") {
74
+ this.emit({ type: "message", role: "assistant", content: event.text });
75
+ } else if (event.type === "final" && typeof event.text === "string") {
76
+ this.output = event.text;
77
+ } else if (event.type === "error" && typeof event.message === "string") {
78
+ this.failure = event.message;
79
+ } else if (event.type === "status" && typeof event.phase === "string") {
80
+ this.emit({ type: "progress", message: event.phase });
81
+ if (event.phase === "turn_end" && event.reason?.kind !== "completed") {
82
+ this.failure =
83
+ typeof event.reason?.error?.message === "string"
84
+ ? event.reason.error.message
85
+ : `dsh turn ended: ${event.reason?.kind ?? "unknown"}`;
86
+ }
87
+ }
88
+ } catch {
89
+ this.failure = "dsh emitted invalid JSON output";
90
+ }
91
+ }
92
+
93
+ private async readStdout(): Promise<void> {
94
+ const decoder = new TextDecoder();
95
+ let buffer = "";
96
+ for await (const chunk of this.proc.stdout) {
97
+ buffer += decoder.decode(chunk, { stream: true });
98
+ let newline = buffer.indexOf("\n");
99
+ while (newline >= 0) {
100
+ this.consumeLine(buffer.slice(0, newline));
101
+ buffer = buffer.slice(newline + 1);
102
+ newline = buffer.indexOf("\n");
103
+ }
104
+ }
105
+ this.consumeLine(buffer + decoder.decode());
106
+ }
107
+
108
+ private async readStderr(): Promise<void> {
109
+ const decoder = new TextDecoder();
110
+ for await (const chunk of this.proc.stderr) {
111
+ const content = scrubSecrets(decoder.decode(chunk, { stream: true }));
112
+ this.stderr = (this.stderr + content).slice(-8000);
113
+ this.emit({ type: "raw_stderr", content });
114
+ }
115
+ }
116
+
117
+ private async run(prompt: string): Promise<ProviderResult> {
118
+ try {
119
+ const [exitCode] = await Promise.all([
120
+ this.proc.exited,
121
+ this.readStdout(),
122
+ this.readStderr(),
123
+ (async () => {
124
+ this.proc.stdin.write(prompt);
125
+ await this.proc.stdin.end();
126
+ })(),
127
+ ]);
128
+ const isError = this.aborted || exitCode !== 0 || !!this.failure || this.output === undefined;
129
+ const failureReason = isError
130
+ ? this.aborted
131
+ ? "dsh session aborted"
132
+ : this.failure || this.stderr.trim() || `dsh exited ${exitCode} without a final result`
133
+ : undefined;
134
+ if (failureReason) this.emit({ type: "error", message: failureReason });
135
+ return {
136
+ exitCode: isError ? exitCode || 1 : 0,
137
+ sessionId: this.sessionId,
138
+ output: this.output,
139
+ isError,
140
+ failureReason,
141
+ };
142
+ } catch (error) {
143
+ await terminateProcessGroup(this.proc.pid);
144
+ const failureReason = scrubSecrets(String(error));
145
+ this.emit({ type: "error", message: failureReason });
146
+ return { exitCode: 1, sessionId: this.sessionId, isError: true, failureReason };
147
+ } finally {
148
+ await rm(this.directory, { recursive: true, force: true });
149
+ }
150
+ }
151
+
152
+ waitForCompletion(): Promise<ProviderResult> {
153
+ return this.completion;
154
+ }
155
+
156
+ async abort(): Promise<void> {
157
+ this.aborted = true;
158
+ await terminateProcessGroup(this.proc.pid);
159
+ }
160
+ }
161
+
162
+ export class DshAdapter implements ProviderAdapter {
163
+ readonly name = "dsh";
164
+ readonly traits: ProviderTraits = {
165
+ hasMcp: false,
166
+ nativeSkillDiscovery: false,
167
+ hasLocalEnvironment: true,
168
+ steerModes: [],
169
+ };
170
+
171
+ async createSession(config: ProviderSessionConfig): Promise<ProviderSession> {
172
+ const env = { ...process.env, ...config.env };
173
+ if (!checkDshCredentials(env).ready) {
174
+ throw new Error("dsh requires DEEPSEEK_API_KEY or OPENROUTER_API_KEY");
175
+ }
176
+ const model = config.model || DEFAULT_MODEL_TIER_MAP.dsh.regular;
177
+ const openrouter = model.startsWith("openrouter/");
178
+ const modelId = openrouter ? model.slice("openrouter/".length) : model;
179
+ const keyEnv = openrouter ? "OPENROUTER_API_KEY" : "DEEPSEEK_API_KEY";
180
+ if (!modelId.trim()) throw new Error("dsh requires a model ID after openrouter/");
181
+ if (!env[keyEnv]?.trim()) throw new Error(`dsh model ${model} requires ${keyEnv}`);
182
+ const binary = env.DSH_BINARY || Bun.which("dsh", { PATH: env.PATH });
183
+ if (!binary) {
184
+ throw new Error(
185
+ `dsh CLI not found. Install ${DSH_PACKAGE} during image provisioning or set DSH_BINARY to a trusted executable.`,
186
+ );
187
+ }
188
+ registerVolatileSecret(env[keyEnv]!, keyEnv);
189
+ const prompt = await resolveSlashSkillPrompt(config.prompt, {
190
+ providerLabel: "dsh",
191
+ skillsDir: join(env.HOME ?? "/home/worker", ".agents", "skills"),
192
+ });
193
+ const directory = await mkdtemp(join(tmpdir(), "swarm-dsh-"));
194
+ try {
195
+ const patchPath = join(directory, "patch.json");
196
+ // JSON is valid YAML. Values stay data, including arbitrary system prompts.
197
+ await writeFile(
198
+ patchPath,
199
+ JSON.stringify([
200
+ {
201
+ id: "agent-default-model",
202
+ config: { provider: openrouter ? "openrouter" : "deepseek-official", model: modelId },
203
+ },
204
+ { id: "system-prompt", config: { personaPrefix: config.systemPrompt } },
205
+ openrouter
206
+ ? {
207
+ id: "llm-pi-ai",
208
+ config: {
209
+ providers: {
210
+ openrouter: {
211
+ apiKeyEnv: keyEnv,
212
+ baseURL: getOpenRouterBaseUrl(env),
213
+ api: "openai-completions",
214
+ // Declare the selected ID even if the bundled catalog predates it.
215
+ models: [{ id: modelId }],
216
+ },
217
+ },
218
+ },
219
+ }
220
+ : { id: "llm-deepseek", config: { apiKeyEnv: keyEnv } },
221
+ ]),
222
+ { mode: 0o600 },
223
+ );
224
+ const proc = registerProcessGroup(
225
+ Bun.spawn([binary, "--profile", "headless", "--patch", patchPath, "--json", "-"], {
226
+ cwd: config.cwd,
227
+ env,
228
+ stdin: "pipe",
229
+ stdout: "pipe",
230
+ stderr: "pipe",
231
+ detached: detachedProcessGroup,
232
+ }),
233
+ );
234
+ return new DshSession(proc, directory, prompt);
235
+ } catch (error) {
236
+ await rm(directory, { recursive: true, force: true });
237
+ throw error;
238
+ }
239
+ }
240
+
241
+ async canResume(_sessionId: string): Promise<boolean> {
242
+ return false;
243
+ }
244
+
245
+ formatCommand(commandName: string): string {
246
+ return `/${commandName}`;
247
+ }
248
+ }
@@ -47,13 +47,17 @@ export async function createProviderAdapter(provider: string): Promise<ProviderA
47
47
  const { OpencodeAdapter } = await import("./opencode-adapter");
48
48
  return new OpencodeAdapter();
49
49
  }
50
+ case "dsh": {
51
+ const { DshAdapter } = await import("./dsh-adapter");
52
+ return new DshAdapter();
53
+ }
50
54
  case "acp": {
51
55
  const { ACPAdapter } = await import("./acp-adapter");
52
56
  return new ACPAdapter();
53
57
  }
54
58
  default:
55
59
  throw new Error(
56
- `Unknown HARNESS_PROVIDER: "${provider}". Supported: claude, pi, codex, devin, claude-managed, opencode, acp`,
60
+ `Unknown HARNESS_PROVIDER: "${provider}". Supported: claude, pi, codex, devin, claude-managed, opencode, acp, dsh`,
57
61
  );
58
62
  }
59
63
  }
@@ -1,5 +1,21 @@
1
1
  {
2
2
  "anthropic": {
3
+ "claude-opus-5-5": {
4
+ "id": "claude-opus-5-5",
5
+ "reasoning": true,
6
+ "reasoningOptions": [
7
+ {
8
+ "type": "effort",
9
+ "values": [
10
+ "low",
11
+ "medium",
12
+ "high",
13
+ "xhigh",
14
+ "max"
15
+ ]
16
+ }
17
+ ]
18
+ },
3
19
  "claude-sonnet-4-6": {
4
20
  "id": "claude-sonnet-4-6",
5
21
  "reasoning": true,
@@ -919,6 +935,40 @@
919
935
  ]
920
936
  }
921
937
  ]
938
+ },
939
+ "gpt-6-sol": {
940
+ "id": "gpt-6-sol",
941
+ "reasoning": true,
942
+ "reasoningOptions": [
943
+ {
944
+ "type": "effort",
945
+ "values": [
946
+ "none",
947
+ "low",
948
+ "medium",
949
+ "high",
950
+ "xhigh",
951
+ "max"
952
+ ]
953
+ }
954
+ ]
955
+ },
956
+ "gpt-6-luna": {
957
+ "id": "gpt-6-luna",
958
+ "reasoning": true,
959
+ "reasoningOptions": [
960
+ {
961
+ "type": "effort",
962
+ "values": [
963
+ "none",
964
+ "low",
965
+ "medium",
966
+ "high",
967
+ "xhigh",
968
+ "max"
969
+ ]
970
+ }
971
+ ]
922
972
  }
923
973
  },
924
974
  "openrouter": {
@@ -56,6 +56,20 @@ const leadOrOperatorOrUser: LegacyRule = {
56
56
  principal.kind === "operator" || principal.kind === "user" || actsAsLead(principal),
57
57
  };
58
58
 
59
+ const extensionWrite: LegacyRule = {
60
+ name: "extension-owner-or-elevated",
61
+ denyReason: "requires extension owner, lead agent, operator, or user authentication",
62
+ evaluate: (principal, resource) => {
63
+ if (leadOrOperatorOrUser.evaluate(principal, resource)) return true;
64
+ return (
65
+ principal.kind === "agent" &&
66
+ principal.agentId !== "" &&
67
+ resource?.kind === "extension" &&
68
+ (resource.extensionId === undefined || resource.createdByAgentId === principal.agentId)
69
+ );
70
+ },
71
+ };
72
+
59
73
  // Deployment-only: agents cannot reset this through swarm_config.
60
74
  const extensionActivation: LegacyRule = {
61
75
  name: "extension-activation",
@@ -242,6 +256,6 @@ export const LEGACY_POLICY = {
242
256
  "script.api.update": leadOnly,
243
257
  "script.api.rotate": leadOnly,
244
258
  "script.api.delete": leadOnly,
245
- "extension.write": leadOrOperatorOrUser,
259
+ "extension.write": extensionWrite,
246
260
  "extension.activate": extensionActivation,
247
261
  } as const satisfies Record<PermissionVerb, LegacyRule>;
package/src/rbac/types.ts CHANGED
@@ -32,6 +32,8 @@ export type RbacResource =
32
32
  | { kind: "app"; appId: string }
33
33
  /** Skills, mcp-servers, memory entries, scripts. */
34
34
  | { kind: "owned"; ownerAgentId?: string | null; scope?: string }
35
+ /** An omitted extensionId denotes a new install; existing rows retain their creator. */
36
+ | { kind: "extension"; extensionId?: string; createdByAgentId?: string | null }
35
37
  | { kind: "none" };
36
38
 
37
39
  export type RbacDecision =
package/src/server.ts CHANGED
@@ -9,6 +9,7 @@ import { registerGithubTaskReactions } from "./github/task-reactions";
9
9
  import { loadGlobalConfigsIntoEnv } from "./http/core";
10
10
  import { resolveTemplate } from "./prompts/resolver";
11
11
  import { isRbacEnabled } from "./rbac";
12
+ import { getSlackConfiguration } from "./slack/config";
12
13
  import { registerAcceptSteerTool } from "./tools/accept-steer";
13
14
  import { registerAppDiffTool } from "./tools/app-diff";
14
15
  import { registerAppGetTool } from "./tools/app-get";
@@ -268,7 +269,12 @@ export function hasCapability(cap: CAPABILITIES_T): boolean {
268
269
  }
269
270
 
270
271
  export function getEnabledCapabilities(): CAPABILITIES_T[] {
271
- return Array.from(getCapabilities());
272
+ const capabilities = Array.from(getCapabilities());
273
+ // Phase 1 has no HTTP receiver. Do not advertise usable Slack tools to
274
+ // workers when HTTP (or an invalid transport) was selected.
275
+ return getSlackConfiguration().mode === "socket"
276
+ ? capabilities
277
+ : capabilities.filter((capability) => capability !== "slack");
272
278
  }
273
279
 
274
280
  /**
package/src/slack/app.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { App, LogLevel } from "@slack/bolt";
2
2
  import { emitBuiltInIntegrationConnectedOnce, ensureSlackRenderV2Activation } from "../be/db";
3
+ import { getSlackConfiguration } from "./config";
3
4
  import { getSlackSocketModeBlockReason, SLACK_DEV_SOCKET_MODE_OPT_IN } from "./socket-mode-guard";
4
5
  import { startTaskWatcher, stopTaskWatcher } from "./watcher";
5
6
 
@@ -16,23 +17,38 @@ export async function initSlackApp(): Promise<App | null> {
16
17
  console.log("[Slack] Already initialized, skipping");
17
18
  return app;
18
19
  }
19
- initialized = true;
20
-
21
- // Check if Slack is explicitly disabled
22
- const slackDisable = process.env.SLACK_DISABLE;
23
- if (slackDisable === "true" || slackDisable === "1") {
20
+ const config = getSlackConfiguration();
21
+ if (config.disabled) {
24
22
  console.log("[Slack] Disabled via SLACK_DISABLE");
25
23
  return null;
26
24
  }
27
25
 
28
- const botToken = process.env.SLACK_BOT_TOKEN;
29
- const appToken = process.env.SLACK_APP_TOKEN;
26
+ if (!config.mode) {
27
+ console.error(
28
+ "[Slack] Invalid SLACK_MODE; expected socket or http. Slack integration disabled",
29
+ );
30
+ return null;
31
+ }
32
+
33
+ if (config.missingCredentials.length > 0) {
34
+ console.log(
35
+ `[Slack] Missing ${config.missingCredentials.join(" or ")} for ${config.mode} mode, Slack integration disabled`,
36
+ );
37
+ return null;
38
+ }
30
39
 
31
- if (!botToken || !appToken) {
32
- console.log("[Slack] Missing SLACK_BOT_TOKEN or SLACK_APP_TOKEN, Slack integration disabled");
40
+ // Phase 1 establishes the transport contract only. Never fall back to a
41
+ // socket when HTTP was explicitly selected; the receiver lands in Phase 3.
42
+ if (config.mode === "http") {
43
+ console.error(
44
+ "[Slack] HTTP mode is configured but unavailable until the HTTP receiver is installed",
45
+ );
33
46
  return null;
34
47
  }
35
48
 
49
+ const botToken = process.env.SLACK_BOT_TOKEN as string;
50
+ const appToken = process.env.SLACK_APP_TOKEN as string;
51
+
36
52
  const socketModeBlockReason = getSlackSocketModeBlockReason(process.env);
37
53
  if (socketModeBlockReason) {
38
54
  console.error(
@@ -51,6 +67,9 @@ export async function initSlackApp(): Promise<App | null> {
51
67
  ...(slackApiUrl ? { clientOptions: { slackApiUrl } } : {}),
52
68
  });
53
69
 
70
+ // Failed validation must remain retryable without requiring stopSlackApp().
71
+ initialized = true;
72
+
54
73
  // Register handlers
55
74
  const { registerMessageHandler } = await import("./handlers");
56
75
  const { registerCommandHandler } = await import("./commands");
@@ -69,7 +88,7 @@ export async function initSlackApp(): Promise<App | null> {
69
88
  return app;
70
89
  }
71
90
 
72
- export async function startSlackApp(): Promise<void> {
91
+ export async function startSlackApp(): Promise<boolean> {
73
92
  if (!app) {
74
93
  await initSlackApp();
75
94
  }
@@ -84,7 +103,10 @@ export async function startSlackApp(): Promise<void> {
84
103
 
85
104
  // Start watching for task completions
86
105
  await startTaskWatcher();
106
+ return true;
87
107
  }
108
+
109
+ return false;
88
110
  }
89
111
 
90
112
  export async function stopSlackApp(): Promise<void> {
@@ -0,0 +1,29 @@
1
+ export const SLACK_MODES = ["socket", "http"] as const;
2
+ export type SlackMode = (typeof SLACK_MODES)[number];
3
+
4
+ export type SlackConfiguration = {
5
+ disabled: boolean;
6
+ mode: SlackMode | null;
7
+ missingCredentials: string[];
8
+ };
9
+
10
+ /** Resolve Slack transport and credential presence without exposing credential values. */
11
+ export function getSlackConfiguration(env: NodeJS.ProcessEnv = process.env): SlackConfiguration {
12
+ const rawMode = env.SLACK_MODE === undefined ? "socket" : env.SLACK_MODE.trim();
13
+ const mode = SLACK_MODES.includes(rawMode as SlackMode) ? (rawMode as SlackMode) : null;
14
+ const missingCredentials = ["SLACK_BOT_TOKEN"];
15
+
16
+ if (mode === "socket") missingCredentials.push("SLACK_APP_TOKEN");
17
+ if (mode === "http") missingCredentials.push("SLACK_SIGNING_SECRET");
18
+
19
+ return {
20
+ disabled: env.SLACK_DISABLE === "true" || env.SLACK_DISABLE === "1",
21
+ mode,
22
+ missingCredentials: missingCredentials.filter((key) => !env[key]?.trim()),
23
+ };
24
+ }
25
+
26
+ export function isSlackConfigured(env: NodeJS.ProcessEnv = process.env): boolean {
27
+ const config = getSlackConfiguration(env);
28
+ return !config.disabled && config.mode !== null && config.missingCredentials.length === 0;
29
+ }
package/src/telemetry.ts CHANGED
@@ -8,6 +8,7 @@
8
8
  */
9
9
  import { randomUUID } from "node:crypto";
10
10
  import pkg from "../package.json";
11
+ import { isSlackConfigured } from "./slack/config";
11
12
  import { isEnvFlagEnabled } from "./utils/env-flag";
12
13
 
13
14
  const TELEMETRY_ENDPOINT = "https://proxy.desplega.sh/v1/events";
@@ -269,9 +270,7 @@ export function _hasEmbeddingKey(env: NodeJS.ProcessEnv = process.env): boolean
269
270
  * Exported for tests; not part of the public API.
270
271
  */
271
272
  export function _hasSlackChannel(env: NodeJS.ProcessEnv = process.env): boolean {
272
- return (
273
- !!(env.SLACK_BOT_TOKEN && env.SLACK_APP_TOKEN) && !isEnvFlagEnabled("SLACK_DISABLE", false, env)
274
- );
273
+ return isSlackConfigured(env);
275
274
  }
276
275
 
277
276
  /**
@@ -333,6 +333,25 @@ describe("PATCH /api/agents/:id/harness-provider", () => {
333
333
  });
334
334
 
335
335
  describe("PATCH /api/agents/:id/runtime", () => {
336
+ test("accepts dsh and persists its harness and model for the worker", async () => {
337
+ const agent = await createAgent({
338
+ name: "dsh-worker",
339
+ isLead: false,
340
+ status: "idle",
341
+ capabilities: [],
342
+ });
343
+ const res = await fetch(`${baseUrl}/api/agents/${agent.id}/runtime`, {
344
+ method: "PATCH",
345
+ headers: { "Content-Type": "application/json" },
346
+ body: JSON.stringify({ harness_provider: "dsh", model: "deepseek-flash" }),
347
+ });
348
+ expect(res.status).toBe(200);
349
+ expect((await getAgentById(agent.id))?.harnessProvider).toBe("dsh");
350
+ const rows = await getSwarmConfigs({ scope: "agent", scopeId: agent.id });
351
+ expect(rows.find((row) => row.key === "HARNESS_PROVIDER")?.value).toBe("dsh");
352
+ expect(rows.find((row) => row.key === "MODEL_OVERRIDE")?.value).toBe("deepseek-flash");
353
+ });
354
+
336
355
  test("updates harness_provider and agent-scoped runtime config rows", async () => {
337
356
  const a = await createAgent({
338
357
  name: "runtime-target-1",
@@ -1,9 +1,38 @@
1
1
  import { describe, expect, test } from "bun:test";
2
+ import type { LiveModelsCatalog } from "../../apps/ui/src/lib/agent-runtime-models";
2
3
  import {
3
4
  getAgentModelDisplay,
4
5
  getAgentModelPresentation,
5
6
  } from "../../apps/ui/src/lib/agents-list-model-display";
6
7
 
8
+ const liveCatalog: LiveModelsCatalog = {
9
+ openrouter: {
10
+ id: "openrouter",
11
+ name: "OpenRouter",
12
+ models: {
13
+ "deepseek/deepseek-v4-pro": { id: "deepseek/deepseek-v4-pro", name: "DeepSeek V4 Pro" },
14
+ "deepseek/deepseek-v4.1-flash": {
15
+ id: "deepseek/deepseek-v4.1-flash",
16
+ name: "DeepSeek V4.1 Flash",
17
+ },
18
+ },
19
+ },
20
+ opencode: {
21
+ id: "opencode",
22
+ name: "OpenCode Zen",
23
+ models: {
24
+ "glm-5.3-flash": { id: "glm-5.3-flash", name: "GLM-5.3-Flash" },
25
+ },
26
+ },
27
+ anthropic: {
28
+ id: "anthropic",
29
+ name: "Anthropic",
30
+ models: {
31
+ "claude-opus-5-5": { id: "claude-opus-5-5", name: "Claude Opus 5.5" },
32
+ },
33
+ },
34
+ };
35
+
7
36
  describe("agents list model display", () => {
8
37
  test("shows configured and last-used models when they diverge", () => {
9
38
  const display = getAgentModelDisplay("claude-opus-4-7", "claude-sonnet-4-6");
@@ -43,6 +72,31 @@ describe("agents list model display", () => {
43
72
  });
44
73
  });
45
74
 
75
+ test.each([
76
+ ["openrouter/deepseek/deepseek-v4-pro", "DeepSeek V4 Pro", "OpenRouter"],
77
+ ["openrouter/deepseek/deepseek-v4.1-flash", "DeepSeek V4.1 Flash", "OpenRouter"],
78
+ ["opencode/glm-5.3-flash", "GLM-5.3-Flash", "OpenCode Zen"],
79
+ ["claude-opus-5-5", "Claude Opus 5.5", "Anthropic"],
80
+ ])("resolves %s from the live catalog", (model, label, provider) => {
81
+ expect(getAgentModelPresentation(model, liveCatalog)).toMatchObject({
82
+ raw: model,
83
+ label,
84
+ provider,
85
+ });
86
+ });
87
+
88
+ test("title-cases an uncatalogued model id without a maintained label entry", () => {
89
+ expect(
90
+ getAgentModelPresentation("opencode/future-model-v8.2-preview", liveCatalog),
91
+ ).toMatchObject({
92
+ label: "Future Model V8.2 Preview",
93
+ provider: null,
94
+ });
95
+ expect(
96
+ getAgentModelPresentation("openrouter/deepseek/deepseek-v5.1-flash", liveCatalog)?.label,
97
+ ).toBe("Deepseek V5.1 Flash");
98
+ });
99
+
46
100
  test("presents latest Anthropic direct model ids as readable labels", () => {
47
101
  expect(getAgentModelPresentation("claude-fable-5-1")).toMatchObject({
48
102
  label: "Claude Fable 5.1",
@@ -54,6 +108,11 @@ describe("agents list model display", () => {
54
108
  provider: "Anthropic",
55
109
  providerId: "anthropic",
56
110
  });
111
+ expect(getAgentModelPresentation("claude-opus-5-5")).toMatchObject({
112
+ label: "Claude Opus 5.5",
113
+ provider: "Anthropic",
114
+ providerId: "anthropic",
115
+ });
57
116
  expect(getAgentModelPresentation("claude-opus-5")).toMatchObject({
58
117
  label: "Claude Opus 5",
59
118
  provider: "Anthropic",
@@ -75,7 +134,7 @@ describe("agents list model display", () => {
75
134
  providerId: "anthropic",
76
135
  });
77
136
  expect(getAgentModelPresentation("opus")).toMatchObject({
78
- label: "Claude Opus 5",
137
+ label: "Claude Opus 5.5",
79
138
  provider: "Anthropic",
80
139
  providerId: "anthropic",
81
140
  });
@@ -25,6 +25,8 @@ const ENV_KEYS = [
25
25
  "SLACK_DISABLE",
26
26
  "SLACK_BOT_TOKEN",
27
27
  "SLACK_APP_TOKEN",
28
+ "SLACK_MODE",
29
+ "SLACK_SIGNING_SECRET",
28
30
  "STEERING_ENABLED",
29
31
  "AGENT_FS_API_URL",
30
32
  "SCRIPTS_ONLY_MCP",
@@ -561,6 +563,29 @@ describe("getBasePrompt: slack section", () => {
561
563
  expect(result).toContain(SLACK_HEADER);
562
564
  });
563
565
 
566
+ test("a tool capability alone does not bypass the default socket credential gate", async () => {
567
+ const result = await getBasePrompt({
568
+ ...minimalArgs,
569
+ serverCapabilities: ["core", "slack"],
570
+ });
571
+ expect(result).not.toContain(SLACK_HEADER);
572
+ });
573
+
574
+ test("supports the HTTP prompt contract without distributing Slack secrets to the worker", async () => {
575
+ process.env.SLACK_MODE = "http";
576
+ const result = await getBasePrompt({
577
+ ...minimalArgs,
578
+ serverCapabilities: ["core", "slack"],
579
+ });
580
+ expect(result).toContain(SLACK_HEADER);
581
+
582
+ const withoutCapability = await getBasePrompt({
583
+ ...minimalArgs,
584
+ serverCapabilities: ["core"],
585
+ });
586
+ expect(withoutCapability).not.toContain(SLACK_HEADER);
587
+ });
588
+
564
589
  test("a scripts-only worker with a Slack task gets the scripts-only variant only", async () => {
565
590
  enableSlack();
566
591
  const result = await getBasePrompt({