@openma/deepseek-harness-acp 0.4.8 → 0.4.10-beta.1
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 +9 -3
- package/cordis.patch.yml +9 -16
- package/dist/bridge.js +132 -56
- package/dist/index.js +25 -6
- package/dist/plugin.js +3217 -0
- package/dist/server.js +3169 -0
- package/dist/stdio.js +91 -0
- package/package.json +13 -2
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,3217 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __esm = (fn, res, err) => function __init() {
|
|
4
|
+
if (err) throw err[0];
|
|
5
|
+
try {
|
|
6
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
7
|
+
} catch (e) {
|
|
8
|
+
throw err = [e], e;
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
var __export = (target, all) => {
|
|
12
|
+
for (var name4 in all)
|
|
13
|
+
__defProp(target, name4, { get: all[name4], enumerable: true });
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// src/bridge/self-harness.ts
|
|
17
|
+
var self_harness_exports = {};
|
|
18
|
+
__export(self_harness_exports, {
|
|
19
|
+
selfHarness: () => selfHarness
|
|
20
|
+
});
|
|
21
|
+
import { createUserMessage, errorChain } from "@deepseek-ai/dsh-llm";
|
|
22
|
+
import { SessionId } from "@deepseek-ai/dsh-session";
|
|
23
|
+
import { foldSessionTitle } from "@deepseek-ai/dsh-session-title";
|
|
24
|
+
import { SANDBOX_MODES, setSandboxMode } from "@deepseek-ai/dsh-sandbox-policy";
|
|
25
|
+
import * as agentModule from "@deepseek-ai/dsh-agent";
|
|
26
|
+
import * as mcpClient from "@deepseek-ai/dsh-mcp-client";
|
|
27
|
+
function selfHarness() {
|
|
28
|
+
const agents = agentModule;
|
|
29
|
+
const installModelSelection = agents.installModelSelection ?? agents.ln;
|
|
30
|
+
return {
|
|
31
|
+
createUserMessage,
|
|
32
|
+
errorChain,
|
|
33
|
+
sessionId: SessionId,
|
|
34
|
+
foldSessionTitle,
|
|
35
|
+
setSandboxMode,
|
|
36
|
+
sandboxModes: SANDBOX_MODES,
|
|
37
|
+
...installModelSelection !== void 0 ? { installModelSelection } : {},
|
|
38
|
+
mcpClient
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
var init_self_harness = __esm({
|
|
42
|
+
"src/bridge/self-harness.ts"() {
|
|
43
|
+
"use strict";
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// src/plugin.ts
|
|
48
|
+
import { existsSync } from "node:fs";
|
|
49
|
+
import { createRequire as createRequire2 } from "node:module";
|
|
50
|
+
import { dirname, join } from "node:path";
|
|
51
|
+
|
|
52
|
+
// src/bridge/index.ts
|
|
53
|
+
var bridge_exports = {};
|
|
54
|
+
__export(bridge_exports, {
|
|
55
|
+
answerFromElicitation: () => answerFromElicitation,
|
|
56
|
+
apply: () => apply,
|
|
57
|
+
askUserQuestionsOverAcp: () => askUserQuestionsOverAcp,
|
|
58
|
+
inject: () => inject,
|
|
59
|
+
installAcpUserQuestionProvider: () => installAcpUserQuestionProvider,
|
|
60
|
+
name: () => name,
|
|
61
|
+
questionsToElicitation: () => questionsToElicitation
|
|
62
|
+
});
|
|
63
|
+
import { randomUUID } from "node:crypto";
|
|
64
|
+
import { isAbsolute } from "node:path";
|
|
65
|
+
import { Readable, Writable } from "node:stream";
|
|
66
|
+
import {
|
|
67
|
+
AgentSideConnection,
|
|
68
|
+
ndJsonStream,
|
|
69
|
+
PROTOCOL_VERSION,
|
|
70
|
+
RequestError
|
|
71
|
+
} from "@agentclientprotocol/sdk";
|
|
72
|
+
import { credentialRef } from "@deepseek-ai/dsh-credentials";
|
|
73
|
+
|
|
74
|
+
// src/version.ts
|
|
75
|
+
import { createRequire } from "node:module";
|
|
76
|
+
var VERSION = createRequire(import.meta.url)("../package.json").version;
|
|
77
|
+
|
|
78
|
+
// src/auth.ts
|
|
79
|
+
var LOGIN_DESCRIPTION = "Save an API key to the harness credential store shared with the dsh Web UI";
|
|
80
|
+
function isDeepseekRoute(provider) {
|
|
81
|
+
return provider === void 0 || provider === "deepseek-official" || provider === "deepseek";
|
|
82
|
+
}
|
|
83
|
+
function credentialEnvNames(provider) {
|
|
84
|
+
if (provider === void 0 || isDeepseekRoute(provider)) return ["DEEPSEEK_API_KEY", "DEEPSEEK_BASE_URL"];
|
|
85
|
+
if (provider === "anthropic") return ["ANTHROPIC_API_KEY"];
|
|
86
|
+
if (provider === "openai" || provider.startsWith("openai")) return ["OPENAI_API_KEY"];
|
|
87
|
+
return [`${provider.replace(/[^a-zA-Z0-9]+/g, "_").toUpperCase()}_API_KEY`];
|
|
88
|
+
}
|
|
89
|
+
function primaryCredentialName(provider) {
|
|
90
|
+
return credentialEnvNames(provider)[0] ?? "DEEPSEEK_API_KEY";
|
|
91
|
+
}
|
|
92
|
+
function providerFromAuthMethodId(methodId) {
|
|
93
|
+
if (!methodId) return void 0;
|
|
94
|
+
for (const prefix of ["api-key:", "terminal-login:"]) {
|
|
95
|
+
if (methodId.startsWith(prefix)) return methodId.slice(prefix.length);
|
|
96
|
+
}
|
|
97
|
+
return void 0;
|
|
98
|
+
}
|
|
99
|
+
function apiKeyFromAuthenticate(params) {
|
|
100
|
+
const meta = params._meta;
|
|
101
|
+
const block = meta && typeof meta === "object" && !Array.isArray(meta) ? meta["api-key"] : void 0;
|
|
102
|
+
const record = block && typeof block === "object" && !Array.isArray(block) ? block : void 0;
|
|
103
|
+
const key = typeof record?.apiKey === "string" ? record.apiKey.trim() : void 0;
|
|
104
|
+
const providerFromMeta = typeof record?.provider === "string" ? record.provider : void 0;
|
|
105
|
+
const provider = providerFromMeta || providerFromAuthMethodId(params.methodId);
|
|
106
|
+
return {
|
|
107
|
+
...provider && provider.length > 0 ? { provider } : {},
|
|
108
|
+
...key && key.length > 0 ? { key } : {}
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function gatewayFromAuthenticate(params) {
|
|
112
|
+
const meta = params._meta;
|
|
113
|
+
const block = meta && typeof meta === "object" && !Array.isArray(meta) ? meta.gateway : void 0;
|
|
114
|
+
const record = block && typeof block === "object" && !Array.isArray(block) ? block : void 0;
|
|
115
|
+
const baseUrl = typeof record?.baseUrl === "string" ? record.baseUrl.trim() : void 0;
|
|
116
|
+
const headers = stringRecord(record?.headers);
|
|
117
|
+
const providerName = typeof record?.providerName === "string" ? record.providerName.trim() : void 0;
|
|
118
|
+
const key = apiKeyFromHeaders(headers);
|
|
119
|
+
return {
|
|
120
|
+
...baseUrl && baseUrl.length > 0 ? { baseUrl } : {},
|
|
121
|
+
...headers ? { headers } : {},
|
|
122
|
+
...providerName && providerName.length > 0 ? { providerName } : {},
|
|
123
|
+
...key && key.length > 0 ? { key } : {}
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function isBrowserAuthMethod(methodId) {
|
|
127
|
+
return methodId === "browser";
|
|
128
|
+
}
|
|
129
|
+
function isGatewayAuthMethod(methodId) {
|
|
130
|
+
return methodId === "gateway";
|
|
131
|
+
}
|
|
132
|
+
function credentialBaseUrlName(provider) {
|
|
133
|
+
if (isDeepseekRoute(provider) || provider === void 0) return "DEEPSEEK_BASE_URL";
|
|
134
|
+
if (provider === "anthropic") return "ANTHROPIC_BASE_URL";
|
|
135
|
+
if (provider === "openai" || provider.startsWith("openai")) return "OPENAI_BASE_URL";
|
|
136
|
+
return `${provider.replace(/[^a-zA-Z0-9]+/g, "_").toUpperCase()}_BASE_URL`;
|
|
137
|
+
}
|
|
138
|
+
function advertisedAuthMethods(providers, clientCapabilities, env = process.env) {
|
|
139
|
+
const methods = [...apiKeyAuthMethods(providers)];
|
|
140
|
+
if (shouldOfferLocalAuthPage(env)) methods.push(browserAuthMethod());
|
|
141
|
+
if (clientCapabilities?.auth?._meta?.["gateway"] === true) methods.push(gatewayAuthMethod());
|
|
142
|
+
return methods;
|
|
143
|
+
}
|
|
144
|
+
function apiKeyAuthMethods(providers) {
|
|
145
|
+
if (providers.length <= 1) {
|
|
146
|
+
const provider = providers[0];
|
|
147
|
+
return [apiKeyAuthMethod(provider?.id, provider?.name, providers.length === 0)];
|
|
148
|
+
}
|
|
149
|
+
return providers.map((provider) => apiKeyAuthMethod(provider.id, provider.name, false));
|
|
150
|
+
}
|
|
151
|
+
function apiKeyAuthMethod(provider, name4, unscoped) {
|
|
152
|
+
const scoped = !unscoped && provider !== void 0;
|
|
153
|
+
return {
|
|
154
|
+
id: scoped ? `api-key:${provider}` : "api-key",
|
|
155
|
+
name: scoped ? `${name4 ?? provider} API key` : "API Key",
|
|
156
|
+
description: LOGIN_DESCRIPTION,
|
|
157
|
+
_meta: { "api-key": scoped ? { provider } : {} }
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
function shouldOfferLocalAuthPage(env = process.env) {
|
|
161
|
+
if (env["NO_BROWSER"]) return false;
|
|
162
|
+
if (env["NODE_ENV"] === "test" || env["CI"] === "true" || env["CI"] === "1") return false;
|
|
163
|
+
if (env["DSH_ACP_AUTH_PAGE"] === "0") return false;
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
function browserAuthMethod() {
|
|
167
|
+
return {
|
|
168
|
+
id: "browser",
|
|
169
|
+
name: "Browser",
|
|
170
|
+
description: "Open a local sign-in page to save an API key. The secret never travels over ACP."
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
function gatewayAuthMethod() {
|
|
174
|
+
return {
|
|
175
|
+
id: "gateway",
|
|
176
|
+
name: "Custom model gateway",
|
|
177
|
+
description: "Use a custom OpenAI-compatible gateway",
|
|
178
|
+
_meta: { gateway: { protocol: "openai", restartRequired: "false" } }
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
function stringRecord(value) {
|
|
182
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
183
|
+
const entries = Object.entries(value).filter(
|
|
184
|
+
(entry) => typeof entry[1] === "string" && entry[1].trim().length > 0
|
|
185
|
+
);
|
|
186
|
+
return entries.length > 0 ? Object.fromEntries(entries) : void 0;
|
|
187
|
+
}
|
|
188
|
+
function apiKeyFromHeaders(headers) {
|
|
189
|
+
if (!headers) return void 0;
|
|
190
|
+
const authorization = headers["Authorization"] ?? headers["authorization"];
|
|
191
|
+
if (authorization) {
|
|
192
|
+
const bearer = authorization.match(/^Bearer\s+(.+)$/i);
|
|
193
|
+
const token = (bearer?.[1] ?? authorization).trim();
|
|
194
|
+
if (token.length > 0) return token;
|
|
195
|
+
}
|
|
196
|
+
const first = Object.values(headers).find((value) => value.trim().length > 0);
|
|
197
|
+
return first?.trim();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// src/auth-page.ts
|
|
201
|
+
import { createServer } from "node:http";
|
|
202
|
+
import { execFile } from "node:child_process";
|
|
203
|
+
function startLocalAuthPage(options) {
|
|
204
|
+
return new Promise((resolve, reject) => {
|
|
205
|
+
const server = createServer((request, response) => {
|
|
206
|
+
void handle(request, response);
|
|
207
|
+
});
|
|
208
|
+
let settle;
|
|
209
|
+
let fail;
|
|
210
|
+
const completed = new Promise((res, rej) => {
|
|
211
|
+
settle = res;
|
|
212
|
+
fail = rej;
|
|
213
|
+
});
|
|
214
|
+
const timeoutMs = options.timeoutMs ?? 10 * 6e4;
|
|
215
|
+
const timer = setTimeout(() => {
|
|
216
|
+
server.close();
|
|
217
|
+
fail?.(new Error("local auth page timed out"));
|
|
218
|
+
}, timeoutMs);
|
|
219
|
+
timer.unref?.();
|
|
220
|
+
const handle = async (request, response) => {
|
|
221
|
+
if (request.method === "GET" || request.method === void 0) {
|
|
222
|
+
response.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
223
|
+
response.end(authPageHtml(options.credentialName));
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
if (request.method === "POST") {
|
|
227
|
+
const body = await readBody(request);
|
|
228
|
+
const key = new URLSearchParams(body).get("api-key")?.trim() ?? "";
|
|
229
|
+
if (key.length === 0) {
|
|
230
|
+
response.writeHead(400, { "content-type": "text/html; charset=utf-8" });
|
|
231
|
+
response.end(authPageHtml(options.credentialName, "Enter an API key."));
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
response.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
235
|
+
response.end(
|
|
236
|
+
"<!doctype html><html><body><p>Saved. You can return to the client.</p></body></html>"
|
|
237
|
+
);
|
|
238
|
+
clearTimeout(timer);
|
|
239
|
+
server.close();
|
|
240
|
+
settle?.(key);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
response.writeHead(405);
|
|
244
|
+
response.end();
|
|
245
|
+
};
|
|
246
|
+
server.once("error", (error) => {
|
|
247
|
+
clearTimeout(timer);
|
|
248
|
+
reject(error);
|
|
249
|
+
});
|
|
250
|
+
server.listen(0, "127.0.0.1", () => {
|
|
251
|
+
const address = server.address();
|
|
252
|
+
if (!address || typeof address === "string") {
|
|
253
|
+
server.close();
|
|
254
|
+
reject(new Error("local auth page failed to bind"));
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
const url = `http://127.0.0.1:${address.port}/`;
|
|
258
|
+
resolve({
|
|
259
|
+
url,
|
|
260
|
+
close: () => {
|
|
261
|
+
clearTimeout(timer);
|
|
262
|
+
server.close();
|
|
263
|
+
},
|
|
264
|
+
completed
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
function openLocalAuthPage(url) {
|
|
270
|
+
const platform = process.platform;
|
|
271
|
+
if (platform === "darwin") {
|
|
272
|
+
execFile("open", [url], () => void 0);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
if (platform === "win32") {
|
|
276
|
+
execFile("cmd", ["/c", "start", "", url], () => void 0);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
execFile("xdg-open", [url], () => void 0);
|
|
280
|
+
}
|
|
281
|
+
function authPageHtml(credentialName, error) {
|
|
282
|
+
const message = error ? `<p>${escapeHtml(error)}</p>` : "";
|
|
283
|
+
return `<!doctype html>
|
|
284
|
+
<html lang="en">
|
|
285
|
+
<head><meta charset="utf-8"><title>DeepSeek Harness sign in</title></head>
|
|
286
|
+
<body>
|
|
287
|
+
<h1>Save API key</h1>
|
|
288
|
+
<p>Stored as ${escapeHtml(credentialName)} in the harness credential store.</p>
|
|
289
|
+
${message}
|
|
290
|
+
<form method="post">
|
|
291
|
+
<label>API key <input type="password" name="api-key" autocomplete="off" required></label>
|
|
292
|
+
<button type="submit">Save</button>
|
|
293
|
+
</form>
|
|
294
|
+
</body>
|
|
295
|
+
</html>`;
|
|
296
|
+
}
|
|
297
|
+
function escapeHtml(value) {
|
|
298
|
+
return value.replace(/[&<>"']/g, (char) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[char] ?? char);
|
|
299
|
+
}
|
|
300
|
+
function readBody(request) {
|
|
301
|
+
return new Promise((resolve, reject) => {
|
|
302
|
+
const chunks = [];
|
|
303
|
+
request.on("data", (chunk) => {
|
|
304
|
+
chunks.push(Buffer.from(chunk));
|
|
305
|
+
});
|
|
306
|
+
request.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
|
|
307
|
+
request.on("error", reject);
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// src/log.ts
|
|
312
|
+
var PREFIX = "[dsh-acp]";
|
|
313
|
+
function logWarn(message) {
|
|
314
|
+
process.stderr.write(`${PREFIX} warn: ${message}
|
|
315
|
+
`);
|
|
316
|
+
}
|
|
317
|
+
function logDebug(message) {
|
|
318
|
+
if (process.env["DSH_ACP_DEBUG"]) process.stderr.write(`${PREFIX} debug: ${message}
|
|
319
|
+
`);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// src/bridge/translate.ts
|
|
323
|
+
var MAX_TITLE_LENGTH = 80;
|
|
324
|
+
var MAX_RESULT_TEXT = 2e4;
|
|
325
|
+
function firstLine(text, limit = MAX_TITLE_LENGTH) {
|
|
326
|
+
const line = text.split("\n", 1)[0] ?? "";
|
|
327
|
+
return line.length > limit ? `${line.slice(0, limit - 1)}\u2026` : line;
|
|
328
|
+
}
|
|
329
|
+
function asString(value) {
|
|
330
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
331
|
+
}
|
|
332
|
+
function pathLocations(args) {
|
|
333
|
+
const locations = [];
|
|
334
|
+
for (const key of ["path", "file_path", "filePath", "file"]) {
|
|
335
|
+
const path = asString(args[key]);
|
|
336
|
+
if (path !== void 0) {
|
|
337
|
+
locations.push({ path });
|
|
338
|
+
break;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return locations;
|
|
342
|
+
}
|
|
343
|
+
function classifyToolCall(name4, rawArguments) {
|
|
344
|
+
let args = {};
|
|
345
|
+
try {
|
|
346
|
+
const parsed = JSON.parse(rawArguments);
|
|
347
|
+
if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
348
|
+
args = parsed;
|
|
349
|
+
}
|
|
350
|
+
} catch {
|
|
351
|
+
args = { arguments: rawArguments };
|
|
352
|
+
}
|
|
353
|
+
const locations = pathLocations(args);
|
|
354
|
+
const path = locations[0]?.path;
|
|
355
|
+
const facts = (kind, title) => ({
|
|
356
|
+
kind,
|
|
357
|
+
title,
|
|
358
|
+
locations,
|
|
359
|
+
rawInput: args
|
|
360
|
+
});
|
|
361
|
+
switch (name4) {
|
|
362
|
+
case "bash": {
|
|
363
|
+
const command = asString(args["command"]);
|
|
364
|
+
const restart = args["restart"] === true;
|
|
365
|
+
return facts("execute", command !== void 0 ? firstLine(command) : restart ? "Restart bash" : "bash");
|
|
366
|
+
}
|
|
367
|
+
case "read":
|
|
368
|
+
return facts("read", path !== void 0 ? `Read ${path}` : "Read file");
|
|
369
|
+
case "write":
|
|
370
|
+
return facts("edit", path !== void 0 ? `Write ${path}` : "Write file");
|
|
371
|
+
case "edit":
|
|
372
|
+
return facts("edit", path !== void 0 ? `Edit ${path}` : "Edit file");
|
|
373
|
+
case "str_replace_editor": {
|
|
374
|
+
const command = asString(args["command"]) ?? "edit";
|
|
375
|
+
const kind = command === "view" ? "read" : "edit";
|
|
376
|
+
return facts(kind, path !== void 0 ? `${command} ${path}` : command);
|
|
377
|
+
}
|
|
378
|
+
case "todo_write":
|
|
379
|
+
return facts("think", "Update plan");
|
|
380
|
+
case "subagent":
|
|
381
|
+
case "subagent_fork": {
|
|
382
|
+
const task = asString(args["task"]) ?? asString(args["prompt"]);
|
|
383
|
+
return facts("other", task !== void 0 ? `Subagent: ${firstLine(task, 60)}` : "Subagent");
|
|
384
|
+
}
|
|
385
|
+
case "skill": {
|
|
386
|
+
const skillName = asString(args["name"]) ?? asString(args["skill"]);
|
|
387
|
+
return facts("other", skillName !== void 0 ? `Skill: ${skillName}` : "Skill");
|
|
388
|
+
}
|
|
389
|
+
default:
|
|
390
|
+
break;
|
|
391
|
+
}
|
|
392
|
+
const lowered = name4.toLowerCase();
|
|
393
|
+
if (/(^|_)(grep|glob|find|search|ls|list)($|_)/.test(lowered)) {
|
|
394
|
+
const query = asString(args["query"]) ?? asString(args["pattern"]);
|
|
395
|
+
return facts("search", query !== void 0 ? `Search for '${firstLine(query, 50)}'` : name4);
|
|
396
|
+
}
|
|
397
|
+
if (/(fetch|web|http|browse)/.test(lowered)) {
|
|
398
|
+
const url = asString(args["url"]) ?? asString(args["query"]);
|
|
399
|
+
return facts("fetch", url !== void 0 ? `Fetch ${firstLine(url, 60)}` : name4);
|
|
400
|
+
}
|
|
401
|
+
if (/(job|task|run|exec)/.test(lowered)) return facts("execute", name4);
|
|
402
|
+
return facts("other", name4);
|
|
403
|
+
}
|
|
404
|
+
function diffsFromToolMeta(meta) {
|
|
405
|
+
if (meta === null || typeof meta !== "object") return [];
|
|
406
|
+
const diffs = meta["diffs"];
|
|
407
|
+
if (!Array.isArray(diffs)) return [];
|
|
408
|
+
const result = [];
|
|
409
|
+
for (const entry of diffs) {
|
|
410
|
+
if (entry === null || typeof entry !== "object") continue;
|
|
411
|
+
const diff = entry;
|
|
412
|
+
const path = asString(diff["path"]);
|
|
413
|
+
const newText = diff["newText"];
|
|
414
|
+
const oldText = diff["oldText"];
|
|
415
|
+
if (path === void 0 || typeof newText !== "string") continue;
|
|
416
|
+
result.push({ path, oldText: typeof oldText === "string" ? oldText : null, newText });
|
|
417
|
+
}
|
|
418
|
+
return result;
|
|
419
|
+
}
|
|
420
|
+
function extractToolResult(data) {
|
|
421
|
+
let failed = data["error"] !== void 0 && data["error"] !== null;
|
|
422
|
+
const parts = [];
|
|
423
|
+
const message = data["message"];
|
|
424
|
+
const content = message !== null && typeof message === "object" ? message["content"] : void 0;
|
|
425
|
+
if (Array.isArray(content)) {
|
|
426
|
+
for (const block of content) {
|
|
427
|
+
if (block === null || typeof block !== "object") continue;
|
|
428
|
+
const b = block;
|
|
429
|
+
if (b["type"] !== "tool-result") continue;
|
|
430
|
+
if (b["isError"] === true) failed = true;
|
|
431
|
+
const inner = b["content"];
|
|
432
|
+
if (!Array.isArray(inner)) continue;
|
|
433
|
+
for (const innerBlock of inner) {
|
|
434
|
+
if (innerBlock === null || typeof innerBlock !== "object") continue;
|
|
435
|
+
const ib = innerBlock;
|
|
436
|
+
if (ib["type"] === "text" && typeof ib["text"] === "string") parts.push(ib["text"]);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
let text = parts.join("");
|
|
441
|
+
if (text.length > MAX_RESULT_TEXT) text = `${text.slice(0, MAX_RESULT_TEXT)}
|
|
442
|
+
\u2026 (truncated)`;
|
|
443
|
+
return { failed, text, diffs: diffsFromToolMeta(data["meta"]) };
|
|
444
|
+
}
|
|
445
|
+
function extractMessageTexts(message) {
|
|
446
|
+
const texts = [];
|
|
447
|
+
const reasoning = [];
|
|
448
|
+
const content = message !== null && typeof message === "object" ? message["content"] : void 0;
|
|
449
|
+
if (Array.isArray(content)) {
|
|
450
|
+
for (const block of content) {
|
|
451
|
+
if (block === null || typeof block !== "object") continue;
|
|
452
|
+
const b = block;
|
|
453
|
+
if (b["type"] === "text" && typeof b["text"] === "string") texts.push(b["text"]);
|
|
454
|
+
else if (b["type"] === "reasoning" && typeof b["text"] === "string") reasoning.push(b["text"]);
|
|
455
|
+
else if (b["type"] === "image") texts.push("[image attachment]");
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
return { text: texts.join(""), reasoning: reasoning.join("") };
|
|
459
|
+
}
|
|
460
|
+
function planEntries(data) {
|
|
461
|
+
const todos = data["todos"];
|
|
462
|
+
if (!Array.isArray(todos)) return void 0;
|
|
463
|
+
const entries = [];
|
|
464
|
+
for (const todo of todos) {
|
|
465
|
+
if (todo === null || typeof todo !== "object") continue;
|
|
466
|
+
const t = todo;
|
|
467
|
+
const content = asString(t["content"]);
|
|
468
|
+
const status = t["status"];
|
|
469
|
+
if (content === void 0) continue;
|
|
470
|
+
entries.push({
|
|
471
|
+
content,
|
|
472
|
+
priority: "medium",
|
|
473
|
+
status: status === "in_progress" || status === "completed" ? status : "pending"
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
return entries;
|
|
477
|
+
}
|
|
478
|
+
function turnEndToStopReason(reason) {
|
|
479
|
+
switch (reason?.kind) {
|
|
480
|
+
case "completed":
|
|
481
|
+
return "end_turn";
|
|
482
|
+
case "max-tokens":
|
|
483
|
+
return "max_tokens";
|
|
484
|
+
case "blocked":
|
|
485
|
+
return "refusal";
|
|
486
|
+
case "interrupted":
|
|
487
|
+
return "cancelled";
|
|
488
|
+
case "aborted": {
|
|
489
|
+
const cause = reason.reason?.kind;
|
|
490
|
+
return cause === "user" || cause === "disposed" ? "cancelled" : "end_turn";
|
|
491
|
+
}
|
|
492
|
+
default:
|
|
493
|
+
return "end_turn";
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
function emptyWindow() {
|
|
497
|
+
return {
|
|
498
|
+
usage: { input: 0, output: 0, cachedRead: 0, cachedWrite: 0, thought: 0 },
|
|
499
|
+
sawUsage: false,
|
|
500
|
+
turnEnds: /* @__PURE__ */ new Map(),
|
|
501
|
+
lastTurnEnd: void 0,
|
|
502
|
+
error: void 0
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
var TOOL_KINDS = /* @__PURE__ */ new Set([
|
|
506
|
+
"read",
|
|
507
|
+
"edit",
|
|
508
|
+
"delete",
|
|
509
|
+
"move",
|
|
510
|
+
"search",
|
|
511
|
+
"execute",
|
|
512
|
+
"think",
|
|
513
|
+
"fetch",
|
|
514
|
+
"switch_mode",
|
|
515
|
+
"other"
|
|
516
|
+
]);
|
|
517
|
+
function factsFromPresentCall(name4, rawArguments, presented) {
|
|
518
|
+
const fallback = classifyToolCall(name4, rawArguments);
|
|
519
|
+
const kind = presented.kind !== void 0 && TOOL_KINDS.has(presented.kind) ? presented.kind : fallback.kind;
|
|
520
|
+
const title = presented.title !== void 0 && presented.title.length > 0 ? presented.title : fallback.title;
|
|
521
|
+
const locations = presented.locations !== void 0 && presented.locations.length > 0 ? presented.locations.map(
|
|
522
|
+
(location) => location.line === void 0 ? { path: location.path } : { path: location.path, line: location.line }
|
|
523
|
+
) : fallback.locations;
|
|
524
|
+
return { kind, title, locations, rawInput: fallback.rawInput };
|
|
525
|
+
}
|
|
526
|
+
var SessionProjection = class {
|
|
527
|
+
/** Context capacity from the latest `request/context` event, when known. */
|
|
528
|
+
contextWindow;
|
|
529
|
+
/** Latest `session/title` text observed on the log. */
|
|
530
|
+
title;
|
|
531
|
+
streamedText = /* @__PURE__ */ new Set();
|
|
532
|
+
streamedReasoning = /* @__PURE__ */ new Set();
|
|
533
|
+
toolCalls = /* @__PURE__ */ new Map();
|
|
534
|
+
window = emptyWindow();
|
|
535
|
+
lastContextUse;
|
|
536
|
+
/** Client renders `_meta.terminal_output` display terminals (Zed extension). */
|
|
537
|
+
terminalOutput;
|
|
538
|
+
/** Session working directory, advertised on display terminals. */
|
|
539
|
+
cwd;
|
|
540
|
+
presentCall;
|
|
541
|
+
subagent;
|
|
542
|
+
constructor(contextWindow, options) {
|
|
543
|
+
this.contextWindow = contextWindow;
|
|
544
|
+
this.terminalOutput = options?.terminalOutput ?? false;
|
|
545
|
+
this.cwd = options?.cwd;
|
|
546
|
+
this.presentCall = options?.presentCall;
|
|
547
|
+
this.subagent = options?.subagent;
|
|
548
|
+
}
|
|
549
|
+
/** Reset per-prompt accumulators; call when a new `session/prompt` starts. */
|
|
550
|
+
beginPrompt() {
|
|
551
|
+
this.window = emptyWindow();
|
|
552
|
+
}
|
|
553
|
+
/** The captured ending for a specific turn, when it already ended. */
|
|
554
|
+
turnEndFor(turn) {
|
|
555
|
+
return this.window.turnEnds.get(turn);
|
|
556
|
+
}
|
|
557
|
+
/** The last turn ending inside the current prompt window. */
|
|
558
|
+
get lastTurnEnd() {
|
|
559
|
+
return this.window.lastTurnEnd;
|
|
560
|
+
}
|
|
561
|
+
/** The first failed-turn error inside the current prompt window. */
|
|
562
|
+
get turnError() {
|
|
563
|
+
return this.window.error;
|
|
564
|
+
}
|
|
565
|
+
/** Accumulated ACP usage for the current prompt window. */
|
|
566
|
+
promptUsage() {
|
|
567
|
+
if (!this.window.sawUsage) return void 0;
|
|
568
|
+
const { input, output, cachedRead, cachedWrite, thought } = this.window.usage;
|
|
569
|
+
return {
|
|
570
|
+
totalTokens: input + output + cachedRead + cachedWrite,
|
|
571
|
+
inputTokens: input,
|
|
572
|
+
outputTokens: output,
|
|
573
|
+
...thought > 0 ? { thoughtTokens: thought } : {},
|
|
574
|
+
...cachedRead > 0 ? { cachedReadTokens: cachedRead } : {},
|
|
575
|
+
...cachedWrite > 0 ? { cachedWriteTokens: cachedWrite } : {}
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* Project one session event onto zero or more ACP updates.
|
|
580
|
+
*/
|
|
581
|
+
onEvent(event) {
|
|
582
|
+
const updates = this.projectEvent(event);
|
|
583
|
+
return this.subagent === void 0 ? updates : updates.map((update) => this.withSubagentAttribution(update));
|
|
584
|
+
}
|
|
585
|
+
/** Project one event before optional subagent attribution is attached. */
|
|
586
|
+
projectEvent(event) {
|
|
587
|
+
const data = event.data ?? {};
|
|
588
|
+
switch (event.type) {
|
|
589
|
+
case "assistant/chunk":
|
|
590
|
+
return this.onChunk(data);
|
|
591
|
+
case "assistant/message":
|
|
592
|
+
return this.onAssistantMessage(data);
|
|
593
|
+
case "tool/call":
|
|
594
|
+
return this.onToolCall(data);
|
|
595
|
+
case "tool/result":
|
|
596
|
+
return this.onToolResult(data);
|
|
597
|
+
case "subagent/start":
|
|
598
|
+
return this.onSubagentStart(data);
|
|
599
|
+
case "subagent/end":
|
|
600
|
+
return this.onSubagentEnd(data);
|
|
601
|
+
case "todo/write": {
|
|
602
|
+
const entries = planEntries(data);
|
|
603
|
+
return entries !== void 0 ? [{ sessionUpdate: "plan", entries }] : [];
|
|
604
|
+
}
|
|
605
|
+
case "user/message": {
|
|
606
|
+
const source = data["source"];
|
|
607
|
+
const kind = source !== null && typeof source === "object" ? asString(source["kind"]) : void 0;
|
|
608
|
+
if (kind === void 0 || kind === "user") return [];
|
|
609
|
+
const content = data["content"];
|
|
610
|
+
const text = Array.isArray(content) ? content.filter(
|
|
611
|
+
(block) => block !== null && typeof block === "object" && block["type"] === "text" && typeof block["text"] === "string"
|
|
612
|
+
).map((block) => block["text"]).join("") : "";
|
|
613
|
+
return [
|
|
614
|
+
{
|
|
615
|
+
sessionUpdate: "session_info_update",
|
|
616
|
+
_meta: {
|
|
617
|
+
dsh: {
|
|
618
|
+
event: "user/message",
|
|
619
|
+
source: kind,
|
|
620
|
+
preview: Array.from(text).slice(0, 160).join("")
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
];
|
|
625
|
+
}
|
|
626
|
+
case "turn/end":
|
|
627
|
+
return this.onTurnEnd(data);
|
|
628
|
+
case "request/context": {
|
|
629
|
+
const contextWindow = data["contextWindow"];
|
|
630
|
+
if (typeof contextWindow === "number" && contextWindow > 0) this.contextWindow = contextWindow;
|
|
631
|
+
return [];
|
|
632
|
+
}
|
|
633
|
+
case "session/title": {
|
|
634
|
+
const title = asString(data["title"]);
|
|
635
|
+
if (title === void 0 || title === this.title) return [];
|
|
636
|
+
this.title = title;
|
|
637
|
+
return [{ sessionUpdate: "session_info_update", title, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }];
|
|
638
|
+
}
|
|
639
|
+
default:
|
|
640
|
+
return [];
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
stepKey(data) {
|
|
644
|
+
return `${String(data["turn"])}:${String(data["step"])}`;
|
|
645
|
+
}
|
|
646
|
+
withSubagentAttribution(update) {
|
|
647
|
+
const value = update;
|
|
648
|
+
const meta = value["_meta"] !== null && typeof value["_meta"] === "object" ? value["_meta"] : {};
|
|
649
|
+
const dsh = meta["dsh"] !== null && typeof meta["dsh"] === "object" ? meta["dsh"] : {};
|
|
650
|
+
return {
|
|
651
|
+
...value,
|
|
652
|
+
_meta: { ...meta, dsh: { ...dsh, subagent: this.subagent } }
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
subagentMeta(data, state) {
|
|
656
|
+
const runId = asString(data["runId"]);
|
|
657
|
+
const childSessionId = asString(data["id"]);
|
|
658
|
+
const provider = asString(data["provider"]);
|
|
659
|
+
if (runId === void 0 || childSessionId === void 0 || provider === void 0) return void 0;
|
|
660
|
+
const parentToolCallId = asString(data["parentToolCallId"]);
|
|
661
|
+
return {
|
|
662
|
+
state,
|
|
663
|
+
runId,
|
|
664
|
+
childSessionId,
|
|
665
|
+
provider,
|
|
666
|
+
local: data["local"] === true,
|
|
667
|
+
...parentToolCallId !== void 0 ? { parentToolCallId } : {}
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
onSubagentStart(data) {
|
|
671
|
+
const subagent = this.subagentMeta(data, "started");
|
|
672
|
+
if (subagent === void 0) return [];
|
|
673
|
+
const runId = subagent["runId"];
|
|
674
|
+
const childSessionId = subagent["childSessionId"];
|
|
675
|
+
return [{
|
|
676
|
+
sessionUpdate: "tool_call",
|
|
677
|
+
toolCallId: `subagent:${runId}`,
|
|
678
|
+
title: `Start subagent ${childSessionId}`,
|
|
679
|
+
kind: "other",
|
|
680
|
+
status: "in_progress",
|
|
681
|
+
rawInput: {
|
|
682
|
+
childSessionId,
|
|
683
|
+
provider: subagent["provider"],
|
|
684
|
+
local: subagent["local"]
|
|
685
|
+
},
|
|
686
|
+
_meta: { dsh: { subagent } }
|
|
687
|
+
}];
|
|
688
|
+
}
|
|
689
|
+
onSubagentEnd(data) {
|
|
690
|
+
const subagent = this.subagentMeta(data, "finished");
|
|
691
|
+
if (subagent === void 0) return [];
|
|
692
|
+
const stopReason = asString(data["stopReason"]) ?? "error";
|
|
693
|
+
const lastAssistantMessage = data["lastAssistantMessage"];
|
|
694
|
+
return [{
|
|
695
|
+
sessionUpdate: "tool_call_update",
|
|
696
|
+
toolCallId: `subagent:${String(subagent["runId"])}`,
|
|
697
|
+
status: stopReason === "completed" ? "completed" : "failed",
|
|
698
|
+
rawOutput: {
|
|
699
|
+
stopReason,
|
|
700
|
+
...Array.isArray(lastAssistantMessage) ? { lastAssistantMessage } : {}
|
|
701
|
+
},
|
|
702
|
+
_meta: { dsh: { subagent } }
|
|
703
|
+
}];
|
|
704
|
+
}
|
|
705
|
+
onChunk(data) {
|
|
706
|
+
const chunk = data["chunk"];
|
|
707
|
+
if (chunk === null || typeof chunk === "undefined" || typeof chunk !== "object") return [];
|
|
708
|
+
const c = chunk;
|
|
709
|
+
const text = c["text"];
|
|
710
|
+
if (typeof text !== "string" || text.length === 0) return [];
|
|
711
|
+
const messageId = this.stepKey(data);
|
|
712
|
+
if (c["type"] === "text-delta") {
|
|
713
|
+
this.streamedText.add(messageId);
|
|
714
|
+
return [{ sessionUpdate: "agent_message_chunk", content: { type: "text", text }, messageId }];
|
|
715
|
+
}
|
|
716
|
+
if (c["type"] === "reasoning-delta") {
|
|
717
|
+
this.streamedReasoning.add(messageId);
|
|
718
|
+
return [{ sessionUpdate: "agent_thought_chunk", content: { type: "text", text }, messageId }];
|
|
719
|
+
}
|
|
720
|
+
return [];
|
|
721
|
+
}
|
|
722
|
+
onAssistantMessage(data) {
|
|
723
|
+
const updates = [];
|
|
724
|
+
const key = this.stepKey(data);
|
|
725
|
+
const message = data["message"];
|
|
726
|
+
const { text, reasoning } = extractMessageTexts(message);
|
|
727
|
+
const model = message !== null && typeof message === "object" ? asString(
|
|
728
|
+
message["source"] !== null && typeof message["source"] === "object" ? message["source"]["model"] : void 0
|
|
729
|
+
) : void 0;
|
|
730
|
+
const completionMeta = {
|
|
731
|
+
dsh: {
|
|
732
|
+
event: "assistant_message",
|
|
733
|
+
...model !== void 0 ? { model } : {}
|
|
734
|
+
}
|
|
735
|
+
};
|
|
736
|
+
if (reasoning.length > 0 && !this.streamedReasoning.has(key)) {
|
|
737
|
+
updates.push({
|
|
738
|
+
sessionUpdate: "agent_thought_chunk",
|
|
739
|
+
content: { type: "text", text: reasoning },
|
|
740
|
+
messageId: key
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
if (text.length > 0 && !this.streamedText.has(key)) {
|
|
744
|
+
updates.push({
|
|
745
|
+
sessionUpdate: "agent_message_chunk",
|
|
746
|
+
content: { type: "text", text },
|
|
747
|
+
messageId: key,
|
|
748
|
+
_meta: completionMeta
|
|
749
|
+
});
|
|
750
|
+
} else {
|
|
751
|
+
updates.push({
|
|
752
|
+
sessionUpdate: "agent_message_chunk",
|
|
753
|
+
content: { type: "text", text: "" },
|
|
754
|
+
messageId: key,
|
|
755
|
+
_meta: completionMeta
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
this.streamedText.delete(key);
|
|
759
|
+
this.streamedReasoning.delete(key);
|
|
760
|
+
const usage = data["usage"];
|
|
761
|
+
if (usage !== null && typeof usage === "object") {
|
|
762
|
+
const u = usage;
|
|
763
|
+
const num = (v) => typeof v === "number" && Number.isFinite(v) && v > 0 ? v : 0;
|
|
764
|
+
const input = num(u["inputTokens"]);
|
|
765
|
+
const output = num(u["outputTokens"]);
|
|
766
|
+
const cachedRead = num(u["cacheReadTokens"]);
|
|
767
|
+
const cachedWrite = num(u["cacheWriteTokens"]);
|
|
768
|
+
this.window.usage.input += input;
|
|
769
|
+
this.window.usage.output += output;
|
|
770
|
+
this.window.usage.cachedRead += cachedRead;
|
|
771
|
+
this.window.usage.cachedWrite += cachedWrite;
|
|
772
|
+
this.window.usage.thought += num(u["reasoningTokens"]);
|
|
773
|
+
this.window.sawUsage = true;
|
|
774
|
+
this.lastContextUse = input + cachedRead + cachedWrite + output;
|
|
775
|
+
if (this.contextWindow !== void 0 && this.lastContextUse > 0) {
|
|
776
|
+
updates.push({
|
|
777
|
+
sessionUpdate: "usage_update",
|
|
778
|
+
used: this.lastContextUse,
|
|
779
|
+
size: this.contextWindow
|
|
780
|
+
});
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
return updates;
|
|
784
|
+
}
|
|
785
|
+
onToolCall(data) {
|
|
786
|
+
const callId = asString(data["callId"]);
|
|
787
|
+
const name4 = asString(data["name"]) ?? "tool";
|
|
788
|
+
if (callId === void 0) return [];
|
|
789
|
+
const rawArguments = typeof data["arguments"] === "string" ? data["arguments"] : "{}";
|
|
790
|
+
let parsedArgs = {};
|
|
791
|
+
try {
|
|
792
|
+
parsedArgs = JSON.parse(rawArguments);
|
|
793
|
+
} catch {
|
|
794
|
+
parsedArgs = { arguments: rawArguments };
|
|
795
|
+
}
|
|
796
|
+
const presented = this.presentCall?.(name4, parsedArgs);
|
|
797
|
+
const facts = presented !== void 0 ? factsFromPresentCall(name4, rawArguments, presented) : classifyToolCall(name4, rawArguments);
|
|
798
|
+
this.toolCalls.set(callId, facts);
|
|
799
|
+
const displayTerminal = this.terminalOutput && facts.kind === "execute";
|
|
800
|
+
return [
|
|
801
|
+
{
|
|
802
|
+
sessionUpdate: "tool_call",
|
|
803
|
+
toolCallId: callId,
|
|
804
|
+
title: facts.title,
|
|
805
|
+
name: name4,
|
|
806
|
+
kind: facts.kind,
|
|
807
|
+
status: "in_progress",
|
|
808
|
+
rawInput: facts.rawInput,
|
|
809
|
+
...facts.locations.length > 0 ? { locations: facts.locations } : {},
|
|
810
|
+
...displayTerminal ? {
|
|
811
|
+
content: [{ type: "terminal", terminalId: callId }],
|
|
812
|
+
_meta: {
|
|
813
|
+
terminal_info: {
|
|
814
|
+
terminal_id: callId,
|
|
815
|
+
...this.cwd !== void 0 ? { cwd: this.cwd } : {}
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
} : {}
|
|
819
|
+
}
|
|
820
|
+
];
|
|
821
|
+
}
|
|
822
|
+
onToolResult(data) {
|
|
823
|
+
const message = data["message"];
|
|
824
|
+
const callId = asString(
|
|
825
|
+
message !== null && typeof message === "object" ? (() => {
|
|
826
|
+
const content2 = message["content"];
|
|
827
|
+
if (!Array.isArray(content2)) return void 0;
|
|
828
|
+
const block = content2.find(
|
|
829
|
+
(b) => b !== null && typeof b === "object" && b["type"] === "tool-result"
|
|
830
|
+
);
|
|
831
|
+
return block === void 0 ? void 0 : block["toolCallId"];
|
|
832
|
+
})() : void 0
|
|
833
|
+
) ?? asString(data["callId"]);
|
|
834
|
+
if (callId === void 0) return [];
|
|
835
|
+
const facts = this.toolCalls.get(callId);
|
|
836
|
+
const { failed, text, diffs } = extractToolResult(data);
|
|
837
|
+
const content = diffs.map((diff) => ({
|
|
838
|
+
type: "diff",
|
|
839
|
+
path: diff.path,
|
|
840
|
+
...diff.oldText !== null ? { oldText: diff.oldText } : {},
|
|
841
|
+
newText: diff.newText
|
|
842
|
+
}));
|
|
843
|
+
const isCommand = facts?.kind === "execute";
|
|
844
|
+
if (this.terminalOutput && isCommand) {
|
|
845
|
+
this.toolCalls.delete(callId);
|
|
846
|
+
return [
|
|
847
|
+
...text.length > 0 ? [
|
|
848
|
+
{
|
|
849
|
+
sessionUpdate: "tool_call_update",
|
|
850
|
+
toolCallId: callId,
|
|
851
|
+
_meta: { terminal_output: { terminal_id: callId, data: text } }
|
|
852
|
+
}
|
|
853
|
+
] : [],
|
|
854
|
+
{
|
|
855
|
+
sessionUpdate: "tool_call_update",
|
|
856
|
+
toolCallId: callId,
|
|
857
|
+
status: failed ? "failed" : "completed",
|
|
858
|
+
...text.length > 0 ? { rawOutput: { output: text, isError: failed } } : {},
|
|
859
|
+
_meta: {
|
|
860
|
+
terminal_exit: { terminal_id: callId, exit_code: failed ? 1 : 0, signal: null }
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
];
|
|
864
|
+
}
|
|
865
|
+
if (text.length > 0 && diffs.length === 0) {
|
|
866
|
+
const block = {
|
|
867
|
+
type: "text",
|
|
868
|
+
text: isCommand ? `\`\`\`sh
|
|
869
|
+
${text.replace(/\n+$/, "")}
|
|
870
|
+
\`\`\`
|
|
871
|
+
` : text
|
|
872
|
+
};
|
|
873
|
+
content.push({ type: "content", content: block });
|
|
874
|
+
}
|
|
875
|
+
this.toolCalls.delete(callId);
|
|
876
|
+
return [
|
|
877
|
+
{
|
|
878
|
+
sessionUpdate: "tool_call_update",
|
|
879
|
+
toolCallId: callId,
|
|
880
|
+
status: failed ? "failed" : "completed",
|
|
881
|
+
...content.length > 0 ? { content } : {},
|
|
882
|
+
...text.length > 0 ? { rawOutput: { output: text, isError: failed } } : {}
|
|
883
|
+
}
|
|
884
|
+
];
|
|
885
|
+
}
|
|
886
|
+
onTurnEnd(data) {
|
|
887
|
+
const reason = data["reason"];
|
|
888
|
+
const turn = data["turn"];
|
|
889
|
+
if (reason !== null && typeof reason === "object") {
|
|
890
|
+
const r = reason;
|
|
891
|
+
if (typeof turn === "number") this.window.turnEnds.set(turn, r);
|
|
892
|
+
this.window.lastTurnEnd = r;
|
|
893
|
+
if (r.kind === "error" && this.window.error === void 0) {
|
|
894
|
+
this.window.error = { message: r.error?.message ?? "model turn failed" };
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
return [];
|
|
898
|
+
}
|
|
899
|
+
};
|
|
900
|
+
|
|
901
|
+
// src/bridge/history.ts
|
|
902
|
+
function userMessage(data) {
|
|
903
|
+
if (data === void 0) return void 0;
|
|
904
|
+
const source = data["source"];
|
|
905
|
+
const kind = source !== null && typeof source === "object" ? source["kind"] : void 0;
|
|
906
|
+
if (kind !== "user") return void 0;
|
|
907
|
+
const content = data["content"];
|
|
908
|
+
if (!Array.isArray(content)) return void 0;
|
|
909
|
+
const parts = [];
|
|
910
|
+
for (const block of content) {
|
|
911
|
+
if (block !== null && typeof block === "object") {
|
|
912
|
+
const b = block;
|
|
913
|
+
if (b["type"] === "text" && typeof b["text"] === "string") parts.push(b["text"]);
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
const text = parts.join("");
|
|
917
|
+
return text.length > 0 ? { text } : {};
|
|
918
|
+
}
|
|
919
|
+
function buildReplay(events) {
|
|
920
|
+
const projection = new SessionProjection();
|
|
921
|
+
const updates = [];
|
|
922
|
+
let plan;
|
|
923
|
+
let title;
|
|
924
|
+
let sawUserInteraction = false;
|
|
925
|
+
for (const event of events) {
|
|
926
|
+
if (event.type === "user/message") {
|
|
927
|
+
const user = userMessage(event.data);
|
|
928
|
+
if (user !== void 0) {
|
|
929
|
+
projection.beginPrompt();
|
|
930
|
+
sawUserInteraction = true;
|
|
931
|
+
if (user.text !== void 0) {
|
|
932
|
+
updates.push({
|
|
933
|
+
sessionUpdate: "user_message_chunk",
|
|
934
|
+
content: { type: "text", text: user.text }
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
continue;
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
if (event.type === "session/title") {
|
|
941
|
+
const t = event.data?.["title"];
|
|
942
|
+
if (typeof t === "string" && t.length > 0) title = t;
|
|
943
|
+
continue;
|
|
944
|
+
}
|
|
945
|
+
for (const update of projection.onEvent(event)) {
|
|
946
|
+
if (update.sessionUpdate === "usage_update") continue;
|
|
947
|
+
if (update.sessionUpdate === "plan") {
|
|
948
|
+
plan = update;
|
|
949
|
+
continue;
|
|
950
|
+
}
|
|
951
|
+
updates.push(update);
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
if (plan !== void 0) updates.push(plan);
|
|
955
|
+
const usage = sawUserInteraction ? projection.promptUsage() : void 0;
|
|
956
|
+
if (usage !== void 0) {
|
|
957
|
+
updates.push({
|
|
958
|
+
sessionUpdate: "session_info_update",
|
|
959
|
+
_meta: { dsh: { event: "prompt/usage", usage } }
|
|
960
|
+
});
|
|
961
|
+
}
|
|
962
|
+
return { updates, title, contextWindow: projection.contextWindow };
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
// src/bridge/prompt.ts
|
|
966
|
+
function deliverPrompt(target, message, running) {
|
|
967
|
+
if (running) {
|
|
968
|
+
target.steer(message);
|
|
969
|
+
return "steer";
|
|
970
|
+
}
|
|
971
|
+
target.followup(message);
|
|
972
|
+
return "followup";
|
|
973
|
+
}
|
|
974
|
+
var UnsupportedPromptContentError = class extends Error {
|
|
975
|
+
constructor(contentType) {
|
|
976
|
+
super(`unsupported prompt content type: ${contentType}`);
|
|
977
|
+
this.contentType = contentType;
|
|
978
|
+
this.name = "UnsupportedPromptContentError";
|
|
979
|
+
}
|
|
980
|
+
contentType;
|
|
981
|
+
};
|
|
982
|
+
var PromptImageError = class extends Error {
|
|
983
|
+
constructor(message, options) {
|
|
984
|
+
super(message, options);
|
|
985
|
+
this.name = "PromptImageError";
|
|
986
|
+
}
|
|
987
|
+
};
|
|
988
|
+
function contextBlock(uri, text) {
|
|
989
|
+
return `<context ref=${JSON.stringify(uri)}>
|
|
990
|
+
${text}
|
|
991
|
+
</context>`;
|
|
992
|
+
}
|
|
993
|
+
var IMAGE_MEDIA_TYPES = /* @__PURE__ */ new Set(["image/png", "image/jpeg", "image/webp", "image/gif"]);
|
|
994
|
+
function canonicalImageMediaType(mimeType) {
|
|
995
|
+
const lower = mimeType.trim().toLowerCase();
|
|
996
|
+
const mapped = lower === "image/jpg" ? "image/jpeg" : lower;
|
|
997
|
+
return IMAGE_MEDIA_TYPES.has(mapped) ? mapped : void 0;
|
|
998
|
+
}
|
|
999
|
+
function attachmentIngestOf(value) {
|
|
1000
|
+
if (value === null || typeof value !== "object") return void 0;
|
|
1001
|
+
const candidate = value;
|
|
1002
|
+
if (typeof candidate.validateImage !== "function" || typeof candidate.saveImage !== "function") {
|
|
1003
|
+
return void 0;
|
|
1004
|
+
}
|
|
1005
|
+
const limits = candidate.imageLimits;
|
|
1006
|
+
if (limits === void 0 || typeof limits.maxImagesPerMessage !== "number" || typeof limits.maxMessageImageBytes !== "number" || typeof limits.maxImageBytes !== "number") {
|
|
1007
|
+
return void 0;
|
|
1008
|
+
}
|
|
1009
|
+
return candidate;
|
|
1010
|
+
}
|
|
1011
|
+
function decodeImageData(data) {
|
|
1012
|
+
if (data.length === 0) throw new PromptImageError("image data is empty");
|
|
1013
|
+
const decoded = Buffer.from(data, "base64");
|
|
1014
|
+
if (decoded.byteLength === 0) throw new PromptImageError("image data is empty");
|
|
1015
|
+
return new Uint8Array(decoded);
|
|
1016
|
+
}
|
|
1017
|
+
function imageName(uri) {
|
|
1018
|
+
if (typeof uri !== "string" || uri.length === 0) return void 0;
|
|
1019
|
+
let leaf;
|
|
1020
|
+
try {
|
|
1021
|
+
leaf = new URL(uri).pathname.split("/").filter(Boolean).at(-1);
|
|
1022
|
+
} catch {
|
|
1023
|
+
leaf = uri.split(/[/\\]/).filter(Boolean).at(-1);
|
|
1024
|
+
}
|
|
1025
|
+
if (leaf === void 0 || leaf.length === 0) return void 0;
|
|
1026
|
+
try {
|
|
1027
|
+
return decodeURIComponent(leaf);
|
|
1028
|
+
} catch {
|
|
1029
|
+
return leaf;
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
function prepareImage(block) {
|
|
1033
|
+
const mediaType = canonicalImageMediaType(block.mimeType);
|
|
1034
|
+
if (mediaType === void 0) {
|
|
1035
|
+
throw new PromptImageError(`unsupported image media type: ${block.mimeType}`);
|
|
1036
|
+
}
|
|
1037
|
+
const data = decodeImageData(block.data);
|
|
1038
|
+
const name4 = imageName(block.uri);
|
|
1039
|
+
return name4 === void 0 ? { data, mediaType } : { data, mediaType, name: name4 };
|
|
1040
|
+
}
|
|
1041
|
+
function saveInput(image) {
|
|
1042
|
+
return image.name === void 0 ? { data: image.data, mediaType: image.mediaType } : { data: image.data, mediaType: image.mediaType, name: image.name };
|
|
1043
|
+
}
|
|
1044
|
+
function flushText(parts, blocks) {
|
|
1045
|
+
const text = parts.join("");
|
|
1046
|
+
parts.length = 0;
|
|
1047
|
+
if (text.length > 0) blocks.push({ type: "text", text });
|
|
1048
|
+
}
|
|
1049
|
+
async function convertPrompt(prompt, attachments) {
|
|
1050
|
+
const preparedImages = [];
|
|
1051
|
+
for (const block of prompt) {
|
|
1052
|
+
if (block.type !== "image") continue;
|
|
1053
|
+
if (attachments === void 0) throw new UnsupportedPromptContentError("image");
|
|
1054
|
+
preparedImages.push(prepareImage(block));
|
|
1055
|
+
}
|
|
1056
|
+
if (preparedImages.length > 0 && attachments !== void 0) {
|
|
1057
|
+
const { maxImagesPerMessage, maxMessageImageBytes, maxImageBytes } = attachments.imageLimits;
|
|
1058
|
+
if (preparedImages.length > maxImagesPerMessage) {
|
|
1059
|
+
throw new PromptImageError("prompt exceeds the configured image-count limit");
|
|
1060
|
+
}
|
|
1061
|
+
const totalBytes = preparedImages.reduce((sum, image) => sum + image.data.byteLength, 0);
|
|
1062
|
+
if (totalBytes > maxMessageImageBytes) {
|
|
1063
|
+
throw new PromptImageError("prompt exceeds the configured aggregate image-byte limit");
|
|
1064
|
+
}
|
|
1065
|
+
for (const image of preparedImages) {
|
|
1066
|
+
if (image.data.byteLength > maxImageBytes) {
|
|
1067
|
+
throw new PromptImageError("image exceeds the configured encoded-byte limit");
|
|
1068
|
+
}
|
|
1069
|
+
try {
|
|
1070
|
+
await attachments.validateImage(saveInput(image));
|
|
1071
|
+
} catch (error) {
|
|
1072
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1073
|
+
throw new PromptImageError(message, { cause: error });
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
const parts = [];
|
|
1078
|
+
const display = [];
|
|
1079
|
+
const blocks = [];
|
|
1080
|
+
let imageIndex = 0;
|
|
1081
|
+
for (const block of prompt) {
|
|
1082
|
+
switch (block.type) {
|
|
1083
|
+
case "text":
|
|
1084
|
+
parts.push(block.text);
|
|
1085
|
+
display.push(block.text);
|
|
1086
|
+
break;
|
|
1087
|
+
case "resource_link":
|
|
1088
|
+
parts.push(`
|
|
1089
|
+
[resource_link name=${JSON.stringify(block.name)} uri=${JSON.stringify(block.uri)}]
|
|
1090
|
+
`);
|
|
1091
|
+
display.push(`@${block.name}`);
|
|
1092
|
+
break;
|
|
1093
|
+
case "resource": {
|
|
1094
|
+
const resource = block.resource;
|
|
1095
|
+
if ("text" in resource && typeof resource.text === "string") {
|
|
1096
|
+
parts.push(`
|
|
1097
|
+
${contextBlock(resource.uri, resource.text)}
|
|
1098
|
+
`);
|
|
1099
|
+
display.push(`@${resource.uri}`);
|
|
1100
|
+
break;
|
|
1101
|
+
}
|
|
1102
|
+
throw new UnsupportedPromptContentError("resource (binary)");
|
|
1103
|
+
}
|
|
1104
|
+
case "image": {
|
|
1105
|
+
if (attachments === void 0) throw new UnsupportedPromptContentError("image");
|
|
1106
|
+
const prepared = preparedImages[imageIndex];
|
|
1107
|
+
imageIndex += 1;
|
|
1108
|
+
if (prepared === void 0) throw new PromptImageError("image block was not prepared");
|
|
1109
|
+
flushText(parts, blocks);
|
|
1110
|
+
let attachment;
|
|
1111
|
+
try {
|
|
1112
|
+
attachment = await attachments.saveImage(saveInput(prepared));
|
|
1113
|
+
} catch (error) {
|
|
1114
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1115
|
+
throw new PromptImageError(message, { cause: error });
|
|
1116
|
+
}
|
|
1117
|
+
blocks.push({ type: "image", attachment });
|
|
1118
|
+
break;
|
|
1119
|
+
}
|
|
1120
|
+
default:
|
|
1121
|
+
throw new UnsupportedPromptContentError(block.type);
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
flushText(parts, blocks);
|
|
1125
|
+
return {
|
|
1126
|
+
blocks,
|
|
1127
|
+
displayText: display.join(" ").trim()
|
|
1128
|
+
};
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
// src/bridge/cordis-protocol.ts
|
|
1132
|
+
var CORDIS_PROTOCOL = 0;
|
|
1133
|
+
var CORDIS_CAPABILITY = Object.freeze({ protocol: CORDIS_PROTOCOL });
|
|
1134
|
+
var CORDIS_METHODS = Object.freeze({
|
|
1135
|
+
inspectSync: "_dsh/cordis/inspect/sync",
|
|
1136
|
+
inspectResolve: "_dsh/cordis/inspect/resolve",
|
|
1137
|
+
inspectQuery: "_dsh/cordis/inspect/query",
|
|
1138
|
+
inspectQueryResolved: "_dsh/cordis/inspect/query-resolved",
|
|
1139
|
+
runHost: "_dsh/cordis/run/host",
|
|
1140
|
+
getClientCode: "_dsh/cordis/run/client-code",
|
|
1141
|
+
resolveRequestRun: "_dsh/cordis/run/resolve",
|
|
1142
|
+
requestRun: "_dsh/cordis/run/request",
|
|
1143
|
+
requestRunResolved: "_dsh/cordis/run/request-resolved",
|
|
1144
|
+
userRun: "_dsh/cordis/run/user",
|
|
1145
|
+
settleUserRun: "_dsh/cordis/run/settle",
|
|
1146
|
+
pluginInvoke: "_dsh/cordis/plugin/invoke",
|
|
1147
|
+
pluginsList: "_dsh/cordis/plugins/list",
|
|
1148
|
+
pluginStart: "_dsh/cordis/plugins/start",
|
|
1149
|
+
pluginStop: "_dsh/cordis/plugins/stop",
|
|
1150
|
+
pluginRetract: "_dsh/cordis/plugins/retract"
|
|
1151
|
+
});
|
|
1152
|
+
function advertisesCordis(meta) {
|
|
1153
|
+
if (meta === null || typeof meta !== "object" || Array.isArray(meta)) return false;
|
|
1154
|
+
const dsh = meta["dsh"];
|
|
1155
|
+
if (dsh === null || typeof dsh !== "object" || Array.isArray(dsh)) return false;
|
|
1156
|
+
const cordis = dsh["cordis"];
|
|
1157
|
+
if (cordis === null || typeof cordis !== "object" || Array.isArray(cordis)) return false;
|
|
1158
|
+
return cordis["protocol"] === CORDIS_PROTOCOL;
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
// src/bridge/rpc.ts
|
|
1162
|
+
var AcpRpc = class {
|
|
1163
|
+
handlers = /* @__PURE__ */ new Map();
|
|
1164
|
+
writeJson;
|
|
1165
|
+
/**
|
|
1166
|
+
* Bind the mux writer. Called once from {@link muxAcpStream}.
|
|
1167
|
+
* @param writeJson - write one JSON-RPC message onto the ACP stream.
|
|
1168
|
+
*/
|
|
1169
|
+
attachWriter(writeJson) {
|
|
1170
|
+
this.writeJson = writeJson;
|
|
1171
|
+
}
|
|
1172
|
+
/**
|
|
1173
|
+
* Register a bridge-owned request method intercepted before the ACP SDK.
|
|
1174
|
+
* @param method - JSON-RPC extension method (e.g. `_dsh/cordis/inspect/sync`).
|
|
1175
|
+
* @param handler - implementation.
|
|
1176
|
+
* @returns disposer.
|
|
1177
|
+
*/
|
|
1178
|
+
registerMethod(method, handler) {
|
|
1179
|
+
if (!method.startsWith("_")) {
|
|
1180
|
+
throw new Error(`acpRpc: extension method ${JSON.stringify(method)} must start with "_"`);
|
|
1181
|
+
}
|
|
1182
|
+
if (this.handlers.has(method)) {
|
|
1183
|
+
throw new Error(`acpRpc: method ${JSON.stringify(method)} is already registered`);
|
|
1184
|
+
}
|
|
1185
|
+
this.handlers.set(method, handler);
|
|
1186
|
+
return () => {
|
|
1187
|
+
this.handlers.delete(method);
|
|
1188
|
+
};
|
|
1189
|
+
}
|
|
1190
|
+
/**
|
|
1191
|
+
* @param method - JSON-RPC method name.
|
|
1192
|
+
* @returns whether a TUI/extension handler owns this request.
|
|
1193
|
+
*/
|
|
1194
|
+
has(method) {
|
|
1195
|
+
return this.handlers.has(method);
|
|
1196
|
+
}
|
|
1197
|
+
/**
|
|
1198
|
+
* Dispatch one intercepted request and write the JSON-RPC response.
|
|
1199
|
+
* @param id - JSON-RPC id.
|
|
1200
|
+
* @param method - method name.
|
|
1201
|
+
* @param params - params object.
|
|
1202
|
+
*/
|
|
1203
|
+
async dispatch(id, method, params) {
|
|
1204
|
+
const handler = this.handlers.get(method);
|
|
1205
|
+
const write = this.writeJson;
|
|
1206
|
+
if (handler === void 0 || write === void 0) return;
|
|
1207
|
+
try {
|
|
1208
|
+
const result = await handler(params);
|
|
1209
|
+
write({ jsonrpc: "2.0", id, result: result ?? null });
|
|
1210
|
+
} catch (error) {
|
|
1211
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1212
|
+
write({ jsonrpc: "2.0", id, error: { code: -32e3, message } });
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
/**
|
|
1216
|
+
* Send a JSON-RPC extension notification on the ACP stream.
|
|
1217
|
+
* @param method - notification method.
|
|
1218
|
+
* @param params - payload.
|
|
1219
|
+
*/
|
|
1220
|
+
notify(method, params) {
|
|
1221
|
+
if (!method.startsWith("_")) {
|
|
1222
|
+
throw new Error(`acpRpc: extension notification ${JSON.stringify(method)} must start with "_"`);
|
|
1223
|
+
}
|
|
1224
|
+
const write = this.writeJson;
|
|
1225
|
+
if (write === void 0) return;
|
|
1226
|
+
write(params === void 0 ? { jsonrpc: "2.0", method } : { jsonrpc: "2.0", method, params });
|
|
1227
|
+
}
|
|
1228
|
+
};
|
|
1229
|
+
function isInterceptedRequest(message, rpc) {
|
|
1230
|
+
return "method" in message && typeof message.method === "string" && "id" in message && message.id !== void 0 && rpc.has(message.method);
|
|
1231
|
+
}
|
|
1232
|
+
function muxAcpStream(stream, rpc) {
|
|
1233
|
+
const dest = stream.writable.getWriter();
|
|
1234
|
+
const writeMessage = (value) => {
|
|
1235
|
+
void dest.write(value).catch(() => {
|
|
1236
|
+
});
|
|
1237
|
+
};
|
|
1238
|
+
rpc.attachWriter(writeMessage);
|
|
1239
|
+
const acpWritable = new WritableStream({
|
|
1240
|
+
write(chunk) {
|
|
1241
|
+
return dest.write(chunk);
|
|
1242
|
+
},
|
|
1243
|
+
close() {
|
|
1244
|
+
return dest.close();
|
|
1245
|
+
},
|
|
1246
|
+
abort(reason) {
|
|
1247
|
+
return dest.abort(reason);
|
|
1248
|
+
}
|
|
1249
|
+
});
|
|
1250
|
+
const { readable, writable } = new TransformStream({
|
|
1251
|
+
transform(message, controller) {
|
|
1252
|
+
if (isInterceptedRequest(message, rpc)) {
|
|
1253
|
+
void rpc.dispatch(message.id, message.method, message.params);
|
|
1254
|
+
return;
|
|
1255
|
+
}
|
|
1256
|
+
controller.enqueue(message);
|
|
1257
|
+
}
|
|
1258
|
+
});
|
|
1259
|
+
void stream.readable.pipeTo(writable).catch(() => {
|
|
1260
|
+
});
|
|
1261
|
+
return { readable, writable: acpWritable };
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
// src/bridge/tui-client.ts
|
|
1265
|
+
function installTuiClientPlane(ctx, rpc, opts) {
|
|
1266
|
+
const { findAgent, advertisement } = opts;
|
|
1267
|
+
ctx.effect(
|
|
1268
|
+
() => rpc.registerMethod(CORDIS_METHODS.inspectSync, (params) => {
|
|
1269
|
+
const inspect = optionalInspect(ctx);
|
|
1270
|
+
if (inspect === void 0) {
|
|
1271
|
+
throw new Error("this agent has no Cordis inspect registry");
|
|
1272
|
+
}
|
|
1273
|
+
const providers = readProviders(params);
|
|
1274
|
+
inspect.syncClientManifest(providers);
|
|
1275
|
+
return { ok: true };
|
|
1276
|
+
}),
|
|
1277
|
+
"acp-bridge: cordis/inspect-sync"
|
|
1278
|
+
);
|
|
1279
|
+
ctx.effect(
|
|
1280
|
+
() => rpc.registerMethod(CORDIS_METHODS.inspectResolve, (params) => {
|
|
1281
|
+
const inspect = optionalInspect(ctx);
|
|
1282
|
+
if (inspect === void 0) {
|
|
1283
|
+
throw new Error("this agent has no Cordis inspect registry");
|
|
1284
|
+
}
|
|
1285
|
+
const body = asObject(params);
|
|
1286
|
+
const agent = requireAgent(findAgent, readString(body, "agentId"));
|
|
1287
|
+
const requestId = readString(body, "requestId");
|
|
1288
|
+
return inspect.resolveClientQuery(agent, requestId, body["resolution"]);
|
|
1289
|
+
}),
|
|
1290
|
+
"acp-bridge: cordis/inspect-resolve"
|
|
1291
|
+
);
|
|
1292
|
+
ctx.effect(
|
|
1293
|
+
() => rpc.registerMethod(CORDIS_METHODS.runHost, async (params) => {
|
|
1294
|
+
const runner = optionalRunner(ctx);
|
|
1295
|
+
if (runner === void 0) {
|
|
1296
|
+
throw new Error("this agent has no dynamic Cordis runner");
|
|
1297
|
+
}
|
|
1298
|
+
const body = asObject(params);
|
|
1299
|
+
const agent = requireAgent(findAgent, readString(body, "agentId"));
|
|
1300
|
+
const requestId = body["requestId"];
|
|
1301
|
+
return await runner.runHostHalf(
|
|
1302
|
+
agent,
|
|
1303
|
+
readString(body, "pluginId"),
|
|
1304
|
+
readString(body, "packageId"),
|
|
1305
|
+
readString(body, "mode"),
|
|
1306
|
+
typeof requestId === "string" ? requestId : null,
|
|
1307
|
+
body["approveFutureVersions"] === true
|
|
1308
|
+
);
|
|
1309
|
+
}),
|
|
1310
|
+
"acp-bridge: cordis/run-host-half"
|
|
1311
|
+
);
|
|
1312
|
+
ctx.effect(
|
|
1313
|
+
() => rpc.registerMethod(CORDIS_METHODS.getClientCode, (params) => {
|
|
1314
|
+
const runner = optionalRunner(ctx);
|
|
1315
|
+
if (runner === void 0) {
|
|
1316
|
+
throw new Error("this agent has no dynamic Cordis runner");
|
|
1317
|
+
}
|
|
1318
|
+
const body = asObject(params);
|
|
1319
|
+
const agent = requireAgent(findAgent, readString(body, "agentId"));
|
|
1320
|
+
return runner.getClientCode(
|
|
1321
|
+
agent,
|
|
1322
|
+
readString(body, "pluginId"),
|
|
1323
|
+
readString(body, "pluginRunId")
|
|
1324
|
+
);
|
|
1325
|
+
}),
|
|
1326
|
+
"acp-bridge: cordis/get-client-code"
|
|
1327
|
+
);
|
|
1328
|
+
ctx.effect(
|
|
1329
|
+
() => rpc.registerMethod(CORDIS_METHODS.resolveRequestRun, async (params) => {
|
|
1330
|
+
const runner = optionalRunner(ctx);
|
|
1331
|
+
if (runner === void 0) {
|
|
1332
|
+
throw new Error("this agent has no dynamic Cordis runner");
|
|
1333
|
+
}
|
|
1334
|
+
const body = asObject(params);
|
|
1335
|
+
return await runner.resolveRequestRun(
|
|
1336
|
+
readString(body, "requestId"),
|
|
1337
|
+
body["resolution"]
|
|
1338
|
+
);
|
|
1339
|
+
}),
|
|
1340
|
+
"acp-bridge: cordis/resolve-request-run"
|
|
1341
|
+
);
|
|
1342
|
+
ctx.effect(
|
|
1343
|
+
() => rpc.registerMethod(CORDIS_METHODS.pluginInvoke, async (params) => {
|
|
1344
|
+
const runner = optionalRunner(ctx);
|
|
1345
|
+
if (runner === void 0) {
|
|
1346
|
+
throw new Error("this agent has no dynamic Cordis runner");
|
|
1347
|
+
}
|
|
1348
|
+
const body = asObject(params);
|
|
1349
|
+
requireAgent(findAgent, readString(body, "agentId"));
|
|
1350
|
+
return await runner.invoke(
|
|
1351
|
+
readString(body, "pluginId"),
|
|
1352
|
+
readString(body, "pluginRunId"),
|
|
1353
|
+
readString(body, "method"),
|
|
1354
|
+
body["args"] ?? null
|
|
1355
|
+
);
|
|
1356
|
+
}),
|
|
1357
|
+
"acp-bridge: cordis/invoke"
|
|
1358
|
+
);
|
|
1359
|
+
ctx.effect(
|
|
1360
|
+
() => rpc.registerMethod(CORDIS_METHODS.pluginsList, (params) => {
|
|
1361
|
+
const runner = requireRunnerMethod(ctx, "listPlugins");
|
|
1362
|
+
const body = asObject(params);
|
|
1363
|
+
const agent = requireAgent(findAgent, readString(body, "agentId"));
|
|
1364
|
+
return runner.listPlugins(agent);
|
|
1365
|
+
}),
|
|
1366
|
+
"acp-bridge: cordis/list-plugins"
|
|
1367
|
+
);
|
|
1368
|
+
ctx.effect(
|
|
1369
|
+
() => rpc.registerMethod(CORDIS_METHODS.pluginStop, async (params) => {
|
|
1370
|
+
const runner = requireRunnerMethod(ctx, "stopFromPanel");
|
|
1371
|
+
const body = asObject(params);
|
|
1372
|
+
const agent = requireAgent(findAgent, readString(body, "agentId"));
|
|
1373
|
+
return await runner.stopFromPanel(agent, readString(body, "pluginId"));
|
|
1374
|
+
}),
|
|
1375
|
+
"acp-bridge: cordis/stop-plugin"
|
|
1376
|
+
);
|
|
1377
|
+
ctx.effect(
|
|
1378
|
+
() => rpc.registerMethod(CORDIS_METHODS.pluginStart, async (params) => {
|
|
1379
|
+
const runner = requireRunnerMethod(ctx, "listPlugins");
|
|
1380
|
+
const body = asObject(params);
|
|
1381
|
+
const agentId = readString(body, "agentId");
|
|
1382
|
+
const agent = requireAgent(findAgent, agentId);
|
|
1383
|
+
const pluginId = readString(body, "pluginId");
|
|
1384
|
+
const plugin = runner.listPlugins(agent).find((row) => row.pluginId === pluginId);
|
|
1385
|
+
if (plugin === void 0) throw new Error(`dynamic plugin "${pluginId}" does not exist`);
|
|
1386
|
+
const packageId = plugin.currentPackageId;
|
|
1387
|
+
if (packageId === void 0) {
|
|
1388
|
+
throw new Error(`dynamic plugin "${pluginId}" has no current package to restore`);
|
|
1389
|
+
}
|
|
1390
|
+
const pkg = plugin.packages.find((row) => row.packageId === packageId);
|
|
1391
|
+
if (pkg === void 0) {
|
|
1392
|
+
throw new Error(`dynamic plugin "${pluginId}" current package "${packageId}" does not exist`);
|
|
1393
|
+
}
|
|
1394
|
+
const started = asObject(await runner.runHostHalf(
|
|
1395
|
+
agent,
|
|
1396
|
+
pluginId,
|
|
1397
|
+
packageId,
|
|
1398
|
+
"run",
|
|
1399
|
+
null,
|
|
1400
|
+
false
|
|
1401
|
+
));
|
|
1402
|
+
if (started["ok"] !== true) return started;
|
|
1403
|
+
const pluginRunId = readString(started, "pluginRunId");
|
|
1404
|
+
const startedHere = started["startedHere"] !== false;
|
|
1405
|
+
if (pkg.hasClientHalf) {
|
|
1406
|
+
rpc.notify(CORDIS_METHODS.userRun, {
|
|
1407
|
+
agentId,
|
|
1408
|
+
pluginId,
|
|
1409
|
+
packageId,
|
|
1410
|
+
pluginRunId,
|
|
1411
|
+
startedHere,
|
|
1412
|
+
mode: "run",
|
|
1413
|
+
hasClientHalf: true
|
|
1414
|
+
});
|
|
1415
|
+
}
|
|
1416
|
+
return { ok: true, status: "starting", pluginId, packageId, pluginRunId };
|
|
1417
|
+
}),
|
|
1418
|
+
"acp-bridge: cordis/start-plugin"
|
|
1419
|
+
);
|
|
1420
|
+
ctx.effect(
|
|
1421
|
+
() => rpc.registerMethod(CORDIS_METHODS.settleUserRun, async (params) => {
|
|
1422
|
+
const runner = requireRunnerMethod(ctx, "settleUserRun");
|
|
1423
|
+
const body = asObject(params);
|
|
1424
|
+
const agent = requireAgent(findAgent, readString(body, "agentId"));
|
|
1425
|
+
return await runner.settleUserRun(
|
|
1426
|
+
agent,
|
|
1427
|
+
readString(body, "pluginId"),
|
|
1428
|
+
body["resolution"]
|
|
1429
|
+
);
|
|
1430
|
+
}),
|
|
1431
|
+
"acp-bridge: cordis/settle-user-run"
|
|
1432
|
+
);
|
|
1433
|
+
ctx.on("cordis/inspect-query", (request) => {
|
|
1434
|
+
if (!advertisement.advertised) return;
|
|
1435
|
+
rpc.notify(CORDIS_METHODS.inspectQuery, request);
|
|
1436
|
+
});
|
|
1437
|
+
ctx.on("cordis/inspect-query-resolved", (resolved) => {
|
|
1438
|
+
if (!advertisement.advertised) return;
|
|
1439
|
+
rpc.notify(CORDIS_METHODS.inspectQueryResolved, resolved);
|
|
1440
|
+
});
|
|
1441
|
+
ctx.on("cordis/request-run", (request) => {
|
|
1442
|
+
if (!advertisement.advertised) return;
|
|
1443
|
+
rpc.notify(CORDIS_METHODS.requestRun, request);
|
|
1444
|
+
});
|
|
1445
|
+
ctx.on("cordis/request-run-resolved", (resolved) => {
|
|
1446
|
+
if (!advertisement.advertised) return;
|
|
1447
|
+
rpc.notify(CORDIS_METHODS.requestRunResolved, resolved);
|
|
1448
|
+
});
|
|
1449
|
+
ctx.on("cordis/dynamic-retract", (retracted) => {
|
|
1450
|
+
if (!advertisement.advertised) return;
|
|
1451
|
+
rpc.notify(CORDIS_METHODS.pluginRetract, retracted);
|
|
1452
|
+
});
|
|
1453
|
+
}
|
|
1454
|
+
function optionalInspect(ctx) {
|
|
1455
|
+
try {
|
|
1456
|
+
const value = ctx.get("cordisInspect");
|
|
1457
|
+
return value !== void 0 && typeof value.syncClientManifest === "function" ? value : void 0;
|
|
1458
|
+
} catch {
|
|
1459
|
+
return void 0;
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
function optionalRunner(ctx) {
|
|
1463
|
+
try {
|
|
1464
|
+
const value = ctx.get("dynamicCordisRunner");
|
|
1465
|
+
return value !== void 0 && typeof value.runHostHalf === "function" ? value : void 0;
|
|
1466
|
+
} catch {
|
|
1467
|
+
return void 0;
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
function requireRunnerMethod(ctx, method) {
|
|
1471
|
+
const runner = optionalRunner(ctx);
|
|
1472
|
+
if (runner === void 0 || typeof runner[method] !== "function") {
|
|
1473
|
+
throw new Error(`this agent has no dynamic Cordis runner method ${String(method)}`);
|
|
1474
|
+
}
|
|
1475
|
+
return runner;
|
|
1476
|
+
}
|
|
1477
|
+
function requireAgent(findAgent, agentId) {
|
|
1478
|
+
const agent = findAgent(agentId);
|
|
1479
|
+
if (agent === void 0) throw new Error(`unknown agent: ${agentId}`);
|
|
1480
|
+
return agent;
|
|
1481
|
+
}
|
|
1482
|
+
function asObject(value) {
|
|
1483
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
1484
|
+
throw new Error("params must be an object");
|
|
1485
|
+
}
|
|
1486
|
+
return value;
|
|
1487
|
+
}
|
|
1488
|
+
function readString(body, field) {
|
|
1489
|
+
const value = body[field];
|
|
1490
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
1491
|
+
throw new Error(`${field} must be a non-empty string`);
|
|
1492
|
+
}
|
|
1493
|
+
return value;
|
|
1494
|
+
}
|
|
1495
|
+
function readProviders(params) {
|
|
1496
|
+
const body = asObject(params);
|
|
1497
|
+
const providers = body["providers"];
|
|
1498
|
+
if (!Array.isArray(providers)) throw new Error("providers must be an array");
|
|
1499
|
+
return providers.map((provider, index) => {
|
|
1500
|
+
if (provider === null || typeof provider !== "object" || Array.isArray(provider)) {
|
|
1501
|
+
throw new Error(`providers[${index}] must be an object`);
|
|
1502
|
+
}
|
|
1503
|
+
const row = provider;
|
|
1504
|
+
if (typeof row["id"] !== "string" || row["id"].trim() === "") {
|
|
1505
|
+
throw new Error(`providers[${index}].id must be a non-empty string`);
|
|
1506
|
+
}
|
|
1507
|
+
if (typeof row["description"] !== "string" || row["description"].trim() === "") {
|
|
1508
|
+
throw new Error(`providers[${index}].description must be a non-empty string`);
|
|
1509
|
+
}
|
|
1510
|
+
if (!Array.isArray(row["methods"])) {
|
|
1511
|
+
throw new Error(`providers[${index}].methods must be an array`);
|
|
1512
|
+
}
|
|
1513
|
+
return provider;
|
|
1514
|
+
});
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
// src/bridge/user-questions.ts
|
|
1518
|
+
import { Service } from "@deepseek-ai/cordis";
|
|
1519
|
+
import {
|
|
1520
|
+
UserQuestionError
|
|
1521
|
+
} from "@deepseek-ai/dsh-user-questions";
|
|
1522
|
+
var META_KEY = "dsh.userQuestions";
|
|
1523
|
+
function fieldName(index) {
|
|
1524
|
+
return `question_${index}`;
|
|
1525
|
+
}
|
|
1526
|
+
function customFieldName(index) {
|
|
1527
|
+
return `${fieldName(index)}_custom`;
|
|
1528
|
+
}
|
|
1529
|
+
function optionValue(index) {
|
|
1530
|
+
return `option_${index}`;
|
|
1531
|
+
}
|
|
1532
|
+
function customValue(index) {
|
|
1533
|
+
return `custom_${index}`;
|
|
1534
|
+
}
|
|
1535
|
+
function descriptionOf(question) {
|
|
1536
|
+
const parts = [question.header === void 0 ? void 0 : question.question, question.detail].filter((part) => part !== void 0 && part.length > 0);
|
|
1537
|
+
return parts.length === 0 ? void 0 : parts.join("\n\n");
|
|
1538
|
+
}
|
|
1539
|
+
function propertyOf(question, index) {
|
|
1540
|
+
const title = question.header ?? question.question;
|
|
1541
|
+
const description = descriptionOf(question);
|
|
1542
|
+
const options = question.options ?? [];
|
|
1543
|
+
if (options.length === 0) {
|
|
1544
|
+
return {
|
|
1545
|
+
type: "string",
|
|
1546
|
+
title,
|
|
1547
|
+
...description === void 0 ? {} : { description }
|
|
1548
|
+
};
|
|
1549
|
+
}
|
|
1550
|
+
const choices = [
|
|
1551
|
+
...options.map((option, optionIndex) => ({
|
|
1552
|
+
const: optionValue(optionIndex),
|
|
1553
|
+
title: option.label,
|
|
1554
|
+
...option.description === void 0 ? {} : { description: option.description }
|
|
1555
|
+
})),
|
|
1556
|
+
{ const: customValue(index), title: "Other" }
|
|
1557
|
+
];
|
|
1558
|
+
if (question.multiSelect === true) {
|
|
1559
|
+
return {
|
|
1560
|
+
type: "array",
|
|
1561
|
+
title,
|
|
1562
|
+
...description === void 0 ? {} : { description },
|
|
1563
|
+
minItems: 1,
|
|
1564
|
+
items: { anyOf: choices }
|
|
1565
|
+
};
|
|
1566
|
+
}
|
|
1567
|
+
return {
|
|
1568
|
+
type: "string",
|
|
1569
|
+
title,
|
|
1570
|
+
...description === void 0 ? {} : { description },
|
|
1571
|
+
oneOf: choices
|
|
1572
|
+
};
|
|
1573
|
+
}
|
|
1574
|
+
function questionsToElicitation(questions, sessionId) {
|
|
1575
|
+
const properties = {};
|
|
1576
|
+
const required = [];
|
|
1577
|
+
questions.forEach((question, index) => {
|
|
1578
|
+
const field = fieldName(index);
|
|
1579
|
+
properties[field] = propertyOf(question, index);
|
|
1580
|
+
if ((question.options?.length ?? 0) > 0) {
|
|
1581
|
+
properties[customFieldName(index)] = {
|
|
1582
|
+
type: "string",
|
|
1583
|
+
title: "Other",
|
|
1584
|
+
description: "Type a custom answer."
|
|
1585
|
+
};
|
|
1586
|
+
}
|
|
1587
|
+
});
|
|
1588
|
+
return {
|
|
1589
|
+
mode: "form",
|
|
1590
|
+
sessionId,
|
|
1591
|
+
message: "The agent needs your input.",
|
|
1592
|
+
requestedSchema: {
|
|
1593
|
+
type: "object",
|
|
1594
|
+
properties,
|
|
1595
|
+
required
|
|
1596
|
+
},
|
|
1597
|
+
_meta: {
|
|
1598
|
+
[META_KEY]: {
|
|
1599
|
+
version: 1,
|
|
1600
|
+
questions
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
};
|
|
1604
|
+
}
|
|
1605
|
+
function answerFromElicitation(questions, response) {
|
|
1606
|
+
if (response.action !== "accept") return void 0;
|
|
1607
|
+
const content = response.content ?? {};
|
|
1608
|
+
return {
|
|
1609
|
+
answers: questions.map((question, index) => {
|
|
1610
|
+
const options = question.options ?? [];
|
|
1611
|
+
const raw = content[fieldName(index)];
|
|
1612
|
+
if (options.length === 0) {
|
|
1613
|
+
const custom2 = typeof raw === "string" ? raw.trim() : "";
|
|
1614
|
+
return {
|
|
1615
|
+
id: question.id,
|
|
1616
|
+
selected: [],
|
|
1617
|
+
...custom2.length === 0 ? {} : { custom: custom2 }
|
|
1618
|
+
};
|
|
1619
|
+
}
|
|
1620
|
+
const values = Array.isArray(raw) ? raw : typeof raw === "string" ? [raw] : [];
|
|
1621
|
+
const selected = values.flatMap((value) => {
|
|
1622
|
+
const match = /^option_(\d+)$/.exec(value);
|
|
1623
|
+
if (match === null) return [];
|
|
1624
|
+
const option = options[Number(match[1])];
|
|
1625
|
+
return option === void 0 ? [] : [option.label];
|
|
1626
|
+
});
|
|
1627
|
+
const customRaw = content[customFieldName(index)];
|
|
1628
|
+
const custom = typeof customRaw === "string" ? customRaw.trim() : "";
|
|
1629
|
+
return {
|
|
1630
|
+
id: question.id,
|
|
1631
|
+
selected: custom.length > 0 && question.multiSelect !== true ? [] : selected,
|
|
1632
|
+
...custom.length === 0 ? {} : { custom }
|
|
1633
|
+
};
|
|
1634
|
+
})
|
|
1635
|
+
};
|
|
1636
|
+
}
|
|
1637
|
+
async function askUserQuestionsOverAcp(request, sessionId, create) {
|
|
1638
|
+
const response = await create(questionsToElicitation(request.questions, sessionId));
|
|
1639
|
+
const answer = answerFromElicitation(request.questions, response);
|
|
1640
|
+
if (answer === void 0) {
|
|
1641
|
+
throw new UserQuestionError("the user cancelled ask_user_question", "ASK_CANCELLED");
|
|
1642
|
+
}
|
|
1643
|
+
return answer;
|
|
1644
|
+
}
|
|
1645
|
+
var ROUTERS_KEY = /* @__PURE__ */ Symbol.for("@openma/deepseek-harness-acp/user-question-routers");
|
|
1646
|
+
var routers = globalThis[ROUTERS_KEY] ??= /* @__PURE__ */ new WeakMap();
|
|
1647
|
+
function installAcpUserQuestionProvider(service, options) {
|
|
1648
|
+
const tracked = service[Service.tracker];
|
|
1649
|
+
const owner = typeof tracked === "object" && tracked !== null ? tracked : service;
|
|
1650
|
+
let router = routers.get(owner);
|
|
1651
|
+
if (router === void 0) {
|
|
1652
|
+
const routes = [];
|
|
1653
|
+
const disposeProvider = service.registerProvider({
|
|
1654
|
+
async ask(request) {
|
|
1655
|
+
for (let index = routes.length - 1; index >= 0; index -= 1) {
|
|
1656
|
+
const route = routes[index];
|
|
1657
|
+
const sessionId = route?.sessionIdForRequest(request);
|
|
1658
|
+
if (route === void 0 || sessionId === void 0) continue;
|
|
1659
|
+
if (!route.formSupported()) {
|
|
1660
|
+
throw new UserQuestionError(
|
|
1661
|
+
"the ACP client does not support form elicitation",
|
|
1662
|
+
"CLIENT_UNSUPPORTED"
|
|
1663
|
+
);
|
|
1664
|
+
}
|
|
1665
|
+
return askUserQuestionsOverAcp(request, sessionId, route.create);
|
|
1666
|
+
}
|
|
1667
|
+
throw new UserQuestionError(
|
|
1668
|
+
"ACP user interaction requires a live root session",
|
|
1669
|
+
"ASK_MISSING_AGENT"
|
|
1670
|
+
);
|
|
1671
|
+
}
|
|
1672
|
+
});
|
|
1673
|
+
router = { routes, disposeProvider };
|
|
1674
|
+
routers.set(owner, router);
|
|
1675
|
+
}
|
|
1676
|
+
router.routes.push(options);
|
|
1677
|
+
let active = true;
|
|
1678
|
+
return () => {
|
|
1679
|
+
if (!active) return;
|
|
1680
|
+
active = false;
|
|
1681
|
+
const index = router.routes.indexOf(options);
|
|
1682
|
+
if (index >= 0) router.routes.splice(index, 1);
|
|
1683
|
+
if (router.routes.length !== 0) return;
|
|
1684
|
+
routers.delete(owner);
|
|
1685
|
+
router.disposeProvider();
|
|
1686
|
+
};
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1689
|
+
// src/bridge/presets.ts
|
|
1690
|
+
function presetDisplayName(preset) {
|
|
1691
|
+
if (preset.id === "cordis") return "Creator";
|
|
1692
|
+
if (preset.id === "code") return "Code";
|
|
1693
|
+
const name4 = preset.name?.trim();
|
|
1694
|
+
return name4 !== void 0 && name4.length > 0 ? name4 : preset.id;
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
// src/bridge/index.ts
|
|
1698
|
+
var name = "acp-bridge";
|
|
1699
|
+
var inject = ["agents", "credentials", "llm", "agentDefaultModel", "sessionPersistence", "approval", "permissionPresets", "commands", "agentPresets", "skills", "subagents", "userQuestions"];
|
|
1700
|
+
function invalidParams(detail) {
|
|
1701
|
+
return RequestError.invalidParams(void 0, detail);
|
|
1702
|
+
}
|
|
1703
|
+
function internalError(detail) {
|
|
1704
|
+
return RequestError.internalError(void 0, detail);
|
|
1705
|
+
}
|
|
1706
|
+
function authRequired(detail) {
|
|
1707
|
+
return RequestError.authRequired(void 0, detail);
|
|
1708
|
+
}
|
|
1709
|
+
var MODE_LABELS = {
|
|
1710
|
+
"read-only": { name: "Read-only", description: "Bash and file mutations are denied by the sandbox" },
|
|
1711
|
+
"workspace-write": {
|
|
1712
|
+
name: "Workspace write",
|
|
1713
|
+
description: "Writes are confined to the session workspace; wider access asks for permission"
|
|
1714
|
+
},
|
|
1715
|
+
"danger-full-access": {
|
|
1716
|
+
name: "Full access",
|
|
1717
|
+
description: "No sandbox confinement and no permission prompts"
|
|
1718
|
+
}
|
|
1719
|
+
};
|
|
1720
|
+
async function apply(ctx, config = {}) {
|
|
1721
|
+
const agents = ctx.agents;
|
|
1722
|
+
const credentials = ctx.credentials;
|
|
1723
|
+
const llm = ctx.llm;
|
|
1724
|
+
const agentDefaultModel = ctx.agentDefaultModel;
|
|
1725
|
+
const sessionPersistence = ctx.sessionPersistence;
|
|
1726
|
+
const approval = ctx.approval;
|
|
1727
|
+
const permissionPresets = ctx.permissionPresets;
|
|
1728
|
+
const commandRuntime = ctx.commands;
|
|
1729
|
+
const agentPresets = ctx.agentPresets;
|
|
1730
|
+
const skillRegistry = ctx.skills;
|
|
1731
|
+
const subagents = ctx.subagents;
|
|
1732
|
+
const userQuestions = ctx.userQuestions;
|
|
1733
|
+
const harness = config.harness ?? (await Promise.resolve().then(() => (init_self_harness(), self_harness_exports))).selfHarness();
|
|
1734
|
+
const { createUserMessage: createUserMessage2, errorChain: errorChain2, sessionId: SessionId2, foldSessionTitle: foldSessionTitle2, setSandboxMode: setSandboxMode2 } = harness;
|
|
1735
|
+
const SANDBOX_MODES2 = harness.sandboxModes;
|
|
1736
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
1737
|
+
const subagentByChild = /* @__PURE__ */ new Map();
|
|
1738
|
+
const subagentByRun = /* @__PURE__ */ new Map();
|
|
1739
|
+
const watchedSubagentParents = /* @__PURE__ */ new WeakSet();
|
|
1740
|
+
let closed = false;
|
|
1741
|
+
let conn;
|
|
1742
|
+
let clientTerminalOutput = false;
|
|
1743
|
+
let clientSubagentTranscript = false;
|
|
1744
|
+
let clientElicitationForm = false;
|
|
1745
|
+
const tuiClient = { advertised: false };
|
|
1746
|
+
const rpc = new AcpRpc();
|
|
1747
|
+
const findAgent = (agentId) => {
|
|
1748
|
+
const fromRegistry = agents.get(agentId);
|
|
1749
|
+
if (fromRegistry !== void 0) return fromRegistry;
|
|
1750
|
+
for (const record of sessions.values()) {
|
|
1751
|
+
if (record.agent.id === agentId) return record.agent;
|
|
1752
|
+
}
|
|
1753
|
+
return void 0;
|
|
1754
|
+
};
|
|
1755
|
+
installTuiClientPlane(ctx, rpc, { findAgent, advertisement: tuiClient });
|
|
1756
|
+
const modelCandidates = () => {
|
|
1757
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1758
|
+
if (config.model !== void 0) seen.add(config.model);
|
|
1759
|
+
for (const model of config.models ?? []) if (model.trim().length > 0) seen.add(model.trim());
|
|
1760
|
+
return [...seen];
|
|
1761
|
+
};
|
|
1762
|
+
const defaultSelection = () => {
|
|
1763
|
+
try {
|
|
1764
|
+
return agentDefaultModel.currentSelection();
|
|
1765
|
+
} catch (error) {
|
|
1766
|
+
logDebug(`agentDefaultModel.currentSelection failed: ${String(error)}`);
|
|
1767
|
+
return {};
|
|
1768
|
+
}
|
|
1769
|
+
};
|
|
1770
|
+
const defaultProvider = () => config.provider ?? defaultSelection().provider;
|
|
1771
|
+
const encodeChoice = (provider, model) => provider === void 0 || provider === defaultProvider() ? model : `${provider}::${model}`;
|
|
1772
|
+
const decodeChoice = (value) => {
|
|
1773
|
+
const i = value.indexOf("::");
|
|
1774
|
+
if (i <= 0) return { provider: defaultProvider(), model: value };
|
|
1775
|
+
return { provider: value.slice(0, i), model: value.slice(i + 2) };
|
|
1776
|
+
};
|
|
1777
|
+
let liveCatalog;
|
|
1778
|
+
const effortCache = /* @__PURE__ */ new Map();
|
|
1779
|
+
const discoverModels = async () => {
|
|
1780
|
+
if (liveCatalog !== void 0) return liveCatalog;
|
|
1781
|
+
const found = [];
|
|
1782
|
+
try {
|
|
1783
|
+
const providers = llm.listProviders();
|
|
1784
|
+
const multi = providers.length > 1;
|
|
1785
|
+
for (const provider of providers) {
|
|
1786
|
+
const models = await llm.listModels(provider.id).catch((error) => {
|
|
1787
|
+
logDebug(`listModels(${provider.id}) failed: ${String(error)}`);
|
|
1788
|
+
return [];
|
|
1789
|
+
});
|
|
1790
|
+
for (const model of models) {
|
|
1791
|
+
found.push({
|
|
1792
|
+
provider: provider.id,
|
|
1793
|
+
model: model.id,
|
|
1794
|
+
label: multi ? `${model.name} (${provider.name})` : model.name
|
|
1795
|
+
});
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
} catch (error) {
|
|
1799
|
+
logDebug(`model discovery failed: ${String(error)}`);
|
|
1800
|
+
}
|
|
1801
|
+
liveCatalog = found;
|
|
1802
|
+
return found;
|
|
1803
|
+
};
|
|
1804
|
+
ctx.on("llm/adapters-updated", () => {
|
|
1805
|
+
liveCatalog = void 0;
|
|
1806
|
+
effortCache.clear();
|
|
1807
|
+
});
|
|
1808
|
+
const routeOf = (record) => {
|
|
1809
|
+
const logged = loggedConfig(record);
|
|
1810
|
+
const provider = record.provider ?? defaultProvider() ?? logged?.provider;
|
|
1811
|
+
const model = record.model ?? config.model ?? defaultSelection().model ?? logged?.model ?? record.agent.options.model;
|
|
1812
|
+
return {
|
|
1813
|
+
...provider !== void 0 ? { provider } : {},
|
|
1814
|
+
...model !== void 0 ? { model } : {}
|
|
1815
|
+
};
|
|
1816
|
+
};
|
|
1817
|
+
const loggedConfig = (record) => {
|
|
1818
|
+
try {
|
|
1819
|
+
const header = record.agent.session.requestHeader?.();
|
|
1820
|
+
const cfg = header?.config;
|
|
1821
|
+
if (cfg === void 0) return void 0;
|
|
1822
|
+
return {
|
|
1823
|
+
provider: cfg.provider,
|
|
1824
|
+
model: cfg.model,
|
|
1825
|
+
...cfg.reasoningEffort !== void 0 ? { reasoningEffort: String(cfg.reasoningEffort) } : {}
|
|
1826
|
+
};
|
|
1827
|
+
} catch {
|
|
1828
|
+
return void 0;
|
|
1829
|
+
}
|
|
1830
|
+
};
|
|
1831
|
+
const effortCatalog = async (provider, model) => {
|
|
1832
|
+
if (provider === void 0 || model === void 0) return void 0;
|
|
1833
|
+
const key = `${provider}::${model}`;
|
|
1834
|
+
if (effortCache.has(key)) return effortCache.get(key);
|
|
1835
|
+
let catalog;
|
|
1836
|
+
try {
|
|
1837
|
+
const reasoning = (await llm.resolveModelInfo(provider, model)).reasoning;
|
|
1838
|
+
if (reasoning !== void 0 && reasoning.efforts.length > 0) {
|
|
1839
|
+
catalog = {
|
|
1840
|
+
efforts: reasoning.efforts.map((effort) => ({
|
|
1841
|
+
id: String(effort.id),
|
|
1842
|
+
name: effort.name,
|
|
1843
|
+
...effort.description !== void 0 ? { description: effort.description } : {}
|
|
1844
|
+
})),
|
|
1845
|
+
...reasoning.defaultEffort !== void 0 ? { defaultEffort: String(reasoning.defaultEffort) } : {}
|
|
1846
|
+
};
|
|
1847
|
+
}
|
|
1848
|
+
} catch (error) {
|
|
1849
|
+
logDebug(`resolveModelInfo(${key}) failed: ${String(error)}`);
|
|
1850
|
+
}
|
|
1851
|
+
effortCache.set(key, catalog);
|
|
1852
|
+
return catalog;
|
|
1853
|
+
};
|
|
1854
|
+
const ensureSelection = (record) => {
|
|
1855
|
+
const install = harness.installModelSelection;
|
|
1856
|
+
if (install === void 0) return void 0;
|
|
1857
|
+
if (record.selection !== void 0) return record.selection;
|
|
1858
|
+
let picked;
|
|
1859
|
+
const selection = {
|
|
1860
|
+
get current() {
|
|
1861
|
+
if (picked !== void 0) return picked;
|
|
1862
|
+
const logged = loggedConfig(record);
|
|
1863
|
+
if (logged !== void 0) return logged;
|
|
1864
|
+
const { provider, model } = routeOf(record);
|
|
1865
|
+
if (provider === void 0 || model === void 0) return void 0;
|
|
1866
|
+
const defaults = defaultSelection();
|
|
1867
|
+
const effort = defaults.model === void 0 || defaults.model === model ? defaults.reasoningEffort : void 0;
|
|
1868
|
+
return { provider, model, ...effort !== void 0 ? { reasoningEffort: effort } : {} };
|
|
1869
|
+
},
|
|
1870
|
+
set current(next) {
|
|
1871
|
+
picked = next;
|
|
1872
|
+
},
|
|
1873
|
+
assembled: void 0
|
|
1874
|
+
};
|
|
1875
|
+
try {
|
|
1876
|
+
install(record.agent.ctx, selection);
|
|
1877
|
+
} catch (error) {
|
|
1878
|
+
logWarn(`installModelSelection failed: ${String(error)}`);
|
|
1879
|
+
return void 0;
|
|
1880
|
+
}
|
|
1881
|
+
record.selection = selection;
|
|
1882
|
+
return selection;
|
|
1883
|
+
};
|
|
1884
|
+
const switchModel = async (record, acpSessionId, value) => {
|
|
1885
|
+
if (record.inflight !== void 0) {
|
|
1886
|
+
throw invalidParams("cannot switch models while a prompt is running");
|
|
1887
|
+
}
|
|
1888
|
+
const choice = decodeChoice(value);
|
|
1889
|
+
const known = /* @__PURE__ */ new Set([
|
|
1890
|
+
...modelCandidates().map((model) => encodeChoice(void 0, model)),
|
|
1891
|
+
...(await discoverModels()).map((entry) => encodeChoice(entry.provider, entry.model)),
|
|
1892
|
+
encodeChoice(record.provider, record.model ?? "")
|
|
1893
|
+
]);
|
|
1894
|
+
if (!known.has(value)) throw invalidParams(`unknown model: ${value}`);
|
|
1895
|
+
if (choice.model === record.model && choice.provider === (record.provider ?? defaultProvider())) return;
|
|
1896
|
+
const sessionId = record.agent.session.id;
|
|
1897
|
+
await record.dispose().catch((error) => {
|
|
1898
|
+
logWarn(`dispose during model switch failed: ${String(error)}`);
|
|
1899
|
+
});
|
|
1900
|
+
let handle;
|
|
1901
|
+
try {
|
|
1902
|
+
const presets = presetsService();
|
|
1903
|
+
handle = await agents.resume({
|
|
1904
|
+
resumeSessionId: sessionId,
|
|
1905
|
+
agentOptions: agentOptionsFor(choice.model, choice.provider),
|
|
1906
|
+
...presetSetup(presets, record.preset) !== void 0 ? { setup: presetSetup(presets, record.preset) } : {}
|
|
1907
|
+
});
|
|
1908
|
+
} catch (error) {
|
|
1909
|
+
sessions.delete(acpSessionId);
|
|
1910
|
+
throw internalError(`model switch failed: ${errorChain2(error)}`);
|
|
1911
|
+
}
|
|
1912
|
+
record.agent = handle.agent;
|
|
1913
|
+
record.dispose = () => handle.dispose();
|
|
1914
|
+
record.model = choice.model;
|
|
1915
|
+
if (choice.provider !== void 0 && choice.provider !== defaultProvider()) {
|
|
1916
|
+
record.provider = choice.provider;
|
|
1917
|
+
} else {
|
|
1918
|
+
delete record.provider;
|
|
1919
|
+
}
|
|
1920
|
+
delete record.selection;
|
|
1921
|
+
ensureSelection(record);
|
|
1922
|
+
if (record.effort !== void 0) {
|
|
1923
|
+
const route = routeOf(record);
|
|
1924
|
+
const catalog = await effortCatalog(route.provider, route.model);
|
|
1925
|
+
if (catalog?.efforts.some((effort) => effort.id === record.effort) === true && route.provider !== void 0 && route.model !== void 0) {
|
|
1926
|
+
const selection = ensureSelection(record);
|
|
1927
|
+
if (selection !== void 0) {
|
|
1928
|
+
selection.current = {
|
|
1929
|
+
provider: route.provider,
|
|
1930
|
+
model: route.model,
|
|
1931
|
+
reasoningEffort: record.effort
|
|
1932
|
+
};
|
|
1933
|
+
}
|
|
1934
|
+
} else {
|
|
1935
|
+
delete record.effort;
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
setApprovalPolicy(record, record.approvals);
|
|
1939
|
+
};
|
|
1940
|
+
const listProviderRoutes = async () => {
|
|
1941
|
+
try {
|
|
1942
|
+
const raw2 = llm.listProviders();
|
|
1943
|
+
if (!Array.isArray(raw2)) return [];
|
|
1944
|
+
return raw2.flatMap((entry) => {
|
|
1945
|
+
if (!entry || typeof entry !== "object") return [];
|
|
1946
|
+
const id = entry.id;
|
|
1947
|
+
if (typeof id !== "string" || id.length === 0) return [];
|
|
1948
|
+
const name4 = entry.name;
|
|
1949
|
+
return [{ id, ...typeof name4 === "string" && name4.length > 0 ? { name: name4 } : {} }];
|
|
1950
|
+
});
|
|
1951
|
+
} catch (error) {
|
|
1952
|
+
logDebug(`listProviders failed: ${String(error)}`);
|
|
1953
|
+
return [];
|
|
1954
|
+
}
|
|
1955
|
+
};
|
|
1956
|
+
const credentialPresent = async (provider) => {
|
|
1957
|
+
const names = credentialEnvNames(provider).filter((name4) => !name4.endsWith("_BASE_URL"));
|
|
1958
|
+
for (const name4 of names) {
|
|
1959
|
+
try {
|
|
1960
|
+
if (await credentials.resolve(credentialRef(name4)) !== void 0) return true;
|
|
1961
|
+
} catch (error) {
|
|
1962
|
+
logWarn(`credential lookup failed: ${String(error)}`);
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
return false;
|
|
1966
|
+
};
|
|
1967
|
+
const requireCredential = async (provider) => {
|
|
1968
|
+
if (await credentialPresent(provider)) return;
|
|
1969
|
+
throw authRequired(
|
|
1970
|
+
'no credential found for this provider: call authenticate with `_meta["api-key"].apiKey` or `_meta.gateway`, use the browser method, run `dsh-acp login`, or save a key in the dsh Web UI (Settings \u2192 Models)'
|
|
1971
|
+
);
|
|
1972
|
+
};
|
|
1973
|
+
const agentOptionsFor = (model, provider) => ({
|
|
1974
|
+
...provider !== void 0 ? { provider } : config.provider !== void 0 ? { provider: config.provider } : {},
|
|
1975
|
+
...model !== void 0 ? { model } : {},
|
|
1976
|
+
...config.maxTokens !== void 0 ? { maxTokens: config.maxTokens } : {}
|
|
1977
|
+
});
|
|
1978
|
+
const ownedRecord = (agent) => {
|
|
1979
|
+
const record = sessions.get(String(agent.session.id));
|
|
1980
|
+
return record?.agent === agent ? record : void 0;
|
|
1981
|
+
};
|
|
1982
|
+
const requireSession = (sessionId) => {
|
|
1983
|
+
const record = sessions.get(sessionId);
|
|
1984
|
+
if (record === void 0) throw invalidParams(`unknown session: ${sessionId}`);
|
|
1985
|
+
return record;
|
|
1986
|
+
};
|
|
1987
|
+
const requireOrRestoreSession = async (sessionId) => {
|
|
1988
|
+
const record = sessions.get(sessionId);
|
|
1989
|
+
if (record !== void 0) return record;
|
|
1990
|
+
logWarn(`restoring session ${sessionId} from the persisted log`);
|
|
1991
|
+
try {
|
|
1992
|
+
return await restoreSession(sessionId, { replay: false });
|
|
1993
|
+
} catch (error) {
|
|
1994
|
+
throw invalidParams(`unknown session: ${sessionId} (${errorChain2(error)})`);
|
|
1995
|
+
}
|
|
1996
|
+
};
|
|
1997
|
+
const restoreSession = async (sessionId, options) => {
|
|
1998
|
+
const persistence = requirePersistence();
|
|
1999
|
+
const existing = sessions.get(sessionId);
|
|
2000
|
+
if (existing !== void 0) {
|
|
2001
|
+
sessions.delete(sessionId);
|
|
2002
|
+
existing.agent.cancel({ kind: "user" });
|
|
2003
|
+
settlePrompt(existing, "cancelled");
|
|
2004
|
+
await existing.dispose().catch((error) => {
|
|
2005
|
+
logWarn(`dispose before reload failed: ${String(error)}`);
|
|
2006
|
+
});
|
|
2007
|
+
}
|
|
2008
|
+
let events;
|
|
2009
|
+
let storedHeader;
|
|
2010
|
+
try {
|
|
2011
|
+
const inspected = await persistence.inspect(SessionId2(sessionId));
|
|
2012
|
+
events = inspected.events;
|
|
2013
|
+
storedHeader = inspected.header;
|
|
2014
|
+
} catch (error) {
|
|
2015
|
+
throw invalidParams(`session not found: ${sessionId} (${errorChain2(error)})`);
|
|
2016
|
+
}
|
|
2017
|
+
const replay = buildReplay(events);
|
|
2018
|
+
if (options.replay) {
|
|
2019
|
+
for (const update of replay.updates) notify(sessionId, update);
|
|
2020
|
+
if (replay.title !== void 0) {
|
|
2021
|
+
notify(sessionId, {
|
|
2022
|
+
sessionUpdate: "session_info_update",
|
|
2023
|
+
title: replay.title
|
|
2024
|
+
});
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
const presets = presetsService();
|
|
2028
|
+
const storedPreset = presetFromLog(storedHeader, events);
|
|
2029
|
+
const presetId = (await presets.resolve(storedPreset)).id;
|
|
2030
|
+
const handle = await agents.resume({
|
|
2031
|
+
resumeSessionId: SessionId2(sessionId),
|
|
2032
|
+
agentOptions: agentOptionsFor(config.model),
|
|
2033
|
+
...presetSetup(presets, presetId) !== void 0 ? { setup: presetSetup(presets, presetId) } : {}
|
|
2034
|
+
});
|
|
2035
|
+
const cwd = options.cwd ?? storedHeader?.cwd;
|
|
2036
|
+
const projection = new SessionProjection(replay.contextWindow, {
|
|
2037
|
+
terminalOutput: clientTerminalOutput,
|
|
2038
|
+
...cwd !== void 0 ? { cwd } : {}
|
|
2039
|
+
});
|
|
2040
|
+
projection.title = replay.title;
|
|
2041
|
+
const record = registerRecord(
|
|
2042
|
+
sessionId,
|
|
2043
|
+
handle.agent,
|
|
2044
|
+
() => handle.dispose(),
|
|
2045
|
+
config.model,
|
|
2046
|
+
config.permissionMode ?? "workspace-write",
|
|
2047
|
+
projection
|
|
2048
|
+
);
|
|
2049
|
+
if (presetId !== void 0) record.preset = presetId;
|
|
2050
|
+
{
|
|
2051
|
+
const permission = permissionService();
|
|
2052
|
+
const storedMode = permission.current(events);
|
|
2053
|
+
if (storedMode !== void 0) record.modeId = storedMode;
|
|
2054
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
2055
|
+
const event = events[index];
|
|
2056
|
+
if (event?.type === "approval/policy") {
|
|
2057
|
+
const policy = event.data?.policy;
|
|
2058
|
+
if (policy === "ask" || policy === "never") record.approvals = policy;
|
|
2059
|
+
break;
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2063
|
+
ensureSelection(record);
|
|
2064
|
+
queueMicrotask(() => publishCommands(sessionId));
|
|
2065
|
+
return record;
|
|
2066
|
+
};
|
|
2067
|
+
const assertOpen = () => {
|
|
2068
|
+
if (closed) throw internalError("the ACP bridge has been disposed");
|
|
2069
|
+
};
|
|
2070
|
+
const notify = (sessionId, update) => {
|
|
2071
|
+
void conn.sessionUpdate({ sessionId, update }).catch((error) => {
|
|
2072
|
+
logWarn(`session/update failed: ${String(error)}`);
|
|
2073
|
+
});
|
|
2074
|
+
};
|
|
2075
|
+
const settlePrompt = (record, reason) => {
|
|
2076
|
+
const inflight = record.inflight;
|
|
2077
|
+
if (inflight === void 0) return;
|
|
2078
|
+
record.inflight = void 0;
|
|
2079
|
+
inflight.resolve(reason);
|
|
2080
|
+
};
|
|
2081
|
+
const watchSubagentParent = (parent) => {
|
|
2082
|
+
if (watchedSubagentParents.has(parent)) return;
|
|
2083
|
+
watchedSubagentParents.add(parent);
|
|
2084
|
+
parent.ctx.on("subagent/start", (info) => {
|
|
2085
|
+
const parentLink = subagentByChild.get(String(parent.id));
|
|
2086
|
+
const rootSessionId = parentLink?.rootSessionId ?? String(parent.session.id);
|
|
2087
|
+
const record = sessions.get(rootSessionId);
|
|
2088
|
+
if (record === void 0) return;
|
|
2089
|
+
const runId = String(info.runId);
|
|
2090
|
+
const childSessionId = String(info.id);
|
|
2091
|
+
const toolCallId = `subagent:${runId}`;
|
|
2092
|
+
const payload = {
|
|
2093
|
+
runId,
|
|
2094
|
+
provider: info.provider,
|
|
2095
|
+
id: childSessionId,
|
|
2096
|
+
local: info.local,
|
|
2097
|
+
...parentLink !== void 0 ? { parentToolCallId: parentLink.toolCallId } : {}
|
|
2098
|
+
};
|
|
2099
|
+
for (const update of record.projection.onEvent({ type: "subagent/start", data: payload })) {
|
|
2100
|
+
notify(rootSessionId, update);
|
|
2101
|
+
}
|
|
2102
|
+
const child = agents.get(info.id);
|
|
2103
|
+
const link = {
|
|
2104
|
+
rootSessionId,
|
|
2105
|
+
childSessionId,
|
|
2106
|
+
runId,
|
|
2107
|
+
provider: info.provider,
|
|
2108
|
+
toolCallId,
|
|
2109
|
+
projection: new SessionProjection(void 0, {
|
|
2110
|
+
terminalOutput: clientTerminalOutput,
|
|
2111
|
+
...child?.session.header.cwd !== void 0 ? { cwd: child.session.header.cwd } : {},
|
|
2112
|
+
subagent: { childSessionId, parentToolCallId: toolCallId, provider: info.provider }
|
|
2113
|
+
})
|
|
2114
|
+
};
|
|
2115
|
+
subagentByChild.set(childSessionId, link);
|
|
2116
|
+
subagentByRun.set(runId, link);
|
|
2117
|
+
if (child !== void 0) watchSubagentParent(child);
|
|
2118
|
+
});
|
|
2119
|
+
parent.ctx.on("subagent/end", (info) => {
|
|
2120
|
+
const runId = String(info.runId);
|
|
2121
|
+
const link = subagentByRun.get(runId);
|
|
2122
|
+
if (link === void 0) return;
|
|
2123
|
+
const record = sessions.get(link.rootSessionId);
|
|
2124
|
+
if (record !== void 0) {
|
|
2125
|
+
const parentLink = subagentByChild.get(String(parent.id));
|
|
2126
|
+
const payload = {
|
|
2127
|
+
runId,
|
|
2128
|
+
provider: info.provider,
|
|
2129
|
+
id: String(info.id),
|
|
2130
|
+
local: info.local,
|
|
2131
|
+
stopReason: info.stopReason,
|
|
2132
|
+
...info.lastAssistantMessage !== void 0 ? { lastAssistantMessage: info.lastAssistantMessage } : {},
|
|
2133
|
+
...parentLink !== void 0 ? { parentToolCallId: parentLink.toolCallId } : {}
|
|
2134
|
+
};
|
|
2135
|
+
for (const update of record.projection.onEvent({ type: "subagent/end", data: payload })) {
|
|
2136
|
+
notify(link.rootSessionId, update);
|
|
2137
|
+
}
|
|
2138
|
+
}
|
|
2139
|
+
subagentByRun.delete(runId);
|
|
2140
|
+
if (subagentByChild.get(link.childSessionId)?.runId === runId) {
|
|
2141
|
+
subagentByChild.delete(link.childSessionId);
|
|
2142
|
+
}
|
|
2143
|
+
});
|
|
2144
|
+
};
|
|
2145
|
+
const presetsService = () => agentPresets;
|
|
2146
|
+
const presetSetup = (presets, presetId) => {
|
|
2147
|
+
if (presetId === void 0) return void 0;
|
|
2148
|
+
return async (agentCtx) => {
|
|
2149
|
+
const started = performance.now();
|
|
2150
|
+
logDebug(`preset setup: mounting "${presetId}"`);
|
|
2151
|
+
await presets.mount(agentCtx, presetId);
|
|
2152
|
+
logDebug(`preset setup: mounted "${presetId}" in ${(performance.now() - started).toFixed(1)}ms`);
|
|
2153
|
+
};
|
|
2154
|
+
};
|
|
2155
|
+
const presetFromLog = (header, events) => {
|
|
2156
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
2157
|
+
const event = events[index];
|
|
2158
|
+
if (event?.type === "agent-preset/selected") {
|
|
2159
|
+
return event.data?.agentPreset;
|
|
2160
|
+
}
|
|
2161
|
+
}
|
|
2162
|
+
return header?.agentPreset;
|
|
2163
|
+
};
|
|
2164
|
+
const modeState = (record) => {
|
|
2165
|
+
const permission = permissionService();
|
|
2166
|
+
if (permission.names.length > 0) {
|
|
2167
|
+
return {
|
|
2168
|
+
currentModeId: record.modeId,
|
|
2169
|
+
availableModes: permission.names.map((name4) => {
|
|
2170
|
+
const spec = permission.resolve(name4);
|
|
2171
|
+
return { id: name4, name: spec.name, description: spec.description };
|
|
2172
|
+
})
|
|
2173
|
+
};
|
|
2174
|
+
}
|
|
2175
|
+
return {
|
|
2176
|
+
currentModeId: record.modeId,
|
|
2177
|
+
availableModes: SANDBOX_MODES2.map((mode) => {
|
|
2178
|
+
const label = MODE_LABELS[mode] ?? { name: mode, description: "" };
|
|
2179
|
+
return { id: mode, name: label.name, description: label.description };
|
|
2180
|
+
})
|
|
2181
|
+
};
|
|
2182
|
+
};
|
|
2183
|
+
const switchPreset = async (record, acpSessionId, presetId) => {
|
|
2184
|
+
if (record.inflight !== void 0) {
|
|
2185
|
+
throw invalidParams("cannot switch presets while a prompt is running");
|
|
2186
|
+
}
|
|
2187
|
+
const presets = presetsService();
|
|
2188
|
+
let resolved;
|
|
2189
|
+
try {
|
|
2190
|
+
resolved = (await presets.resolve(presetId)).id;
|
|
2191
|
+
} catch (error) {
|
|
2192
|
+
throw invalidParams(`unknown preset: ${presetId} (${errorChain2(error)})`);
|
|
2193
|
+
}
|
|
2194
|
+
if (resolved === record.preset) return;
|
|
2195
|
+
const previous = record.preset;
|
|
2196
|
+
const sessionId = record.agent.session.id;
|
|
2197
|
+
const resumeWith = (id) => agents.resume({
|
|
2198
|
+
resumeSessionId: sessionId,
|
|
2199
|
+
agentOptions: agentOptionsFor(record.model ?? config.model, record.provider),
|
|
2200
|
+
...presetSetup(presets, id) !== void 0 ? { setup: presetSetup(presets, id) } : {}
|
|
2201
|
+
});
|
|
2202
|
+
await record.dispose().catch((error) => {
|
|
2203
|
+
logWarn(`dispose during preset switch failed: ${String(error)}`);
|
|
2204
|
+
});
|
|
2205
|
+
let handle;
|
|
2206
|
+
try {
|
|
2207
|
+
handle = await resumeWith(resolved);
|
|
2208
|
+
} catch (error) {
|
|
2209
|
+
try {
|
|
2210
|
+
handle = await resumeWith(previous);
|
|
2211
|
+
} catch (restoreError) {
|
|
2212
|
+
sessions.delete(acpSessionId);
|
|
2213
|
+
throw internalError(
|
|
2214
|
+
`preset switch failed: ${errorChain2(error)}; restore also failed: ${errorChain2(restoreError)}`
|
|
2215
|
+
);
|
|
2216
|
+
}
|
|
2217
|
+
record.agent = handle.agent;
|
|
2218
|
+
record.dispose = () => handle.dispose();
|
|
2219
|
+
delete record.selection;
|
|
2220
|
+
ensureSelection(record);
|
|
2221
|
+
setApprovalPolicy(record, record.approvals);
|
|
2222
|
+
publishCommands(acpSessionId, record);
|
|
2223
|
+
throw invalidParams(`preset "${resolved}" failed to mount: ${errorChain2(error)}`);
|
|
2224
|
+
}
|
|
2225
|
+
record.agent = handle.agent;
|
|
2226
|
+
record.dispose = () => handle.dispose();
|
|
2227
|
+
record.preset = resolved;
|
|
2228
|
+
try {
|
|
2229
|
+
handle.agent.session.append("agent-preset/selected", { agentPreset: resolved });
|
|
2230
|
+
} catch (error) {
|
|
2231
|
+
logWarn(`recording preset selection failed: ${String(error)}`);
|
|
2232
|
+
}
|
|
2233
|
+
delete record.selection;
|
|
2234
|
+
ensureSelection(record);
|
|
2235
|
+
setApprovalPolicy(record, record.approvals);
|
|
2236
|
+
publishCommands(acpSessionId, record);
|
|
2237
|
+
};
|
|
2238
|
+
const setApprovalPolicy = (record, policy) => {
|
|
2239
|
+
try {
|
|
2240
|
+
approval.setPolicy(record.agent, policy);
|
|
2241
|
+
} catch (error) {
|
|
2242
|
+
logWarn(`approval policy switch failed: ${String(error)}`);
|
|
2243
|
+
}
|
|
2244
|
+
record.approvals = policy;
|
|
2245
|
+
};
|
|
2246
|
+
const permissionService = () => permissionPresets;
|
|
2247
|
+
const applyMode = (record, sessionId, modeId) => {
|
|
2248
|
+
const permission = permissionService();
|
|
2249
|
+
if (permission.names.includes(modeId)) {
|
|
2250
|
+
let bundledApproval = record.approvals;
|
|
2251
|
+
permission.apply(record.agent.session, modeId, (policy) => {
|
|
2252
|
+
bundledApproval = policy;
|
|
2253
|
+
});
|
|
2254
|
+
setApprovalPolicy(record, bundledApproval);
|
|
2255
|
+
record.modeId = permission.resolve(modeId).sandbox;
|
|
2256
|
+
notify(sessionId, { sessionUpdate: "current_mode_update", currentModeId: modeId });
|
|
2257
|
+
return;
|
|
2258
|
+
}
|
|
2259
|
+
const mode = SANDBOX_MODES2.find((candidate) => candidate === modeId);
|
|
2260
|
+
if (mode === void 0) throw invalidParams(`unknown mode: ${modeId}`);
|
|
2261
|
+
setSandboxMode2(record.agent.session, mode);
|
|
2262
|
+
setApprovalPolicy(record, mode === "danger-full-access" ? "never" : "ask");
|
|
2263
|
+
record.modeId = mode;
|
|
2264
|
+
notify(sessionId, { sessionUpdate: "current_mode_update", currentModeId: mode });
|
|
2265
|
+
};
|
|
2266
|
+
const modelChoices = async (record) => {
|
|
2267
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2268
|
+
const options = [];
|
|
2269
|
+
const push = (value, name4) => {
|
|
2270
|
+
if (seen.has(value)) return;
|
|
2271
|
+
seen.add(value);
|
|
2272
|
+
options.push({ value, name: name4 });
|
|
2273
|
+
};
|
|
2274
|
+
const current = encodeChoice(
|
|
2275
|
+
record.provider,
|
|
2276
|
+
record.model ?? config.model ?? defaultSelection().model ?? record.agent.options.model ?? ""
|
|
2277
|
+
);
|
|
2278
|
+
if (current.length === 0) return [];
|
|
2279
|
+
const discovered = await discoverModels();
|
|
2280
|
+
push(current, discovered.find((c) => encodeChoice(c.provider, c.model) === current)?.label ?? current);
|
|
2281
|
+
for (const choice of discovered) {
|
|
2282
|
+
push(encodeChoice(choice.provider, choice.model), choice.label);
|
|
2283
|
+
}
|
|
2284
|
+
for (const model of modelCandidates()) push(encodeChoice(void 0, model), model);
|
|
2285
|
+
return options;
|
|
2286
|
+
};
|
|
2287
|
+
const configOptions = async (record) => {
|
|
2288
|
+
const result = [];
|
|
2289
|
+
{
|
|
2290
|
+
const permission = permissionService();
|
|
2291
|
+
const levels = permission.names.length > 0 ? permission.names.map((name4) => {
|
|
2292
|
+
const spec = permission.resolve(name4);
|
|
2293
|
+
return { value: name4, name: spec.name, description: spec.description };
|
|
2294
|
+
}) : SANDBOX_MODES2.map((mode) => {
|
|
2295
|
+
const label = MODE_LABELS[mode] ?? { name: mode, description: "" };
|
|
2296
|
+
return { value: mode, name: label.name, description: label.description };
|
|
2297
|
+
});
|
|
2298
|
+
result.push({
|
|
2299
|
+
type: "select",
|
|
2300
|
+
id: "mode",
|
|
2301
|
+
name: "Permissions",
|
|
2302
|
+
category: "mode",
|
|
2303
|
+
currentValue: record.modeId,
|
|
2304
|
+
options: levels
|
|
2305
|
+
});
|
|
2306
|
+
}
|
|
2307
|
+
if (commandRuntime.list(record.agent).some((command) => command.name === "plan")) {
|
|
2308
|
+
const active = [...record.agent.session.events].reverse().map((event) => event).find((event) => event.type === "plan/mode")?.data?.["active"] === true;
|
|
2309
|
+
result.push({
|
|
2310
|
+
type: "select",
|
|
2311
|
+
id: "collaboration_mode",
|
|
2312
|
+
name: "Collaboration mode",
|
|
2313
|
+
currentValue: active ? "plan" : "default",
|
|
2314
|
+
options: [
|
|
2315
|
+
{ value: "default", name: "Default" },
|
|
2316
|
+
{ value: "plan", name: "Plan" }
|
|
2317
|
+
]
|
|
2318
|
+
});
|
|
2319
|
+
}
|
|
2320
|
+
const models = await modelChoices(record);
|
|
2321
|
+
if (models.length >= 2) {
|
|
2322
|
+
result.push({
|
|
2323
|
+
type: "select",
|
|
2324
|
+
id: "model",
|
|
2325
|
+
name: "Model",
|
|
2326
|
+
category: "model",
|
|
2327
|
+
currentValue: models[0].value,
|
|
2328
|
+
options: models
|
|
2329
|
+
});
|
|
2330
|
+
}
|
|
2331
|
+
const route = routeOf(record);
|
|
2332
|
+
const efforts = await effortCatalog(route.provider, route.model);
|
|
2333
|
+
if (efforts !== void 0 && efforts.efforts.length >= 2) {
|
|
2334
|
+
const known = new Set(efforts.efforts.map((effort) => effort.id));
|
|
2335
|
+
const preferred = [
|
|
2336
|
+
record.effort,
|
|
2337
|
+
loggedConfig(record)?.reasoningEffort,
|
|
2338
|
+
// The user's saved product default (Web UI → settings.yaml),
|
|
2339
|
+
// e.g. reasoningEffort: max, outranks the adapter's default.
|
|
2340
|
+
defaultSelection().reasoningEffort,
|
|
2341
|
+
efforts.defaultEffort
|
|
2342
|
+
].find(
|
|
2343
|
+
(candidate) => candidate !== void 0 && known.has(candidate)
|
|
2344
|
+
);
|
|
2345
|
+
result.push({
|
|
2346
|
+
type: "select",
|
|
2347
|
+
id: "effort",
|
|
2348
|
+
name: "Reasoning",
|
|
2349
|
+
category: "thought_level",
|
|
2350
|
+
currentValue: preferred ?? efforts.efforts[0].id,
|
|
2351
|
+
options: efforts.efforts.map((effort) => ({
|
|
2352
|
+
value: effort.id,
|
|
2353
|
+
name: effort.name,
|
|
2354
|
+
...effort.description !== void 0 ? { description: effort.description } : {}
|
|
2355
|
+
}))
|
|
2356
|
+
});
|
|
2357
|
+
}
|
|
2358
|
+
const presets = presetsService();
|
|
2359
|
+
if (record.preset !== void 0) {
|
|
2360
|
+
try {
|
|
2361
|
+
const roster = await presets.list();
|
|
2362
|
+
if (roster.length >= 2) {
|
|
2363
|
+
result.push({
|
|
2364
|
+
type: "select",
|
|
2365
|
+
id: "agent",
|
|
2366
|
+
name: "Agent",
|
|
2367
|
+
currentValue: record.preset,
|
|
2368
|
+
options: roster.map((preset) => ({
|
|
2369
|
+
value: preset.id,
|
|
2370
|
+
name: presetDisplayName(preset),
|
|
2371
|
+
description: `Agent preset \u201C${preset.id}\u201D`
|
|
2372
|
+
}))
|
|
2373
|
+
});
|
|
2374
|
+
}
|
|
2375
|
+
} catch (error) {
|
|
2376
|
+
logWarn(`preset roster unavailable: ${String(error)}`);
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2379
|
+
return result;
|
|
2380
|
+
};
|
|
2381
|
+
const BUILTIN_COMMANDS = [
|
|
2382
|
+
{ name: "status", description: "Show adapter, model, mode, and token status" },
|
|
2383
|
+
{
|
|
2384
|
+
name: "model",
|
|
2385
|
+
description: "Select the model for this conversation",
|
|
2386
|
+
input: { hint: "[model] \u2014 blank lists available models" }
|
|
2387
|
+
}
|
|
2388
|
+
];
|
|
2389
|
+
const availableCommandsFor = async (record) => {
|
|
2390
|
+
const list = [...BUILTIN_COMMANDS];
|
|
2391
|
+
const seen = new Set(list.map((command) => command.name));
|
|
2392
|
+
try {
|
|
2393
|
+
for (const descriptor of commandRuntime.list(record.agent)) {
|
|
2394
|
+
if (seen.has(descriptor.name)) continue;
|
|
2395
|
+
seen.add(descriptor.name);
|
|
2396
|
+
list.push({
|
|
2397
|
+
name: descriptor.name,
|
|
2398
|
+
description: descriptor.description,
|
|
2399
|
+
...descriptor.input !== void 0 ? { input: { hint: descriptor.input.hint } } : {},
|
|
2400
|
+
...descriptor.name === "plan" ? {
|
|
2401
|
+
_meta: {
|
|
2402
|
+
commandAction: {
|
|
2403
|
+
kind: "setConfigOption",
|
|
2404
|
+
configId: "collaboration_mode",
|
|
2405
|
+
value: "plan",
|
|
2406
|
+
resetValue: "default",
|
|
2407
|
+
presentation: "state"
|
|
2408
|
+
}
|
|
2409
|
+
}
|
|
2410
|
+
} : {}
|
|
2411
|
+
});
|
|
2412
|
+
}
|
|
2413
|
+
} catch (error) {
|
|
2414
|
+
logWarn(`command listing failed: ${String(error)}`);
|
|
2415
|
+
}
|
|
2416
|
+
try {
|
|
2417
|
+
const skills = await skillRegistry.list({
|
|
2418
|
+
...record.agent.session.header.cwd !== void 0 ? { cwd: record.agent.session.header.cwd } : {},
|
|
2419
|
+
scope: record.agent
|
|
2420
|
+
});
|
|
2421
|
+
for (const skill of skills) {
|
|
2422
|
+
if (skill.invocation.userInvocable !== true) continue;
|
|
2423
|
+
if (seen.has(skill.name)) continue;
|
|
2424
|
+
seen.add(skill.name);
|
|
2425
|
+
list.push({
|
|
2426
|
+
name: skill.name,
|
|
2427
|
+
description: skill.description,
|
|
2428
|
+
input: { hint: "instructions for the skill" }
|
|
2429
|
+
});
|
|
2430
|
+
}
|
|
2431
|
+
} catch (error) {
|
|
2432
|
+
logDebug(`skill listing failed: ${String(error)}`);
|
|
2433
|
+
}
|
|
2434
|
+
return list;
|
|
2435
|
+
};
|
|
2436
|
+
const publishCommands = (sessionId, record) => {
|
|
2437
|
+
const target = record ?? sessions.get(sessionId);
|
|
2438
|
+
if (target === void 0) return;
|
|
2439
|
+
void availableCommandsFor(target).then((availableCommands) => {
|
|
2440
|
+
notify(sessionId, { sessionUpdate: "available_commands_update", availableCommands });
|
|
2441
|
+
}).catch((error) => {
|
|
2442
|
+
logWarn(`publishing commands failed: ${String(error)}`);
|
|
2443
|
+
});
|
|
2444
|
+
};
|
|
2445
|
+
const publishConfigOptions = (sessionId, record) => {
|
|
2446
|
+
void configOptions(record).then((options) => {
|
|
2447
|
+
notify(sessionId, {
|
|
2448
|
+
sessionUpdate: "config_option_update",
|
|
2449
|
+
configOptions: options
|
|
2450
|
+
});
|
|
2451
|
+
}).catch((error) => {
|
|
2452
|
+
logWarn(`publishing config options failed: ${String(error)}`);
|
|
2453
|
+
});
|
|
2454
|
+
};
|
|
2455
|
+
const syncPermissionState = (record, sessionId) => {
|
|
2456
|
+
const events = record.agent.session.events;
|
|
2457
|
+
if (events === void 0) return;
|
|
2458
|
+
const permission = permissionService();
|
|
2459
|
+
const storedMode = permission.current(events);
|
|
2460
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
2461
|
+
const event = events[index];
|
|
2462
|
+
if (event?.type === "approval/policy") {
|
|
2463
|
+
const policy = event.data?.policy;
|
|
2464
|
+
if (policy === "ask" || policy === "never") record.approvals = policy;
|
|
2465
|
+
break;
|
|
2466
|
+
}
|
|
2467
|
+
}
|
|
2468
|
+
if (storedMode !== void 0 && storedMode !== record.modeId) {
|
|
2469
|
+
record.modeId = storedMode;
|
|
2470
|
+
notify(sessionId, {
|
|
2471
|
+
sessionUpdate: "current_mode_update",
|
|
2472
|
+
currentModeId: record.modeId
|
|
2473
|
+
});
|
|
2474
|
+
}
|
|
2475
|
+
};
|
|
2476
|
+
const describeCredential = async (provider) => {
|
|
2477
|
+
const names = credentialEnvNames(provider).filter((candidate) => !candidate.endsWith("_BASE_URL"));
|
|
2478
|
+
for (const name4 of names) {
|
|
2479
|
+
try {
|
|
2480
|
+
const info = await credentials.describe(credentialRef(name4));
|
|
2481
|
+
if (info.configured) return info.source ?? "credential store";
|
|
2482
|
+
} catch (error) {
|
|
2483
|
+
logWarn(`credential describe failed: ${String(error)}`);
|
|
2484
|
+
}
|
|
2485
|
+
}
|
|
2486
|
+
return "not configured";
|
|
2487
|
+
};
|
|
2488
|
+
const saveCredential = async (provider, key) => {
|
|
2489
|
+
await credentials.set(credentialRef(primaryCredentialName(provider)), key);
|
|
2490
|
+
};
|
|
2491
|
+
const saveGateway = async (provider, key, baseUrl) => {
|
|
2492
|
+
await saveCredential(provider, key);
|
|
2493
|
+
await credentials.set(credentialRef(credentialBaseUrlName(provider)), baseUrl);
|
|
2494
|
+
};
|
|
2495
|
+
const logoutStored = async () => {
|
|
2496
|
+
const routes = await listProviderRoutes();
|
|
2497
|
+
const providers = routes.length > 0 ? routes.map((route) => route.id) : [config.provider];
|
|
2498
|
+
for (const provider of providers) {
|
|
2499
|
+
try {
|
|
2500
|
+
await credentials.unset(credentialRef(primaryCredentialName(provider)));
|
|
2501
|
+
} catch (error) {
|
|
2502
|
+
logWarn(`credential unset failed: ${String(error)}`);
|
|
2503
|
+
}
|
|
2504
|
+
}
|
|
2505
|
+
};
|
|
2506
|
+
const modelCommandText = async (record, acpSessionId, query) => {
|
|
2507
|
+
const discovered = await discoverModels();
|
|
2508
|
+
const catalog = [
|
|
2509
|
+
...discovered.map((entry) => ({
|
|
2510
|
+
value: encodeChoice(entry.provider, entry.model),
|
|
2511
|
+
label: entry.label
|
|
2512
|
+
})),
|
|
2513
|
+
...modelCandidates().filter((model) => !discovered.some((entry) => entry.model === model)).map((model) => ({ value: encodeChoice(void 0, model), label: model }))
|
|
2514
|
+
];
|
|
2515
|
+
const route = routeOf(record);
|
|
2516
|
+
const currentValue = encodeChoice(record.provider, record.model ?? route.model ?? "");
|
|
2517
|
+
if (query === "") {
|
|
2518
|
+
const lines = catalog.map(
|
|
2519
|
+
(entry) => `${entry.value === currentValue ? "\u2192" : " "} ${entry.label} \u2014 ${entry.value}`
|
|
2520
|
+
);
|
|
2521
|
+
return [
|
|
2522
|
+
`model: ${route.model ?? "(product default)"}${record.effort !== void 0 ? ` (effort ${record.effort})` : ""}`,
|
|
2523
|
+
"",
|
|
2524
|
+
...lines.length > 0 ? lines : ["no models discovered \u2014 check credentials with /status"],
|
|
2525
|
+
"",
|
|
2526
|
+
"switch with /model <name>"
|
|
2527
|
+
].join("\n");
|
|
2528
|
+
}
|
|
2529
|
+
const lowered = query.toLowerCase();
|
|
2530
|
+
const exact = catalog.filter(
|
|
2531
|
+
(entry) => entry.value.toLowerCase() === lowered || decodeChoice(entry.value).model.toLowerCase() === lowered
|
|
2532
|
+
);
|
|
2533
|
+
const matches = exact.length > 0 ? exact : catalog.filter(
|
|
2534
|
+
(entry) => entry.value.toLowerCase().includes(lowered) || entry.label.toLowerCase().includes(lowered)
|
|
2535
|
+
);
|
|
2536
|
+
if (matches.length === 0) return `no model matches "${query}" \u2014 see /model`;
|
|
2537
|
+
if (matches.length > 1) {
|
|
2538
|
+
return [`"${query}" is ambiguous:`, ...matches.map((entry) => ` ${entry.label} \u2014 ${entry.value}`)].join(
|
|
2539
|
+
"\n"
|
|
2540
|
+
);
|
|
2541
|
+
}
|
|
2542
|
+
const target = matches[0];
|
|
2543
|
+
if (target.value === currentValue) return `already on ${target.label}`;
|
|
2544
|
+
await switchModel(record, acpSessionId, target.value);
|
|
2545
|
+
publishCommands(acpSessionId, record);
|
|
2546
|
+
publishConfigOptions(acpSessionId, record);
|
|
2547
|
+
return `model \u2192 ${target.label}`;
|
|
2548
|
+
};
|
|
2549
|
+
const statusText = async (record) => {
|
|
2550
|
+
const used = record.projection.contextWindow;
|
|
2551
|
+
const route = routeOf(record);
|
|
2552
|
+
const effort = record.effort ?? loggedConfig(record)?.reasoningEffort;
|
|
2553
|
+
const lines = [
|
|
2554
|
+
`**dsh-acp** ${VERSION} \u2014 DeepSeek Harness ACP bridge`,
|
|
2555
|
+
"",
|
|
2556
|
+
`| | |`,
|
|
2557
|
+
`|---|---|`,
|
|
2558
|
+
`| Provider | ${route.provider ?? "(composition default)"} |`,
|
|
2559
|
+
`| Model | ${route.model ?? "(composition default)"} |`,
|
|
2560
|
+
`| Reasoning | ${effort ?? "(adapter default)"} |`,
|
|
2561
|
+
`| Credential | ${await describeCredential(route.provider)} |`,
|
|
2562
|
+
...record.preset !== void 0 ? [`| Preset | ${record.preset} |`] : [],
|
|
2563
|
+
`| Permission mode | ${record.modeId} |`,
|
|
2564
|
+
`| Approvals | ${record.approvals} |`,
|
|
2565
|
+
`| Workspace | ${record.agent.session.header.cwd ?? process.cwd()} |`,
|
|
2566
|
+
`| Session | ${String(record.agent.session.id)} |`,
|
|
2567
|
+
...used !== void 0 ? [`| Context window | ${used.toLocaleString()} tokens |`] : []
|
|
2568
|
+
];
|
|
2569
|
+
return lines.join("\n");
|
|
2570
|
+
};
|
|
2571
|
+
const registerRecord = (sessionId, agent, dispose, model, modeId, projection) => {
|
|
2572
|
+
const record = {
|
|
2573
|
+
agent,
|
|
2574
|
+
dispose,
|
|
2575
|
+
projection: projection ?? new SessionProjection(void 0, {
|
|
2576
|
+
terminalOutput: clientTerminalOutput,
|
|
2577
|
+
...agent.session.header.cwd !== void 0 ? { cwd: agent.session.header.cwd } : {}
|
|
2578
|
+
}),
|
|
2579
|
+
modeId,
|
|
2580
|
+
model,
|
|
2581
|
+
approvals: modeId === "danger-full-access" ? "never" : "ask",
|
|
2582
|
+
cancelled: false,
|
|
2583
|
+
inflight: void 0
|
|
2584
|
+
};
|
|
2585
|
+
sessions.set(sessionId, record);
|
|
2586
|
+
watchSubagentParent(agent);
|
|
2587
|
+
return record;
|
|
2588
|
+
};
|
|
2589
|
+
ctx.on("session/event", (session, event) => {
|
|
2590
|
+
const sessionId = String(session.header.id);
|
|
2591
|
+
const record = sessions.get(sessionId);
|
|
2592
|
+
if (record === void 0 || record.agent.session !== session) {
|
|
2593
|
+
const child = subagentByChild.get(sessionId);
|
|
2594
|
+
if (child !== void 0 && clientSubagentTranscript) {
|
|
2595
|
+
for (const update of child.projection.onEvent(event)) {
|
|
2596
|
+
notify(child.rootSessionId, update);
|
|
2597
|
+
}
|
|
2598
|
+
}
|
|
2599
|
+
return;
|
|
2600
|
+
}
|
|
2601
|
+
try {
|
|
2602
|
+
const harnessEvent = event;
|
|
2603
|
+
for (const update of record.projection.onEvent(harnessEvent)) {
|
|
2604
|
+
notify(sessionId, update);
|
|
2605
|
+
}
|
|
2606
|
+
if (harnessEvent.type === "plan/mode") publishConfigOptions(sessionId, record);
|
|
2607
|
+
} finally {
|
|
2608
|
+
const inflight = record.inflight;
|
|
2609
|
+
if (inflight !== void 0 && event.type === "turn/end" && inflight.turn === event.data.turn) {
|
|
2610
|
+
if (event.data.reason.kind === "error") {
|
|
2611
|
+
record.inflight = void 0;
|
|
2612
|
+
inflight.reject(internalError(`turn failed: ${event.data.reason.error.message}`));
|
|
2613
|
+
}
|
|
2614
|
+
}
|
|
2615
|
+
}
|
|
2616
|
+
});
|
|
2617
|
+
ctx.on("agent/inbox/claimed", ({ agent, message, turn }) => {
|
|
2618
|
+
const record = ownedRecord(agent);
|
|
2619
|
+
const inflight = record?.inflight;
|
|
2620
|
+
if (inflight !== void 0 && inflight.messageId === message.id) inflight.turn = turn;
|
|
2621
|
+
});
|
|
2622
|
+
ctx.on("agent/error", ({ agent, turn, error }) => {
|
|
2623
|
+
const record = ownedRecord(agent);
|
|
2624
|
+
const inflight = record?.inflight;
|
|
2625
|
+
if (record === void 0 || inflight === void 0 || inflight.turn === turn) return;
|
|
2626
|
+
record.inflight = void 0;
|
|
2627
|
+
inflight.reject(internalError(`turn failed: ${errorChain2(error)}`));
|
|
2628
|
+
});
|
|
2629
|
+
ctx.on("approval/request", (request, next) => {
|
|
2630
|
+
const record = ownedRecord(request.agent);
|
|
2631
|
+
if (record === void 0 || request.callId === void 0) return next();
|
|
2632
|
+
return conn.requestPermission({
|
|
2633
|
+
sessionId: String(record.agent.session.id),
|
|
2634
|
+
toolCall: { toolCallId: request.callId },
|
|
2635
|
+
options: [
|
|
2636
|
+
{ optionId: "allow-once", name: "Allow once", kind: "allow_once" },
|
|
2637
|
+
{ optionId: "allow-always", name: "Always allow (this session)", kind: "allow_always" },
|
|
2638
|
+
{ optionId: "reject-once", name: "Reject", kind: "reject_once" }
|
|
2639
|
+
]
|
|
2640
|
+
}).then(({ outcome }) => {
|
|
2641
|
+
if (outcome.outcome === "cancelled") return "cancelled";
|
|
2642
|
+
if (outcome.optionId === "allow-always") {
|
|
2643
|
+
setApprovalPolicy(record, "never");
|
|
2644
|
+
return "allowed-once";
|
|
2645
|
+
}
|
|
2646
|
+
return outcome.optionId === "allow-once" ? "allowed-once" : "rejected";
|
|
2647
|
+
});
|
|
2648
|
+
});
|
|
2649
|
+
const makeAgent = (connection) => {
|
|
2650
|
+
conn = connection;
|
|
2651
|
+
return {
|
|
2652
|
+
async initialize(params) {
|
|
2653
|
+
const requested = params.protocolVersion;
|
|
2654
|
+
const capsMeta = params.clientCapabilities?.["_meta"];
|
|
2655
|
+
clientTerminalOutput = capsMeta !== null && typeof capsMeta === "object" ? capsMeta["terminal_output"] === true : false;
|
|
2656
|
+
clientSubagentTranscript = capsMeta !== null && typeof capsMeta === "object" ? capsMeta["subagent-transcript"] === true : false;
|
|
2657
|
+
clientElicitationForm = params.clientCapabilities?.elicitation?.form !== void 0 && params.clientCapabilities.elicitation.form !== null;
|
|
2658
|
+
tuiClient.advertised = advertisesCordis(capsMeta);
|
|
2659
|
+
const providers = await listProviderRoutes();
|
|
2660
|
+
return {
|
|
2661
|
+
protocolVersion: typeof requested === "number" && requested >= 1 && requested < PROTOCOL_VERSION ? requested : PROTOCOL_VERSION,
|
|
2662
|
+
agentInfo: { name: "dsh-acp", title: "DeepSeek Harness", version: VERSION },
|
|
2663
|
+
agentCapabilities: {
|
|
2664
|
+
_meta: { dsh: { cordis: { ...CORDIS_CAPABILITY } } },
|
|
2665
|
+
loadSession: true,
|
|
2666
|
+
promptCapabilities: {
|
|
2667
|
+
image: attachmentIngestOf(ctx.get("attachments")) !== void 0,
|
|
2668
|
+
audio: false,
|
|
2669
|
+
embeddedContext: true
|
|
2670
|
+
},
|
|
2671
|
+
// Stdio servers always work; streamable HTTP maps onto
|
|
2672
|
+
// mcp-client's second transport. Legacy SSE does not.
|
|
2673
|
+
mcpCapabilities: { http: true, sse: false },
|
|
2674
|
+
auth: { logout: {} },
|
|
2675
|
+
sessionCapabilities: { list: {} }
|
|
2676
|
+
},
|
|
2677
|
+
authMethods: advertisedAuthMethods(
|
|
2678
|
+
providers,
|
|
2679
|
+
params.clientCapabilities
|
|
2680
|
+
)
|
|
2681
|
+
};
|
|
2682
|
+
},
|
|
2683
|
+
async authenticate(params) {
|
|
2684
|
+
const gateway = gatewayFromAuthenticate(params);
|
|
2685
|
+
if (isGatewayAuthMethod(params.methodId) || gateway.baseUrl) {
|
|
2686
|
+
if (!gateway.baseUrl || !gateway.key) {
|
|
2687
|
+
throw authRequired(
|
|
2688
|
+
"authenticate gateway requires `_meta.gateway.baseUrl` and an Authorization header"
|
|
2689
|
+
);
|
|
2690
|
+
}
|
|
2691
|
+
await saveGateway(
|
|
2692
|
+
gateway.providerName ?? config.provider,
|
|
2693
|
+
gateway.key,
|
|
2694
|
+
gateway.baseUrl
|
|
2695
|
+
);
|
|
2696
|
+
return;
|
|
2697
|
+
}
|
|
2698
|
+
const submitted = apiKeyFromAuthenticate(params);
|
|
2699
|
+
const provider = submitted.provider ?? providerFromAuthMethodId(params.methodId) ?? config.provider;
|
|
2700
|
+
if (submitted.key) {
|
|
2701
|
+
await saveCredential(provider, submitted.key);
|
|
2702
|
+
return;
|
|
2703
|
+
}
|
|
2704
|
+
if (await credentialPresent(provider)) return;
|
|
2705
|
+
if (isBrowserAuthMethod(params.methodId) && shouldOfferLocalAuthPage()) {
|
|
2706
|
+
const page = await startLocalAuthPage({
|
|
2707
|
+
credentialName: primaryCredentialName(provider)
|
|
2708
|
+
});
|
|
2709
|
+
openLocalAuthPage(page.url);
|
|
2710
|
+
try {
|
|
2711
|
+
const key = await page.completed;
|
|
2712
|
+
await saveCredential(provider, key);
|
|
2713
|
+
return;
|
|
2714
|
+
} finally {
|
|
2715
|
+
page.close();
|
|
2716
|
+
}
|
|
2717
|
+
}
|
|
2718
|
+
await requireCredential(provider);
|
|
2719
|
+
},
|
|
2720
|
+
async logout(_params) {
|
|
2721
|
+
await logoutStored();
|
|
2722
|
+
},
|
|
2723
|
+
async newSession(params) {
|
|
2724
|
+
assertOpen();
|
|
2725
|
+
const started = performance.now();
|
|
2726
|
+
let checkpoint = started;
|
|
2727
|
+
const mark = (name4) => {
|
|
2728
|
+
const now = performance.now();
|
|
2729
|
+
logDebug(`session/new ${name4}: ${(now - checkpoint).toFixed(1)}ms (+${(now - started).toFixed(1)}ms)`);
|
|
2730
|
+
checkpoint = now;
|
|
2731
|
+
};
|
|
2732
|
+
await requireCredential(config.provider);
|
|
2733
|
+
mark("credential");
|
|
2734
|
+
validateCwd(params.cwd);
|
|
2735
|
+
syncMcpServers(params.mcpServers, params.cwd);
|
|
2736
|
+
mark("request setup");
|
|
2737
|
+
const sessionId = SessionId2(randomUUID());
|
|
2738
|
+
const presets = presetsService();
|
|
2739
|
+
const presetId = (await presets.resolve(void 0)).id;
|
|
2740
|
+
mark("preset resolve");
|
|
2741
|
+
const handle = await agents.create({
|
|
2742
|
+
sessionId,
|
|
2743
|
+
meta: { cwd: params.cwd, ...presetId !== void 0 ? { agentPreset: presetId } : {} },
|
|
2744
|
+
agentOptions: agentOptionsFor(config.model),
|
|
2745
|
+
...presetSetup(presets, presetId) !== void 0 ? { setup: presetSetup(presets, presetId) } : {}
|
|
2746
|
+
});
|
|
2747
|
+
mark("agent create");
|
|
2748
|
+
if (closed) {
|
|
2749
|
+
await handle.dispose();
|
|
2750
|
+
throw internalError("connection closed during session/new");
|
|
2751
|
+
}
|
|
2752
|
+
const record = registerRecord(
|
|
2753
|
+
String(sessionId),
|
|
2754
|
+
handle.agent,
|
|
2755
|
+
() => handle.dispose(),
|
|
2756
|
+
config.model,
|
|
2757
|
+
config.permissionMode ?? "workspace-write"
|
|
2758
|
+
);
|
|
2759
|
+
if (presetId !== void 0) record.preset = presetId;
|
|
2760
|
+
ensureSelection(record);
|
|
2761
|
+
queueMicrotask(() => publishCommands(String(sessionId)));
|
|
2762
|
+
const options = await configOptions(record);
|
|
2763
|
+
mark("response surface");
|
|
2764
|
+
return {
|
|
2765
|
+
sessionId: String(sessionId),
|
|
2766
|
+
modes: modeState(record),
|
|
2767
|
+
...options.length > 0 ? { configOptions: options } : {}
|
|
2768
|
+
};
|
|
2769
|
+
},
|
|
2770
|
+
async loadSession(params) {
|
|
2771
|
+
assertOpen();
|
|
2772
|
+
await requireCredential(config.provider);
|
|
2773
|
+
validateCwd(params.cwd);
|
|
2774
|
+
syncMcpServers(params.mcpServers, params.cwd);
|
|
2775
|
+
requirePersistence();
|
|
2776
|
+
const record = await restoreSession(params.sessionId, { replay: true, cwd: params.cwd });
|
|
2777
|
+
const options = await configOptions(record);
|
|
2778
|
+
return {
|
|
2779
|
+
modes: modeState(record),
|
|
2780
|
+
...options.length > 0 ? { configOptions: options } : {}
|
|
2781
|
+
};
|
|
2782
|
+
},
|
|
2783
|
+
async listSessions(params) {
|
|
2784
|
+
assertOpen();
|
|
2785
|
+
const persistence = requirePersistence();
|
|
2786
|
+
const headers = await persistence.list();
|
|
2787
|
+
headers.sort((a, b) => (b.createdAt ?? 0) - (a.createdAt ?? 0));
|
|
2788
|
+
const filtered = params.cwd !== void 0 && params.cwd !== null ? headers.filter((header) => header.cwd === params.cwd) : headers;
|
|
2789
|
+
const page = filtered.slice(0, 100);
|
|
2790
|
+
const withTitles = await Promise.allSettled(
|
|
2791
|
+
page.slice(0, 20).map(async (header) => {
|
|
2792
|
+
const { events } = await persistence.inspect(header.id);
|
|
2793
|
+
return foldSessionTitle2(events)?.title;
|
|
2794
|
+
})
|
|
2795
|
+
);
|
|
2796
|
+
const sessionsInfo = page.map((header, index) => {
|
|
2797
|
+
const settled = withTitles[index];
|
|
2798
|
+
const title = settled !== void 0 && settled.status === "fulfilled" ? settled.value : void 0;
|
|
2799
|
+
return {
|
|
2800
|
+
sessionId: String(header.id),
|
|
2801
|
+
cwd: header.cwd ?? "",
|
|
2802
|
+
...title !== void 0 ? { title } : {},
|
|
2803
|
+
...header.createdAt !== void 0 ? { updatedAt: new Date(header.createdAt).toISOString() } : {}
|
|
2804
|
+
};
|
|
2805
|
+
});
|
|
2806
|
+
return { sessions: sessionsInfo };
|
|
2807
|
+
},
|
|
2808
|
+
async prompt(params) {
|
|
2809
|
+
assertOpen();
|
|
2810
|
+
const record = await requireOrRestoreSession(params.sessionId);
|
|
2811
|
+
let converted;
|
|
2812
|
+
try {
|
|
2813
|
+
converted = await convertPrompt(
|
|
2814
|
+
params.prompt,
|
|
2815
|
+
attachmentIngestOf(ctx.get("attachments"))
|
|
2816
|
+
);
|
|
2817
|
+
} catch (error) {
|
|
2818
|
+
if (error instanceof UnsupportedPromptContentError || error instanceof PromptImageError) {
|
|
2819
|
+
throw invalidParams(error.message);
|
|
2820
|
+
}
|
|
2821
|
+
throw error;
|
|
2822
|
+
}
|
|
2823
|
+
if (converted.blocks.length === 0) throw invalidParams("empty prompt");
|
|
2824
|
+
if (record.inflight !== void 0) {
|
|
2825
|
+
await requireCredential(record.provider ?? config.provider);
|
|
2826
|
+
if (agents.get(record.agent.id) !== record.agent) {
|
|
2827
|
+
throw internalError("prompt was not steered: the agent was disposed outside the bridge");
|
|
2828
|
+
}
|
|
2829
|
+
const message2 = createUserMessage2({
|
|
2830
|
+
content: converted.blocks,
|
|
2831
|
+
source: { kind: "user" }
|
|
2832
|
+
});
|
|
2833
|
+
try {
|
|
2834
|
+
deliverPrompt(record.agent, message2, true);
|
|
2835
|
+
} catch (error) {
|
|
2836
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
2837
|
+
throw internalError(`prompt was not steered: ${detail}`);
|
|
2838
|
+
}
|
|
2839
|
+
return { stopReason: "end_turn" };
|
|
2840
|
+
}
|
|
2841
|
+
const trimmed = converted.displayText.trim();
|
|
2842
|
+
const commandMatch = trimmed.match(/^\/(\w[\w-]*)\b/);
|
|
2843
|
+
const respond = (text) => {
|
|
2844
|
+
notify(params.sessionId, {
|
|
2845
|
+
sessionUpdate: "agent_message_chunk",
|
|
2846
|
+
content: { type: "text", text }
|
|
2847
|
+
});
|
|
2848
|
+
return { stopReason: "end_turn" };
|
|
2849
|
+
};
|
|
2850
|
+
if (commandMatch?.[1] === "status") return respond(await statusText(record));
|
|
2851
|
+
if (commandMatch?.[1] === "model") {
|
|
2852
|
+
return respond(
|
|
2853
|
+
await modelCommandText(
|
|
2854
|
+
record,
|
|
2855
|
+
params.sessionId,
|
|
2856
|
+
trimmed.slice(commandMatch[0].length).trim()
|
|
2857
|
+
)
|
|
2858
|
+
);
|
|
2859
|
+
}
|
|
2860
|
+
if (commandMatch !== null && commandMatch[1] !== void 0) {
|
|
2861
|
+
let execution;
|
|
2862
|
+
try {
|
|
2863
|
+
execution = await commandRuntime.execute(
|
|
2864
|
+
record.agent,
|
|
2865
|
+
trimmed,
|
|
2866
|
+
new AbortController().signal
|
|
2867
|
+
);
|
|
2868
|
+
} catch (error) {
|
|
2869
|
+
return respond(`\u26A0 /${commandMatch[1]} failed: ${errorChain2(error)}`);
|
|
2870
|
+
}
|
|
2871
|
+
if (execution !== void 0) {
|
|
2872
|
+
const { result } = execution;
|
|
2873
|
+
const text = result.text ?? (result.kind === "success" ? `/${commandMatch[1]} \u2713` : `/${commandMatch[1]} failed`);
|
|
2874
|
+
syncPermissionState(record, params.sessionId);
|
|
2875
|
+
publishCommands(params.sessionId, record);
|
|
2876
|
+
publishConfigOptions(params.sessionId, record);
|
|
2877
|
+
return respond(result.kind === "error" ? `\u26A0 ${text}` : text);
|
|
2878
|
+
}
|
|
2879
|
+
}
|
|
2880
|
+
await requireCredential(record.provider ?? config.provider);
|
|
2881
|
+
if (agents.get(record.agent.id) !== record.agent) {
|
|
2882
|
+
throw internalError("prompt was not queued: the agent was disposed outside the bridge");
|
|
2883
|
+
}
|
|
2884
|
+
record.cancelled = false;
|
|
2885
|
+
const message = createUserMessage2({
|
|
2886
|
+
content: converted.blocks,
|
|
2887
|
+
source: { kind: "user" }
|
|
2888
|
+
});
|
|
2889
|
+
if (record.inflight !== void 0) {
|
|
2890
|
+
try {
|
|
2891
|
+
deliverPrompt(record.agent, message, true);
|
|
2892
|
+
} catch (error) {
|
|
2893
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
2894
|
+
throw internalError(`prompt was not steered: ${detail}`);
|
|
2895
|
+
}
|
|
2896
|
+
return { stopReason: "end_turn" };
|
|
2897
|
+
}
|
|
2898
|
+
record.projection.beginPrompt();
|
|
2899
|
+
const stopReason = await new Promise((resolve, reject) => {
|
|
2900
|
+
const inflight = { resolve, reject, messageId: message.id, turn: void 0 };
|
|
2901
|
+
record.inflight = inflight;
|
|
2902
|
+
try {
|
|
2903
|
+
deliverPrompt(record.agent, message, false);
|
|
2904
|
+
} catch (error) {
|
|
2905
|
+
record.inflight = void 0;
|
|
2906
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
2907
|
+
throw internalError(`prompt was not queued: ${detail}`);
|
|
2908
|
+
}
|
|
2909
|
+
void record.agent.whenIdle().then(() => {
|
|
2910
|
+
if (record.inflight !== inflight) return;
|
|
2911
|
+
record.inflight = void 0;
|
|
2912
|
+
if (record.cancelled) {
|
|
2913
|
+
resolve("cancelled");
|
|
2914
|
+
return;
|
|
2915
|
+
}
|
|
2916
|
+
const end = inflight.turn !== void 0 ? record.projection.turnEndFor(inflight.turn) ?? record.projection.lastTurnEnd : record.projection.lastTurnEnd;
|
|
2917
|
+
resolve(end === void 0 ? "cancelled" : turnEndToStopReason(end));
|
|
2918
|
+
});
|
|
2919
|
+
});
|
|
2920
|
+
const usage = record.projection.promptUsage();
|
|
2921
|
+
return { stopReason, ...usage !== void 0 ? { usage } : {} };
|
|
2922
|
+
},
|
|
2923
|
+
async setSessionMode(params) {
|
|
2924
|
+
const record = await requireOrRestoreSession(params.sessionId);
|
|
2925
|
+
applyMode(record, params.sessionId, params.modeId);
|
|
2926
|
+
notify(params.sessionId, {
|
|
2927
|
+
sessionUpdate: "config_option_update",
|
|
2928
|
+
configOptions: await configOptions(record)
|
|
2929
|
+
});
|
|
2930
|
+
return {};
|
|
2931
|
+
},
|
|
2932
|
+
async setSessionConfigOption(params) {
|
|
2933
|
+
const record = await requireOrRestoreSession(params.sessionId);
|
|
2934
|
+
const value = typeof params.value === "string" ? params.value : void 0;
|
|
2935
|
+
if (value === void 0) throw invalidParams(`invalid value: ${String(params.value)}`);
|
|
2936
|
+
switch (params.configId) {
|
|
2937
|
+
case "mode": {
|
|
2938
|
+
applyMode(record, params.sessionId, value);
|
|
2939
|
+
break;
|
|
2940
|
+
}
|
|
2941
|
+
case "agent":
|
|
2942
|
+
case "preset": {
|
|
2943
|
+
await switchPreset(record, params.sessionId, value);
|
|
2944
|
+
break;
|
|
2945
|
+
}
|
|
2946
|
+
case "effort": {
|
|
2947
|
+
const route = routeOf(record);
|
|
2948
|
+
const catalog = await effortCatalog(route.provider, route.model);
|
|
2949
|
+
if (catalog === void 0 || !catalog.efforts.some((effort) => effort.id === value)) {
|
|
2950
|
+
throw invalidParams(`unknown effort: ${value}`);
|
|
2951
|
+
}
|
|
2952
|
+
const selection = ensureSelection(record);
|
|
2953
|
+
if (selection === void 0 || route.provider === void 0 || route.model === void 0) {
|
|
2954
|
+
throw invalidParams("reasoning effort switching is unavailable on this host");
|
|
2955
|
+
}
|
|
2956
|
+
selection.current = {
|
|
2957
|
+
provider: route.provider,
|
|
2958
|
+
model: route.model,
|
|
2959
|
+
reasoningEffort: value
|
|
2960
|
+
};
|
|
2961
|
+
record.effort = value;
|
|
2962
|
+
break;
|
|
2963
|
+
}
|
|
2964
|
+
case "collaboration_mode": {
|
|
2965
|
+
if (value !== "default" && value !== "plan") {
|
|
2966
|
+
throw invalidParams(`unknown collaboration mode: ${value}`);
|
|
2967
|
+
}
|
|
2968
|
+
const execution = await commandRuntime.execute(
|
|
2969
|
+
record.agent,
|
|
2970
|
+
value === "plan" ? "/plan" : "/plan off",
|
|
2971
|
+
new AbortController().signal
|
|
2972
|
+
);
|
|
2973
|
+
if (execution === void 0) {
|
|
2974
|
+
throw invalidParams("plan mode is unavailable on this session");
|
|
2975
|
+
}
|
|
2976
|
+
if (execution.result.kind === "error") {
|
|
2977
|
+
throw invalidParams(execution.result.text);
|
|
2978
|
+
}
|
|
2979
|
+
break;
|
|
2980
|
+
}
|
|
2981
|
+
case "model": {
|
|
2982
|
+
await switchModel(record, params.sessionId, value);
|
|
2983
|
+
break;
|
|
2984
|
+
}
|
|
2985
|
+
default:
|
|
2986
|
+
throw invalidParams(`unknown config option: ${params.configId}`);
|
|
2987
|
+
}
|
|
2988
|
+
return { configOptions: await configOptions(record) };
|
|
2989
|
+
},
|
|
2990
|
+
async closeSession(params) {
|
|
2991
|
+
const record = sessions.get(params.sessionId);
|
|
2992
|
+
if (record === void 0) return;
|
|
2993
|
+
sessions.delete(params.sessionId);
|
|
2994
|
+
record.agent.cancel({ kind: "user" });
|
|
2995
|
+
settlePrompt(record, "cancelled");
|
|
2996
|
+
await record.dispose().catch((error) => {
|
|
2997
|
+
logWarn(`session close failed: ${String(error)}`);
|
|
2998
|
+
});
|
|
2999
|
+
},
|
|
3000
|
+
cancel(params) {
|
|
3001
|
+
const record = sessions.get(params.sessionId);
|
|
3002
|
+
if (record === void 0) return Promise.resolve();
|
|
3003
|
+
record.cancelled = true;
|
|
3004
|
+
record.agent.cancel({ kind: "user" });
|
|
3005
|
+
settlePrompt(record, "cancelled");
|
|
3006
|
+
return Promise.resolve();
|
|
3007
|
+
}
|
|
3008
|
+
};
|
|
3009
|
+
};
|
|
3010
|
+
function requirePersistence() {
|
|
3011
|
+
return sessionPersistence;
|
|
3012
|
+
}
|
|
3013
|
+
function validateCwd(cwd) {
|
|
3014
|
+
if (!isAbsolute(cwd)) throw invalidParams(`cwd must be an absolute path: ${cwd}`);
|
|
3015
|
+
}
|
|
3016
|
+
const mcpMounts = /* @__PURE__ */ new Map();
|
|
3017
|
+
let warnedNoMcpClient = false;
|
|
3018
|
+
const sanitizeServerName = (raw2) => {
|
|
3019
|
+
const cleaned = raw2.replace(/[^A-Za-z0-9_-]/g, "_").slice(0, 32);
|
|
3020
|
+
return cleaned.length > 0 ? cleaned : "server";
|
|
3021
|
+
};
|
|
3022
|
+
const mcpConfigFor = (server, cwd, serverName) => {
|
|
3023
|
+
const envList = (list) => Object.fromEntries(
|
|
3024
|
+
(Array.isArray(list) ? list : []).map((e) => [
|
|
3025
|
+
e.name,
|
|
3026
|
+
e.value
|
|
3027
|
+
])
|
|
3028
|
+
);
|
|
3029
|
+
if (typeof server["command"] === "string") {
|
|
3030
|
+
return {
|
|
3031
|
+
transport: "stdio",
|
|
3032
|
+
serverName,
|
|
3033
|
+
command: server["command"],
|
|
3034
|
+
args: Array.isArray(server["args"]) ? server["args"] : [],
|
|
3035
|
+
env: envList(server["env"]),
|
|
3036
|
+
cwd: typeof server["cwd"] === "string" ? server["cwd"] : cwd,
|
|
3037
|
+
// A dead or misconfigured server must not take the session
|
|
3038
|
+
// down; the client surfaces missing tools on its own.
|
|
3039
|
+
failOnStartupError: false
|
|
3040
|
+
};
|
|
3041
|
+
}
|
|
3042
|
+
if (server["type"] === "http" && typeof server["url"] === "string") {
|
|
3043
|
+
return {
|
|
3044
|
+
transport: "streamable-http",
|
|
3045
|
+
serverName,
|
|
3046
|
+
url: server["url"],
|
|
3047
|
+
headers: envList(server["headers"]),
|
|
3048
|
+
failOnStartupError: false
|
|
3049
|
+
};
|
|
3050
|
+
}
|
|
3051
|
+
return void 0;
|
|
3052
|
+
};
|
|
3053
|
+
const syncMcpServers = (servers, cwd) => {
|
|
3054
|
+
if (servers === void 0 || servers.length === 0) return;
|
|
3055
|
+
const mcpClient2 = harness.mcpClient;
|
|
3056
|
+
if (mcpClient2 === void 0) {
|
|
3057
|
+
if (!warnedNoMcpClient) {
|
|
3058
|
+
warnedNoMcpClient = true;
|
|
3059
|
+
logWarn(
|
|
3060
|
+
`ignoring ${servers.length} MCP server(s): this DeepSeek Harness installation lacks @deepseek-ai/dsh-mcp-client`
|
|
3061
|
+
);
|
|
3062
|
+
}
|
|
3063
|
+
return;
|
|
3064
|
+
}
|
|
3065
|
+
const taken = /* @__PURE__ */ new Set();
|
|
3066
|
+
for (const entry of servers) {
|
|
3067
|
+
const server = entry;
|
|
3068
|
+
const base = sanitizeServerName(typeof server["name"] === "string" ? server["name"] : "server");
|
|
3069
|
+
let serverName = base;
|
|
3070
|
+
for (let n = 2; taken.has(serverName); n += 1) serverName = `${base.slice(0, 28)}_${n}`;
|
|
3071
|
+
const cfg = mcpConfigFor(server, cwd, serverName);
|
|
3072
|
+
if (cfg === void 0) {
|
|
3073
|
+
logWarn(`skipping MCP server "${serverName}": unsupported transport`);
|
|
3074
|
+
continue;
|
|
3075
|
+
}
|
|
3076
|
+
const configJson = JSON.stringify(cfg);
|
|
3077
|
+
const existing = mcpMounts.get(serverName);
|
|
3078
|
+
if (existing !== void 0) {
|
|
3079
|
+
if (existing.configJson === configJson) {
|
|
3080
|
+
taken.add(serverName);
|
|
3081
|
+
continue;
|
|
3082
|
+
}
|
|
3083
|
+
try {
|
|
3084
|
+
void existing.fiber.dispose();
|
|
3085
|
+
} catch (error) {
|
|
3086
|
+
logWarn(`disposing MCP server "${serverName}": ${String(error)}`);
|
|
3087
|
+
}
|
|
3088
|
+
mcpMounts.delete(serverName);
|
|
3089
|
+
}
|
|
3090
|
+
try {
|
|
3091
|
+
const fiber = ctx.plugin(mcpClient2, cfg);
|
|
3092
|
+
mcpMounts.set(serverName, { configJson, fiber });
|
|
3093
|
+
taken.add(serverName);
|
|
3094
|
+
logDebug(`mounted MCP server "${serverName}" (${String(cfg["transport"])})`);
|
|
3095
|
+
} catch (error) {
|
|
3096
|
+
logWarn(`failed to mount MCP server "${serverName}": ${String(error)}`);
|
|
3097
|
+
}
|
|
3098
|
+
}
|
|
3099
|
+
};
|
|
3100
|
+
const raw = config.stream ?? ndJsonStream(
|
|
3101
|
+
Writable.toWeb(process.stdout),
|
|
3102
|
+
Readable.toWeb(process.stdin)
|
|
3103
|
+
);
|
|
3104
|
+
const stream = muxAcpStream(raw, rpc);
|
|
3105
|
+
conn = new AgentSideConnection(makeAgent, stream);
|
|
3106
|
+
const disposeUserQuestions = installAcpUserQuestionProvider(userQuestions, {
|
|
3107
|
+
formSupported: () => clientElicitationForm,
|
|
3108
|
+
sessionIdForRequest: (request) => {
|
|
3109
|
+
if (request.agent === void 0) return void 0;
|
|
3110
|
+
const record = ownedRecord(request.agent);
|
|
3111
|
+
return record === void 0 ? void 0 : String(record.agent.session.id);
|
|
3112
|
+
},
|
|
3113
|
+
create: (request) => conn.unstable_createElicitation(request)
|
|
3114
|
+
});
|
|
3115
|
+
let quiescing;
|
|
3116
|
+
const quiesce = () => {
|
|
3117
|
+
if (quiescing !== void 0) return quiescing;
|
|
3118
|
+
closed = true;
|
|
3119
|
+
const records = [...sessions.values()];
|
|
3120
|
+
sessions.clear();
|
|
3121
|
+
subagentByChild.clear();
|
|
3122
|
+
subagentByRun.clear();
|
|
3123
|
+
for (const record of records) {
|
|
3124
|
+
record.agent.cancel({ kind: "user" });
|
|
3125
|
+
settlePrompt(record, "cancelled");
|
|
3126
|
+
}
|
|
3127
|
+
quiescing = (async () => {
|
|
3128
|
+
try {
|
|
3129
|
+
await subagents.drainContinuableDescendants(records.map((record) => record.agent));
|
|
3130
|
+
} catch (error) {
|
|
3131
|
+
logWarn(`continuable subagent teardown failed: ${String(error)}`);
|
|
3132
|
+
}
|
|
3133
|
+
const disposals = await Promise.allSettled(records.map((record) => record.dispose()));
|
|
3134
|
+
const failures = disposals.filter(
|
|
3135
|
+
(result) => result.status === "rejected"
|
|
3136
|
+
);
|
|
3137
|
+
if (failures.length > 0) {
|
|
3138
|
+
const detail = failures.map((failure) => errorChain2(failure.reason)).join("; ");
|
|
3139
|
+
throw new AggregateError(
|
|
3140
|
+
failures.map((failure) => failure.reason),
|
|
3141
|
+
`ACP agent teardown failed for ${failures.length} session(s): ${detail}`
|
|
3142
|
+
);
|
|
3143
|
+
}
|
|
3144
|
+
})();
|
|
3145
|
+
return quiescing;
|
|
3146
|
+
};
|
|
3147
|
+
void conn.closed.catch((error) => {
|
|
3148
|
+
logWarn(`connection closed with an error: ${String(error)}`);
|
|
3149
|
+
}).then(quiesce).catch((error) => {
|
|
3150
|
+
logWarn(`connection-close teardown failed: ${String(error)}`);
|
|
3151
|
+
});
|
|
3152
|
+
ctx.effect(() => quiesce, "acp-bridge.connection");
|
|
3153
|
+
ctx.effect(() => disposeUserQuestions, "acp-bridge.user-questions");
|
|
3154
|
+
}
|
|
3155
|
+
|
|
3156
|
+
// src/server.ts
|
|
3157
|
+
var server_exports = {};
|
|
3158
|
+
__export(server_exports, {
|
|
3159
|
+
apply: () => apply2,
|
|
3160
|
+
inject: () => inject2,
|
|
3161
|
+
name: () => name2
|
|
3162
|
+
});
|
|
3163
|
+
init_self_harness();
|
|
3164
|
+
var name2 = "acp-server";
|
|
3165
|
+
var inject2 = inject;
|
|
3166
|
+
function apply2(ctx, config = {}) {
|
|
3167
|
+
if (ctx.get("acpServer") !== void 0) return;
|
|
3168
|
+
const harness = selfHarness();
|
|
3169
|
+
const server = {
|
|
3170
|
+
connect(stream) {
|
|
3171
|
+
return ctx.plugin(bridge_exports, { ...config, stream, harness });
|
|
3172
|
+
}
|
|
3173
|
+
};
|
|
3174
|
+
ctx.provide("acpServer", server);
|
|
3175
|
+
}
|
|
3176
|
+
|
|
3177
|
+
// src/plugin.ts
|
|
3178
|
+
var name3 = "dsh-acp-plugin";
|
|
3179
|
+
var inject3 = [.../* @__PURE__ */ new Set([...inject.filter((service) => service !== "agentPresets"), "loader"])];
|
|
3180
|
+
function shippedPresetRoot(ctx) {
|
|
3181
|
+
const anchors = [ctx.baseUrl, import.meta.url].filter((value) => typeof value === "string");
|
|
3182
|
+
for (const anchor of anchors) {
|
|
3183
|
+
try {
|
|
3184
|
+
const manifest = createRequire2(anchor).resolve("@deepseek-ai/dsh/package.json");
|
|
3185
|
+
const root = join(dirname(manifest), "config", "agent-presets");
|
|
3186
|
+
if (existsSync(root)) return root;
|
|
3187
|
+
} catch {
|
|
3188
|
+
}
|
|
3189
|
+
}
|
|
3190
|
+
throw new Error("dsh-acp-plugin: cannot resolve the dsh shipped agent presets");
|
|
3191
|
+
}
|
|
3192
|
+
async function mountService(ctx, service, specifier, config) {
|
|
3193
|
+
if (ctx.get(service) !== void 0) return;
|
|
3194
|
+
const exports = await ctx.loader.import(specifier);
|
|
3195
|
+
const plugin = ctx.loader.unwrapExports(exports);
|
|
3196
|
+
await ctx.plugin(plugin, config);
|
|
3197
|
+
if (ctx.get(service) === void 0) {
|
|
3198
|
+
throw new Error(`dsh-acp-plugin: ${specifier} did not provide ${service}`);
|
|
3199
|
+
}
|
|
3200
|
+
}
|
|
3201
|
+
async function apply3(ctx, config = {}) {
|
|
3202
|
+
if (ctx.get("acpServer") !== void 0) return;
|
|
3203
|
+
await mountService(ctx, "agentPresets", "@deepseek-ai/dsh-agent-presets", {
|
|
3204
|
+
default: "standard",
|
|
3205
|
+
roots: [{ path: shippedPresetRoot(ctx), trust: "system" }]
|
|
3206
|
+
});
|
|
3207
|
+
await mountService(ctx, "dynamicCordisRunner", "@deepseek-ai/dsh-cordis-host-runner");
|
|
3208
|
+
await ctx.plugin(server_exports, config);
|
|
3209
|
+
if (ctx.get("acpServer") === void 0) {
|
|
3210
|
+
throw new Error("dsh-acp-plugin: ACP server did not activate");
|
|
3211
|
+
}
|
|
3212
|
+
}
|
|
3213
|
+
export {
|
|
3214
|
+
apply3 as apply,
|
|
3215
|
+
inject3 as inject,
|
|
3216
|
+
name3 as name
|
|
3217
|
+
};
|