@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.
- package/README.md +2 -1
- package/dist/{acp-adapter-4s7q25ft.js → acp-adapter-ahmjxhek.js} +4 -4
- package/dist/{actions-cs6rhmvc.js → actions-jzrfpkmn.js} +30 -26
- package/dist/{anthropic-messages-2y8nktar.js → anthropic-messages-9vqsbznt.js} +112 -84
- package/dist/{app-j54aq0dj.js → app-03na2vwb.js} +9 -6
- package/dist/{assistant-w1pgn7vf.js → assistant-hfkxvhgt.js} +33 -29
- package/dist/{azure-openai-responses-f38x2avq.js → azure-openai-responses-04qwdwfq.js} +27 -13
- package/dist/{boot-reembed-smy30z32.js → boot-reembed-c98xrad3.js} +8 -6
- package/dist/{boot-reembed-42vgkrrj.js → boot-reembed-zt3qm9xn.js} +9 -7
- package/dist/{boot-scrub-logs-b6s72fsw.js → boot-scrub-logs-jcqd8ksj.js} +6 -4
- package/dist/{claude-adapter-fh7rmcj9.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-xhynatn9.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-sakxd2rg.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-sqmh7jj1.js → cli-5mbmvxnn.js} +5 -5
- package/dist/{cli-v9sk4dzw.js → cli-5taqtpze.js} +235 -8
- package/dist/{cli-dsyg66vt.js → cli-6ks9xf1n.js} +7 -5
- package/dist/cli-7g9s78jf.js +2827 -0
- package/dist/{cli-jtvs5m8q.js → cli-7pnz2v6s.js} +2 -2
- package/dist/{cli-jytf5mw9.js → cli-83wkg402.js} +2 -2
- package/dist/{cli-3pft2z6r.js → cli-a9scg8sv.js} +12 -10
- package/dist/{cli-4yc5d8fk.js → cli-avkd6mqv.js} +50 -0
- package/dist/{cli-ht5rs63n.js → cli-b2g6mzjc.js} +15 -6
- package/dist/{cli-3g61md6v.js → cli-bw0dfwkr.js} +1 -1
- package/dist/{cli-8wrympd7.js → cli-bzr0ftht.js} +26 -7
- package/dist/{cli-ks8a1cgq.js → cli-cdq08v3r.js} +2 -2
- package/dist/cli-cgj4hc12.js +18902 -0
- package/dist/cli-d6hk10xx.js +14075 -0
- package/dist/{cli-bdt7w021.js → cli-djvxhnc4.js} +31 -3324
- package/dist/{cli-s25avqg9.js → cli-e781c6f9.js} +1 -0
- package/dist/{cli-b4dyetqt.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-y8tayczk.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-wfve107r.js → cli-n0q9b7j4.js} +46 -1870
- package/dist/cli-n7h1kqpf.js +29 -0
- package/dist/{cli-ng4q6a9t.js → cli-nq0kgyxe.js} +7 -7
- package/dist/{cli-72tgtzr1.js → cli-qrqhc31j.js} +151 -125
- package/dist/{cli-vbk025fm.js → cli-r8tdds6s.js} +1 -1
- package/dist/{cli-ch38q5z4.js → cli-rj7de4b2.js} +6788 -2541
- package/dist/{cli-ftdjz1mk.js → cli-s5s1f2yn.js} +56 -40
- package/dist/{cli-kj76p1ez.js → cli-scwq51gn.js} +13 -7
- package/dist/{cli-7bwaerjt.js → cli-tdh4hjr2.js} +3 -2
- package/dist/{cli-c2mk1kgf.js → cli-v4en75c5.js} +8 -1
- package/dist/{cli-kf8qp2vf.js → cli-vw03hg5t.js} +3 -2
- package/dist/{cli-gfez0qmk.js → cli-ybpp1srm.js} +6 -6
- package/dist/cli-yn0a417a.js +4069 -0
- package/dist/{cli-gxf2ebyz.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-k6yq95sn.js → codex-adapter-8548daap.js} +12 -11
- package/dist/{codex-session-runner-3rp84pk8.js → codex-session-runner-2dad8p4x.js} +12 -11
- package/dist/{commands-pbr5jzy5.js → commands-355dbpa8.js} +6 -4
- package/dist/{db-z0xaa59a.js → db-4jjvwz1s.js} +6 -4
- package/dist/{dispatcher-pwpgsj0x.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-5mzk4mbg.js → handlers-2y6dqpvf.js} +32 -28
- package/dist/{hook-8bz33zee.js → hook-0s8knepr.js} +11 -9
- package/dist/{hook-kbh84zb1.js → hook-k0gec254.js} +12 -10
- package/dist/{http-pppxg5vr.js → http-jp2nb4zw.js} +300 -237
- package/dist/{index-k1hq1qt6.js → index-0wzg3sp3.js} +39 -24
- package/dist/{index-68bd9b5f.js → index-3mq6ce93.js} +29 -25
- package/dist/{index-z0zwjtkn.js → index-42s7eezq.js} +29 -25
- package/dist/{cli-pdz9qde4.js → index-9ch1n761.js} +10 -11
- package/dist/{index-y0te09pn.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-mjpxsr6k.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-xvmwnh1d.js → keepalive-9pfw7t02.js} +8 -6
- package/dist/{lead-pf8nvxt8.js → lead-7vcmcdq4.js} +44 -39
- package/dist/{maintenance-jk4q0wgg.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-6207x9sj.js → oauth-refresh-sweep-5fwa7th2.js} +7 -5
- package/dist/{onboard-b0csgm0q.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-b6rfehth.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-4e201wtd.js → pricing-refresh-vetcm563.js} +8 -6
- package/dist/{rbac-roles-mkc5n5qa.js → rbac-roles-cfzgr6r1.js} +8 -6
- package/dist/{rbac-roles-zp6e4geh.js → rbac-roles-cmxaddmy.js} +7 -5
- package/dist/{render-v2-4fazz9tf.js → render-v2-f9hzff72.js} +9 -6
- package/dist/{seed-pricing-thxpn9vn.js → seed-pricing-jggk6rsy.js} +7 -5
- package/dist/{setup-wb0z8cat.js → setup-8mfceb9t.js} +4 -5
- package/dist/virtual-modules-ax661xp0.js +54 -0
- package/dist/{work-objects-h3498kwe.js → work-objects-vdsdcczj.js} +32 -28
- package/dist/{worker-2m2tzxp3.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/assistant.ts +10 -1
- package/src/slack/config.ts +29 -0
- package/src/slack/handlers.ts +10 -1
- package/src/slack/thread-buffer.ts +14 -3
- 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-assistant.test.ts +134 -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> {
|
package/src/slack/assistant.ts
CHANGED
|
@@ -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(
|
|
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
|
+
}
|
package/src/slack/handlers.ts
CHANGED
|
@@ -523,7 +523,15 @@ export function registerMessageHandler(app: App): void {
|
|
|
523
523
|
);
|
|
524
524
|
|
|
525
525
|
if (nowMessage || msg.files?.length) {
|
|
526
|
-
bufferThreadMessage(
|
|
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(
|
|
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",
|