@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.
- package/README.md +1 -1
- package/dist/{acp-adapter-pfeje2v5.js → acp-adapter-ahmjxhek.js} +4 -4
- package/dist/{actions-0sttc7f5.js → actions-jzrfpkmn.js} +30 -26
- package/dist/{anthropic-messages-2y8nktar.js → anthropic-messages-9vqsbznt.js} +112 -84
- package/dist/{app-p8teva95.js → app-03na2vwb.js} +9 -6
- package/dist/{assistant-r45jnsrt.js → assistant-hfkxvhgt.js} +31 -27
- package/dist/{azure-openai-responses-f38x2avq.js → azure-openai-responses-04qwdwfq.js} +27 -13
- package/dist/{boot-reembed-je26h5y7.js → boot-reembed-c98xrad3.js} +8 -6
- package/dist/{boot-reembed-1qmybsd4.js → boot-reembed-zt3qm9xn.js} +9 -7
- package/dist/{boot-scrub-logs-gte3h1mm.js → boot-scrub-logs-jcqd8ksj.js} +6 -4
- package/dist/{claude-adapter-csc1mzka.js → claude-adapter-mgryg8g3.js} +17 -15
- package/dist/{claude-managed-adapter-dsmkcrjy.js → claude-managed-adapter-ea2aga6w.js} +3 -3
- package/dist/{claude-sdk-session-fcd6f078.js → claude-sdk-session-jjf4zss1.js} +17 -15
- package/dist/{cli-r2fpe7cx.js → cli-00w2c66p.js} +61 -55
- package/dist/{cli-fm4ytsdk.js → cli-0d9nbqxf.js} +1 -1
- package/dist/cli-0x5qzvkg.js +4279 -0
- package/dist/{cli-d3p82erb.js → cli-18amys3y.js} +9 -9
- package/dist/{cli-wbyk465z.js → cli-1hwnb4hc.js} +1 -1
- package/dist/{cli-xw7vak96.js → cli-3bcmp015.js} +5 -5
- package/dist/cli-3er6gcsn.js +1849 -0
- package/dist/{cli-c4eeb5q2.js → cli-5b6b62tz.js} +1 -1
- package/dist/{cli-4ahpr4sw.js → cli-5mbmvxnn.js} +5 -5
- package/dist/{cli-xy1cchaf.js → cli-5taqtpze.js} +235 -8
- package/dist/{cli-fmy65jdn.js → cli-6ks9xf1n.js} +7 -5
- package/dist/cli-7g9s78jf.js +2827 -0
- package/dist/{cli-jew3jda5.js → cli-7pnz2v6s.js} +2 -2
- package/dist/{cli-r84ctdxn.js → cli-83wkg402.js} +2 -2
- package/dist/{cli-jvy9qgjs.js → cli-a9scg8sv.js} +4 -4
- package/dist/{cli-4yc5d8fk.js → cli-avkd6mqv.js} +50 -0
- package/dist/{cli-ht5rs63n.js → cli-b2g6mzjc.js} +15 -6
- package/dist/{cli-jbmc0r7r.js → cli-bw0dfwkr.js} +1 -1
- package/dist/{cli-8wrympd7.js → cli-bzr0ftht.js} +26 -7
- package/dist/{cli-wpv5rgg2.js → cli-cdq08v3r.js} +2 -2
- package/dist/cli-cgj4hc12.js +18902 -0
- package/dist/cli-d6hk10xx.js +14075 -0
- package/dist/{cli-zvgj5zg8.js → cli-djvxhnc4.js} +31 -3324
- package/dist/{cli-s25avqg9.js → cli-e781c6f9.js} +1 -0
- package/dist/{cli-3bpzj8me.js → cli-eazstkad.js} +2 -2
- package/dist/{cli-s2qjdd6z.js → cli-eetrvv91.js} +1 -1
- package/dist/cli-h38x0qhg.js +6912 -0
- package/dist/{cli-9xvdm3bx.js → cli-h60qfasa.js} +742 -278
- package/dist/{pi-mono-adapter-jcqt71cg.js → cli-ja7mrygn.js} +67343 -101083
- package/dist/{cli-j8dggbcr.js → cli-k0g1sjzm.js} +23 -31
- package/dist/{cli-gphgnvpf.js → cli-m04pt18h.js} +2 -2
- package/dist/{cli-rgvp6r2x.js → cli-mcnhgnn1.js} +193 -4
- package/dist/{cli-j4nv2wqy.js → cli-mzgyhxbe.js} +10 -4
- package/dist/{cli-qn042eh7.js → cli-n0q9b7j4.js} +46 -1870
- package/dist/cli-n7h1kqpf.js +29 -0
- package/dist/{cli-ckz5t2hb.js → cli-nq0kgyxe.js} +5 -5
- package/dist/{cli-z3wkng35.js → cli-qrqhc31j.js} +151 -125
- package/dist/{cli-56h18ng2.js → cli-r8tdds6s.js} +1 -1
- package/dist/{cli-ch38q5z4.js → cli-rj7de4b2.js} +6788 -2541
- package/dist/{cli-qd65dvr6.js → cli-s5s1f2yn.js} +56 -40
- package/dist/{cli-kj76p1ez.js → cli-scwq51gn.js} +13 -7
- package/dist/{cli-2nnd56zt.js → cli-tdh4hjr2.js} +3 -2
- package/dist/{cli-c2mk1kgf.js → cli-v4en75c5.js} +8 -1
- package/dist/{cli-b9dd0h3k.js → cli-vw03hg5t.js} +3 -2
- package/dist/{cli-zvkq5csn.js → cli-ybpp1srm.js} +6 -6
- package/dist/cli-yn0a417a.js +4069 -0
- package/dist/{cli-gpqcs935.js → cli-ysc1kbet.js} +2 -2
- package/dist/{cli-zfzznn7c.js → cli-ytxs7mn1.js} +26 -2
- package/dist/cli-zcsyfw2z.js +3335 -0
- package/dist/{cli-4j8k1m4w.js → cli-zt1227k1.js} +1162 -192
- package/dist/cli-zy5w9gpt.js +230 -0
- package/dist/cli.js +26 -34
- package/dist/{codex-adapter-fxmpsabw.js → codex-adapter-8548daap.js} +12 -11
- package/dist/{codex-session-runner-rr1e47ea.js → codex-session-runner-2dad8p4x.js} +12 -11
- package/dist/{commands-0f5rxz51.js → commands-355dbpa8.js} +6 -4
- package/dist/{db-r3jxf3wy.js → db-4jjvwz1s.js} +6 -4
- package/dist/{dispatcher-06vph6q4.js → dispatcher-ca590nkz.js} +29 -25
- package/dist/dsh-adapter-rgvj6ykp.js +19 -0
- package/dist/{e2b-413b8t4v.js → e2b-93x1k0j5.js} +6 -7
- package/dist/{google-generative-ai-ndjmj5fa.js → google-generative-ai-xtw7tggr.js} +33 -73
- package/dist/{google-vertex-fr665tqs.js → google-vertex-zdqpms6n.js} +38 -68
- package/dist/{handlers-0hgtc910.js → handlers-2y6dqpvf.js} +32 -28
- package/dist/{hook-9kvrv1er.js → hook-0s8knepr.js} +11 -9
- package/dist/{hook-fbfhkat3.js → hook-k0gec254.js} +12 -10
- package/dist/{http-xbyx8k5j.js → http-jp2nb4zw.js} +300 -237
- package/dist/{index-tfsdxyv6.js → index-0wzg3sp3.js} +39 -24
- package/dist/{index-rnyahfn5.js → index-3mq6ce93.js} +29 -25
- package/dist/{index-jsw3aynb.js → index-42s7eezq.js} +29 -25
- package/dist/{cli-pdz9qde4.js → index-9ch1n761.js} +10 -11
- package/dist/{index-gpfn1741.js → index-b56xkbc7.js} +8 -6
- package/dist/{index-66xz5e9j.js → index-h5gs261z.js} +521 -9
- package/dist/{index-2sp0384z.js → index-kkeftv5h.js} +738 -21
- package/dist/{index-sgk50mbg.js → index-s37dbevy.js} +30 -26
- package/dist/index-w097eg0x.js +5 -0
- package/dist/jiti-loader-b4837mtm.js +34 -0
- package/dist/jiti-static-loader-q2g5ds0y.js +31267 -0
- package/dist/{keepalive-s3xpr7sw.js → keepalive-9pfw7t02.js} +8 -6
- package/dist/{lead-rq4n5r16.js → lead-7vcmcdq4.js} +44 -39
- package/dist/{maintenance-f0atj1ab.js → maintenance-x277jbpd.js} +9 -7
- package/dist/{mistral-conversations-qhr84ph3.js → mistral-conversations-zx0kn554.js} +24 -18
- package/dist/{node-x1g4v0pq.js → node-5qtycf6a.js} +16 -11
- package/dist/{oauth-refresh-sweep-q0t3bxre.js → oauth-refresh-sweep-5fwa7th2.js} +7 -5
- package/dist/{onboard-br5j1kch.js → onboard-17sv0f51.js} +103 -35
- package/dist/{openai-codex-responses-wy6a43wd.js → openai-codex-responses-4vjqzvjp.js} +33 -27
- package/dist/{openai-completions-32rdwmze.js → openai-completions-pxsmym7g.js} +52 -64
- package/dist/{openai-responses-6xwf7bzg.js → openai-responses-tmn0rqp0.js} +23 -24
- package/dist/{opencode-adapter-28axqa09.js → opencode-adapter-qbmv4tpy.js} +6 -6
- package/dist/{otel-impl-vdqge2nt.js → otel-impl-srjy4be4.js} +4 -4
- package/dist/{pi-messages-b1mbr6gp.js → pi-messages-txpjj39q.js} +3 -3
- package/dist/pi-mono-adapter-946a0sdd.js +1316 -0
- package/dist/{pricing-refresh-hb3691fd.js → pricing-refresh-vetcm563.js} +8 -6
- package/dist/{rbac-roles-jh280k9g.js → rbac-roles-cfzgr6r1.js} +8 -6
- package/dist/{rbac-roles-t78xt39b.js → rbac-roles-cmxaddmy.js} +7 -5
- package/dist/{render-v2-7p8r2ymg.js → render-v2-f9hzff72.js} +9 -6
- package/dist/{seed-pricing-hf889enh.js → seed-pricing-jggk6rsy.js} +7 -5
- package/dist/{setup-w6mb03jv.js → setup-8mfceb9t.js} +4 -5
- package/dist/virtual-modules-ax661xp0.js +54 -0
- package/dist/{work-objects-nt8gy1kj.js → work-objects-vdsdcczj.js} +32 -28
- package/dist/{worker-dwy617t0.js → worker-cd0nhyv1.js} +44 -39
- package/openapi.json +36 -14
- package/package.json +6 -6
- package/src/be/automation-preflight.ts +6 -4
- package/src/be/extensions/db.ts +35 -4
- package/src/be/models-catalog.ts +2 -0
- package/src/be/modelsdev-cache.json +213 -0
- package/src/be/rbac-audit.ts +2 -0
- package/src/be/seed-pricing.ts +3 -1
- package/src/be/swarm-config-guard.ts +1 -0
- package/src/commands/onboard/compose-generator.ts +6 -1
- package/src/commands/onboard/env-generator.ts +6 -1
- package/src/commands/onboard/manifest.ts +1 -0
- package/src/commands/onboard/steps/integration-slack.tsx +56 -16
- package/src/commands/onboard/steps/review.tsx +5 -0
- package/src/commands/onboard/types.ts +6 -0
- package/src/commands/onboard.tsx +5 -1
- package/src/commands/provider-credentials.ts +14 -2
- package/src/extensions/lifecycle.ts +15 -21
- package/src/http/agents.ts +2 -2
- package/src/http/config.ts +1 -1
- package/src/http/core.ts +5 -3
- package/src/http/extensions.ts +108 -34
- package/src/http/status.ts +22 -10
- package/src/prompts/base-prompt.ts +15 -7
- package/src/providers/claude-managed-models.ts +8 -0
- package/src/providers/codex-models.ts +15 -1
- package/src/providers/dsh-adapter.ts +248 -0
- package/src/providers/index.ts +5 -1
- package/src/providers/modelsdev-reasoning.json +50 -0
- package/src/rbac/legacy-policy.ts +15 -1
- package/src/rbac/types.ts +2 -0
- package/src/server.ts +7 -1
- package/src/slack/app.ts +32 -10
- package/src/slack/config.ts +29 -0
- package/src/telemetry.ts +2 -3
- package/src/tests/agents-harness-provider.test.ts +19 -0
- package/src/tests/agents-list-model-display.test.ts +60 -1
- package/src/tests/base-prompt.test.ts +25 -0
- package/src/tests/claude-managed-adapter.test.ts +6 -0
- package/src/tests/codex-gpt6-release.test.ts +97 -0
- package/src/tests/config-api-only-keys.test.ts +9 -0
- package/src/tests/context-window.test.ts +1 -0
- package/src/tests/dsh-adapter.test.ts +276 -0
- package/src/tests/e2e-nightly-report.test.ts +3 -3
- package/src/tests/e2e-redact.test.ts +2 -0
- package/src/tests/env-flag.test.ts +6 -0
- package/src/tests/extensions-db.test.ts +41 -0
- package/src/tests/extensions-http.test.ts +148 -4
- package/src/tests/extensions-lifecycle.test.ts +18 -0
- package/src/tests/extensions-mcp-tools.test.ts +9 -5
- package/src/tests/internal-ai/complete-structured.test.ts +32 -9
- package/src/tests/internal-ai/credentials.test.ts +4 -4
- package/src/tests/internal-ai/models.test.ts +20 -0
- package/src/tests/migration-046-budgets.test.ts +2 -0
- package/src/tests/model-control.test.ts +2 -2
- package/src/tests/onboard-compose.test.ts +14 -0
- package/src/tests/onboard-env.test.ts +17 -0
- package/src/tests/onboard-manifest.test.ts +12 -0
- package/src/tests/opus-5-5-registry.test.ts +48 -0
- package/src/tests/pricing-refresh.test.ts +1 -1
- package/src/tests/pricing-routes.test.ts +15 -3
- package/src/tests/provider-registration.test.ts +5 -2
- package/src/tests/rbac-admission-e2e.test.ts +54 -0
- package/src/tests/rbac-e2e-helpers.ts +2 -0
- package/src/tests/rbac-engine.test.ts +32 -0
- package/src/tests/rbac-wire-e2e.test.ts +33 -0
- package/src/tests/reasoning-effort.test.ts +8 -0
- package/src/tests/reload-config.test.ts +33 -0
- package/src/tests/session-costs-model-key-normalize.test.ts +1 -0
- package/src/tests/slack-config.test.ts +136 -0
- package/src/tests/slack-mode-lifecycle.test.ts +69 -0
- package/src/tests/status.test.ts +22 -2
- package/src/tests/task-tool-preload-mcp.test.ts +3 -1
- package/src/tests/telemetry-init.test.ts +18 -0
- package/src/tests/workflow-llm.test.ts +3 -3
- package/src/tools/extension-common.ts +4 -0
- package/src/tools/extension-install.ts +1 -1
- package/src/tools/extension-list.ts +4 -3
- package/src/types.ts +17 -8
- package/src/utils/context-window.ts +1 -0
- package/src/utils/credentials.ts +8 -0
- package/src/utils/internal-ai/complete-structured.ts +8 -0
- package/src/utils/internal-ai/models.ts +5 -5
- package/templates/schedules/daily-status-report/content.md +25 -12
- package/templates/schedules/daily-workflow-health-audit/content.md +1 -1
- package/templates/skills/swarm-extensions/content.md +9 -5
- package/dist/cli-4wppfkcm.js +0 -16384
- package/dist/cli-55qrx3q8.js +0 -6041
- package/dist/cli-cg4vnyh4.js +0 -305
- package/dist/cli-jyetr64j.js +0 -52
- package/dist/cli-pp9ax9pa.js +0 -520
- package/dist/cli-qa66qaf0.js +0 -6076
- package/dist/cli-qghpvca2.js +0 -590
- package/dist/cli-rembwgt8.js +0 -861
- package/dist/cli-tcvj7j2r.js +0 -1971
- package/dist/cli-vfcth11k.js +0 -36
- package/dist/cli-w1d1bgkk.js +0 -1422
- package/dist/cli-y6x0gqtg.js +0 -5977
- package/dist/es5-tj3tn4jj.js +0 -844
- package/dist/index-1kq5hw2p.js +0 -467
- package/dist/index-1p8wdstt.js +0 -7
- package/dist/index-8fdjzb2f.js +0 -100
- package/dist/index-bxmwzbgw.js +0 -365
- package/dist/index-nzerxd5z.js +0 -7
- package/dist/index-pyymnzat.js +0 -1168
- package/dist/index-qv6atbab.js +0 -619
- package/dist/index-rja32qa7.js +0 -801
- package/dist/index-tzg5srfp.js +0 -211
- package/dist/index-vhncw983.js +0 -90
- package/dist/index-xk036zsk.js +0 -859
- package/dist/index-zyazbzcb.js +0 -6
- 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
|
+
}
|
package/src/providers/index.ts
CHANGED
|
@@ -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":
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
29
|
-
|
|
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
|
-
|
|
32
|
-
|
|
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<
|
|
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({
|