@openma/deepseek-harness-acp 0.4.5 → 0.4.7
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 +146 -108
- package/assets/icon.svg +8 -0
- package/cordis.patch.yml +7 -1
- package/dist/bridge.js +1467 -285
- package/dist/index.js +162 -2214
- package/package.json +6 -4
package/dist/index.js
CHANGED
|
@@ -1,89 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
-
var __esm = (fn, res, err) => function __init() {
|
|
5
|
-
if (err) throw err[0];
|
|
6
|
-
try {
|
|
7
|
-
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
8
|
-
} catch (e) {
|
|
9
|
-
throw err = [e], e;
|
|
10
|
-
}
|
|
11
|
-
};
|
|
12
|
-
var __export = (target, all) => {
|
|
13
|
-
for (var name3 in all)
|
|
14
|
-
__defProp(target, name3, { get: all[name3], enumerable: true });
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
// src/bridge/self-harness.ts
|
|
18
|
-
var self_harness_exports = {};
|
|
19
|
-
__export(self_harness_exports, {
|
|
20
|
-
selfHarness: () => selfHarness
|
|
21
|
-
});
|
|
22
|
-
import { createUserMessage, errorChain } from "@deepseek-ai/dsh-llm";
|
|
23
|
-
import { SessionId } from "@deepseek-ai/dsh-session";
|
|
24
|
-
import { foldSessionTitle } from "@deepseek-ai/dsh-session-title";
|
|
25
|
-
import { SANDBOX_MODES, setSandboxMode } from "@deepseek-ai/dsh-sandbox-policy";
|
|
26
|
-
import { credentialRef } from "@deepseek-ai/dsh-credentials";
|
|
27
|
-
import * as agentModule from "@deepseek-ai/dsh-agent";
|
|
28
|
-
import * as mcpClient from "@deepseek-ai/dsh-mcp-client";
|
|
29
|
-
function selfHarness() {
|
|
30
|
-
const agents = agentModule;
|
|
31
|
-
const installModelSelection = agents.installModelSelection ?? agents.ln;
|
|
32
|
-
return {
|
|
33
|
-
createUserMessage,
|
|
34
|
-
errorChain,
|
|
35
|
-
sessionId: SessionId,
|
|
36
|
-
foldSessionTitle,
|
|
37
|
-
setSandboxMode,
|
|
38
|
-
sandboxModes: SANDBOX_MODES,
|
|
39
|
-
credentialRef,
|
|
40
|
-
...installModelSelection !== void 0 ? { installModelSelection } : {},
|
|
41
|
-
mcpClient
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
var init_self_harness = __esm({
|
|
45
|
-
"src/bridge/self-harness.ts"() {
|
|
46
|
-
"use strict";
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
2
|
|
|
50
3
|
// src/index.ts
|
|
51
4
|
import { spawnSync } from "node:child_process";
|
|
52
5
|
|
|
53
|
-
// src/
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
// src/bridge/index.ts
|
|
61
|
-
var bridge_exports = {};
|
|
62
|
-
__export(bridge_exports, {
|
|
63
|
-
apply: () => apply,
|
|
64
|
-
inject: () => inject,
|
|
65
|
-
name: () => name
|
|
66
|
-
});
|
|
67
|
-
import { randomUUID } from "node:crypto";
|
|
68
|
-
import { isAbsolute } from "node:path";
|
|
69
|
-
import { Readable, Writable } from "node:stream";
|
|
70
|
-
import {
|
|
71
|
-
AgentSideConnection,
|
|
72
|
-
ndJsonStream,
|
|
73
|
-
PROTOCOL_VERSION,
|
|
74
|
-
RequestError
|
|
75
|
-
} from "@agentclientprotocol/sdk";
|
|
76
|
-
|
|
77
|
-
// src/version.ts
|
|
78
|
-
import { createRequire } from "node:module";
|
|
79
|
-
var VERSION = createRequire(import.meta.url)("../package.json").version;
|
|
6
|
+
// src/harness.ts
|
|
7
|
+
import { execFileSync } from "node:child_process";
|
|
8
|
+
import { existsSync, readFileSync, realpathSync } from "node:fs";
|
|
9
|
+
import { delimiter, dirname, isAbsolute, join, resolve, sep } from "node:path";
|
|
10
|
+
import { pathToFileURL, fileURLToPath } from "node:url";
|
|
80
11
|
|
|
81
12
|
// src/log.ts
|
|
82
13
|
var PREFIX = "[dsh-acp]";
|
|
83
|
-
function logWarn(message) {
|
|
84
|
-
process.stderr.write(`${PREFIX} warn: ${message}
|
|
85
|
-
`);
|
|
86
|
-
}
|
|
87
14
|
function logError(message) {
|
|
88
15
|
process.stderr.write(`${PREFIX} error: ${message}
|
|
89
16
|
`);
|
|
@@ -97,1830 +24,7 @@ function logInfo(message) {
|
|
|
97
24
|
`);
|
|
98
25
|
}
|
|
99
26
|
|
|
100
|
-
// src/bridge/translate.ts
|
|
101
|
-
var MAX_TITLE_LENGTH = 80;
|
|
102
|
-
var MAX_RESULT_TEXT = 2e4;
|
|
103
|
-
function firstLine(text, limit = MAX_TITLE_LENGTH) {
|
|
104
|
-
const line = text.split("\n", 1)[0] ?? "";
|
|
105
|
-
return line.length > limit ? `${line.slice(0, limit - 1)}\u2026` : line;
|
|
106
|
-
}
|
|
107
|
-
function asString(value) {
|
|
108
|
-
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
109
|
-
}
|
|
110
|
-
function pathLocations(args) {
|
|
111
|
-
const locations = [];
|
|
112
|
-
for (const key of ["path", "file_path", "filePath", "file"]) {
|
|
113
|
-
const path = asString(args[key]);
|
|
114
|
-
if (path !== void 0) {
|
|
115
|
-
locations.push({ path });
|
|
116
|
-
break;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
return locations;
|
|
120
|
-
}
|
|
121
|
-
function classifyToolCall(name3, rawArguments) {
|
|
122
|
-
let args = {};
|
|
123
|
-
try {
|
|
124
|
-
const parsed = JSON.parse(rawArguments);
|
|
125
|
-
if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
126
|
-
args = parsed;
|
|
127
|
-
}
|
|
128
|
-
} catch {
|
|
129
|
-
args = { arguments: rawArguments };
|
|
130
|
-
}
|
|
131
|
-
const locations = pathLocations(args);
|
|
132
|
-
const path = locations[0]?.path;
|
|
133
|
-
const facts = (kind, title) => ({
|
|
134
|
-
kind,
|
|
135
|
-
title,
|
|
136
|
-
locations,
|
|
137
|
-
rawInput: args
|
|
138
|
-
});
|
|
139
|
-
switch (name3) {
|
|
140
|
-
case "bash": {
|
|
141
|
-
const command = asString(args["command"]);
|
|
142
|
-
const restart = args["restart"] === true;
|
|
143
|
-
return facts("execute", command !== void 0 ? firstLine(command) : restart ? "Restart bash" : "bash");
|
|
144
|
-
}
|
|
145
|
-
case "read":
|
|
146
|
-
return facts("read", path !== void 0 ? `Read ${path}` : "Read file");
|
|
147
|
-
case "write":
|
|
148
|
-
return facts("edit", path !== void 0 ? `Write ${path}` : "Write file");
|
|
149
|
-
case "edit":
|
|
150
|
-
return facts("edit", path !== void 0 ? `Edit ${path}` : "Edit file");
|
|
151
|
-
case "str_replace_editor": {
|
|
152
|
-
const command = asString(args["command"]) ?? "edit";
|
|
153
|
-
const kind = command === "view" ? "read" : "edit";
|
|
154
|
-
return facts(kind, path !== void 0 ? `${command} ${path}` : command);
|
|
155
|
-
}
|
|
156
|
-
case "todo_write":
|
|
157
|
-
return facts("think", "Update plan");
|
|
158
|
-
case "subagent":
|
|
159
|
-
case "subagent_fork": {
|
|
160
|
-
const task = asString(args["task"]) ?? asString(args["prompt"]);
|
|
161
|
-
return facts("other", task !== void 0 ? `Subagent: ${firstLine(task, 60)}` : "Subagent");
|
|
162
|
-
}
|
|
163
|
-
case "skill": {
|
|
164
|
-
const skillName = asString(args["name"]) ?? asString(args["skill"]);
|
|
165
|
-
return facts("other", skillName !== void 0 ? `Skill: ${skillName}` : "Skill");
|
|
166
|
-
}
|
|
167
|
-
default:
|
|
168
|
-
break;
|
|
169
|
-
}
|
|
170
|
-
const lowered = name3.toLowerCase();
|
|
171
|
-
if (/(^|_)(grep|glob|find|search|ls|list)($|_)/.test(lowered)) {
|
|
172
|
-
const query = asString(args["query"]) ?? asString(args["pattern"]);
|
|
173
|
-
return facts("search", query !== void 0 ? `Search for '${firstLine(query, 50)}'` : name3);
|
|
174
|
-
}
|
|
175
|
-
if (/(fetch|web|http|browse)/.test(lowered)) {
|
|
176
|
-
const url = asString(args["url"]) ?? asString(args["query"]);
|
|
177
|
-
return facts("fetch", url !== void 0 ? `Fetch ${firstLine(url, 60)}` : name3);
|
|
178
|
-
}
|
|
179
|
-
if (/(job|task|run|exec)/.test(lowered)) return facts("execute", name3);
|
|
180
|
-
return facts("other", name3);
|
|
181
|
-
}
|
|
182
|
-
function diffsFromToolMeta(meta) {
|
|
183
|
-
if (meta === null || typeof meta !== "object") return [];
|
|
184
|
-
const diffs = meta["diffs"];
|
|
185
|
-
if (!Array.isArray(diffs)) return [];
|
|
186
|
-
const result = [];
|
|
187
|
-
for (const entry of diffs) {
|
|
188
|
-
if (entry === null || typeof entry !== "object") continue;
|
|
189
|
-
const diff = entry;
|
|
190
|
-
const path = asString(diff["path"]);
|
|
191
|
-
const newText = diff["newText"];
|
|
192
|
-
const oldText = diff["oldText"];
|
|
193
|
-
if (path === void 0 || typeof newText !== "string") continue;
|
|
194
|
-
result.push({ path, oldText: typeof oldText === "string" ? oldText : null, newText });
|
|
195
|
-
}
|
|
196
|
-
return result;
|
|
197
|
-
}
|
|
198
|
-
function extractToolResult(data) {
|
|
199
|
-
let failed = data["error"] !== void 0 && data["error"] !== null;
|
|
200
|
-
const parts = [];
|
|
201
|
-
const message = data["message"];
|
|
202
|
-
const content = message !== null && typeof message === "object" ? message["content"] : void 0;
|
|
203
|
-
if (Array.isArray(content)) {
|
|
204
|
-
for (const block of content) {
|
|
205
|
-
if (block === null || typeof block !== "object") continue;
|
|
206
|
-
const b = block;
|
|
207
|
-
if (b["type"] !== "tool-result") continue;
|
|
208
|
-
if (b["isError"] === true) failed = true;
|
|
209
|
-
const inner = b["content"];
|
|
210
|
-
if (!Array.isArray(inner)) continue;
|
|
211
|
-
for (const innerBlock of inner) {
|
|
212
|
-
if (innerBlock === null || typeof innerBlock !== "object") continue;
|
|
213
|
-
const ib = innerBlock;
|
|
214
|
-
if (ib["type"] === "text" && typeof ib["text"] === "string") parts.push(ib["text"]);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
let text = parts.join("");
|
|
219
|
-
if (text.length > MAX_RESULT_TEXT) text = `${text.slice(0, MAX_RESULT_TEXT)}
|
|
220
|
-
\u2026 (truncated)`;
|
|
221
|
-
return { failed, text, diffs: diffsFromToolMeta(data["meta"]) };
|
|
222
|
-
}
|
|
223
|
-
function extractMessageTexts(message) {
|
|
224
|
-
const texts = [];
|
|
225
|
-
const reasoning = [];
|
|
226
|
-
const content = message !== null && typeof message === "object" ? message["content"] : void 0;
|
|
227
|
-
if (Array.isArray(content)) {
|
|
228
|
-
for (const block of content) {
|
|
229
|
-
if (block === null || typeof block !== "object") continue;
|
|
230
|
-
const b = block;
|
|
231
|
-
if (b["type"] === "text" && typeof b["text"] === "string") texts.push(b["text"]);
|
|
232
|
-
else if (b["type"] === "reasoning" && typeof b["text"] === "string") reasoning.push(b["text"]);
|
|
233
|
-
else if (b["type"] === "image") texts.push("[image attachment]");
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
return { text: texts.join(""), reasoning: reasoning.join("") };
|
|
237
|
-
}
|
|
238
|
-
function planEntries(data) {
|
|
239
|
-
const todos = data["todos"];
|
|
240
|
-
if (!Array.isArray(todos)) return [];
|
|
241
|
-
const entries = [];
|
|
242
|
-
for (const todo of todos) {
|
|
243
|
-
if (todo === null || typeof todo !== "object") continue;
|
|
244
|
-
const t = todo;
|
|
245
|
-
const content = asString(t["content"]);
|
|
246
|
-
const status = t["status"];
|
|
247
|
-
if (content === void 0) continue;
|
|
248
|
-
entries.push({
|
|
249
|
-
content,
|
|
250
|
-
priority: "medium",
|
|
251
|
-
status: status === "in_progress" || status === "completed" ? status : "pending"
|
|
252
|
-
});
|
|
253
|
-
}
|
|
254
|
-
return entries;
|
|
255
|
-
}
|
|
256
|
-
function turnEndToStopReason(reason) {
|
|
257
|
-
switch (reason?.kind) {
|
|
258
|
-
case "completed":
|
|
259
|
-
return "end_turn";
|
|
260
|
-
case "max-tokens":
|
|
261
|
-
return "max_tokens";
|
|
262
|
-
case "blocked":
|
|
263
|
-
return "refusal";
|
|
264
|
-
case "interrupted":
|
|
265
|
-
return "cancelled";
|
|
266
|
-
case "aborted": {
|
|
267
|
-
const cause = reason.reason?.kind;
|
|
268
|
-
return cause === "user" || cause === "disposed" ? "cancelled" : "end_turn";
|
|
269
|
-
}
|
|
270
|
-
default:
|
|
271
|
-
return "end_turn";
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
function emptyWindow() {
|
|
275
|
-
return {
|
|
276
|
-
usage: { input: 0, output: 0, cachedRead: 0, cachedWrite: 0, thought: 0 },
|
|
277
|
-
sawUsage: false,
|
|
278
|
-
turnEnds: /* @__PURE__ */ new Map(),
|
|
279
|
-
lastTurnEnd: void 0,
|
|
280
|
-
error: void 0
|
|
281
|
-
};
|
|
282
|
-
}
|
|
283
|
-
var SessionProjection = class {
|
|
284
|
-
/** Context capacity from the latest `request/context` event, when known. */
|
|
285
|
-
contextWindow;
|
|
286
|
-
/** Latest `session/title` text observed on the log. */
|
|
287
|
-
title;
|
|
288
|
-
streamedText = /* @__PURE__ */ new Set();
|
|
289
|
-
streamedReasoning = /* @__PURE__ */ new Set();
|
|
290
|
-
toolCalls = /* @__PURE__ */ new Map();
|
|
291
|
-
window = emptyWindow();
|
|
292
|
-
lastContextUse;
|
|
293
|
-
/** Client renders `_meta.terminal_output` display terminals (Zed extension). */
|
|
294
|
-
terminalOutput;
|
|
295
|
-
/** Session working directory, advertised on display terminals. */
|
|
296
|
-
cwd;
|
|
297
|
-
constructor(contextWindow, options) {
|
|
298
|
-
this.contextWindow = contextWindow;
|
|
299
|
-
this.terminalOutput = options?.terminalOutput ?? false;
|
|
300
|
-
this.cwd = options?.cwd;
|
|
301
|
-
}
|
|
302
|
-
/** Reset per-prompt accumulators; call when a new `session/prompt` starts. */
|
|
303
|
-
beginPrompt() {
|
|
304
|
-
this.window = emptyWindow();
|
|
305
|
-
}
|
|
306
|
-
/** The captured ending for a specific turn, when it already ended. */
|
|
307
|
-
turnEndFor(turn) {
|
|
308
|
-
return this.window.turnEnds.get(turn);
|
|
309
|
-
}
|
|
310
|
-
/** The last turn ending inside the current prompt window. */
|
|
311
|
-
get lastTurnEnd() {
|
|
312
|
-
return this.window.lastTurnEnd;
|
|
313
|
-
}
|
|
314
|
-
/** The first failed-turn error inside the current prompt window. */
|
|
315
|
-
get turnError() {
|
|
316
|
-
return this.window.error;
|
|
317
|
-
}
|
|
318
|
-
/** Accumulated ACP usage for the current prompt window. */
|
|
319
|
-
promptUsage() {
|
|
320
|
-
if (!this.window.sawUsage) return void 0;
|
|
321
|
-
const { input, output, cachedRead, cachedWrite, thought } = this.window.usage;
|
|
322
|
-
return {
|
|
323
|
-
totalTokens: input + output + cachedRead + cachedWrite,
|
|
324
|
-
inputTokens: input,
|
|
325
|
-
outputTokens: output,
|
|
326
|
-
...thought > 0 ? { thoughtTokens: thought } : {},
|
|
327
|
-
...cachedRead > 0 ? { cachedReadTokens: cachedRead } : {},
|
|
328
|
-
...cachedWrite > 0 ? { cachedWriteTokens: cachedWrite } : {}
|
|
329
|
-
};
|
|
330
|
-
}
|
|
331
|
-
/**
|
|
332
|
-
* Project one session event onto zero or more ACP updates.
|
|
333
|
-
*/
|
|
334
|
-
onEvent(event) {
|
|
335
|
-
const data = event.data ?? {};
|
|
336
|
-
switch (event.type) {
|
|
337
|
-
case "assistant/chunk":
|
|
338
|
-
return this.onChunk(data);
|
|
339
|
-
case "assistant/message":
|
|
340
|
-
return this.onAssistantMessage(data);
|
|
341
|
-
case "tool/call":
|
|
342
|
-
return this.onToolCall(data);
|
|
343
|
-
case "tool/result":
|
|
344
|
-
return this.onToolResult(data);
|
|
345
|
-
case "todo/write": {
|
|
346
|
-
const entries = planEntries(data);
|
|
347
|
-
return entries.length > 0 ? [{ sessionUpdate: "plan", entries }] : [];
|
|
348
|
-
}
|
|
349
|
-
case "turn/end":
|
|
350
|
-
return this.onTurnEnd(data);
|
|
351
|
-
case "request/context": {
|
|
352
|
-
const contextWindow = data["contextWindow"];
|
|
353
|
-
if (typeof contextWindow === "number" && contextWindow > 0) this.contextWindow = contextWindow;
|
|
354
|
-
return [];
|
|
355
|
-
}
|
|
356
|
-
case "session/title": {
|
|
357
|
-
const title = asString(data["title"]);
|
|
358
|
-
if (title === void 0 || title === this.title) return [];
|
|
359
|
-
this.title = title;
|
|
360
|
-
return [{ sessionUpdate: "session_info_update", title, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }];
|
|
361
|
-
}
|
|
362
|
-
default:
|
|
363
|
-
return [];
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
stepKey(data) {
|
|
367
|
-
return `${String(data["turn"])}:${String(data["step"])}`;
|
|
368
|
-
}
|
|
369
|
-
onChunk(data) {
|
|
370
|
-
const chunk = data["chunk"];
|
|
371
|
-
if (chunk === null || typeof chunk === "undefined" || typeof chunk !== "object") return [];
|
|
372
|
-
const c = chunk;
|
|
373
|
-
const text = c["text"];
|
|
374
|
-
if (typeof text !== "string" || text.length === 0) return [];
|
|
375
|
-
if (c["type"] === "text-delta") {
|
|
376
|
-
this.streamedText.add(this.stepKey(data));
|
|
377
|
-
return [{ sessionUpdate: "agent_message_chunk", content: { type: "text", text } }];
|
|
378
|
-
}
|
|
379
|
-
if (c["type"] === "reasoning-delta") {
|
|
380
|
-
this.streamedReasoning.add(this.stepKey(data));
|
|
381
|
-
return [{ sessionUpdate: "agent_thought_chunk", content: { type: "text", text } }];
|
|
382
|
-
}
|
|
383
|
-
return [];
|
|
384
|
-
}
|
|
385
|
-
onAssistantMessage(data) {
|
|
386
|
-
const updates = [];
|
|
387
|
-
const key = this.stepKey(data);
|
|
388
|
-
const { text, reasoning } = extractMessageTexts(data["message"]);
|
|
389
|
-
if (reasoning.length > 0 && !this.streamedReasoning.has(key)) {
|
|
390
|
-
updates.push({ sessionUpdate: "agent_thought_chunk", content: { type: "text", text: reasoning } });
|
|
391
|
-
}
|
|
392
|
-
if (text.length > 0 && !this.streamedText.has(key)) {
|
|
393
|
-
updates.push({ sessionUpdate: "agent_message_chunk", content: { type: "text", text } });
|
|
394
|
-
}
|
|
395
|
-
this.streamedText.delete(key);
|
|
396
|
-
this.streamedReasoning.delete(key);
|
|
397
|
-
const usage = data["usage"];
|
|
398
|
-
if (usage !== null && typeof usage === "object") {
|
|
399
|
-
const u = usage;
|
|
400
|
-
const num = (v) => typeof v === "number" && Number.isFinite(v) && v > 0 ? v : 0;
|
|
401
|
-
const input = num(u["inputTokens"]);
|
|
402
|
-
const output = num(u["outputTokens"]);
|
|
403
|
-
const cachedRead = num(u["cacheReadTokens"]);
|
|
404
|
-
const cachedWrite = num(u["cacheWriteTokens"]);
|
|
405
|
-
this.window.usage.input += input;
|
|
406
|
-
this.window.usage.output += output;
|
|
407
|
-
this.window.usage.cachedRead += cachedRead;
|
|
408
|
-
this.window.usage.cachedWrite += cachedWrite;
|
|
409
|
-
this.window.usage.thought += num(u["reasoningTokens"]);
|
|
410
|
-
this.window.sawUsage = true;
|
|
411
|
-
this.lastContextUse = input + cachedRead + cachedWrite + output;
|
|
412
|
-
if (this.contextWindow !== void 0 && this.lastContextUse > 0) {
|
|
413
|
-
updates.push({
|
|
414
|
-
sessionUpdate: "usage_update",
|
|
415
|
-
used: this.lastContextUse,
|
|
416
|
-
size: this.contextWindow
|
|
417
|
-
});
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
return updates;
|
|
421
|
-
}
|
|
422
|
-
onToolCall(data) {
|
|
423
|
-
const callId = asString(data["callId"]);
|
|
424
|
-
const name3 = asString(data["name"]) ?? "tool";
|
|
425
|
-
if (callId === void 0) return [];
|
|
426
|
-
const rawArguments = typeof data["arguments"] === "string" ? data["arguments"] : "{}";
|
|
427
|
-
const facts = classifyToolCall(name3, rawArguments);
|
|
428
|
-
this.toolCalls.set(callId, facts);
|
|
429
|
-
const displayTerminal = this.terminalOutput && facts.kind === "execute";
|
|
430
|
-
return [
|
|
431
|
-
{
|
|
432
|
-
sessionUpdate: "tool_call",
|
|
433
|
-
toolCallId: callId,
|
|
434
|
-
title: facts.title,
|
|
435
|
-
name: name3,
|
|
436
|
-
kind: facts.kind,
|
|
437
|
-
status: "in_progress",
|
|
438
|
-
rawInput: facts.rawInput,
|
|
439
|
-
...facts.locations.length > 0 ? { locations: facts.locations } : {},
|
|
440
|
-
...displayTerminal ? {
|
|
441
|
-
content: [{ type: "terminal", terminalId: callId }],
|
|
442
|
-
_meta: {
|
|
443
|
-
terminal_info: {
|
|
444
|
-
terminal_id: callId,
|
|
445
|
-
...this.cwd !== void 0 ? { cwd: this.cwd } : {}
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
} : {}
|
|
449
|
-
}
|
|
450
|
-
];
|
|
451
|
-
}
|
|
452
|
-
onToolResult(data) {
|
|
453
|
-
const message = data["message"];
|
|
454
|
-
const callId = asString(
|
|
455
|
-
message !== null && typeof message === "object" ? (() => {
|
|
456
|
-
const content2 = message["content"];
|
|
457
|
-
if (!Array.isArray(content2)) return void 0;
|
|
458
|
-
const block = content2.find(
|
|
459
|
-
(b) => b !== null && typeof b === "object" && b["type"] === "tool-result"
|
|
460
|
-
);
|
|
461
|
-
return block === void 0 ? void 0 : block["toolCallId"];
|
|
462
|
-
})() : void 0
|
|
463
|
-
) ?? asString(data["callId"]);
|
|
464
|
-
if (callId === void 0) return [];
|
|
465
|
-
const facts = this.toolCalls.get(callId);
|
|
466
|
-
const { failed, text, diffs } = extractToolResult(data);
|
|
467
|
-
const content = diffs.map((diff) => ({
|
|
468
|
-
type: "diff",
|
|
469
|
-
path: diff.path,
|
|
470
|
-
...diff.oldText !== null ? { oldText: diff.oldText } : {},
|
|
471
|
-
newText: diff.newText
|
|
472
|
-
}));
|
|
473
|
-
const isCommand = facts?.kind === "execute";
|
|
474
|
-
if (this.terminalOutput && isCommand) {
|
|
475
|
-
this.toolCalls.delete(callId);
|
|
476
|
-
return [
|
|
477
|
-
...text.length > 0 ? [
|
|
478
|
-
{
|
|
479
|
-
sessionUpdate: "tool_call_update",
|
|
480
|
-
toolCallId: callId,
|
|
481
|
-
_meta: { terminal_output: { terminal_id: callId, data: text } }
|
|
482
|
-
}
|
|
483
|
-
] : [],
|
|
484
|
-
{
|
|
485
|
-
sessionUpdate: "tool_call_update",
|
|
486
|
-
toolCallId: callId,
|
|
487
|
-
status: failed ? "failed" : "completed",
|
|
488
|
-
...text.length > 0 ? { rawOutput: { output: text, isError: failed } } : {},
|
|
489
|
-
_meta: {
|
|
490
|
-
terminal_exit: { terminal_id: callId, exit_code: failed ? 1 : 0, signal: null }
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
];
|
|
494
|
-
}
|
|
495
|
-
if (text.length > 0 && diffs.length === 0) {
|
|
496
|
-
const block = {
|
|
497
|
-
type: "text",
|
|
498
|
-
text: isCommand ? `\`\`\`sh
|
|
499
|
-
${text.replace(/\n+$/, "")}
|
|
500
|
-
\`\`\`
|
|
501
|
-
` : text
|
|
502
|
-
};
|
|
503
|
-
content.push({ type: "content", content: block });
|
|
504
|
-
}
|
|
505
|
-
this.toolCalls.delete(callId);
|
|
506
|
-
return [
|
|
507
|
-
{
|
|
508
|
-
sessionUpdate: "tool_call_update",
|
|
509
|
-
toolCallId: callId,
|
|
510
|
-
status: failed ? "failed" : "completed",
|
|
511
|
-
...content.length > 0 ? { content } : {},
|
|
512
|
-
...text.length > 0 ? { rawOutput: { output: text, isError: failed } } : {}
|
|
513
|
-
}
|
|
514
|
-
];
|
|
515
|
-
}
|
|
516
|
-
onTurnEnd(data) {
|
|
517
|
-
const reason = data["reason"];
|
|
518
|
-
const turn = data["turn"];
|
|
519
|
-
if (reason !== null && typeof reason === "object") {
|
|
520
|
-
const r = reason;
|
|
521
|
-
if (typeof turn === "number") this.window.turnEnds.set(turn, r);
|
|
522
|
-
this.window.lastTurnEnd = r;
|
|
523
|
-
if (r.kind === "error" && this.window.error === void 0) {
|
|
524
|
-
this.window.error = { message: r.error?.message ?? "model turn failed" };
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
return [];
|
|
528
|
-
}
|
|
529
|
-
};
|
|
530
|
-
|
|
531
|
-
// src/bridge/history.ts
|
|
532
|
-
function userText(data) {
|
|
533
|
-
if (data === void 0) return void 0;
|
|
534
|
-
const source = data["source"];
|
|
535
|
-
const kind = source !== null && typeof source === "object" ? source["kind"] : void 0;
|
|
536
|
-
if (kind !== "user") return void 0;
|
|
537
|
-
const content = data["content"];
|
|
538
|
-
if (!Array.isArray(content)) return void 0;
|
|
539
|
-
const parts = [];
|
|
540
|
-
for (const block of content) {
|
|
541
|
-
if (block !== null && typeof block === "object") {
|
|
542
|
-
const b = block;
|
|
543
|
-
if (b["type"] === "text" && typeof b["text"] === "string") parts.push(b["text"]);
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
const text = parts.join("");
|
|
547
|
-
return text.length > 0 ? text : void 0;
|
|
548
|
-
}
|
|
549
|
-
function buildReplay(events) {
|
|
550
|
-
const projection = new SessionProjection();
|
|
551
|
-
const updates = [];
|
|
552
|
-
let plan;
|
|
553
|
-
let title;
|
|
554
|
-
for (const event of events) {
|
|
555
|
-
if (event.type === "user/message") {
|
|
556
|
-
const text = userText(event.data);
|
|
557
|
-
if (text !== void 0) {
|
|
558
|
-
updates.push({ sessionUpdate: "user_message_chunk", content: { type: "text", text } });
|
|
559
|
-
}
|
|
560
|
-
continue;
|
|
561
|
-
}
|
|
562
|
-
if (event.type === "session/title") {
|
|
563
|
-
const t = event.data?.["title"];
|
|
564
|
-
if (typeof t === "string" && t.length > 0) title = t;
|
|
565
|
-
continue;
|
|
566
|
-
}
|
|
567
|
-
for (const update of projection.onEvent(event)) {
|
|
568
|
-
if (update.sessionUpdate === "usage_update") continue;
|
|
569
|
-
if (update.sessionUpdate === "plan") {
|
|
570
|
-
plan = update;
|
|
571
|
-
continue;
|
|
572
|
-
}
|
|
573
|
-
updates.push(update);
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
if (plan !== void 0) updates.push(plan);
|
|
577
|
-
return { updates, title, contextWindow: projection.contextWindow };
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
// src/bridge/prompt.ts
|
|
581
|
-
var UnsupportedPromptContentError = class extends Error {
|
|
582
|
-
constructor(contentType) {
|
|
583
|
-
super(`unsupported prompt content type: ${contentType}`);
|
|
584
|
-
this.contentType = contentType;
|
|
585
|
-
}
|
|
586
|
-
contentType;
|
|
587
|
-
};
|
|
588
|
-
function contextBlock(uri, text) {
|
|
589
|
-
return `<context ref=${JSON.stringify(uri)}>
|
|
590
|
-
${text}
|
|
591
|
-
</context>`;
|
|
592
|
-
}
|
|
593
|
-
function convertPrompt(prompt) {
|
|
594
|
-
const parts = [];
|
|
595
|
-
const display = [];
|
|
596
|
-
for (const block of prompt) {
|
|
597
|
-
switch (block.type) {
|
|
598
|
-
case "text":
|
|
599
|
-
parts.push(block.text);
|
|
600
|
-
display.push(block.text);
|
|
601
|
-
break;
|
|
602
|
-
case "resource_link":
|
|
603
|
-
parts.push(`
|
|
604
|
-
[resource_link name=${JSON.stringify(block.name)} uri=${JSON.stringify(block.uri)}]
|
|
605
|
-
`);
|
|
606
|
-
display.push(`@${block.name}`);
|
|
607
|
-
break;
|
|
608
|
-
case "resource": {
|
|
609
|
-
const resource = block.resource;
|
|
610
|
-
if ("text" in resource && typeof resource.text === "string") {
|
|
611
|
-
parts.push(`
|
|
612
|
-
${contextBlock(resource.uri, resource.text)}
|
|
613
|
-
`);
|
|
614
|
-
display.push(`@${resource.uri}`);
|
|
615
|
-
break;
|
|
616
|
-
}
|
|
617
|
-
throw new UnsupportedPromptContentError("resource (binary)");
|
|
618
|
-
}
|
|
619
|
-
default:
|
|
620
|
-
throw new UnsupportedPromptContentError(block.type);
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
const text = parts.join("");
|
|
624
|
-
return {
|
|
625
|
-
blocks: text.length > 0 ? [{ type: "text", text }] : [],
|
|
626
|
-
displayText: display.join(" ").trim()
|
|
627
|
-
};
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
// src/bridge/index.ts
|
|
631
|
-
var name = "acp-bridge";
|
|
632
|
-
var inject = ["agents"];
|
|
633
|
-
function invalidParams(detail) {
|
|
634
|
-
return RequestError.invalidParams(void 0, detail);
|
|
635
|
-
}
|
|
636
|
-
function internalError(detail) {
|
|
637
|
-
return RequestError.internalError(void 0, detail);
|
|
638
|
-
}
|
|
639
|
-
function authRequired(detail) {
|
|
640
|
-
return RequestError.authRequired(void 0, detail);
|
|
641
|
-
}
|
|
642
|
-
function envCredentialPresent() {
|
|
643
|
-
return Boolean(process.env["DEEPSEEK_API_KEY"] || process.env["DEEPSEEK_BASE_URL"]);
|
|
644
|
-
}
|
|
645
|
-
var MODE_LABELS = {
|
|
646
|
-
"read-only": { name: "Read-only", description: "Bash and file mutations are denied by the sandbox" },
|
|
647
|
-
"workspace-write": {
|
|
648
|
-
name: "Workspace write",
|
|
649
|
-
description: "Writes are confined to the session workspace; wider access asks for permission"
|
|
650
|
-
},
|
|
651
|
-
"danger-full-access": {
|
|
652
|
-
name: "Full access",
|
|
653
|
-
description: "No sandbox confinement and no permission prompts"
|
|
654
|
-
}
|
|
655
|
-
};
|
|
656
|
-
async function apply(ctx, config = {}) {
|
|
657
|
-
const agents = ctx.agents;
|
|
658
|
-
const harness = config.harness ?? (await Promise.resolve().then(() => (init_self_harness(), self_harness_exports))).selfHarness();
|
|
659
|
-
const { createUserMessage: createUserMessage2, errorChain: errorChain2, sessionId: SessionId2, foldSessionTitle: foldSessionTitle2, setSandboxMode: setSandboxMode2 } = harness;
|
|
660
|
-
const SANDBOX_MODES2 = harness.sandboxModes;
|
|
661
|
-
const sessions = /* @__PURE__ */ new Map();
|
|
662
|
-
let closed = false;
|
|
663
|
-
let conn;
|
|
664
|
-
let clientTerminalOutput = false;
|
|
665
|
-
const modelCandidates = () => {
|
|
666
|
-
const seen = /* @__PURE__ */ new Set();
|
|
667
|
-
if (config.model !== void 0) seen.add(config.model);
|
|
668
|
-
for (const model of config.models ?? []) if (model.trim().length > 0) seen.add(model.trim());
|
|
669
|
-
return [...seen];
|
|
670
|
-
};
|
|
671
|
-
const defaultSelection = () => {
|
|
672
|
-
const service = ctx.get("agentDefaultModel");
|
|
673
|
-
if (service === void 0) return {};
|
|
674
|
-
try {
|
|
675
|
-
return service.currentSelection();
|
|
676
|
-
} catch (error) {
|
|
677
|
-
logDebug(`agentDefaultModel.currentSelection failed: ${String(error)}`);
|
|
678
|
-
return {};
|
|
679
|
-
}
|
|
680
|
-
};
|
|
681
|
-
const defaultProvider = () => config.provider ?? defaultSelection().provider;
|
|
682
|
-
const encodeChoice = (provider, model) => provider === void 0 || provider === defaultProvider() ? model : `${provider}::${model}`;
|
|
683
|
-
const decodeChoice = (value) => {
|
|
684
|
-
const i = value.indexOf("::");
|
|
685
|
-
if (i <= 0) return { provider: defaultProvider(), model: value };
|
|
686
|
-
return { provider: value.slice(0, i), model: value.slice(i + 2) };
|
|
687
|
-
};
|
|
688
|
-
let liveCatalog;
|
|
689
|
-
const effortCache = /* @__PURE__ */ new Map();
|
|
690
|
-
const discoverModels = async () => {
|
|
691
|
-
if (liveCatalog !== void 0) return liveCatalog;
|
|
692
|
-
const llm = ctx.get("llm");
|
|
693
|
-
const found = [];
|
|
694
|
-
if (llm !== void 0) {
|
|
695
|
-
try {
|
|
696
|
-
const providers = llm.listProviders();
|
|
697
|
-
const multi = providers.length > 1;
|
|
698
|
-
for (const provider of providers) {
|
|
699
|
-
const models = await llm.listModels(provider.id).catch((error) => {
|
|
700
|
-
logDebug(`listModels(${provider.id}) failed: ${String(error)}`);
|
|
701
|
-
return [];
|
|
702
|
-
});
|
|
703
|
-
for (const model of models) {
|
|
704
|
-
found.push({
|
|
705
|
-
provider: provider.id,
|
|
706
|
-
model: model.id,
|
|
707
|
-
label: multi ? `${model.name} (${provider.name})` : model.name
|
|
708
|
-
});
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
} catch (error) {
|
|
712
|
-
logDebug(`model discovery failed: ${String(error)}`);
|
|
713
|
-
}
|
|
714
|
-
}
|
|
715
|
-
liveCatalog = found;
|
|
716
|
-
return found;
|
|
717
|
-
};
|
|
718
|
-
ctx.on("llm/adapters-updated", () => {
|
|
719
|
-
liveCatalog = void 0;
|
|
720
|
-
effortCache.clear();
|
|
721
|
-
});
|
|
722
|
-
const routeOf = (record) => {
|
|
723
|
-
const logged = loggedConfig(record);
|
|
724
|
-
const provider = record.provider ?? defaultProvider() ?? logged?.provider;
|
|
725
|
-
const model = record.model ?? config.model ?? defaultSelection().model ?? logged?.model ?? record.agent.options.model;
|
|
726
|
-
return {
|
|
727
|
-
...provider !== void 0 ? { provider } : {},
|
|
728
|
-
...model !== void 0 ? { model } : {}
|
|
729
|
-
};
|
|
730
|
-
};
|
|
731
|
-
const loggedConfig = (record) => {
|
|
732
|
-
try {
|
|
733
|
-
const header = record.agent.session.requestHeader?.();
|
|
734
|
-
const cfg = header?.config;
|
|
735
|
-
if (cfg === void 0) return void 0;
|
|
736
|
-
return {
|
|
737
|
-
provider: cfg.provider,
|
|
738
|
-
model: cfg.model,
|
|
739
|
-
...cfg.reasoningEffort !== void 0 ? { reasoningEffort: String(cfg.reasoningEffort) } : {}
|
|
740
|
-
};
|
|
741
|
-
} catch {
|
|
742
|
-
return void 0;
|
|
743
|
-
}
|
|
744
|
-
};
|
|
745
|
-
const effortCatalog = async (provider, model) => {
|
|
746
|
-
if (provider === void 0 || model === void 0) return void 0;
|
|
747
|
-
const key = `${provider}::${model}`;
|
|
748
|
-
if (effortCache.has(key)) return effortCache.get(key);
|
|
749
|
-
const llm = ctx.get("llm");
|
|
750
|
-
let catalog;
|
|
751
|
-
try {
|
|
752
|
-
const reasoning = (await llm?.resolveModelInfo?.(provider, model))?.reasoning;
|
|
753
|
-
if (reasoning !== void 0 && reasoning.efforts.length > 0) {
|
|
754
|
-
catalog = {
|
|
755
|
-
efforts: reasoning.efforts.map((effort) => ({
|
|
756
|
-
id: String(effort.id),
|
|
757
|
-
name: effort.name,
|
|
758
|
-
...effort.description !== void 0 ? { description: effort.description } : {}
|
|
759
|
-
})),
|
|
760
|
-
...reasoning.defaultEffort !== void 0 ? { defaultEffort: String(reasoning.defaultEffort) } : {}
|
|
761
|
-
};
|
|
762
|
-
}
|
|
763
|
-
} catch (error) {
|
|
764
|
-
logDebug(`resolveModelInfo(${key}) failed: ${String(error)}`);
|
|
765
|
-
}
|
|
766
|
-
effortCache.set(key, catalog);
|
|
767
|
-
return catalog;
|
|
768
|
-
};
|
|
769
|
-
const ensureSelection = (record) => {
|
|
770
|
-
const install = harness.installModelSelection;
|
|
771
|
-
if (install === void 0) return void 0;
|
|
772
|
-
if (record.selection !== void 0) return record.selection;
|
|
773
|
-
let picked;
|
|
774
|
-
const selection = {
|
|
775
|
-
get current() {
|
|
776
|
-
if (picked !== void 0) return picked;
|
|
777
|
-
const logged = loggedConfig(record);
|
|
778
|
-
if (logged !== void 0) return logged;
|
|
779
|
-
const { provider, model } = routeOf(record);
|
|
780
|
-
if (provider === void 0 || model === void 0) return void 0;
|
|
781
|
-
const defaults = defaultSelection();
|
|
782
|
-
const effort = defaults.model === void 0 || defaults.model === model ? defaults.reasoningEffort : void 0;
|
|
783
|
-
return { provider, model, ...effort !== void 0 ? { reasoningEffort: effort } : {} };
|
|
784
|
-
},
|
|
785
|
-
set current(next) {
|
|
786
|
-
picked = next;
|
|
787
|
-
},
|
|
788
|
-
assembled: void 0
|
|
789
|
-
};
|
|
790
|
-
try {
|
|
791
|
-
install(record.agent.ctx, selection);
|
|
792
|
-
} catch (error) {
|
|
793
|
-
logWarn(`installModelSelection failed: ${String(error)}`);
|
|
794
|
-
return void 0;
|
|
795
|
-
}
|
|
796
|
-
record.selection = selection;
|
|
797
|
-
return selection;
|
|
798
|
-
};
|
|
799
|
-
const switchModel = async (record, acpSessionId, value) => {
|
|
800
|
-
if (record.inflight !== void 0) {
|
|
801
|
-
throw invalidParams("cannot switch models while a prompt is running");
|
|
802
|
-
}
|
|
803
|
-
const choice = decodeChoice(value);
|
|
804
|
-
const known = /* @__PURE__ */ new Set([
|
|
805
|
-
...modelCandidates().map((model) => encodeChoice(void 0, model)),
|
|
806
|
-
...(await discoverModels()).map((entry) => encodeChoice(entry.provider, entry.model)),
|
|
807
|
-
encodeChoice(record.provider, record.model ?? "")
|
|
808
|
-
]);
|
|
809
|
-
if (!known.has(value)) throw invalidParams(`unknown model: ${value}`);
|
|
810
|
-
if (choice.model === record.model && choice.provider === (record.provider ?? defaultProvider())) return;
|
|
811
|
-
const sessionId = record.agent.session.id;
|
|
812
|
-
await record.dispose().catch((error) => {
|
|
813
|
-
logWarn(`dispose during model switch failed: ${String(error)}`);
|
|
814
|
-
});
|
|
815
|
-
let handle;
|
|
816
|
-
try {
|
|
817
|
-
const presets = presetsService();
|
|
818
|
-
handle = await agents.resume({
|
|
819
|
-
resumeSessionId: sessionId,
|
|
820
|
-
agentOptions: agentOptionsFor(choice.model, choice.provider),
|
|
821
|
-
...presetSetup(presets, record.preset) !== void 0 ? { setup: presetSetup(presets, record.preset) } : {}
|
|
822
|
-
});
|
|
823
|
-
} catch (error) {
|
|
824
|
-
sessions.delete(acpSessionId);
|
|
825
|
-
throw internalError(`model switch failed: ${errorChain2(error)}`);
|
|
826
|
-
}
|
|
827
|
-
record.agent = handle.agent;
|
|
828
|
-
record.dispose = () => handle.dispose();
|
|
829
|
-
record.model = choice.model;
|
|
830
|
-
if (choice.provider !== void 0 && choice.provider !== defaultProvider()) {
|
|
831
|
-
record.provider = choice.provider;
|
|
832
|
-
} else {
|
|
833
|
-
delete record.provider;
|
|
834
|
-
}
|
|
835
|
-
delete record.selection;
|
|
836
|
-
ensureSelection(record);
|
|
837
|
-
if (record.effort !== void 0) {
|
|
838
|
-
const route = routeOf(record);
|
|
839
|
-
const catalog = await effortCatalog(route.provider, route.model);
|
|
840
|
-
if (catalog?.efforts.some((effort) => effort.id === record.effort) === true && route.provider !== void 0 && route.model !== void 0) {
|
|
841
|
-
const selection = ensureSelection(record);
|
|
842
|
-
if (selection !== void 0) {
|
|
843
|
-
selection.current = {
|
|
844
|
-
provider: route.provider,
|
|
845
|
-
model: route.model,
|
|
846
|
-
reasoningEffort: record.effort
|
|
847
|
-
};
|
|
848
|
-
}
|
|
849
|
-
} else {
|
|
850
|
-
delete record.effort;
|
|
851
|
-
}
|
|
852
|
-
}
|
|
853
|
-
setApprovalPolicy(record, record.approvals);
|
|
854
|
-
};
|
|
855
|
-
const credentialPresent = async () => {
|
|
856
|
-
if (envCredentialPresent()) return true;
|
|
857
|
-
const brand = harness.credentialRef;
|
|
858
|
-
if (brand === void 0) return false;
|
|
859
|
-
const deadline = Date.now() + 5e3;
|
|
860
|
-
for (; ; ) {
|
|
861
|
-
const seam = ctx.get("credentials");
|
|
862
|
-
if (seam !== void 0) {
|
|
863
|
-
try {
|
|
864
|
-
return await seam.resolve(brand("DEEPSEEK_API_KEY")) !== void 0;
|
|
865
|
-
} catch (error) {
|
|
866
|
-
logWarn(`credential lookup failed: ${String(error)}`);
|
|
867
|
-
return false;
|
|
868
|
-
}
|
|
869
|
-
}
|
|
870
|
-
if (Date.now() >= deadline) {
|
|
871
|
-
logDebug("credential gate: no credentials service registered within 5s");
|
|
872
|
-
return false;
|
|
873
|
-
}
|
|
874
|
-
await new Promise((resolve2) => setTimeout(resolve2, 100));
|
|
875
|
-
}
|
|
876
|
-
};
|
|
877
|
-
const agentOptionsFor = (model, provider) => ({
|
|
878
|
-
...provider !== void 0 ? { provider } : config.provider !== void 0 ? { provider: config.provider } : {},
|
|
879
|
-
...model !== void 0 ? { model } : {},
|
|
880
|
-
...config.maxTokens !== void 0 ? { maxTokens: config.maxTokens } : {}
|
|
881
|
-
});
|
|
882
|
-
const ownedRecord = (agent) => {
|
|
883
|
-
const record = sessions.get(String(agent.session.id));
|
|
884
|
-
return record?.agent === agent ? record : void 0;
|
|
885
|
-
};
|
|
886
|
-
const requireSession = (sessionId) => {
|
|
887
|
-
const record = sessions.get(sessionId);
|
|
888
|
-
if (record === void 0) throw invalidParams(`unknown session: ${sessionId}`);
|
|
889
|
-
return record;
|
|
890
|
-
};
|
|
891
|
-
const requireOrRestoreSession = async (sessionId) => {
|
|
892
|
-
const record = sessions.get(sessionId);
|
|
893
|
-
if (record !== void 0) return record;
|
|
894
|
-
if (ctx.get("sessionPersistence") === void 0) throw invalidParams(`unknown session: ${sessionId}`);
|
|
895
|
-
logWarn(`restoring session ${sessionId} from the persisted log`);
|
|
896
|
-
try {
|
|
897
|
-
return await restoreSession(sessionId, { replay: false });
|
|
898
|
-
} catch (error) {
|
|
899
|
-
throw invalidParams(`unknown session: ${sessionId} (${errorChain2(error)})`);
|
|
900
|
-
}
|
|
901
|
-
};
|
|
902
|
-
const restoreSession = async (sessionId, options) => {
|
|
903
|
-
const persistence = requirePersistence();
|
|
904
|
-
const existing = sessions.get(sessionId);
|
|
905
|
-
if (existing !== void 0) {
|
|
906
|
-
sessions.delete(sessionId);
|
|
907
|
-
existing.agent.cancel({ kind: "user" });
|
|
908
|
-
settlePrompt(existing, "cancelled");
|
|
909
|
-
await existing.dispose().catch((error) => {
|
|
910
|
-
logWarn(`dispose before reload failed: ${String(error)}`);
|
|
911
|
-
});
|
|
912
|
-
}
|
|
913
|
-
let events;
|
|
914
|
-
let storedHeader;
|
|
915
|
-
try {
|
|
916
|
-
const inspected = await persistence.inspect(SessionId2(sessionId));
|
|
917
|
-
events = inspected.events;
|
|
918
|
-
storedHeader = inspected.header;
|
|
919
|
-
} catch (error) {
|
|
920
|
-
throw invalidParams(`session not found: ${sessionId} (${errorChain2(error)})`);
|
|
921
|
-
}
|
|
922
|
-
const replay = buildReplay(events);
|
|
923
|
-
if (options.replay) {
|
|
924
|
-
for (const update of replay.updates) notify(sessionId, update);
|
|
925
|
-
if (replay.title !== void 0) {
|
|
926
|
-
notify(sessionId, {
|
|
927
|
-
sessionUpdate: "session_info_update",
|
|
928
|
-
title: replay.title
|
|
929
|
-
});
|
|
930
|
-
}
|
|
931
|
-
}
|
|
932
|
-
const presets = presetsService();
|
|
933
|
-
const storedPreset = presetFromLog(storedHeader, events);
|
|
934
|
-
const presetId = presets === void 0 ? void 0 : (await presets.resolve(storedPreset)).id;
|
|
935
|
-
const handle = await agents.resume({
|
|
936
|
-
resumeSessionId: SessionId2(sessionId),
|
|
937
|
-
agentOptions: agentOptionsFor(config.model),
|
|
938
|
-
...presetSetup(presets, presetId) !== void 0 ? { setup: presetSetup(presets, presetId) } : {}
|
|
939
|
-
});
|
|
940
|
-
const cwd = options.cwd ?? storedHeader?.cwd;
|
|
941
|
-
const projection = new SessionProjection(replay.contextWindow, {
|
|
942
|
-
terminalOutput: clientTerminalOutput,
|
|
943
|
-
...cwd !== void 0 ? { cwd } : {}
|
|
944
|
-
});
|
|
945
|
-
projection.title = replay.title;
|
|
946
|
-
const record = registerRecord(
|
|
947
|
-
sessionId,
|
|
948
|
-
handle.agent,
|
|
949
|
-
() => handle.dispose(),
|
|
950
|
-
config.model,
|
|
951
|
-
config.permissionMode ?? "workspace-write",
|
|
952
|
-
projection
|
|
953
|
-
);
|
|
954
|
-
if (presetId !== void 0) record.preset = presetId;
|
|
955
|
-
{
|
|
956
|
-
const permission = permissionService();
|
|
957
|
-
const storedMode = permission?.current(events);
|
|
958
|
-
if (storedMode !== void 0) record.modeId = storedMode;
|
|
959
|
-
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
960
|
-
const event = events[index];
|
|
961
|
-
if (event?.type === "approval/policy") {
|
|
962
|
-
const policy = event.data?.policy;
|
|
963
|
-
if (policy === "ask" || policy === "never") record.approvals = policy;
|
|
964
|
-
break;
|
|
965
|
-
}
|
|
966
|
-
}
|
|
967
|
-
}
|
|
968
|
-
ensureSelection(record);
|
|
969
|
-
queueMicrotask(() => publishCommands(sessionId));
|
|
970
|
-
return record;
|
|
971
|
-
};
|
|
972
|
-
const assertOpen = () => {
|
|
973
|
-
if (closed) throw internalError("the ACP bridge has been disposed");
|
|
974
|
-
};
|
|
975
|
-
const notify = (sessionId, update) => {
|
|
976
|
-
void conn.sessionUpdate({ sessionId, update }).catch((error) => {
|
|
977
|
-
logWarn(`session/update failed: ${String(error)}`);
|
|
978
|
-
});
|
|
979
|
-
};
|
|
980
|
-
const settlePrompt = (record, reason) => {
|
|
981
|
-
const inflight = record.inflight;
|
|
982
|
-
if (inflight === void 0) return;
|
|
983
|
-
record.inflight = void 0;
|
|
984
|
-
inflight.resolve(reason);
|
|
985
|
-
};
|
|
986
|
-
const presetsService = () => ctx.get("agentPresets");
|
|
987
|
-
const presetSetup = (presets, presetId) => {
|
|
988
|
-
if (presets === void 0 || presetId === void 0) return void 0;
|
|
989
|
-
return async (agentCtx) => {
|
|
990
|
-
logDebug(`preset setup: mounting "${presetId}"`);
|
|
991
|
-
await presets.mount(agentCtx, presetId);
|
|
992
|
-
logDebug(`preset setup: mounted "${presetId}"`);
|
|
993
|
-
};
|
|
994
|
-
};
|
|
995
|
-
const presetFromLog = (header, events) => {
|
|
996
|
-
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
997
|
-
const event = events[index];
|
|
998
|
-
if (event?.type === "agent-preset/selected") {
|
|
999
|
-
return event.data?.agentPreset;
|
|
1000
|
-
}
|
|
1001
|
-
}
|
|
1002
|
-
return header?.agentPreset;
|
|
1003
|
-
};
|
|
1004
|
-
const presetLabel = (id) => id.charAt(0).toUpperCase() + id.slice(1);
|
|
1005
|
-
const modeState = (record) => {
|
|
1006
|
-
const permission = permissionService();
|
|
1007
|
-
if (permission !== void 0 && permission.names.length > 0) {
|
|
1008
|
-
return {
|
|
1009
|
-
currentModeId: record.modeId,
|
|
1010
|
-
availableModes: permission.names.map((name3) => {
|
|
1011
|
-
const spec = permission.resolve(name3);
|
|
1012
|
-
return { id: name3, name: spec.name, description: spec.description };
|
|
1013
|
-
})
|
|
1014
|
-
};
|
|
1015
|
-
}
|
|
1016
|
-
return {
|
|
1017
|
-
currentModeId: record.modeId,
|
|
1018
|
-
availableModes: SANDBOX_MODES2.map((mode) => {
|
|
1019
|
-
const label = MODE_LABELS[mode] ?? { name: mode, description: "" };
|
|
1020
|
-
return { id: mode, name: label.name, description: label.description };
|
|
1021
|
-
})
|
|
1022
|
-
};
|
|
1023
|
-
};
|
|
1024
|
-
const switchPreset = async (record, acpSessionId, presetId) => {
|
|
1025
|
-
if (record.inflight !== void 0) {
|
|
1026
|
-
throw invalidParams("cannot switch presets while a prompt is running");
|
|
1027
|
-
}
|
|
1028
|
-
const presets = presetsService();
|
|
1029
|
-
if (presets === void 0) throw invalidParams(`unknown mode: ${presetId}`);
|
|
1030
|
-
let resolved;
|
|
1031
|
-
try {
|
|
1032
|
-
resolved = (await presets.resolve(presetId)).id;
|
|
1033
|
-
} catch (error) {
|
|
1034
|
-
throw invalidParams(`unknown preset: ${presetId} (${errorChain2(error)})`);
|
|
1035
|
-
}
|
|
1036
|
-
if (resolved === record.preset) return;
|
|
1037
|
-
try {
|
|
1038
|
-
record.agent.session.append("agent-preset/selected", { agentPreset: resolved });
|
|
1039
|
-
} catch (error) {
|
|
1040
|
-
logWarn(`recording preset selection failed: ${String(error)}`);
|
|
1041
|
-
}
|
|
1042
|
-
const sessionId = record.agent.session.id;
|
|
1043
|
-
await record.dispose().catch((error) => {
|
|
1044
|
-
logWarn(`dispose during preset switch failed: ${String(error)}`);
|
|
1045
|
-
});
|
|
1046
|
-
let handle;
|
|
1047
|
-
try {
|
|
1048
|
-
handle = await agents.resume({
|
|
1049
|
-
resumeSessionId: sessionId,
|
|
1050
|
-
agentOptions: agentOptionsFor(record.model ?? config.model, record.provider),
|
|
1051
|
-
setup: presetSetup(presets, resolved)
|
|
1052
|
-
});
|
|
1053
|
-
} catch (error) {
|
|
1054
|
-
sessions.delete(acpSessionId);
|
|
1055
|
-
throw internalError(`preset switch failed: ${errorChain2(error)}`);
|
|
1056
|
-
}
|
|
1057
|
-
record.agent = handle.agent;
|
|
1058
|
-
record.dispose = () => handle.dispose();
|
|
1059
|
-
record.preset = resolved;
|
|
1060
|
-
delete record.selection;
|
|
1061
|
-
ensureSelection(record);
|
|
1062
|
-
setApprovalPolicy(record, record.approvals);
|
|
1063
|
-
publishCommands(acpSessionId, record);
|
|
1064
|
-
};
|
|
1065
|
-
const setApprovalPolicy = (record, policy) => {
|
|
1066
|
-
try {
|
|
1067
|
-
ctx.get("approval")?.setPolicy?.(record.agent, policy);
|
|
1068
|
-
} catch (error) {
|
|
1069
|
-
logWarn(`approval policy switch failed: ${String(error)}`);
|
|
1070
|
-
}
|
|
1071
|
-
record.approvals = policy;
|
|
1072
|
-
};
|
|
1073
|
-
const permissionService = () => ctx.get("permissionPresets") ?? ctx.get("permission");
|
|
1074
|
-
const applyMode = (record, sessionId, modeId) => {
|
|
1075
|
-
const permission = permissionService();
|
|
1076
|
-
if (permission !== void 0 && permission.names.includes(modeId)) {
|
|
1077
|
-
let bundledApproval = record.approvals;
|
|
1078
|
-
permission.apply(record.agent.session, modeId, (policy) => {
|
|
1079
|
-
bundledApproval = policy;
|
|
1080
|
-
});
|
|
1081
|
-
setApprovalPolicy(record, bundledApproval);
|
|
1082
|
-
record.modeId = permission.resolve(modeId).sandbox;
|
|
1083
|
-
notify(sessionId, { sessionUpdate: "current_mode_update", currentModeId: modeId });
|
|
1084
|
-
return;
|
|
1085
|
-
}
|
|
1086
|
-
const mode = SANDBOX_MODES2.find((candidate) => candidate === modeId);
|
|
1087
|
-
if (mode === void 0) throw invalidParams(`unknown mode: ${modeId}`);
|
|
1088
|
-
setSandboxMode2(record.agent.session, mode);
|
|
1089
|
-
setApprovalPolicy(record, mode === "danger-full-access" ? "never" : "ask");
|
|
1090
|
-
record.modeId = mode;
|
|
1091
|
-
notify(sessionId, { sessionUpdate: "current_mode_update", currentModeId: mode });
|
|
1092
|
-
};
|
|
1093
|
-
const modelChoices = async (record) => {
|
|
1094
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1095
|
-
const options = [];
|
|
1096
|
-
const push = (value, name3) => {
|
|
1097
|
-
if (seen.has(value)) return;
|
|
1098
|
-
seen.add(value);
|
|
1099
|
-
options.push({ value, name: name3 });
|
|
1100
|
-
};
|
|
1101
|
-
const current = encodeChoice(
|
|
1102
|
-
record.provider,
|
|
1103
|
-
record.model ?? config.model ?? defaultSelection().model ?? record.agent.options.model ?? ""
|
|
1104
|
-
);
|
|
1105
|
-
if (current.length === 0) return [];
|
|
1106
|
-
const discovered = await discoverModels();
|
|
1107
|
-
push(current, discovered.find((c) => encodeChoice(c.provider, c.model) === current)?.label ?? current);
|
|
1108
|
-
for (const choice of discovered) {
|
|
1109
|
-
push(encodeChoice(choice.provider, choice.model), choice.label);
|
|
1110
|
-
}
|
|
1111
|
-
for (const model of modelCandidates()) push(encodeChoice(void 0, model), model);
|
|
1112
|
-
return options;
|
|
1113
|
-
};
|
|
1114
|
-
const configOptions = async (record) => {
|
|
1115
|
-
const result = [];
|
|
1116
|
-
{
|
|
1117
|
-
const permission = permissionService();
|
|
1118
|
-
const levels = permission !== void 0 && permission.names.length > 0 ? permission.names.map((name3) => {
|
|
1119
|
-
const spec = permission.resolve(name3);
|
|
1120
|
-
return { value: name3, name: spec.name, description: spec.description };
|
|
1121
|
-
}) : SANDBOX_MODES2.map((mode) => {
|
|
1122
|
-
const label = MODE_LABELS[mode] ?? { name: mode, description: "" };
|
|
1123
|
-
return { value: mode, name: label.name, description: label.description };
|
|
1124
|
-
});
|
|
1125
|
-
result.push({
|
|
1126
|
-
type: "select",
|
|
1127
|
-
id: "mode",
|
|
1128
|
-
name: "Permissions",
|
|
1129
|
-
category: "mode",
|
|
1130
|
-
currentValue: record.modeId,
|
|
1131
|
-
options: levels
|
|
1132
|
-
});
|
|
1133
|
-
}
|
|
1134
|
-
const models = await modelChoices(record);
|
|
1135
|
-
if (models.length >= 2) {
|
|
1136
|
-
result.push({
|
|
1137
|
-
type: "select",
|
|
1138
|
-
id: "model",
|
|
1139
|
-
name: "Model",
|
|
1140
|
-
category: "model",
|
|
1141
|
-
currentValue: models[0].value,
|
|
1142
|
-
options: models
|
|
1143
|
-
});
|
|
1144
|
-
}
|
|
1145
|
-
const route = routeOf(record);
|
|
1146
|
-
const efforts = await effortCatalog(route.provider, route.model);
|
|
1147
|
-
if (efforts !== void 0 && efforts.efforts.length >= 2) {
|
|
1148
|
-
const known = new Set(efforts.efforts.map((effort) => effort.id));
|
|
1149
|
-
const preferred = [
|
|
1150
|
-
record.effort,
|
|
1151
|
-
loggedConfig(record)?.reasoningEffort,
|
|
1152
|
-
// The user's saved product default (Web UI → settings.yaml),
|
|
1153
|
-
// e.g. reasoningEffort: max, outranks the adapter's default.
|
|
1154
|
-
defaultSelection().reasoningEffort,
|
|
1155
|
-
efforts.defaultEffort
|
|
1156
|
-
].find(
|
|
1157
|
-
(candidate) => candidate !== void 0 && known.has(candidate)
|
|
1158
|
-
);
|
|
1159
|
-
result.push({
|
|
1160
|
-
type: "select",
|
|
1161
|
-
id: "effort",
|
|
1162
|
-
name: "Reasoning",
|
|
1163
|
-
category: "thought_level",
|
|
1164
|
-
currentValue: preferred ?? efforts.efforts[0].id,
|
|
1165
|
-
options: efforts.efforts.map((effort) => ({
|
|
1166
|
-
value: effort.id,
|
|
1167
|
-
name: effort.name,
|
|
1168
|
-
...effort.description !== void 0 ? { description: effort.description } : {}
|
|
1169
|
-
}))
|
|
1170
|
-
});
|
|
1171
|
-
}
|
|
1172
|
-
const presets = presetsService();
|
|
1173
|
-
if (presets !== void 0 && record.preset !== void 0) {
|
|
1174
|
-
try {
|
|
1175
|
-
const roster = await presets.list();
|
|
1176
|
-
if (roster.length >= 2) {
|
|
1177
|
-
result.push({
|
|
1178
|
-
type: "select",
|
|
1179
|
-
id: "preset",
|
|
1180
|
-
name: "Preset",
|
|
1181
|
-
currentValue: record.preset,
|
|
1182
|
-
options: roster.map((preset) => ({
|
|
1183
|
-
value: preset.id,
|
|
1184
|
-
name: presetLabel(preset.id),
|
|
1185
|
-
description: `Agent preset \u201C${preset.id}\u201D`
|
|
1186
|
-
}))
|
|
1187
|
-
});
|
|
1188
|
-
}
|
|
1189
|
-
} catch (error) {
|
|
1190
|
-
logWarn(`preset roster unavailable: ${String(error)}`);
|
|
1191
|
-
}
|
|
1192
|
-
}
|
|
1193
|
-
return result;
|
|
1194
|
-
};
|
|
1195
|
-
const BUILTIN_COMMANDS = [
|
|
1196
|
-
{ name: "status", description: "Show adapter, model, mode, and token status" },
|
|
1197
|
-
{
|
|
1198
|
-
name: "login",
|
|
1199
|
-
description: "Save a DeepSeek API key into the harness credential store (~/.dsh/.credentials.yaml)",
|
|
1200
|
-
input: { hint: "<api-key>" }
|
|
1201
|
-
},
|
|
1202
|
-
{ name: "logout", description: "Remove the API key stored in the harness credential store" },
|
|
1203
|
-
{
|
|
1204
|
-
name: "model",
|
|
1205
|
-
description: "Select the model for this conversation",
|
|
1206
|
-
input: { hint: "[model] \u2014 blank lists available models" }
|
|
1207
|
-
}
|
|
1208
|
-
];
|
|
1209
|
-
const availableCommandsFor = async (record) => {
|
|
1210
|
-
const list = [...BUILTIN_COMMANDS];
|
|
1211
|
-
const seen = new Set(list.map((command) => command.name));
|
|
1212
|
-
const commandsSvc = ctx.get("commands");
|
|
1213
|
-
if (commandsSvc !== void 0) {
|
|
1214
|
-
try {
|
|
1215
|
-
for (const descriptor of commandsSvc.list(record.agent)) {
|
|
1216
|
-
if (seen.has(descriptor.name)) continue;
|
|
1217
|
-
seen.add(descriptor.name);
|
|
1218
|
-
list.push({
|
|
1219
|
-
name: descriptor.name,
|
|
1220
|
-
description: descriptor.description,
|
|
1221
|
-
...descriptor.input !== void 0 ? { input: { hint: descriptor.input.hint } } : {}
|
|
1222
|
-
});
|
|
1223
|
-
}
|
|
1224
|
-
} catch (error) {
|
|
1225
|
-
logWarn(`command listing failed: ${String(error)}`);
|
|
1226
|
-
}
|
|
1227
|
-
}
|
|
1228
|
-
const skillsSvc = ctx.get("skills");
|
|
1229
|
-
if (skillsSvc !== void 0) {
|
|
1230
|
-
try {
|
|
1231
|
-
const skills = await skillsSvc.list({
|
|
1232
|
-
...record.agent.session.header.cwd !== void 0 ? { cwd: record.agent.session.header.cwd } : {},
|
|
1233
|
-
scope: record.agent
|
|
1234
|
-
});
|
|
1235
|
-
for (const skill of skills) {
|
|
1236
|
-
if (skill.invocation.userInvocable !== true) continue;
|
|
1237
|
-
if (seen.has(skill.name)) continue;
|
|
1238
|
-
seen.add(skill.name);
|
|
1239
|
-
list.push({
|
|
1240
|
-
name: skill.name,
|
|
1241
|
-
description: skill.description,
|
|
1242
|
-
input: { hint: "instructions for the skill" }
|
|
1243
|
-
});
|
|
1244
|
-
}
|
|
1245
|
-
} catch (error) {
|
|
1246
|
-
logDebug(`skill listing failed: ${String(error)}`);
|
|
1247
|
-
}
|
|
1248
|
-
}
|
|
1249
|
-
return list;
|
|
1250
|
-
};
|
|
1251
|
-
const publishCommands = (sessionId, record) => {
|
|
1252
|
-
const target = record ?? sessions.get(sessionId);
|
|
1253
|
-
if (target === void 0) return;
|
|
1254
|
-
void availableCommandsFor(target).then((availableCommands) => {
|
|
1255
|
-
notify(sessionId, { sessionUpdate: "available_commands_update", availableCommands });
|
|
1256
|
-
}).catch((error) => {
|
|
1257
|
-
logWarn(`publishing commands failed: ${String(error)}`);
|
|
1258
|
-
});
|
|
1259
|
-
};
|
|
1260
|
-
const publishConfigOptions = (sessionId, record) => {
|
|
1261
|
-
void configOptions(record).then((options) => {
|
|
1262
|
-
notify(sessionId, {
|
|
1263
|
-
sessionUpdate: "config_option_update",
|
|
1264
|
-
configOptions: options
|
|
1265
|
-
});
|
|
1266
|
-
}).catch((error) => {
|
|
1267
|
-
logWarn(`publishing config options failed: ${String(error)}`);
|
|
1268
|
-
});
|
|
1269
|
-
};
|
|
1270
|
-
const syncPermissionState = (record, sessionId) => {
|
|
1271
|
-
const events = record.agent.session.events;
|
|
1272
|
-
if (events === void 0) return;
|
|
1273
|
-
const permission = permissionService();
|
|
1274
|
-
const storedMode = permission?.current(events);
|
|
1275
|
-
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
1276
|
-
const event = events[index];
|
|
1277
|
-
if (event?.type === "approval/policy") {
|
|
1278
|
-
const policy = event.data?.policy;
|
|
1279
|
-
if (policy === "ask" || policy === "never") record.approvals = policy;
|
|
1280
|
-
break;
|
|
1281
|
-
}
|
|
1282
|
-
}
|
|
1283
|
-
if (storedMode !== void 0 && storedMode !== record.modeId) {
|
|
1284
|
-
record.modeId = storedMode;
|
|
1285
|
-
notify(sessionId, {
|
|
1286
|
-
sessionUpdate: "current_mode_update",
|
|
1287
|
-
currentModeId: record.modeId
|
|
1288
|
-
});
|
|
1289
|
-
}
|
|
1290
|
-
};
|
|
1291
|
-
const credentialStore = () => {
|
|
1292
|
-
const brand = harness.credentialRef;
|
|
1293
|
-
const store = ctx.get("credentials");
|
|
1294
|
-
if (brand === void 0 || store === void 0) return void 0;
|
|
1295
|
-
return { store, ref: brand("DEEPSEEK_API_KEY") };
|
|
1296
|
-
};
|
|
1297
|
-
const maskKey = (value) => value.length <= 8 ? "\u2026" : `${value.slice(0, 4)}\u2026${value.slice(-4)}`;
|
|
1298
|
-
const describeCredential = async () => {
|
|
1299
|
-
if (envCredentialPresent()) {
|
|
1300
|
-
const viaEnv = process.env["DEEPSEEK_API_KEY"] !== void 0 ? "DEEPSEEK_API_KEY" : "DEEPSEEK_BASE_URL";
|
|
1301
|
-
return `process environment (${viaEnv})`;
|
|
1302
|
-
}
|
|
1303
|
-
const cs = credentialStore();
|
|
1304
|
-
if (cs === void 0) return "not configured";
|
|
1305
|
-
try {
|
|
1306
|
-
const info = await cs.store.describe(cs.ref);
|
|
1307
|
-
if (!info.configured) return "not configured";
|
|
1308
|
-
return info.source ?? "credential store";
|
|
1309
|
-
} catch (error) {
|
|
1310
|
-
logWarn(`credential describe failed: ${String(error)}`);
|
|
1311
|
-
return "unknown";
|
|
1312
|
-
}
|
|
1313
|
-
};
|
|
1314
|
-
const loginText = async (rawArgument) => {
|
|
1315
|
-
const key = rawArgument.trim();
|
|
1316
|
-
const cs = credentialStore();
|
|
1317
|
-
if (cs === void 0) {
|
|
1318
|
-
return "This composition has no writable credential store; export DEEPSEEK_API_KEY in the environment that launches the agent instead.";
|
|
1319
|
-
}
|
|
1320
|
-
if (key.length === 0) {
|
|
1321
|
-
return [
|
|
1322
|
-
"Usage: `/login <api-key>` \u2014 stores the key in the harness credential store",
|
|
1323
|
-
"(`~/.dsh/.credentials.yaml`, the same file the dsh Web UI writes; mode 600).",
|
|
1324
|
-
"",
|
|
1325
|
-
`Current credential source: ${await describeCredential()}.`,
|
|
1326
|
-
"",
|
|
1327
|
-
"Note: text entered here also lands in your client's chat history. If you",
|
|
1328
|
-
"prefer to keep the key out of it, save the key in the dsh Web UI instead."
|
|
1329
|
-
].join("\n");
|
|
1330
|
-
}
|
|
1331
|
-
try {
|
|
1332
|
-
await cs.store.set(cs.ref, key);
|
|
1333
|
-
} catch (error) {
|
|
1334
|
-
return [
|
|
1335
|
-
`Could not store the key: ${error instanceof Error ? error.message : String(error)}`,
|
|
1336
|
-
"",
|
|
1337
|
-
"A read-only source (usually the process environment) is currently supplying",
|
|
1338
|
-
"this credential, so a stored value would be shadowed. Unset DEEPSEEK_API_KEY",
|
|
1339
|
-
"in the launching environment, or keep using it as the credential."
|
|
1340
|
-
].join("\n");
|
|
1341
|
-
}
|
|
1342
|
-
return `Saved DEEPSEEK_API_KEY (${maskKey(key)}) to the harness credential store \u2014 source now: ${await describeCredential()}. The dsh Web UI sees the same key.`;
|
|
1343
|
-
};
|
|
1344
|
-
const logoutText = async () => {
|
|
1345
|
-
const cs = credentialStore();
|
|
1346
|
-
if (cs === void 0) return "This composition has no writable credential store; nothing to remove.";
|
|
1347
|
-
try {
|
|
1348
|
-
await cs.store.unset(cs.ref);
|
|
1349
|
-
} catch (error) {
|
|
1350
|
-
return `Could not remove the stored key: ${error instanceof Error ? error.message : String(error)}`;
|
|
1351
|
-
}
|
|
1352
|
-
const remaining = await describeCredential();
|
|
1353
|
-
return remaining === "not configured" ? "Removed the stored DEEPSEEK_API_KEY. No credential is configured now." : `Removed the stored DEEPSEEK_API_KEY; requests now authenticate via: ${remaining}.`;
|
|
1354
|
-
};
|
|
1355
|
-
const modelCommandText = async (record, acpSessionId, query) => {
|
|
1356
|
-
const discovered = await discoverModels();
|
|
1357
|
-
const catalog = [
|
|
1358
|
-
...discovered.map((entry) => ({
|
|
1359
|
-
value: encodeChoice(entry.provider, entry.model),
|
|
1360
|
-
label: entry.label
|
|
1361
|
-
})),
|
|
1362
|
-
...modelCandidates().filter((model) => !discovered.some((entry) => entry.model === model)).map((model) => ({ value: encodeChoice(void 0, model), label: model }))
|
|
1363
|
-
];
|
|
1364
|
-
const route = routeOf(record);
|
|
1365
|
-
const currentValue = encodeChoice(record.provider, record.model ?? route.model ?? "");
|
|
1366
|
-
if (query === "") {
|
|
1367
|
-
const lines = catalog.map(
|
|
1368
|
-
(entry) => `${entry.value === currentValue ? "\u2192" : " "} ${entry.label} \u2014 ${entry.value}`
|
|
1369
|
-
);
|
|
1370
|
-
return [
|
|
1371
|
-
`model: ${route.model ?? "(product default)"}${record.effort !== void 0 ? ` (effort ${record.effort})` : ""}`,
|
|
1372
|
-
"",
|
|
1373
|
-
...lines.length > 0 ? lines : ["no models discovered \u2014 check credentials with /status"],
|
|
1374
|
-
"",
|
|
1375
|
-
"switch with /model <name>"
|
|
1376
|
-
].join("\n");
|
|
1377
|
-
}
|
|
1378
|
-
const lowered = query.toLowerCase();
|
|
1379
|
-
const exact = catalog.filter(
|
|
1380
|
-
(entry) => entry.value.toLowerCase() === lowered || decodeChoice(entry.value).model.toLowerCase() === lowered
|
|
1381
|
-
);
|
|
1382
|
-
const matches = exact.length > 0 ? exact : catalog.filter(
|
|
1383
|
-
(entry) => entry.value.toLowerCase().includes(lowered) || entry.label.toLowerCase().includes(lowered)
|
|
1384
|
-
);
|
|
1385
|
-
if (matches.length === 0) return `no model matches "${query}" \u2014 see /model`;
|
|
1386
|
-
if (matches.length > 1) {
|
|
1387
|
-
return [`"${query}" is ambiguous:`, ...matches.map((entry) => ` ${entry.label} \u2014 ${entry.value}`)].join(
|
|
1388
|
-
"\n"
|
|
1389
|
-
);
|
|
1390
|
-
}
|
|
1391
|
-
const target = matches[0];
|
|
1392
|
-
if (target.value === currentValue) return `already on ${target.label}`;
|
|
1393
|
-
await switchModel(record, acpSessionId, target.value);
|
|
1394
|
-
publishCommands(acpSessionId, record);
|
|
1395
|
-
publishConfigOptions(acpSessionId, record);
|
|
1396
|
-
return `model \u2192 ${target.label}`;
|
|
1397
|
-
};
|
|
1398
|
-
const statusText = async (record) => {
|
|
1399
|
-
const used = record.projection.contextWindow;
|
|
1400
|
-
const route = routeOf(record);
|
|
1401
|
-
const effort = record.effort ?? loggedConfig(record)?.reasoningEffort;
|
|
1402
|
-
const lines = [
|
|
1403
|
-
`**dsh-acp** ${VERSION} \u2014 DeepSeek Harness ACP bridge`,
|
|
1404
|
-
"",
|
|
1405
|
-
`| | |`,
|
|
1406
|
-
`|---|---|`,
|
|
1407
|
-
`| Provider | ${route.provider ?? "(composition default)"} |`,
|
|
1408
|
-
`| Model | ${route.model ?? "(composition default)"} |`,
|
|
1409
|
-
`| Reasoning | ${effort ?? "(adapter default)"} |`,
|
|
1410
|
-
`| Credential | ${await describeCredential()} |`,
|
|
1411
|
-
...record.preset !== void 0 ? [`| Preset | ${record.preset} |`] : [],
|
|
1412
|
-
`| Permission mode | ${record.modeId} |`,
|
|
1413
|
-
`| Approvals | ${record.approvals} |`,
|
|
1414
|
-
`| Workspace | ${record.agent.session.header.cwd ?? process.cwd()} |`,
|
|
1415
|
-
`| Session | ${String(record.agent.session.id)} |`,
|
|
1416
|
-
...used !== void 0 ? [`| Context window | ${used.toLocaleString()} tokens |`] : []
|
|
1417
|
-
];
|
|
1418
|
-
return lines.join("\n");
|
|
1419
|
-
};
|
|
1420
|
-
const registerRecord = (sessionId, agent, dispose, model, modeId, projection) => {
|
|
1421
|
-
const record = {
|
|
1422
|
-
agent,
|
|
1423
|
-
dispose,
|
|
1424
|
-
projection: projection ?? new SessionProjection(void 0, {
|
|
1425
|
-
terminalOutput: clientTerminalOutput,
|
|
1426
|
-
...agent.session.header.cwd !== void 0 ? { cwd: agent.session.header.cwd } : {}
|
|
1427
|
-
}),
|
|
1428
|
-
modeId,
|
|
1429
|
-
model,
|
|
1430
|
-
approvals: modeId === "danger-full-access" ? "never" : "ask",
|
|
1431
|
-
cancelled: false,
|
|
1432
|
-
inflight: void 0
|
|
1433
|
-
};
|
|
1434
|
-
sessions.set(sessionId, record);
|
|
1435
|
-
return record;
|
|
1436
|
-
};
|
|
1437
|
-
ctx.on("session/event", (session, event) => {
|
|
1438
|
-
const sessionId = String(session.header.id);
|
|
1439
|
-
const record = sessions.get(sessionId);
|
|
1440
|
-
if (record === void 0 || record.agent.session !== session) return;
|
|
1441
|
-
try {
|
|
1442
|
-
for (const update of record.projection.onEvent(event)) {
|
|
1443
|
-
notify(sessionId, update);
|
|
1444
|
-
}
|
|
1445
|
-
} finally {
|
|
1446
|
-
const inflight = record.inflight;
|
|
1447
|
-
if (inflight !== void 0 && event.type === "turn/end" && inflight.turn === event.data.turn) {
|
|
1448
|
-
if (event.data.reason.kind === "error") {
|
|
1449
|
-
record.inflight = void 0;
|
|
1450
|
-
inflight.reject(internalError(`turn failed: ${event.data.reason.error.message}`));
|
|
1451
|
-
}
|
|
1452
|
-
}
|
|
1453
|
-
}
|
|
1454
|
-
});
|
|
1455
|
-
ctx.on("agent/inbox/claimed", ({ agent, message, turn }) => {
|
|
1456
|
-
const record = ownedRecord(agent);
|
|
1457
|
-
const inflight = record?.inflight;
|
|
1458
|
-
if (inflight !== void 0 && inflight.messageId === message.id) inflight.turn = turn;
|
|
1459
|
-
});
|
|
1460
|
-
ctx.on("agent/error", ({ agent, turn, error }) => {
|
|
1461
|
-
const record = ownedRecord(agent);
|
|
1462
|
-
const inflight = record?.inflight;
|
|
1463
|
-
if (record === void 0 || inflight === void 0 || inflight.turn === turn) return;
|
|
1464
|
-
record.inflight = void 0;
|
|
1465
|
-
inflight.reject(internalError(`turn failed: ${errorChain2(error)}`));
|
|
1466
|
-
});
|
|
1467
|
-
ctx.on("approval/request", (request, next) => {
|
|
1468
|
-
const record = ownedRecord(request.agent);
|
|
1469
|
-
if (record === void 0 || request.callId === void 0) return next();
|
|
1470
|
-
return conn.requestPermission({
|
|
1471
|
-
sessionId: String(record.agent.session.id),
|
|
1472
|
-
toolCall: { toolCallId: request.callId },
|
|
1473
|
-
options: [
|
|
1474
|
-
{ optionId: "allow-once", name: "Allow once", kind: "allow_once" },
|
|
1475
|
-
{ optionId: "allow-always", name: "Always allow (this session)", kind: "allow_always" },
|
|
1476
|
-
{ optionId: "reject-once", name: "Reject", kind: "reject_once" }
|
|
1477
|
-
]
|
|
1478
|
-
}).then(({ outcome }) => {
|
|
1479
|
-
if (outcome.outcome === "cancelled") return "cancelled";
|
|
1480
|
-
if (outcome.optionId === "allow-always") {
|
|
1481
|
-
try {
|
|
1482
|
-
ctx.get("approval")?.setPolicy?.(record.agent, "never");
|
|
1483
|
-
} catch (error) {
|
|
1484
|
-
logWarn(`approval policy switch failed: ${String(error)}`);
|
|
1485
|
-
}
|
|
1486
|
-
return "allowed-once";
|
|
1487
|
-
}
|
|
1488
|
-
return outcome.optionId === "allow-once" ? "allowed-once" : "rejected";
|
|
1489
|
-
});
|
|
1490
|
-
});
|
|
1491
|
-
const makeAgent = (connection) => {
|
|
1492
|
-
conn = connection;
|
|
1493
|
-
return {
|
|
1494
|
-
initialize(params) {
|
|
1495
|
-
const hasPersistence = ctx.get("sessionPersistence") !== void 0;
|
|
1496
|
-
const requested = params.protocolVersion;
|
|
1497
|
-
const capsMeta = params.clientCapabilities?.["_meta"];
|
|
1498
|
-
clientTerminalOutput = capsMeta !== null && typeof capsMeta === "object" ? capsMeta["terminal_output"] === true : false;
|
|
1499
|
-
return Promise.resolve({
|
|
1500
|
-
protocolVersion: typeof requested === "number" && requested >= 1 && requested < PROTOCOL_VERSION ? requested : PROTOCOL_VERSION,
|
|
1501
|
-
agentInfo: { name: "dsh-acp", title: "DeepSeek Harness", version: VERSION },
|
|
1502
|
-
agentCapabilities: {
|
|
1503
|
-
loadSession: hasPersistence,
|
|
1504
|
-
promptCapabilities: { image: false, audio: false, embeddedContext: true },
|
|
1505
|
-
// Stdio servers always work; streamable HTTP maps onto
|
|
1506
|
-
// mcp-client's second transport. Legacy SSE does not.
|
|
1507
|
-
mcpCapabilities: { http: true, sse: false },
|
|
1508
|
-
...hasPersistence ? { sessionCapabilities: { list: {} } } : {}
|
|
1509
|
-
},
|
|
1510
|
-
authMethods: [
|
|
1511
|
-
// ACP Terminal Auth (see the registry's AUTHENTICATION.md):
|
|
1512
|
-
// the client runs `dsh-acp login` in an interactive
|
|
1513
|
-
// terminal; the key lands in the shared harness
|
|
1514
|
-
// credential store (~/.dsh/.credentials.yaml).
|
|
1515
|
-
{
|
|
1516
|
-
id: "terminal-login",
|
|
1517
|
-
name: "Log in with a DeepSeek API key",
|
|
1518
|
-
description: "Interactive terminal setup \u2014 saves the key to the harness credential store shared with the dsh Web UI",
|
|
1519
|
-
_meta: { "terminal-auth": { args: ["login"], env: {} } }
|
|
1520
|
-
}
|
|
1521
|
-
]
|
|
1522
|
-
});
|
|
1523
|
-
},
|
|
1524
|
-
async authenticate(_params) {
|
|
1525
|
-
if (!await credentialPresent()) {
|
|
1526
|
-
throw authRequired(
|
|
1527
|
-
"no DeepSeek credential found: run `dsh-acp login`, save one in the dsh Web UI (Settings \u2192 Models), or set DEEPSEEK_API_KEY"
|
|
1528
|
-
);
|
|
1529
|
-
}
|
|
1530
|
-
},
|
|
1531
|
-
async newSession(params) {
|
|
1532
|
-
assertOpen();
|
|
1533
|
-
validateCwd(params.cwd);
|
|
1534
|
-
syncMcpServers(params.mcpServers, params.cwd);
|
|
1535
|
-
const sessionId = SessionId2(randomUUID());
|
|
1536
|
-
const presets = presetsService();
|
|
1537
|
-
const presetId = presets === void 0 ? void 0 : (await presets.resolve(void 0)).id;
|
|
1538
|
-
const handle = await agents.create({
|
|
1539
|
-
sessionId,
|
|
1540
|
-
meta: { cwd: params.cwd, ...presetId !== void 0 ? { agentPreset: presetId } : {} },
|
|
1541
|
-
agentOptions: agentOptionsFor(config.model),
|
|
1542
|
-
...presetSetup(presets, presetId) !== void 0 ? { setup: presetSetup(presets, presetId) } : {}
|
|
1543
|
-
});
|
|
1544
|
-
if (closed) {
|
|
1545
|
-
await handle.dispose();
|
|
1546
|
-
throw internalError("connection closed during session/new");
|
|
1547
|
-
}
|
|
1548
|
-
const record = registerRecord(
|
|
1549
|
-
String(sessionId),
|
|
1550
|
-
handle.agent,
|
|
1551
|
-
() => handle.dispose(),
|
|
1552
|
-
config.model,
|
|
1553
|
-
config.permissionMode ?? "workspace-write"
|
|
1554
|
-
);
|
|
1555
|
-
if (presetId !== void 0) record.preset = presetId;
|
|
1556
|
-
ensureSelection(record);
|
|
1557
|
-
queueMicrotask(() => publishCommands(String(sessionId)));
|
|
1558
|
-
const options = await configOptions(record);
|
|
1559
|
-
return {
|
|
1560
|
-
sessionId: String(sessionId),
|
|
1561
|
-
modes: modeState(record),
|
|
1562
|
-
...options.length > 0 ? { configOptions: options } : {}
|
|
1563
|
-
};
|
|
1564
|
-
},
|
|
1565
|
-
async loadSession(params) {
|
|
1566
|
-
assertOpen();
|
|
1567
|
-
validateCwd(params.cwd);
|
|
1568
|
-
syncMcpServers(params.mcpServers, params.cwd);
|
|
1569
|
-
requirePersistence();
|
|
1570
|
-
const record = await restoreSession(params.sessionId, { replay: true, cwd: params.cwd });
|
|
1571
|
-
const options = await configOptions(record);
|
|
1572
|
-
return {
|
|
1573
|
-
modes: modeState(record),
|
|
1574
|
-
...options.length > 0 ? { configOptions: options } : {}
|
|
1575
|
-
};
|
|
1576
|
-
},
|
|
1577
|
-
async listSessions(params) {
|
|
1578
|
-
assertOpen();
|
|
1579
|
-
const persistence = requirePersistence();
|
|
1580
|
-
const headers = await persistence.list();
|
|
1581
|
-
headers.sort((a, b) => (b.createdAt ?? 0) - (a.createdAt ?? 0));
|
|
1582
|
-
const filtered = params.cwd !== void 0 && params.cwd !== null ? headers.filter((header) => header.cwd === params.cwd) : headers;
|
|
1583
|
-
const page = filtered.slice(0, 100);
|
|
1584
|
-
const withTitles = await Promise.allSettled(
|
|
1585
|
-
page.slice(0, 20).map(async (header) => {
|
|
1586
|
-
const { events } = await persistence.inspect(header.id);
|
|
1587
|
-
return foldSessionTitle2(events)?.title;
|
|
1588
|
-
})
|
|
1589
|
-
);
|
|
1590
|
-
const sessionsInfo = page.map((header, index) => {
|
|
1591
|
-
const settled = withTitles[index];
|
|
1592
|
-
const title = settled !== void 0 && settled.status === "fulfilled" ? settled.value : void 0;
|
|
1593
|
-
return {
|
|
1594
|
-
sessionId: String(header.id),
|
|
1595
|
-
cwd: header.cwd ?? "",
|
|
1596
|
-
...title !== void 0 ? { title } : {},
|
|
1597
|
-
...header.createdAt !== void 0 ? { updatedAt: new Date(header.createdAt).toISOString() } : {}
|
|
1598
|
-
};
|
|
1599
|
-
});
|
|
1600
|
-
return { sessions: sessionsInfo };
|
|
1601
|
-
},
|
|
1602
|
-
async prompt(params) {
|
|
1603
|
-
assertOpen();
|
|
1604
|
-
const record = await requireOrRestoreSession(params.sessionId);
|
|
1605
|
-
if (record.inflight !== void 0) {
|
|
1606
|
-
throw invalidParams("a prompt is already in flight for this session");
|
|
1607
|
-
}
|
|
1608
|
-
let converted;
|
|
1609
|
-
try {
|
|
1610
|
-
converted = convertPrompt(params.prompt);
|
|
1611
|
-
} catch (error) {
|
|
1612
|
-
if (error instanceof UnsupportedPromptContentError) throw invalidParams(error.message);
|
|
1613
|
-
throw error;
|
|
1614
|
-
}
|
|
1615
|
-
if (converted.blocks.length === 0) throw invalidParams("empty prompt");
|
|
1616
|
-
const trimmed = converted.displayText.trim();
|
|
1617
|
-
const commandMatch = trimmed.match(/^\/(\w[\w-]*)\b/);
|
|
1618
|
-
const respond = (text) => {
|
|
1619
|
-
notify(params.sessionId, {
|
|
1620
|
-
sessionUpdate: "agent_message_chunk",
|
|
1621
|
-
content: { type: "text", text }
|
|
1622
|
-
});
|
|
1623
|
-
return { stopReason: "end_turn" };
|
|
1624
|
-
};
|
|
1625
|
-
if (commandMatch?.[1] === "status") return respond(await statusText(record));
|
|
1626
|
-
if (commandMatch?.[1] === "login") {
|
|
1627
|
-
return respond(await loginText(trimmed.slice(commandMatch[0].length)));
|
|
1628
|
-
}
|
|
1629
|
-
if (commandMatch?.[1] === "logout") return respond(await logoutText());
|
|
1630
|
-
if (commandMatch?.[1] === "model") {
|
|
1631
|
-
return respond(
|
|
1632
|
-
await modelCommandText(
|
|
1633
|
-
record,
|
|
1634
|
-
params.sessionId,
|
|
1635
|
-
trimmed.slice(commandMatch[0].length).trim()
|
|
1636
|
-
)
|
|
1637
|
-
);
|
|
1638
|
-
}
|
|
1639
|
-
if (commandMatch !== null && commandMatch[1] !== void 0) {
|
|
1640
|
-
const commandsSvc = ctx.get("commands");
|
|
1641
|
-
if (commandsSvc !== void 0) {
|
|
1642
|
-
let execution;
|
|
1643
|
-
try {
|
|
1644
|
-
execution = await commandsSvc.execute(
|
|
1645
|
-
record.agent,
|
|
1646
|
-
trimmed,
|
|
1647
|
-
new AbortController().signal
|
|
1648
|
-
);
|
|
1649
|
-
} catch (error) {
|
|
1650
|
-
return respond(`\u26A0 /${commandMatch[1]} failed: ${errorChain2(error)}`);
|
|
1651
|
-
}
|
|
1652
|
-
if (execution !== void 0) {
|
|
1653
|
-
const { result } = execution;
|
|
1654
|
-
const text = result.text ?? (result.kind === "success" ? `/${commandMatch[1]} \u2713` : `/${commandMatch[1]} failed`);
|
|
1655
|
-
syncPermissionState(record, params.sessionId);
|
|
1656
|
-
publishCommands(params.sessionId, record);
|
|
1657
|
-
publishConfigOptions(params.sessionId, record);
|
|
1658
|
-
return respond(result.kind === "error" ? `\u26A0 ${text}` : text);
|
|
1659
|
-
}
|
|
1660
|
-
}
|
|
1661
|
-
}
|
|
1662
|
-
if (!await credentialPresent()) {
|
|
1663
|
-
return respond(
|
|
1664
|
-
[
|
|
1665
|
-
"No DeepSeek credential is configured, so this prompt was not sent to the model.",
|
|
1666
|
-
"",
|
|
1667
|
-
"Configure one of:",
|
|
1668
|
-
"- `/login <api-key>` \u2014 store a key in the harness credential store right here,",
|
|
1669
|
-
"- the dsh Web UI (`dsh web`, Settings \u2192 Models),",
|
|
1670
|
-
"- `DEEPSEEK_API_KEY` in the environment that launches this agent."
|
|
1671
|
-
].join("\n")
|
|
1672
|
-
);
|
|
1673
|
-
}
|
|
1674
|
-
if (agents.get(record.agent.id) !== record.agent) {
|
|
1675
|
-
throw internalError("prompt was not queued: the agent was disposed outside the bridge");
|
|
1676
|
-
}
|
|
1677
|
-
record.cancelled = false;
|
|
1678
|
-
record.projection.beginPrompt();
|
|
1679
|
-
const message = createUserMessage2({
|
|
1680
|
-
content: converted.blocks,
|
|
1681
|
-
source: { kind: "user" }
|
|
1682
|
-
});
|
|
1683
|
-
const stopReason = await new Promise((resolve2, reject) => {
|
|
1684
|
-
const inflight = { resolve: resolve2, reject, messageId: message.id, turn: void 0 };
|
|
1685
|
-
record.inflight = inflight;
|
|
1686
|
-
try {
|
|
1687
|
-
record.agent.followup(message);
|
|
1688
|
-
} catch (error) {
|
|
1689
|
-
record.inflight = void 0;
|
|
1690
|
-
const detail = error instanceof Error ? error.message : String(error);
|
|
1691
|
-
throw internalError(`prompt was not queued: ${detail}`);
|
|
1692
|
-
}
|
|
1693
|
-
void record.agent.whenIdle().then(() => {
|
|
1694
|
-
if (record.inflight !== inflight) return;
|
|
1695
|
-
record.inflight = void 0;
|
|
1696
|
-
if (record.cancelled) {
|
|
1697
|
-
resolve2("cancelled");
|
|
1698
|
-
return;
|
|
1699
|
-
}
|
|
1700
|
-
const end = inflight.turn !== void 0 ? record.projection.turnEndFor(inflight.turn) ?? record.projection.lastTurnEnd : record.projection.lastTurnEnd;
|
|
1701
|
-
resolve2(end === void 0 ? "cancelled" : turnEndToStopReason(end));
|
|
1702
|
-
});
|
|
1703
|
-
});
|
|
1704
|
-
const usage = record.projection.promptUsage();
|
|
1705
|
-
return { stopReason, ...usage !== void 0 ? { usage } : {} };
|
|
1706
|
-
},
|
|
1707
|
-
async setSessionMode(params) {
|
|
1708
|
-
const record = await requireOrRestoreSession(params.sessionId);
|
|
1709
|
-
applyMode(record, params.sessionId, params.modeId);
|
|
1710
|
-
notify(params.sessionId, {
|
|
1711
|
-
sessionUpdate: "config_option_update",
|
|
1712
|
-
configOptions: await configOptions(record)
|
|
1713
|
-
});
|
|
1714
|
-
return {};
|
|
1715
|
-
},
|
|
1716
|
-
async setSessionConfigOption(params) {
|
|
1717
|
-
const record = await requireOrRestoreSession(params.sessionId);
|
|
1718
|
-
const value = typeof params.value === "string" ? params.value : void 0;
|
|
1719
|
-
if (value === void 0) throw invalidParams(`invalid value: ${String(params.value)}`);
|
|
1720
|
-
switch (params.configId) {
|
|
1721
|
-
case "mode": {
|
|
1722
|
-
applyMode(record, params.sessionId, value);
|
|
1723
|
-
break;
|
|
1724
|
-
}
|
|
1725
|
-
case "preset": {
|
|
1726
|
-
await switchPreset(record, params.sessionId, value);
|
|
1727
|
-
break;
|
|
1728
|
-
}
|
|
1729
|
-
case "effort": {
|
|
1730
|
-
const route = routeOf(record);
|
|
1731
|
-
const catalog = await effortCatalog(route.provider, route.model);
|
|
1732
|
-
if (catalog === void 0 || !catalog.efforts.some((effort) => effort.id === value)) {
|
|
1733
|
-
throw invalidParams(`unknown effort: ${value}`);
|
|
1734
|
-
}
|
|
1735
|
-
const selection = ensureSelection(record);
|
|
1736
|
-
if (selection === void 0 || route.provider === void 0 || route.model === void 0) {
|
|
1737
|
-
throw invalidParams("reasoning effort switching is unavailable on this host");
|
|
1738
|
-
}
|
|
1739
|
-
selection.current = {
|
|
1740
|
-
provider: route.provider,
|
|
1741
|
-
model: route.model,
|
|
1742
|
-
reasoningEffort: value
|
|
1743
|
-
};
|
|
1744
|
-
record.effort = value;
|
|
1745
|
-
break;
|
|
1746
|
-
}
|
|
1747
|
-
case "model": {
|
|
1748
|
-
await switchModel(record, params.sessionId, value);
|
|
1749
|
-
break;
|
|
1750
|
-
}
|
|
1751
|
-
default:
|
|
1752
|
-
throw invalidParams(`unknown config option: ${params.configId}`);
|
|
1753
|
-
}
|
|
1754
|
-
return { configOptions: await configOptions(record) };
|
|
1755
|
-
},
|
|
1756
|
-
async closeSession(params) {
|
|
1757
|
-
const record = sessions.get(params.sessionId);
|
|
1758
|
-
if (record === void 0) return;
|
|
1759
|
-
sessions.delete(params.sessionId);
|
|
1760
|
-
record.agent.cancel({ kind: "user" });
|
|
1761
|
-
settlePrompt(record, "cancelled");
|
|
1762
|
-
await record.dispose().catch((error) => {
|
|
1763
|
-
logWarn(`session close failed: ${String(error)}`);
|
|
1764
|
-
});
|
|
1765
|
-
},
|
|
1766
|
-
cancel(params) {
|
|
1767
|
-
const record = sessions.get(params.sessionId);
|
|
1768
|
-
if (record === void 0) return Promise.resolve();
|
|
1769
|
-
record.cancelled = true;
|
|
1770
|
-
record.agent.cancel({ kind: "user" });
|
|
1771
|
-
settlePrompt(record, "cancelled");
|
|
1772
|
-
return Promise.resolve();
|
|
1773
|
-
}
|
|
1774
|
-
};
|
|
1775
|
-
};
|
|
1776
|
-
function requirePersistence() {
|
|
1777
|
-
const persistence = getPersistence();
|
|
1778
|
-
if (persistence === void 0) {
|
|
1779
|
-
throw internalError("session persistence is not composed; session history is unavailable");
|
|
1780
|
-
}
|
|
1781
|
-
return persistence;
|
|
1782
|
-
}
|
|
1783
|
-
function getPersistence() {
|
|
1784
|
-
return ctx.get("sessionPersistence");
|
|
1785
|
-
}
|
|
1786
|
-
function validateCwd(cwd) {
|
|
1787
|
-
if (!isAbsolute(cwd)) throw invalidParams(`cwd must be an absolute path: ${cwd}`);
|
|
1788
|
-
}
|
|
1789
|
-
const mcpMounts = /* @__PURE__ */ new Map();
|
|
1790
|
-
let warnedNoMcpClient = false;
|
|
1791
|
-
const sanitizeServerName = (raw) => {
|
|
1792
|
-
const cleaned = raw.replace(/[^A-Za-z0-9_-]/g, "_").slice(0, 32);
|
|
1793
|
-
return cleaned.length > 0 ? cleaned : "server";
|
|
1794
|
-
};
|
|
1795
|
-
const mcpConfigFor = (server, cwd, serverName) => {
|
|
1796
|
-
const envList = (list) => Object.fromEntries(
|
|
1797
|
-
(Array.isArray(list) ? list : []).map((e) => [
|
|
1798
|
-
e.name,
|
|
1799
|
-
e.value
|
|
1800
|
-
])
|
|
1801
|
-
);
|
|
1802
|
-
if (typeof server["command"] === "string") {
|
|
1803
|
-
return {
|
|
1804
|
-
transport: "stdio",
|
|
1805
|
-
serverName,
|
|
1806
|
-
command: server["command"],
|
|
1807
|
-
args: Array.isArray(server["args"]) ? server["args"] : [],
|
|
1808
|
-
env: envList(server["env"]),
|
|
1809
|
-
cwd: typeof server["cwd"] === "string" ? server["cwd"] : cwd,
|
|
1810
|
-
// A dead or misconfigured server must not take the session
|
|
1811
|
-
// down; the client surfaces missing tools on its own.
|
|
1812
|
-
failOnStartupError: false
|
|
1813
|
-
};
|
|
1814
|
-
}
|
|
1815
|
-
if (server["type"] === "http" && typeof server["url"] === "string") {
|
|
1816
|
-
return {
|
|
1817
|
-
transport: "streamable-http",
|
|
1818
|
-
serverName,
|
|
1819
|
-
url: server["url"],
|
|
1820
|
-
headers: envList(server["headers"]),
|
|
1821
|
-
failOnStartupError: false
|
|
1822
|
-
};
|
|
1823
|
-
}
|
|
1824
|
-
return void 0;
|
|
1825
|
-
};
|
|
1826
|
-
const syncMcpServers = (servers, cwd) => {
|
|
1827
|
-
if (servers === void 0 || servers.length === 0) return;
|
|
1828
|
-
const mcpClient2 = harness.mcpClient;
|
|
1829
|
-
if (mcpClient2 === void 0) {
|
|
1830
|
-
if (!warnedNoMcpClient) {
|
|
1831
|
-
warnedNoMcpClient = true;
|
|
1832
|
-
logWarn(
|
|
1833
|
-
`ignoring ${servers.length} MCP server(s): this DeepSeek Harness installation lacks @deepseek-ai/dsh-mcp-client`
|
|
1834
|
-
);
|
|
1835
|
-
}
|
|
1836
|
-
return;
|
|
1837
|
-
}
|
|
1838
|
-
const taken = /* @__PURE__ */ new Set();
|
|
1839
|
-
for (const entry of servers) {
|
|
1840
|
-
const server = entry;
|
|
1841
|
-
const base = sanitizeServerName(typeof server["name"] === "string" ? server["name"] : "server");
|
|
1842
|
-
let serverName = base;
|
|
1843
|
-
for (let n = 2; taken.has(serverName); n += 1) serverName = `${base.slice(0, 28)}_${n}`;
|
|
1844
|
-
const cfg = mcpConfigFor(server, cwd, serverName);
|
|
1845
|
-
if (cfg === void 0) {
|
|
1846
|
-
logWarn(`skipping MCP server "${serverName}": unsupported transport`);
|
|
1847
|
-
continue;
|
|
1848
|
-
}
|
|
1849
|
-
const configJson = JSON.stringify(cfg);
|
|
1850
|
-
const existing = mcpMounts.get(serverName);
|
|
1851
|
-
if (existing !== void 0) {
|
|
1852
|
-
if (existing.configJson === configJson) {
|
|
1853
|
-
taken.add(serverName);
|
|
1854
|
-
continue;
|
|
1855
|
-
}
|
|
1856
|
-
try {
|
|
1857
|
-
void existing.fiber.dispose();
|
|
1858
|
-
} catch (error) {
|
|
1859
|
-
logWarn(`disposing MCP server "${serverName}": ${String(error)}`);
|
|
1860
|
-
}
|
|
1861
|
-
mcpMounts.delete(serverName);
|
|
1862
|
-
}
|
|
1863
|
-
try {
|
|
1864
|
-
const fiber = ctx.plugin(mcpClient2, cfg);
|
|
1865
|
-
mcpMounts.set(serverName, { configJson, fiber });
|
|
1866
|
-
taken.add(serverName);
|
|
1867
|
-
logDebug(`mounted MCP server "${serverName}" (${String(cfg["transport"])})`);
|
|
1868
|
-
} catch (error) {
|
|
1869
|
-
logWarn(`failed to mount MCP server "${serverName}": ${String(error)}`);
|
|
1870
|
-
}
|
|
1871
|
-
}
|
|
1872
|
-
};
|
|
1873
|
-
const stream = config.stream ?? ndJsonStream(
|
|
1874
|
-
Writable.toWeb(process.stdout),
|
|
1875
|
-
Readable.toWeb(process.stdin)
|
|
1876
|
-
);
|
|
1877
|
-
conn = new AgentSideConnection(makeAgent, stream);
|
|
1878
|
-
let quiescing;
|
|
1879
|
-
const quiesce = () => {
|
|
1880
|
-
if (quiescing !== void 0) return quiescing;
|
|
1881
|
-
closed = true;
|
|
1882
|
-
const records = [...sessions.values()];
|
|
1883
|
-
sessions.clear();
|
|
1884
|
-
for (const record of records) {
|
|
1885
|
-
record.agent.cancel({ kind: "user" });
|
|
1886
|
-
settlePrompt(record, "cancelled");
|
|
1887
|
-
}
|
|
1888
|
-
quiescing = (async () => {
|
|
1889
|
-
const subagents = ctx.get("subagents");
|
|
1890
|
-
if (subagents !== void 0) {
|
|
1891
|
-
try {
|
|
1892
|
-
await subagents.drainContinuableDescendants(records.map((record) => record.agent));
|
|
1893
|
-
} catch (error) {
|
|
1894
|
-
logWarn(`continuable subagent teardown failed: ${String(error)}`);
|
|
1895
|
-
}
|
|
1896
|
-
}
|
|
1897
|
-
const disposals = await Promise.allSettled(records.map((record) => record.dispose()));
|
|
1898
|
-
const failures = disposals.filter(
|
|
1899
|
-
(result) => result.status === "rejected"
|
|
1900
|
-
);
|
|
1901
|
-
if (failures.length > 0) {
|
|
1902
|
-
const detail = failures.map((failure) => errorChain2(failure.reason)).join("; ");
|
|
1903
|
-
throw new AggregateError(
|
|
1904
|
-
failures.map((failure) => failure.reason),
|
|
1905
|
-
`ACP agent teardown failed for ${failures.length} session(s): ${detail}`
|
|
1906
|
-
);
|
|
1907
|
-
}
|
|
1908
|
-
})();
|
|
1909
|
-
return quiescing;
|
|
1910
|
-
};
|
|
1911
|
-
void conn.closed.catch((error) => {
|
|
1912
|
-
logWarn(`connection closed with an error: ${String(error)}`);
|
|
1913
|
-
}).then(quiesce).catch((error) => {
|
|
1914
|
-
logWarn(`connection-close teardown failed: ${String(error)}`);
|
|
1915
|
-
});
|
|
1916
|
-
ctx.effect(() => quiesce, "acp-bridge.connection");
|
|
1917
|
-
}
|
|
1918
|
-
|
|
1919
27
|
// src/harness.ts
|
|
1920
|
-
import { execFileSync } from "node:child_process";
|
|
1921
|
-
import { existsSync, readFileSync, realpathSync } from "node:fs";
|
|
1922
|
-
import { delimiter, dirname, isAbsolute as isAbsolute2, join, resolve, sep } from "node:path";
|
|
1923
|
-
import { pathToFileURL, fileURLToPath } from "node:url";
|
|
1924
28
|
var HarnessNotFoundError = class extends Error {
|
|
1925
29
|
constructor(probed) {
|
|
1926
30
|
super(
|
|
@@ -1940,12 +44,12 @@ ${probed.map((p) => ` ${p}`).join("\n")}` : ""
|
|
|
1940
44
|
}
|
|
1941
45
|
};
|
|
1942
46
|
var ANCHOR = "@deepseek-ai/dsh-agent";
|
|
1943
|
-
function packageDirFrom(base,
|
|
47
|
+
function packageDirFrom(base, name) {
|
|
1944
48
|
let dir = resolve(base);
|
|
1945
49
|
for (; ; ) {
|
|
1946
|
-
const candidate = join(dir, "node_modules", ...
|
|
50
|
+
const candidate = join(dir, "node_modules", ...name.split("/"));
|
|
1947
51
|
if (existsSync(join(candidate, "package.json"))) return candidate;
|
|
1948
|
-
const direct = join(dir, ...
|
|
52
|
+
const direct = join(dir, ...name.split("/"));
|
|
1949
53
|
if (dir.endsWith(`${sep}node_modules`) && existsSync(join(direct, "package.json"))) return direct;
|
|
1950
54
|
const parent = dirname(dir);
|
|
1951
55
|
if (parent === dir) return void 0;
|
|
@@ -2009,7 +113,7 @@ function hostFromPathLookup() {
|
|
|
2009
113
|
function hostFromGlobalNpm() {
|
|
2010
114
|
try {
|
|
2011
115
|
const root = execFileSync("npm", ["root", "-g"], { encoding: "utf8", timeout: 1e4 }).trim();
|
|
2012
|
-
if (root.length > 0 &&
|
|
116
|
+
if (root.length > 0 && isAbsolute(root)) return hostAt(root, `npm root -g (${root})`);
|
|
2013
117
|
} catch {
|
|
2014
118
|
}
|
|
2015
119
|
return void 0;
|
|
@@ -2036,260 +140,15 @@ function resolveHost(explicit) {
|
|
|
2036
140
|
probed.push("dsh-acp package tree (vendored)");
|
|
2037
141
|
throw new HarnessNotFoundError(probed);
|
|
2038
142
|
}
|
|
2039
|
-
function entryFile(packageDir, subpath) {
|
|
2040
|
-
const manifest = JSON.parse(readFileSync(join(packageDir, "package.json"), "utf8"));
|
|
2041
|
-
const key = subpath === void 0 ? "." : `./${subpath}`;
|
|
2042
|
-
const exportsMap = manifest.exports;
|
|
2043
|
-
let target;
|
|
2044
|
-
if (typeof exportsMap === "string") {
|
|
2045
|
-
if (key === ".") target = exportsMap;
|
|
2046
|
-
} else if (exportsMap !== null && typeof exportsMap === "object") {
|
|
2047
|
-
const entry = exportsMap[key];
|
|
2048
|
-
target = entry;
|
|
2049
|
-
}
|
|
2050
|
-
if (target !== null && typeof target === "object") {
|
|
2051
|
-
const conditions = target;
|
|
2052
|
-
target = conditions["import"] ?? conditions["default"] ?? conditions["node"];
|
|
2053
|
-
if (target !== null && typeof target === "object") {
|
|
2054
|
-
const nested = target;
|
|
2055
|
-
target = nested["default"] ?? nested["import"];
|
|
2056
|
-
}
|
|
2057
|
-
}
|
|
2058
|
-
if (typeof target !== "string" && key === "." && typeof manifest.main === "string") {
|
|
2059
|
-
target = manifest.main;
|
|
2060
|
-
}
|
|
2061
|
-
if (typeof target !== "string") {
|
|
2062
|
-
throw new Error(`cannot resolve entry ${key} of ${packageDir}`);
|
|
2063
|
-
}
|
|
2064
|
-
return join(packageDir, target);
|
|
2065
|
-
}
|
|
2066
|
-
async function loadHostModule(host, specifier) {
|
|
2067
|
-
const [scope, packageName, ...rest] = specifier.split("/");
|
|
2068
|
-
if (scope === void 0 || packageName === void 0) throw new Error(`invalid specifier: ${specifier}`);
|
|
2069
|
-
const name3 = `${scope}/${packageName}`;
|
|
2070
|
-
const packageDir = packageDirFrom(host.base, name3);
|
|
2071
|
-
if (packageDir === void 0) {
|
|
2072
|
-
throw new Error(
|
|
2073
|
-
`the DeepSeek Harness installation at ${host.base} (${host.source}) does not provide ${name3}; update it with: npm install -g @deepseek-ai/dsh`
|
|
2074
|
-
);
|
|
2075
|
-
}
|
|
2076
|
-
const file = entryFile(packageDir, rest.length > 0 ? rest.join("/") : void 0);
|
|
2077
|
-
logDebug(`loading ${specifier} from ${file}`);
|
|
2078
|
-
return await import(pathToFileURL(file).href);
|
|
2079
|
-
}
|
|
2080
|
-
var PLUGIN_SPECIFIERS = {
|
|
2081
|
-
timer: "@deepseek-ai/cordis-plugin-timer",
|
|
2082
|
-
llmRuntime: "@deepseek-ai/dsh-llm",
|
|
2083
|
-
sessionStore: "@deepseek-ai/dsh-session",
|
|
2084
|
-
sessionTitle: "@deepseek-ai/dsh-session-title",
|
|
2085
|
-
systemPrompt: "@deepseek-ai/dsh-system-prompt",
|
|
2086
|
-
tools: "@deepseek-ai/dsh-tools",
|
|
2087
|
-
agents: "@deepseek-ai/dsh-agent",
|
|
2088
|
-
agentLoop: "@deepseek-ai/dsh-agent-loop",
|
|
2089
|
-
llmRetry: "@deepseek-ai/dsh-llm-retry",
|
|
2090
|
-
jobs: "@deepseek-ai/dsh-jobs-local",
|
|
2091
|
-
invariants: "@deepseek-ai/dsh-invariants",
|
|
2092
|
-
sessionInvariant: "@deepseek-ai/dsh-session/invariant",
|
|
2093
|
-
agentInvariant: "@deepseek-ai/dsh-agent/invariant",
|
|
2094
|
-
scopeInvariant: "@deepseek-ai/dsh-scope/invariant",
|
|
2095
|
-
agentLoopInvariant: "@deepseek-ai/dsh-agent-loop/invariant",
|
|
2096
|
-
homePaths: "@deepseek-ai/dsh-home-paths",
|
|
2097
|
-
bashEnv: "@deepseek-ai/dsh-shell-env",
|
|
2098
|
-
toolBash: "@deepseek-ai/dsh-tool-bash",
|
|
2099
|
-
workspaceContext: "@deepseek-ai/dsh-agent-instructions",
|
|
2100
|
-
llmDeepseek: "@deepseek-ai/dsh-llm-deepseek",
|
|
2101
|
-
persistence: "@deepseek-ai/dsh-session-persistence-jsonl",
|
|
2102
|
-
checkpoints: "@deepseek-ai/dsh-session-checkpoint-policy",
|
|
2103
|
-
sandbox: "@deepseek-ai/dsh-sandbox-local",
|
|
2104
|
-
sandboxPolicy: "@deepseek-ai/dsh-sandbox-policy",
|
|
2105
|
-
subprocess: "@deepseek-ai/dsh-subprocess-local",
|
|
2106
|
-
bash: "@deepseek-ai/dsh-bash-sandbox",
|
|
2107
|
-
approval: "@deepseek-ai/dsh-user-approval",
|
|
2108
|
-
fs: "@deepseek-ai/dsh-fs-sandbox",
|
|
2109
|
-
fsObservationPolicy: "@deepseek-ai/dsh-fs-observation-policy",
|
|
2110
|
-
toolFs: "@deepseek-ai/dsh-tool-fs",
|
|
2111
|
-
toolTodo: "@deepseek-ai/dsh-tool-todo",
|
|
2112
|
-
tokenMeter: "@deepseek-ai/dsh-token-meter",
|
|
2113
|
-
compaction: "@deepseek-ai/dsh-compaction-basic"
|
|
2114
|
-
};
|
|
2115
|
-
var OPTIONAL_PLUGIN_SPECIFIERS = {
|
|
2116
|
-
credentials: "@deepseek-ai/dsh-credentials-local",
|
|
2117
|
-
credentialsSeam: "@deepseek-ai/dsh-credentials",
|
|
2118
|
-
mcpClient: "@deepseek-ai/dsh-mcp-client"
|
|
2119
|
-
};
|
|
2120
|
-
async function loadKit(host) {
|
|
2121
|
-
const keys = Object.keys(PLUGIN_SPECIFIERS);
|
|
2122
|
-
const optionalKeys = Object.keys(OPTIONAL_PLUGIN_SPECIFIERS);
|
|
2123
|
-
const [cordis, ...pluginModules] = await Promise.all([
|
|
2124
|
-
loadHostModule(host, "@deepseek-ai/cordis"),
|
|
2125
|
-
...keys.map((key) => loadHostModule(host, PLUGIN_SPECIFIERS[key])),
|
|
2126
|
-
...optionalKeys.map(
|
|
2127
|
-
(key) => loadHostModule(host, OPTIONAL_PLUGIN_SPECIFIERS[key]).catch((error) => {
|
|
2128
|
-
logDebug(`optional module ${OPTIONAL_PLUGIN_SPECIFIERS[key]} unavailable: ${String(error)}`);
|
|
2129
|
-
return void 0;
|
|
2130
|
-
})
|
|
2131
|
-
)
|
|
2132
|
-
]);
|
|
2133
|
-
const plugins = {};
|
|
2134
|
-
[...keys, ...optionalKeys].forEach((key, index) => {
|
|
2135
|
-
const module = pluginModules[index];
|
|
2136
|
-
if (module !== void 0) plugins[key] = module;
|
|
2137
|
-
});
|
|
2138
|
-
return {
|
|
2139
|
-
host,
|
|
2140
|
-
cordis,
|
|
2141
|
-
llm: plugins["llmRuntime"],
|
|
2142
|
-
session: plugins["sessionStore"],
|
|
2143
|
-
sessionTitle: plugins["sessionTitle"],
|
|
2144
|
-
sandboxPolicy: plugins["sandboxPolicy"],
|
|
2145
|
-
plugins
|
|
2146
|
-
};
|
|
2147
|
-
}
|
|
2148
|
-
function pluginOf(module) {
|
|
2149
|
-
return module["default"] ?? module;
|
|
2150
|
-
}
|
|
2151
|
-
|
|
2152
|
-
// src/app.ts
|
|
2153
|
-
var name2 = "dsh-acp-app";
|
|
2154
|
-
var SESSION_TITLE_CONFIG = { fallbackMaxWords: 5, fallbackMaxBytes: 40, maxTitleBytes: 80 };
|
|
2155
|
-
function applySpine(ctx, kit, settings) {
|
|
2156
|
-
const p = kit.plugins;
|
|
2157
|
-
const anyCtx = ctx;
|
|
2158
|
-
const resolveDshHome = p["homePaths"].resolveDshHome;
|
|
2159
|
-
const dshHome = resolveDshHome?.();
|
|
2160
|
-
anyCtx.plugin(pluginOf(p["timer"]));
|
|
2161
|
-
anyCtx.plugin(pluginOf(p["llmRuntime"]));
|
|
2162
|
-
anyCtx.plugin(pluginOf(p["sessionStore"]));
|
|
2163
|
-
anyCtx.plugin(pluginOf(p["sessionTitle"]), SESSION_TITLE_CONFIG);
|
|
2164
|
-
anyCtx.plugin(pluginOf(p["systemPrompt"]), {
|
|
2165
|
-
includeHarnessIdentity: true,
|
|
2166
|
-
includeRuntimeContext: true,
|
|
2167
|
-
persona: settings.persona
|
|
2168
|
-
});
|
|
2169
|
-
anyCtx.plugin(pluginOf(p["tools"]), {});
|
|
2170
|
-
anyCtx.plugin(pluginOf(p["agents"]));
|
|
2171
|
-
anyCtx.plugin(p["llmRetry"]);
|
|
2172
|
-
anyCtx.plugin(pluginOf(p["jobs"]), {});
|
|
2173
|
-
anyCtx.plugin(pluginOf(p["invariants"]), {});
|
|
2174
|
-
anyCtx.plugin(p["sessionInvariant"]);
|
|
2175
|
-
anyCtx.plugin(p["agentInvariant"]);
|
|
2176
|
-
anyCtx.plugin(p["scopeInvariant"]);
|
|
2177
|
-
anyCtx.plugin(p["agentLoopInvariant"]);
|
|
2178
|
-
anyCtx.plugin(p["bashEnv"], dshHome !== void 0 ? { dshHome } : {});
|
|
2179
|
-
anyCtx.plugin(p["toolBash"], {});
|
|
2180
|
-
anyCtx.plugin(p["workspaceContext"], { maxBytes: 65536 });
|
|
2181
|
-
anyCtx.plugin(pluginOf(p["agentLoop"]), { agents: [] });
|
|
2182
|
-
}
|
|
2183
|
-
async function apply2(ctx, config) {
|
|
2184
|
-
const { settings, kit } = config;
|
|
2185
|
-
const p = kit.plugins;
|
|
2186
|
-
const anyCtx = ctx;
|
|
2187
|
-
const credentialsSeam = p["credentialsSeam"];
|
|
2188
|
-
const agentsModule = p["agents"];
|
|
2189
|
-
const installModelSelection = agentsModule.installModelSelection ?? agentsModule.ln;
|
|
2190
|
-
const harness = {
|
|
2191
|
-
createUserMessage: kit.llm.createUserMessage,
|
|
2192
|
-
errorChain: kit.llm.errorChain,
|
|
2193
|
-
sessionId: kit.session.SessionId,
|
|
2194
|
-
foldSessionTitle: kit.sessionTitle.foldSessionTitle,
|
|
2195
|
-
setSandboxMode: kit.sandboxPolicy.setSandboxMode,
|
|
2196
|
-
sandboxModes: kit.sandboxPolicy.SANDBOX_MODES,
|
|
2197
|
-
...installModelSelection !== void 0 ? { installModelSelection } : {},
|
|
2198
|
-
...credentialsSeam?.credentialRef !== void 0 ? { credentialRef: credentialsSeam.credentialRef } : {},
|
|
2199
|
-
...p["mcpClient"] !== void 0 ? { mcpClient: p["mcpClient"] } : {}
|
|
2200
|
-
};
|
|
2201
|
-
await ctx.effect(async function* () {
|
|
2202
|
-
const spine = anyCtx.plugin(
|
|
2203
|
-
{ name: "dsh-acp-spine", apply: (child) => applySpine(child, kit, settings) },
|
|
2204
|
-
{}
|
|
2205
|
-
);
|
|
2206
|
-
await spine;
|
|
2207
|
-
yield spine.dispose;
|
|
2208
|
-
const credentialsModule = p["credentials"];
|
|
2209
|
-
if (credentialsModule !== void 0) {
|
|
2210
|
-
const credentials = anyCtx.plugin(pluginOf(credentialsModule), {});
|
|
2211
|
-
await credentials;
|
|
2212
|
-
yield credentials.dispose;
|
|
2213
|
-
}
|
|
2214
|
-
const llm = anyCtx.plugin(p["llmDeepseek"], {
|
|
2215
|
-
thinking: settings.thinking ? "enabled" : "disabled",
|
|
2216
|
-
reasoningEffort: settings.reasoningEffort
|
|
2217
|
-
});
|
|
2218
|
-
await llm;
|
|
2219
|
-
yield llm.dispose;
|
|
2220
|
-
const persistence = anyCtx.plugin(pluginOf(p["persistence"]), {
|
|
2221
|
-
root: settings.sessionRoot,
|
|
2222
|
-
compression: "none"
|
|
2223
|
-
});
|
|
2224
|
-
await persistence;
|
|
2225
|
-
yield persistence.dispose;
|
|
2226
|
-
const checkpoints = anyCtx.plugin(p["checkpoints"]);
|
|
2227
|
-
await checkpoints;
|
|
2228
|
-
yield checkpoints.dispose;
|
|
2229
|
-
const sandbox = anyCtx.plugin(pluginOf(p["sandbox"]), {});
|
|
2230
|
-
await sandbox;
|
|
2231
|
-
yield sandbox.dispose;
|
|
2232
|
-
const sandboxPolicy = anyCtx.plugin(pluginOf(p["sandboxPolicy"]), {
|
|
2233
|
-
mode: settings.permissionMode,
|
|
2234
|
-
workspaceRoot: process.cwd()
|
|
2235
|
-
});
|
|
2236
|
-
await sandboxPolicy;
|
|
2237
|
-
yield sandboxPolicy.dispose;
|
|
2238
|
-
const subprocess = anyCtx.plugin(pluginOf(p["subprocess"]));
|
|
2239
|
-
await subprocess;
|
|
2240
|
-
yield subprocess.dispose;
|
|
2241
|
-
const bash = anyCtx.plugin(pluginOf(p["bash"]), { timeoutMs: settings.bashTimeoutMs });
|
|
2242
|
-
await bash;
|
|
2243
|
-
yield bash.dispose;
|
|
2244
|
-
const approval = anyCtx.plugin(pluginOf(p["approval"]), {
|
|
2245
|
-
policy: settings.permissionMode === "danger-full-access" ? "never" : "ask"
|
|
2246
|
-
});
|
|
2247
|
-
await approval;
|
|
2248
|
-
yield approval.dispose;
|
|
2249
|
-
const fs = anyCtx.plugin(pluginOf(p["fs"]), {});
|
|
2250
|
-
await fs;
|
|
2251
|
-
yield fs.dispose;
|
|
2252
|
-
const fsPolicy = anyCtx.plugin(p["fsObservationPolicy"]);
|
|
2253
|
-
await fsPolicy;
|
|
2254
|
-
yield fsPolicy.dispose;
|
|
2255
|
-
const fsTools = anyCtx.plugin(p["toolFs"], {});
|
|
2256
|
-
await fsTools;
|
|
2257
|
-
yield fsTools.dispose;
|
|
2258
|
-
const todo = anyCtx.plugin(p["toolTodo"], { allowParallelInProgress: true });
|
|
2259
|
-
await todo;
|
|
2260
|
-
yield todo.dispose;
|
|
2261
|
-
const meter = anyCtx.plugin(pluginOf(p["tokenMeter"]));
|
|
2262
|
-
await meter;
|
|
2263
|
-
yield meter.dispose;
|
|
2264
|
-
const compaction = anyCtx.plugin(pluginOf(p["compaction"]), {
|
|
2265
|
-
thresholdRatio: 0.8,
|
|
2266
|
-
retainRatio: 0.08,
|
|
2267
|
-
maxTokens: 8192,
|
|
2268
|
-
compactionRetries: 1
|
|
2269
|
-
});
|
|
2270
|
-
await compaction;
|
|
2271
|
-
yield compaction.dispose;
|
|
2272
|
-
const bridge = anyCtx.plugin(bridge_exports, {
|
|
2273
|
-
provider: settings.provider ?? "deepseek-official",
|
|
2274
|
-
model: settings.model ?? "deepseek-v4-flash",
|
|
2275
|
-
models: settings.models,
|
|
2276
|
-
...settings.maxTokens !== void 0 ? { maxTokens: settings.maxTokens } : {},
|
|
2277
|
-
permissionMode: settings.permissionMode,
|
|
2278
|
-
harness
|
|
2279
|
-
});
|
|
2280
|
-
await bridge;
|
|
2281
|
-
yield bridge.dispose;
|
|
2282
|
-
}, "dsh-acp-app.composition");
|
|
2283
|
-
}
|
|
2284
143
|
|
|
2285
144
|
// src/profile-boot.ts
|
|
2286
145
|
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
|
|
2287
|
-
import { createRequire
|
|
2288
|
-
import { dirname as dirname2, join as join2, sep as sep2 } from "node:path";
|
|
146
|
+
import { createRequire } from "node:module";
|
|
147
|
+
import { dirname as dirname2, isAbsolute as isAbsolute2, join as join2, resolve as resolve2, sep as sep2 } from "node:path";
|
|
2289
148
|
import { pathToFileURL as pathToFileURL2 } from "node:url";
|
|
2290
149
|
var BIN = "dsh-acp";
|
|
2291
150
|
function ownRoot() {
|
|
2292
|
-
return dirname2(
|
|
151
|
+
return dirname2(createRequire(import.meta.url).resolve("../package.json"));
|
|
2293
152
|
}
|
|
2294
153
|
var OWN_BRIDGE_SPECIFIER = "@openma/deepseek-harness-acp/bridge";
|
|
2295
154
|
function rewriteBridgeRows(row, absoluteEntry) {
|
|
@@ -2311,8 +170,55 @@ function bundlePatchPath(packageJsonPath) {
|
|
|
2311
170
|
}
|
|
2312
171
|
return join2(dirname2(packageJsonPath), declared);
|
|
2313
172
|
}
|
|
173
|
+
function resolveBundlePackageJson(specifier) {
|
|
174
|
+
const candidate = resolve2(specifier);
|
|
175
|
+
if (existsSync2(candidate)) {
|
|
176
|
+
return candidate.endsWith("package.json") ? candidate : join2(candidate, "package.json");
|
|
177
|
+
}
|
|
178
|
+
const req = createRequire(join2(process.cwd(), "noop.js"));
|
|
179
|
+
try {
|
|
180
|
+
return req.resolve(`${specifier}/package.json`);
|
|
181
|
+
} catch (cause) {
|
|
182
|
+
throw new Error(`${BIN}: cannot resolve bundle ${JSON.stringify(specifier)}`, { cause });
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
function rewriteBundleRows(row, packageJsonPath, manifest) {
|
|
186
|
+
const name = row["name"];
|
|
187
|
+
if (typeof name === "string" && !name.startsWith("cordis:") && !isAbsolute2(name)) {
|
|
188
|
+
const req = createRequire(packageJsonPath);
|
|
189
|
+
try {
|
|
190
|
+
row["name"] = req.resolve(name);
|
|
191
|
+
} catch (cause) {
|
|
192
|
+
if (name === manifest.name && manifest.main !== void 0) {
|
|
193
|
+
const entry = resolve2(dirname2(packageJsonPath), manifest.main);
|
|
194
|
+
if (existsSync2(entry)) row["name"] = entry;
|
|
195
|
+
else throw new Error(`${BIN}: bundle entry does not exist: ${entry}`, { cause });
|
|
196
|
+
} else {
|
|
197
|
+
throw new Error(
|
|
198
|
+
`${BIN}: bundle ${manifest.name ?? packageJsonPath} cannot resolve plugin ${JSON.stringify(name)}`,
|
|
199
|
+
{ cause }
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
const insert = row["insert"];
|
|
205
|
+
if (Array.isArray(insert)) {
|
|
206
|
+
for (const nested of insert) {
|
|
207
|
+
if (nested !== null && typeof nested === "object") {
|
|
208
|
+
rewriteBundleRows(nested, packageJsonPath, manifest);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
function loadBundleLayer(appBoot, specifier) {
|
|
214
|
+
const packageJsonPath = resolveBundlePackageJson(specifier);
|
|
215
|
+
const manifest = JSON.parse(readFileSync2(packageJsonPath, "utf8"));
|
|
216
|
+
const patches = appBoot.loadOverlayPatches(BIN, bundlePatchPath(packageJsonPath));
|
|
217
|
+
for (const row of patches) rewriteBundleRows(row, packageJsonPath, manifest);
|
|
218
|
+
return patches;
|
|
219
|
+
}
|
|
2314
220
|
async function bootAcpProfile(host, overrides) {
|
|
2315
|
-
const req =
|
|
221
|
+
const req = createRequire(join2(host.base, "noop.js"));
|
|
2316
222
|
const dshPkgPath = req.resolve("@deepseek-ai/dsh/package.json");
|
|
2317
223
|
const dshDir = dirname2(dshPkgPath);
|
|
2318
224
|
const appBoot = await import(pathToFileURL2(req.resolve("@deepseek-ai/dsh-app-boot")).href);
|
|
@@ -2345,6 +251,10 @@ async function bootAcpProfile(host, overrides) {
|
|
|
2345
251
|
];
|
|
2346
252
|
logDebug(`profile: virtual (dsh-base + ${ownRoot()})`);
|
|
2347
253
|
}
|
|
254
|
+
for (const bundle of overrides?.bundles ?? []) {
|
|
255
|
+
bundlePatches.push(...loadBundleLayer(appBoot, bundle));
|
|
256
|
+
logDebug(`profile: added bundle ${bundle}`);
|
|
257
|
+
}
|
|
2348
258
|
for (const row of bundlePatches) rewriteBridgeRows(row, bridgeEntry);
|
|
2349
259
|
const homePatchPath = join2(home, "cordis.patch.yml");
|
|
2350
260
|
const homePatches = appBoot.loadOptionalPatches(BIN, homePatchPath) ?? [];
|
|
@@ -2406,10 +316,45 @@ async function bootAcpProfile(host, overrides) {
|
|
|
2406
316
|
return ctx;
|
|
2407
317
|
}
|
|
2408
318
|
|
|
319
|
+
// src/auth.ts
|
|
320
|
+
function parseLoginArgv(argv) {
|
|
321
|
+
let provider;
|
|
322
|
+
const rest = [];
|
|
323
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
324
|
+
const arg = argv[i];
|
|
325
|
+
if (arg === void 0) continue;
|
|
326
|
+
if (arg === "--provider") {
|
|
327
|
+
provider = argv[i + 1];
|
|
328
|
+
i += 1;
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
if (arg.startsWith("--provider=")) {
|
|
332
|
+
provider = arg.slice("--provider=".length);
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
rest.push(arg);
|
|
336
|
+
}
|
|
337
|
+
const key = rest[0];
|
|
338
|
+
return {
|
|
339
|
+
...provider && provider.length > 0 ? { provider } : {},
|
|
340
|
+
...key && key.length > 0 ? { key } : {}
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
function isDeepseekRoute(provider) {
|
|
344
|
+
return provider === void 0 || provider === "deepseek-official" || provider === "deepseek";
|
|
345
|
+
}
|
|
346
|
+
function credentialEnvNames(provider) {
|
|
347
|
+
if (provider === void 0 || isDeepseekRoute(provider)) return ["DEEPSEEK_API_KEY", "DEEPSEEK_BASE_URL"];
|
|
348
|
+
if (provider === "anthropic") return ["ANTHROPIC_API_KEY"];
|
|
349
|
+
if (provider === "openai" || provider.startsWith("openai")) return ["OPENAI_API_KEY"];
|
|
350
|
+
return [`${provider.replace(/[^a-zA-Z0-9]+/g, "_").toUpperCase()}_API_KEY`];
|
|
351
|
+
}
|
|
352
|
+
function primaryCredentialName(provider) {
|
|
353
|
+
return credentialEnvNames(provider)[0] ?? "DEEPSEEK_API_KEY";
|
|
354
|
+
}
|
|
355
|
+
|
|
2409
356
|
// src/login.ts
|
|
2410
|
-
import {
|
|
2411
|
-
import { join as join3 } from "node:path";
|
|
2412
|
-
import { pathToFileURL as pathToFileURL3 } from "node:url";
|
|
357
|
+
import { credentialRef } from "@deepseek-ai/dsh-credentials";
|
|
2413
358
|
async function readSecret(promptText) {
|
|
2414
359
|
const stdin = process.stdin;
|
|
2415
360
|
if (!stdin.isTTY) {
|
|
@@ -2423,7 +368,7 @@ async function readSecret(promptText) {
|
|
|
2423
368
|
return Buffer.concat(chunks).toString("utf8").trim();
|
|
2424
369
|
}
|
|
2425
370
|
process.stderr.write(promptText);
|
|
2426
|
-
return await new Promise((
|
|
371
|
+
return await new Promise((resolve3) => {
|
|
2427
372
|
stdin.setRawMode(true);
|
|
2428
373
|
stdin.resume();
|
|
2429
374
|
stdin.setEncoding("utf8");
|
|
@@ -2435,7 +380,7 @@ async function readSecret(promptText) {
|
|
|
2435
380
|
stdin.pause();
|
|
2436
381
|
stdin.off("data", onData);
|
|
2437
382
|
process.stderr.write("\n");
|
|
2438
|
-
|
|
383
|
+
resolve3(buffer.trim());
|
|
2439
384
|
return;
|
|
2440
385
|
}
|
|
2441
386
|
if (char === "") {
|
|
@@ -2453,37 +398,25 @@ async function readSecret(promptText) {
|
|
|
2453
398
|
stdin.on("data", onData);
|
|
2454
399
|
});
|
|
2455
400
|
}
|
|
2456
|
-
async function runLogin(ctx,
|
|
2457
|
-
const
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
} catch {
|
|
2463
|
-
credentialRef2 = void 0;
|
|
2464
|
-
}
|
|
2465
|
-
const store = ctx.get("credentials");
|
|
2466
|
-
if (store === void 0 || credentialRef2 === void 0) {
|
|
2467
|
-
process.stderr.write(
|
|
2468
|
-
"This composition has no writable credential store; export DEEPSEEK_API_KEY instead.\n"
|
|
2469
|
-
);
|
|
2470
|
-
return 1;
|
|
2471
|
-
}
|
|
2472
|
-
const ref = credentialRef2("DEEPSEEK_API_KEY");
|
|
2473
|
-
const before = await store.describe(ref);
|
|
401
|
+
async function runLogin(ctx, argv) {
|
|
402
|
+
const parsed = parseLoginArgv(argv);
|
|
403
|
+
const name = primaryCredentialName(parsed.provider);
|
|
404
|
+
const credentials = ctx.credentials;
|
|
405
|
+
const ref = credentialRef(name);
|
|
406
|
+
const before = await credentials.describe(ref);
|
|
2474
407
|
if (before.configured && before.source !== void 0) {
|
|
2475
|
-
process.stderr.write(`A key is already configured (source: ${before.source}).
|
|
408
|
+
process.stderr.write(`A key is already configured for ${name} (source: ${before.source}).
|
|
2476
409
|
`);
|
|
2477
410
|
}
|
|
2478
|
-
const key =
|
|
411
|
+
const key = parsed.key ?? await readSecret(`${name} (input hidden): `);
|
|
2479
412
|
if (key.length === 0) {
|
|
2480
413
|
process.stderr.write("No key entered; nothing saved.\n");
|
|
2481
414
|
return 1;
|
|
2482
415
|
}
|
|
2483
|
-
await
|
|
416
|
+
await credentials.set(ref, key);
|
|
2484
417
|
const masked = key.length <= 8 ? "\u2026" : `${key.slice(0, 4)}\u2026${key.slice(-4)}`;
|
|
2485
418
|
process.stderr.write(
|
|
2486
|
-
`Saved ${masked}
|
|
419
|
+
`Saved ${masked} as ${name} in the harness credential store (~/.dsh/.credentials.yaml, mode 600).
|
|
2487
420
|
The dsh Web UI and every dsh surface share this credential.
|
|
2488
421
|
`
|
|
2489
422
|
);
|
|
@@ -2492,19 +425,20 @@ The dsh Web UI and every dsh surface share this credential.
|
|
|
2492
425
|
|
|
2493
426
|
// src/settings.ts
|
|
2494
427
|
import { homedir } from "node:os";
|
|
2495
|
-
import { join as
|
|
428
|
+
import { join as join3 } from "node:path";
|
|
2496
429
|
var DEFAULT_PERSONA = `You are a coding assistant powered by the {{model}} model, working inside an editor via the Agent Client Protocol. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox \u2014 a \`[sandbox: file access denied \u2026]\` result is policy, not a command bug.
|
|
2497
430
|
|
|
2498
431
|
Verify your work by running the code or tests. Keep answers brief and factual.`;
|
|
2499
432
|
var PERMISSION_MODES = ["read-only", "workspace-write", "danger-full-access"];
|
|
2500
433
|
var SettingsError = class extends Error {
|
|
2501
434
|
};
|
|
2502
|
-
function envString(
|
|
2503
|
-
const value = process.env[
|
|
435
|
+
function envString(name) {
|
|
436
|
+
const value = process.env[name];
|
|
2504
437
|
return value !== void 0 && value.length > 0 ? value : void 0;
|
|
2505
438
|
}
|
|
2506
439
|
function parseFlags(argv) {
|
|
2507
440
|
const flags = /* @__PURE__ */ new Map();
|
|
441
|
+
const bundles = [];
|
|
2508
442
|
for (let i = 0; i < argv.length; i += 1) {
|
|
2509
443
|
const arg = argv[i];
|
|
2510
444
|
if (arg === void 0 || !arg.startsWith("--")) {
|
|
@@ -2512,19 +446,23 @@ function parseFlags(argv) {
|
|
|
2512
446
|
}
|
|
2513
447
|
const eq = arg.indexOf("=");
|
|
2514
448
|
if (eq >= 0) {
|
|
2515
|
-
|
|
449
|
+
const name2 = arg.slice(2, eq);
|
|
450
|
+
const value = arg.slice(eq + 1);
|
|
451
|
+
if (name2 === "bundle") bundles.push(value);
|
|
452
|
+
else flags.set(name2, value);
|
|
2516
453
|
continue;
|
|
2517
454
|
}
|
|
2518
|
-
const
|
|
455
|
+
const name = arg.slice(2);
|
|
2519
456
|
const next = argv[i + 1];
|
|
2520
|
-
if (next !== void 0 && !next.startsWith("--") && FLAGS_WITH_VALUES.has(
|
|
2521
|
-
|
|
457
|
+
if (next !== void 0 && !next.startsWith("--") && FLAGS_WITH_VALUES.has(name)) {
|
|
458
|
+
if (name === "bundle") bundles.push(next);
|
|
459
|
+
else flags.set(name, next);
|
|
2522
460
|
i += 1;
|
|
2523
461
|
} else {
|
|
2524
|
-
flags.set(
|
|
462
|
+
flags.set(name, true);
|
|
2525
463
|
}
|
|
2526
464
|
}
|
|
2527
|
-
return { flags };
|
|
465
|
+
return { flags, bundles };
|
|
2528
466
|
}
|
|
2529
467
|
var FLAGS_WITH_VALUES = /* @__PURE__ */ new Set([
|
|
2530
468
|
"dsh-path",
|
|
@@ -2536,19 +474,24 @@ var FLAGS_WITH_VALUES = /* @__PURE__ */ new Set([
|
|
|
2536
474
|
"session-root",
|
|
2537
475
|
"persona",
|
|
2538
476
|
"reasoning-effort",
|
|
2539
|
-
"bash-timeout"
|
|
477
|
+
"bash-timeout",
|
|
478
|
+
"bundle"
|
|
2540
479
|
]);
|
|
2541
|
-
function stringFlag(parsed,
|
|
2542
|
-
const value = parsed.flags.get(
|
|
480
|
+
function stringFlag(parsed, name) {
|
|
481
|
+
const value = parsed.flags.get(name);
|
|
2543
482
|
if (value === void 0) return void 0;
|
|
2544
|
-
if (value === true) throw new SettingsError(`--${
|
|
483
|
+
if (value === true) throw new SettingsError(`--${name} requires a value`);
|
|
2545
484
|
return value;
|
|
2546
485
|
}
|
|
2547
486
|
function resolveSettings(argv) {
|
|
2548
487
|
const parsed = parseFlags(argv);
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
488
|
+
if (parsed.flags.has("bundle")) stringFlag(parsed, "bundle");
|
|
489
|
+
if (parsed.bundles.some((bundle) => bundle.length === 0)) {
|
|
490
|
+
throw new SettingsError("--bundle requires a value");
|
|
491
|
+
}
|
|
492
|
+
for (const name of parsed.flags.keys()) {
|
|
493
|
+
if (!FLAGS_WITH_VALUES.has(name) && name !== "no-thinking") {
|
|
494
|
+
throw new SettingsError(`unknown flag: --${name}`);
|
|
2552
495
|
}
|
|
2553
496
|
}
|
|
2554
497
|
const model = stringFlag(parsed, "model") ?? envString("DSH_MODEL");
|
|
@@ -2580,12 +523,13 @@ function resolveSettings(argv) {
|
|
|
2580
523
|
}
|
|
2581
524
|
return {
|
|
2582
525
|
dshPath: stringFlag(parsed, "dsh-path") ?? envString("DSH_PATH"),
|
|
526
|
+
bundles: parsed.bundles,
|
|
2583
527
|
provider: stringFlag(parsed, "provider") ?? envString("DSH_PROVIDER"),
|
|
2584
528
|
model,
|
|
2585
529
|
models,
|
|
2586
530
|
maxTokens,
|
|
2587
531
|
permissionMode,
|
|
2588
|
-
sessionRoot: stringFlag(parsed, "session-root") ?? envString("DSH_SESSION_ROOT") ??
|
|
532
|
+
sessionRoot: stringFlag(parsed, "session-root") ?? envString("DSH_SESSION_ROOT") ?? join3(homedir(), ".dsh-acp", "sessions"),
|
|
2589
533
|
persona: stringFlag(parsed, "persona") ?? envString("DSH_SYSTEM_PROMPT") ?? DEFAULT_PERSONA,
|
|
2590
534
|
thinking: !parsed.flags.has("no-thinking"),
|
|
2591
535
|
reasoningEffort: reasoningRaw,
|
|
@@ -2593,7 +537,8 @@ function resolveSettings(argv) {
|
|
|
2593
537
|
};
|
|
2594
538
|
}
|
|
2595
539
|
var HELP_TEXT = `Usage: dsh-acp [options]
|
|
2596
|
-
dsh-acp login [api-key]
|
|
540
|
+
dsh-acp login [--provider <route>] [api-key]
|
|
541
|
+
Save a provider API key (interactive when omitted)
|
|
2597
542
|
dsh-acp update Self-update via npm
|
|
2598
543
|
|
|
2599
544
|
Agent Client Protocol (ACP) stdio server for DeepSeek Harness. Speak ACP on
|
|
@@ -2604,6 +549,7 @@ Options:
|
|
|
2604
549
|
package dir, or any dir carrying node_modules/@deepseek-ai
|
|
2605
550
|
(DSH_PATH; auto-detected when unset \u2014 own tree, ./node_modules,
|
|
2606
551
|
dsh on PATH, npm root -g)
|
|
552
|
+
--bundle <package-dir> Add a dsh bundle layer to the agent tree (repeatable)
|
|
2607
553
|
--provider <route> Provider route (DSH_PROVIDER, default deepseek-official)
|
|
2608
554
|
--model <id> Default model (DSH_MODEL, default deepseek-v4-flash)
|
|
2609
555
|
--models <a,b,...> Selectable models for the session "Model" option
|
|
@@ -2626,6 +572,10 @@ Environment:
|
|
|
2626
572
|
DSH_ACP_DEBUG Verbose stderr diagnostics
|
|
2627
573
|
`;
|
|
2628
574
|
|
|
575
|
+
// src/version.ts
|
|
576
|
+
import { createRequire as createRequire2 } from "node:module";
|
|
577
|
+
var VERSION = createRequire2(import.meta.url)("../package.json").version;
|
|
578
|
+
|
|
2629
579
|
// src/index.ts
|
|
2630
580
|
var NAME = "@openma/deepseek-harness-acp";
|
|
2631
581
|
async function main() {
|
|
@@ -2640,10 +590,8 @@ async function main() {
|
|
|
2640
590
|
}
|
|
2641
591
|
if (argv[0] === "login") {
|
|
2642
592
|
let booted;
|
|
2643
|
-
let hostBase;
|
|
2644
593
|
try {
|
|
2645
594
|
const host = resolveHost(void 0);
|
|
2646
|
-
hostBase = host.base;
|
|
2647
595
|
booted = await bootAcpProfile(host, { serve: false });
|
|
2648
596
|
} catch (error) {
|
|
2649
597
|
if (error instanceof HarnessNotFoundError) {
|
|
@@ -2656,7 +604,6 @@ async function main() {
|
|
|
2656
604
|
try {
|
|
2657
605
|
process.exitCode = await runLogin(
|
|
2658
606
|
booted,
|
|
2659
|
-
hostBase,
|
|
2660
607
|
argv.slice(1)
|
|
2661
608
|
);
|
|
2662
609
|
} finally {
|
|
@@ -2694,19 +641,20 @@ ${HELP_TEXT}`);
|
|
|
2694
641
|
const host = resolveHost(settings.dshPath);
|
|
2695
642
|
logDebug(`harness host: ${host.base} via ${host.source} (${host.version ?? "unknown version"})`);
|
|
2696
643
|
if (process.env["DSH_ACP_COMPOSE"] === "spine") {
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
const booted = await bootAcpProfile(host, {
|
|
2703
|
-
...settings.provider !== void 0 ? { provider: settings.provider } : {},
|
|
2704
|
-
...settings.model !== void 0 ? { model: settings.model } : {},
|
|
2705
|
-
...settings.permissionMode !== void 0 ? { permissionMode: settings.permissionMode } : {},
|
|
2706
|
-
...settings.maxTokens !== void 0 ? { maxTokens: settings.maxTokens } : {}
|
|
2707
|
-
});
|
|
2708
|
-
ctx = booted;
|
|
644
|
+
logError(
|
|
645
|
+
"DSH_ACP_COMPOSE=spine is no longer supported: the ACP bridge injects dsh-base services (credentials, models, permissions, presets). Unset DSH_ACP_COMPOSE to use the profile engine."
|
|
646
|
+
);
|
|
647
|
+
process.exitCode = 1;
|
|
648
|
+
return;
|
|
2709
649
|
}
|
|
650
|
+
const booted = await bootAcpProfile(host, {
|
|
651
|
+
bundles: settings.bundles,
|
|
652
|
+
...settings.provider !== void 0 ? { provider: settings.provider } : {},
|
|
653
|
+
...settings.model !== void 0 ? { model: settings.model } : {},
|
|
654
|
+
...settings.permissionMode !== void 0 ? { permissionMode: settings.permissionMode } : {},
|
|
655
|
+
...settings.maxTokens !== void 0 ? { maxTokens: settings.maxTokens } : {}
|
|
656
|
+
});
|
|
657
|
+
ctx = booted;
|
|
2710
658
|
} catch (error) {
|
|
2711
659
|
if (error instanceof HarnessNotFoundError) {
|
|
2712
660
|
logError(error.message);
|