@desplega.ai/agent-swarm 1.152.1 → 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 (228) hide show
  1. package/README.md +2 -1
  2. package/dist/{acp-adapter-4s7q25ft.js → acp-adapter-ahmjxhek.js} +4 -4
  3. package/dist/{actions-cs6rhmvc.js → actions-jzrfpkmn.js} +30 -26
  4. package/dist/{anthropic-messages-2y8nktar.js → anthropic-messages-9vqsbznt.js} +112 -84
  5. package/dist/{app-j54aq0dj.js → app-03na2vwb.js} +9 -6
  6. package/dist/{assistant-w1pgn7vf.js → assistant-hfkxvhgt.js} +33 -29
  7. package/dist/{azure-openai-responses-f38x2avq.js → azure-openai-responses-04qwdwfq.js} +27 -13
  8. package/dist/{boot-reembed-smy30z32.js → boot-reembed-c98xrad3.js} +8 -6
  9. package/dist/{boot-reembed-42vgkrrj.js → boot-reembed-zt3qm9xn.js} +9 -7
  10. package/dist/{boot-scrub-logs-b6s72fsw.js → boot-scrub-logs-jcqd8ksj.js} +6 -4
  11. package/dist/{claude-adapter-fh7rmcj9.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-xhynatn9.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-sakxd2rg.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-sqmh7jj1.js → cli-5mbmvxnn.js} +5 -5
  23. package/dist/{cli-v9sk4dzw.js → cli-5taqtpze.js} +235 -8
  24. package/dist/{cli-dsyg66vt.js → cli-6ks9xf1n.js} +7 -5
  25. package/dist/cli-7g9s78jf.js +2827 -0
  26. package/dist/{cli-jtvs5m8q.js → cli-7pnz2v6s.js} +2 -2
  27. package/dist/{cli-jytf5mw9.js → cli-83wkg402.js} +2 -2
  28. package/dist/{cli-3pft2z6r.js → cli-a9scg8sv.js} +12 -10
  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-3g61md6v.js → cli-bw0dfwkr.js} +1 -1
  32. package/dist/{cli-8wrympd7.js → cli-bzr0ftht.js} +26 -7
  33. package/dist/{cli-ks8a1cgq.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-bdt7w021.js → cli-djvxhnc4.js} +31 -3324
  37. package/dist/{cli-s25avqg9.js → cli-e781c6f9.js} +1 -0
  38. package/dist/{cli-b4dyetqt.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-y8tayczk.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-wfve107r.js → cli-n0q9b7j4.js} +46 -1870
  48. package/dist/cli-n7h1kqpf.js +29 -0
  49. package/dist/{cli-ng4q6a9t.js → cli-nq0kgyxe.js} +7 -7
  50. package/dist/{cli-72tgtzr1.js → cli-qrqhc31j.js} +151 -125
  51. package/dist/{cli-vbk025fm.js → cli-r8tdds6s.js} +1 -1
  52. package/dist/{cli-ch38q5z4.js → cli-rj7de4b2.js} +6788 -2541
  53. package/dist/{cli-ftdjz1mk.js → cli-s5s1f2yn.js} +56 -40
  54. package/dist/{cli-kj76p1ez.js → cli-scwq51gn.js} +13 -7
  55. package/dist/{cli-7bwaerjt.js → cli-tdh4hjr2.js} +3 -2
  56. package/dist/{cli-c2mk1kgf.js → cli-v4en75c5.js} +8 -1
  57. package/dist/{cli-kf8qp2vf.js → cli-vw03hg5t.js} +3 -2
  58. package/dist/{cli-gfez0qmk.js → cli-ybpp1srm.js} +6 -6
  59. package/dist/cli-yn0a417a.js +4069 -0
  60. package/dist/{cli-gxf2ebyz.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-k6yq95sn.js → codex-adapter-8548daap.js} +12 -11
  67. package/dist/{codex-session-runner-3rp84pk8.js → codex-session-runner-2dad8p4x.js} +12 -11
  68. package/dist/{commands-pbr5jzy5.js → commands-355dbpa8.js} +6 -4
  69. package/dist/{db-z0xaa59a.js → db-4jjvwz1s.js} +6 -4
  70. package/dist/{dispatcher-pwpgsj0x.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-5mzk4mbg.js → handlers-2y6dqpvf.js} +32 -28
  76. package/dist/{hook-8bz33zee.js → hook-0s8knepr.js} +11 -9
  77. package/dist/{hook-kbh84zb1.js → hook-k0gec254.js} +12 -10
  78. package/dist/{http-pppxg5vr.js → http-jp2nb4zw.js} +300 -237
  79. package/dist/{index-k1hq1qt6.js → index-0wzg3sp3.js} +39 -24
  80. package/dist/{index-68bd9b5f.js → index-3mq6ce93.js} +29 -25
  81. package/dist/{index-z0zwjtkn.js → index-42s7eezq.js} +29 -25
  82. package/dist/{cli-pdz9qde4.js → index-9ch1n761.js} +10 -11
  83. package/dist/{index-y0te09pn.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-mjpxsr6k.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-xvmwnh1d.js → keepalive-9pfw7t02.js} +8 -6
  91. package/dist/{lead-pf8nvxt8.js → lead-7vcmcdq4.js} +44 -39
  92. package/dist/{maintenance-jk4q0wgg.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-6207x9sj.js → oauth-refresh-sweep-5fwa7th2.js} +7 -5
  96. package/dist/{onboard-b0csgm0q.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-b6rfehth.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-4e201wtd.js → pricing-refresh-vetcm563.js} +8 -6
  105. package/dist/{rbac-roles-mkc5n5qa.js → rbac-roles-cfzgr6r1.js} +8 -6
  106. package/dist/{rbac-roles-zp6e4geh.js → rbac-roles-cmxaddmy.js} +7 -5
  107. package/dist/{render-v2-4fazz9tf.js → render-v2-f9hzff72.js} +9 -6
  108. package/dist/{seed-pricing-thxpn9vn.js → seed-pricing-jggk6rsy.js} +7 -5
  109. package/dist/{setup-wb0z8cat.js → setup-8mfceb9t.js} +4 -5
  110. package/dist/virtual-modules-ax661xp0.js +54 -0
  111. package/dist/{work-objects-h3498kwe.js → work-objects-vdsdcczj.js} +32 -28
  112. package/dist/{worker-2m2tzxp3.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/assistant.ts +10 -1
  147. package/src/slack/config.ts +29 -0
  148. package/src/slack/handlers.ts +10 -1
  149. package/src/slack/thread-buffer.ts +14 -3
  150. package/src/telemetry.ts +2 -3
  151. package/src/tests/agents-harness-provider.test.ts +19 -0
  152. package/src/tests/agents-list-model-display.test.ts +60 -1
  153. package/src/tests/base-prompt.test.ts +25 -0
  154. package/src/tests/claude-managed-adapter.test.ts +6 -0
  155. package/src/tests/codex-gpt6-release.test.ts +97 -0
  156. package/src/tests/config-api-only-keys.test.ts +9 -0
  157. package/src/tests/context-window.test.ts +1 -0
  158. package/src/tests/dsh-adapter.test.ts +276 -0
  159. package/src/tests/e2e-nightly-report.test.ts +3 -3
  160. package/src/tests/e2e-redact.test.ts +2 -0
  161. package/src/tests/env-flag.test.ts +6 -0
  162. package/src/tests/extensions-db.test.ts +41 -0
  163. package/src/tests/extensions-http.test.ts +148 -4
  164. package/src/tests/extensions-lifecycle.test.ts +18 -0
  165. package/src/tests/extensions-mcp-tools.test.ts +9 -5
  166. package/src/tests/internal-ai/complete-structured.test.ts +32 -9
  167. package/src/tests/internal-ai/credentials.test.ts +4 -4
  168. package/src/tests/internal-ai/models.test.ts +20 -0
  169. package/src/tests/migration-046-budgets.test.ts +2 -0
  170. package/src/tests/model-control.test.ts +2 -2
  171. package/src/tests/onboard-compose.test.ts +14 -0
  172. package/src/tests/onboard-env.test.ts +17 -0
  173. package/src/tests/onboard-manifest.test.ts +12 -0
  174. package/src/tests/opus-5-5-registry.test.ts +48 -0
  175. package/src/tests/pricing-refresh.test.ts +1 -1
  176. package/src/tests/pricing-routes.test.ts +15 -3
  177. package/src/tests/provider-registration.test.ts +5 -2
  178. package/src/tests/rbac-admission-e2e.test.ts +54 -0
  179. package/src/tests/rbac-e2e-helpers.ts +2 -0
  180. package/src/tests/rbac-engine.test.ts +32 -0
  181. package/src/tests/rbac-wire-e2e.test.ts +33 -0
  182. package/src/tests/reasoning-effort.test.ts +8 -0
  183. package/src/tests/reload-config.test.ts +33 -0
  184. package/src/tests/session-costs-model-key-normalize.test.ts +1 -0
  185. package/src/tests/slack-assistant.test.ts +134 -0
  186. package/src/tests/slack-config.test.ts +136 -0
  187. package/src/tests/slack-mode-lifecycle.test.ts +69 -0
  188. package/src/tests/status.test.ts +22 -2
  189. package/src/tests/task-tool-preload-mcp.test.ts +3 -1
  190. package/src/tests/telemetry-init.test.ts +18 -0
  191. package/src/tests/workflow-llm.test.ts +3 -3
  192. package/src/tools/extension-common.ts +4 -0
  193. package/src/tools/extension-install.ts +1 -1
  194. package/src/tools/extension-list.ts +4 -3
  195. package/src/types.ts +17 -8
  196. package/src/utils/context-window.ts +1 -0
  197. package/src/utils/credentials.ts +8 -0
  198. package/src/utils/internal-ai/complete-structured.ts +8 -0
  199. package/src/utils/internal-ai/models.ts +5 -5
  200. package/templates/schedules/daily-status-report/content.md +25 -12
  201. package/templates/schedules/daily-workflow-health-audit/content.md +1 -1
  202. package/templates/skills/swarm-extensions/content.md +9 -5
  203. package/dist/cli-4wppfkcm.js +0 -16384
  204. package/dist/cli-55qrx3q8.js +0 -6041
  205. package/dist/cli-cg4vnyh4.js +0 -305
  206. package/dist/cli-jyetr64j.js +0 -52
  207. package/dist/cli-pp9ax9pa.js +0 -520
  208. package/dist/cli-qa66qaf0.js +0 -6076
  209. package/dist/cli-qghpvca2.js +0 -590
  210. package/dist/cli-rembwgt8.js +0 -861
  211. package/dist/cli-tcvj7j2r.js +0 -1971
  212. package/dist/cli-vfcth11k.js +0 -36
  213. package/dist/cli-w1d1bgkk.js +0 -1422
  214. package/dist/cli-y6x0gqtg.js +0 -5977
  215. package/dist/es5-tj3tn4jj.js +0 -844
  216. package/dist/index-1kq5hw2p.js +0 -467
  217. package/dist/index-1p8wdstt.js +0 -7
  218. package/dist/index-8fdjzb2f.js +0 -100
  219. package/dist/index-bxmwzbgw.js +0 -365
  220. package/dist/index-nzerxd5z.js +0 -7
  221. package/dist/index-pyymnzat.js +0 -1168
  222. package/dist/index-qv6atbab.js +0 -619
  223. package/dist/index-rja32qa7.js +0 -801
  224. package/dist/index-tzg5srfp.js +0 -211
  225. package/dist/index-vhncw983.js +0 -90
  226. package/dist/index-xk036zsk.js +0 -859
  227. package/dist/index-zyazbzcb.js +0 -6
  228. 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> {
@@ -129,7 +129,15 @@ export function createAssistant(): Assistant {
129
129
 
130
130
  // Follow-up message → buffer text and file metadata until the flush.
131
131
  if (workingAgent && workingAgent.status !== "offline" && isAdditiveSlack()) {
132
- bufferThreadMessage(channelId, threadTs, messageText, userId, message.ts, files);
132
+ bufferThreadMessage(
133
+ channelId,
134
+ threadTs,
135
+ messageText,
136
+ userId,
137
+ message.ts,
138
+ files,
139
+ cachedBotUserId ?? undefined,
140
+ );
133
141
  const count = getBufferMessageCount(`${channelId}:${threadTs}`);
134
142
  const event = count === 1 ? "accepted" : "buffered";
135
143
  await ackSlackMessage(client, channelId, message.ts, reactionName(event), event);
@@ -147,6 +155,7 @@ export function createAssistant(): Assistant {
147
155
  // raw `messageText`, not this rendered copy.
148
156
  const renderedMessageText = await rewriteSlackMentions(
149
157
  buildEffectiveText(messageText, inbound.files, inbound.failed),
158
+ cachedBotUserId ?? undefined,
150
159
  );
151
160
 
152
161
  if (workingAgent && workingAgent.status !== "offline") {
@@ -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
+ }
@@ -523,7 +523,15 @@ export function registerMessageHandler(app: App): void {
523
523
  );
524
524
 
525
525
  if (nowMessage || msg.files?.length) {
526
- bufferThreadMessage(msg.channel, msg.thread_ts, nowMessage, msg.user, msg.ts, msg.files);
526
+ bufferThreadMessage(
527
+ msg.channel,
528
+ msg.thread_ts,
529
+ nowMessage,
530
+ msg.user,
531
+ msg.ts,
532
+ msg.files,
533
+ botUserId,
534
+ );
527
535
  }
528
536
 
529
537
  // Instant flush — no dependency
@@ -563,6 +571,7 @@ export function registerMessageHandler(app: App): void {
563
571
  msg.user,
564
572
  msg.ts,
565
573
  msg.files,
574
+ botUserId,
566
575
  );
567
576
 
568
577
  // Slack feedback: react with the accepted reaction on first buffer, buffered on appends
@@ -30,6 +30,7 @@ interface BufferedMessage {
30
30
  channelId: string;
31
31
  threadTs: string;
32
32
  files?: SlackFile[];
33
+ botUserId?: string;
33
34
  }
34
35
 
35
36
  const BUFFER_TIMEOUT_MS = Number(process.env.ADDITIVE_SLACK_BUFFER_MS) || 10_000;
@@ -62,6 +63,7 @@ export function bufferThreadMessage(
62
63
  userId: string,
63
64
  ts: string,
64
65
  files?: SlackFile[],
66
+ botUserId?: string,
65
67
  ): void {
66
68
  slackBuffer.enqueue(makeKey(channelId, threadTs), {
67
69
  text,
@@ -70,6 +72,7 @@ export function bufferThreadMessage(
70
72
  channelId,
71
73
  threadTs,
72
74
  files,
75
+ botUserId,
73
76
  });
74
77
  }
75
78
 
@@ -98,7 +101,11 @@ export async function instantFlush(key: string): Promise<void> {
98
101
  /**
99
102
  * Fetch thread context from Slack for the buffer flush task description.
100
103
  */
101
- async function getThreadContextForBuffer(channelId: string, threadTs: string): Promise<string> {
104
+ async function getThreadContextForBuffer(
105
+ channelId: string,
106
+ threadTs: string,
107
+ botUserId?: string,
108
+ ): Promise<string> {
102
109
  const app = getSlackApp();
103
110
  if (!app) return "";
104
111
 
@@ -126,7 +133,7 @@ async function getThreadContextForBuffer(channelId: string, threadTs: string): P
126
133
  })
127
134
  .join("\n");
128
135
 
129
- return await rewriteSlackMentions(formatted);
136
+ return await rewriteSlackMentions(formatted, botUserId);
130
137
  } catch (error) {
131
138
  console.error("[Slack] Failed to fetch thread context for buffer:", error);
132
139
  return "";
@@ -153,6 +160,9 @@ async function slackFlush(
153
160
  // Buffer is guaranteed to have at least one item — the first carries the
154
161
  // original requester's userId (same semantics as the pre-refactor version).
155
162
  const originalRequesterId = items[0]!.userId;
163
+ // Every buffered item is resolved from the same bot's auth.test() cache —
164
+ // any item carrying it is representative of the whole flush.
165
+ const botUserId = items.find((item) => item.botUserId)?.botUserId;
156
166
 
157
167
  console.log(`[Slack] Flushing buffer: ${key} (${items.length} messages, immediate=${immediate})`);
158
168
 
@@ -182,6 +192,7 @@ async function slackFlush(
182
192
  ),
183
193
  )
184
194
  .join("\n---\n"),
195
+ botUserId,
185
196
  );
186
197
  const description = `[Thread follow-up — ${items.length} message(s) buffered]\n\n${combinedText}`;
187
198
 
@@ -234,7 +245,7 @@ async function slackFlush(
234
245
  const lead = await getLeadAgent();
235
246
 
236
247
  // Thread context for the task
237
- const threadContext = await getThreadContextForBuffer(channelId, threadTs);
248
+ const threadContext = await getThreadContextForBuffer(channelId, threadTs, botUserId);
238
249
  const fullDescription = threadContext
239
250
  ? `<thread_context>\n${threadContext}\n</thread_context>\n\n${description}`
240
251
  : description;
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",