@openma/deepseek-harness-acp 0.4.5 → 0.4.6
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 +144 -108
- package/assets/icon.svg +8 -0
- package/cordis.patch.yml +7 -1
- package/dist/bridge.js +460 -252
- package/dist/index.js +92 -2208
- package/package.json +5 -4
package/dist/bridge.js
CHANGED
|
@@ -22,7 +22,6 @@ import { createUserMessage, errorChain } from "@deepseek-ai/dsh-llm";
|
|
|
22
22
|
import { SessionId } from "@deepseek-ai/dsh-session";
|
|
23
23
|
import { foldSessionTitle } from "@deepseek-ai/dsh-session-title";
|
|
24
24
|
import { SANDBOX_MODES, setSandboxMode } from "@deepseek-ai/dsh-sandbox-policy";
|
|
25
|
-
import { credentialRef } from "@deepseek-ai/dsh-credentials";
|
|
26
25
|
import * as agentModule from "@deepseek-ai/dsh-agent";
|
|
27
26
|
import * as mcpClient from "@deepseek-ai/dsh-mcp-client";
|
|
28
27
|
function selfHarness() {
|
|
@@ -35,7 +34,6 @@ function selfHarness() {
|
|
|
35
34
|
foldSessionTitle,
|
|
36
35
|
setSandboxMode,
|
|
37
36
|
sandboxModes: SANDBOX_MODES,
|
|
38
|
-
credentialRef,
|
|
39
37
|
...installModelSelection !== void 0 ? { installModelSelection } : {},
|
|
40
38
|
mcpClient
|
|
41
39
|
};
|
|
@@ -56,11 +54,245 @@ import {
|
|
|
56
54
|
PROTOCOL_VERSION,
|
|
57
55
|
RequestError
|
|
58
56
|
} from "@agentclientprotocol/sdk";
|
|
57
|
+
import { credentialRef } from "@deepseek-ai/dsh-credentials";
|
|
59
58
|
|
|
60
59
|
// src/version.ts
|
|
61
60
|
import { createRequire } from "node:module";
|
|
62
61
|
var VERSION = createRequire(import.meta.url)("../package.json").version;
|
|
63
62
|
|
|
63
|
+
// src/auth.ts
|
|
64
|
+
var LOGIN_DESCRIPTION = "Save an API key to the harness credential store shared with the dsh Web UI";
|
|
65
|
+
function isDeepseekRoute(provider) {
|
|
66
|
+
return provider === void 0 || provider === "deepseek-official" || provider === "deepseek";
|
|
67
|
+
}
|
|
68
|
+
function credentialEnvNames(provider) {
|
|
69
|
+
if (provider === void 0 || isDeepseekRoute(provider)) return ["DEEPSEEK_API_KEY", "DEEPSEEK_BASE_URL"];
|
|
70
|
+
if (provider === "anthropic") return ["ANTHROPIC_API_KEY"];
|
|
71
|
+
if (provider === "openai" || provider.startsWith("openai")) return ["OPENAI_API_KEY"];
|
|
72
|
+
return [`${provider.replace(/[^a-zA-Z0-9]+/g, "_").toUpperCase()}_API_KEY`];
|
|
73
|
+
}
|
|
74
|
+
function primaryCredentialName(provider) {
|
|
75
|
+
return credentialEnvNames(provider)[0] ?? "DEEPSEEK_API_KEY";
|
|
76
|
+
}
|
|
77
|
+
function providerFromAuthMethodId(methodId) {
|
|
78
|
+
if (!methodId) return void 0;
|
|
79
|
+
for (const prefix of ["api-key:", "terminal-login:"]) {
|
|
80
|
+
if (methodId.startsWith(prefix)) return methodId.slice(prefix.length);
|
|
81
|
+
}
|
|
82
|
+
return void 0;
|
|
83
|
+
}
|
|
84
|
+
function apiKeyFromAuthenticate(params) {
|
|
85
|
+
const meta = params._meta;
|
|
86
|
+
const block = meta && typeof meta === "object" && !Array.isArray(meta) ? meta["api-key"] : void 0;
|
|
87
|
+
const record = block && typeof block === "object" && !Array.isArray(block) ? block : void 0;
|
|
88
|
+
const key = typeof record?.apiKey === "string" ? record.apiKey.trim() : void 0;
|
|
89
|
+
const providerFromMeta = typeof record?.provider === "string" ? record.provider : void 0;
|
|
90
|
+
const provider = providerFromMeta || providerFromAuthMethodId(params.methodId);
|
|
91
|
+
return {
|
|
92
|
+
...provider && provider.length > 0 ? { provider } : {},
|
|
93
|
+
...key && key.length > 0 ? { key } : {}
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function gatewayFromAuthenticate(params) {
|
|
97
|
+
const meta = params._meta;
|
|
98
|
+
const block = meta && typeof meta === "object" && !Array.isArray(meta) ? meta.gateway : void 0;
|
|
99
|
+
const record = block && typeof block === "object" && !Array.isArray(block) ? block : void 0;
|
|
100
|
+
const baseUrl = typeof record?.baseUrl === "string" ? record.baseUrl.trim() : void 0;
|
|
101
|
+
const headers = stringRecord(record?.headers);
|
|
102
|
+
const providerName = typeof record?.providerName === "string" ? record.providerName.trim() : void 0;
|
|
103
|
+
const key = apiKeyFromHeaders(headers);
|
|
104
|
+
return {
|
|
105
|
+
...baseUrl && baseUrl.length > 0 ? { baseUrl } : {},
|
|
106
|
+
...headers ? { headers } : {},
|
|
107
|
+
...providerName && providerName.length > 0 ? { providerName } : {},
|
|
108
|
+
...key && key.length > 0 ? { key } : {}
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function isBrowserAuthMethod(methodId) {
|
|
112
|
+
return methodId === "browser";
|
|
113
|
+
}
|
|
114
|
+
function isGatewayAuthMethod(methodId) {
|
|
115
|
+
return methodId === "gateway";
|
|
116
|
+
}
|
|
117
|
+
function credentialBaseUrlName(provider) {
|
|
118
|
+
if (isDeepseekRoute(provider) || provider === void 0) return "DEEPSEEK_BASE_URL";
|
|
119
|
+
if (provider === "anthropic") return "ANTHROPIC_BASE_URL";
|
|
120
|
+
if (provider === "openai" || provider.startsWith("openai")) return "OPENAI_BASE_URL";
|
|
121
|
+
return `${provider.replace(/[^a-zA-Z0-9]+/g, "_").toUpperCase()}_BASE_URL`;
|
|
122
|
+
}
|
|
123
|
+
function advertisedAuthMethods(providers, clientCapabilities, env = process.env) {
|
|
124
|
+
const methods = [...apiKeyAuthMethods(providers)];
|
|
125
|
+
if (shouldOfferLocalAuthPage(env)) methods.push(browserAuthMethod());
|
|
126
|
+
if (clientCapabilities?.auth?._meta?.["gateway"] === true) methods.push(gatewayAuthMethod());
|
|
127
|
+
return methods;
|
|
128
|
+
}
|
|
129
|
+
function apiKeyAuthMethods(providers) {
|
|
130
|
+
if (providers.length <= 1) {
|
|
131
|
+
const provider = providers[0];
|
|
132
|
+
return [apiKeyAuthMethod(provider?.id, provider?.name, providers.length === 0)];
|
|
133
|
+
}
|
|
134
|
+
return providers.map((provider) => apiKeyAuthMethod(provider.id, provider.name, false));
|
|
135
|
+
}
|
|
136
|
+
function apiKeyAuthMethod(provider, name3, unscoped) {
|
|
137
|
+
const scoped = !unscoped && provider !== void 0;
|
|
138
|
+
return {
|
|
139
|
+
id: scoped ? `api-key:${provider}` : "api-key",
|
|
140
|
+
name: scoped ? `${name3 ?? provider} API key` : "API Key",
|
|
141
|
+
description: LOGIN_DESCRIPTION,
|
|
142
|
+
_meta: { "api-key": scoped ? { provider } : {} }
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function shouldOfferLocalAuthPage(env = process.env) {
|
|
146
|
+
if (env["NO_BROWSER"]) return false;
|
|
147
|
+
if (env["NODE_ENV"] === "test" || env["CI"] === "true" || env["CI"] === "1") return false;
|
|
148
|
+
if (env["DSH_ACP_AUTH_PAGE"] === "0") return false;
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
function browserAuthMethod() {
|
|
152
|
+
return {
|
|
153
|
+
id: "browser",
|
|
154
|
+
name: "Browser",
|
|
155
|
+
description: "Open a local sign-in page to save an API key. The secret never travels over ACP."
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
function gatewayAuthMethod() {
|
|
159
|
+
return {
|
|
160
|
+
id: "gateway",
|
|
161
|
+
name: "Custom model gateway",
|
|
162
|
+
description: "Use a custom OpenAI-compatible gateway",
|
|
163
|
+
_meta: { gateway: { protocol: "openai", restartRequired: "false" } }
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
function stringRecord(value) {
|
|
167
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
168
|
+
const entries = Object.entries(value).filter(
|
|
169
|
+
(entry) => typeof entry[1] === "string" && entry[1].trim().length > 0
|
|
170
|
+
);
|
|
171
|
+
return entries.length > 0 ? Object.fromEntries(entries) : void 0;
|
|
172
|
+
}
|
|
173
|
+
function apiKeyFromHeaders(headers) {
|
|
174
|
+
if (!headers) return void 0;
|
|
175
|
+
const authorization = headers["Authorization"] ?? headers["authorization"];
|
|
176
|
+
if (authorization) {
|
|
177
|
+
const bearer = authorization.match(/^Bearer\s+(.+)$/i);
|
|
178
|
+
const token = (bearer?.[1] ?? authorization).trim();
|
|
179
|
+
if (token.length > 0) return token;
|
|
180
|
+
}
|
|
181
|
+
const first = Object.values(headers).find((value) => value.trim().length > 0);
|
|
182
|
+
return first?.trim();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// src/auth-page.ts
|
|
186
|
+
import { createServer } from "node:http";
|
|
187
|
+
import { execFile } from "node:child_process";
|
|
188
|
+
function startLocalAuthPage(options) {
|
|
189
|
+
return new Promise((resolve, reject) => {
|
|
190
|
+
const server = createServer((request, response) => {
|
|
191
|
+
void handle(request, response);
|
|
192
|
+
});
|
|
193
|
+
let settle;
|
|
194
|
+
let fail;
|
|
195
|
+
const completed = new Promise((res, rej) => {
|
|
196
|
+
settle = res;
|
|
197
|
+
fail = rej;
|
|
198
|
+
});
|
|
199
|
+
const timeoutMs = options.timeoutMs ?? 10 * 6e4;
|
|
200
|
+
const timer = setTimeout(() => {
|
|
201
|
+
server.close();
|
|
202
|
+
fail?.(new Error("local auth page timed out"));
|
|
203
|
+
}, timeoutMs);
|
|
204
|
+
timer.unref?.();
|
|
205
|
+
const handle = async (request, response) => {
|
|
206
|
+
if (request.method === "GET" || request.method === void 0) {
|
|
207
|
+
response.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
208
|
+
response.end(authPageHtml(options.credentialName));
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
if (request.method === "POST") {
|
|
212
|
+
const body = await readBody(request);
|
|
213
|
+
const key = new URLSearchParams(body).get("api-key")?.trim() ?? "";
|
|
214
|
+
if (key.length === 0) {
|
|
215
|
+
response.writeHead(400, { "content-type": "text/html; charset=utf-8" });
|
|
216
|
+
response.end(authPageHtml(options.credentialName, "Enter an API key."));
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
response.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
220
|
+
response.end(
|
|
221
|
+
"<!doctype html><html><body><p>Saved. You can return to the client.</p></body></html>"
|
|
222
|
+
);
|
|
223
|
+
clearTimeout(timer);
|
|
224
|
+
server.close();
|
|
225
|
+
settle?.(key);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
response.writeHead(405);
|
|
229
|
+
response.end();
|
|
230
|
+
};
|
|
231
|
+
server.once("error", (error) => {
|
|
232
|
+
clearTimeout(timer);
|
|
233
|
+
reject(error);
|
|
234
|
+
});
|
|
235
|
+
server.listen(0, "127.0.0.1", () => {
|
|
236
|
+
const address = server.address();
|
|
237
|
+
if (!address || typeof address === "string") {
|
|
238
|
+
server.close();
|
|
239
|
+
reject(new Error("local auth page failed to bind"));
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
const url = `http://127.0.0.1:${address.port}/`;
|
|
243
|
+
resolve({
|
|
244
|
+
url,
|
|
245
|
+
close: () => {
|
|
246
|
+
clearTimeout(timer);
|
|
247
|
+
server.close();
|
|
248
|
+
},
|
|
249
|
+
completed
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
function openLocalAuthPage(url) {
|
|
255
|
+
const platform = process.platform;
|
|
256
|
+
if (platform === "darwin") {
|
|
257
|
+
execFile("open", [url], () => void 0);
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
if (platform === "win32") {
|
|
261
|
+
execFile("cmd", ["/c", "start", "", url], () => void 0);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
execFile("xdg-open", [url], () => void 0);
|
|
265
|
+
}
|
|
266
|
+
function authPageHtml(credentialName, error) {
|
|
267
|
+
const message = error ? `<p>${escapeHtml(error)}</p>` : "";
|
|
268
|
+
return `<!doctype html>
|
|
269
|
+
<html lang="en">
|
|
270
|
+
<head><meta charset="utf-8"><title>DeepSeek Harness sign in</title></head>
|
|
271
|
+
<body>
|
|
272
|
+
<h1>Save API key</h1>
|
|
273
|
+
<p>Stored as ${escapeHtml(credentialName)} in the harness credential store.</p>
|
|
274
|
+
${message}
|
|
275
|
+
<form method="post">
|
|
276
|
+
<label>API key <input type="password" name="api-key" autocomplete="off" required></label>
|
|
277
|
+
<button type="submit">Save</button>
|
|
278
|
+
</form>
|
|
279
|
+
</body>
|
|
280
|
+
</html>`;
|
|
281
|
+
}
|
|
282
|
+
function escapeHtml(value) {
|
|
283
|
+
return value.replace(/[&<>"']/g, (char) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[char] ?? char);
|
|
284
|
+
}
|
|
285
|
+
function readBody(request) {
|
|
286
|
+
return new Promise((resolve, reject) => {
|
|
287
|
+
const chunks = [];
|
|
288
|
+
request.on("data", (chunk) => {
|
|
289
|
+
chunks.push(Buffer.from(chunk));
|
|
290
|
+
});
|
|
291
|
+
request.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
|
|
292
|
+
request.on("error", reject);
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
|
|
64
296
|
// src/log.ts
|
|
65
297
|
var PREFIX = "[dsh-acp]";
|
|
66
298
|
function logWarn(message) {
|
|
@@ -604,7 +836,7 @@ ${contextBlock(resource.uri, resource.text)}
|
|
|
604
836
|
|
|
605
837
|
// src/bridge/index.ts
|
|
606
838
|
var name = "acp-bridge";
|
|
607
|
-
var inject = ["agents"];
|
|
839
|
+
var inject = ["agents", "credentials", "llm", "agentDefaultModel", "sessionPersistence", "approval", "permissionPresets", "commands", "agentPresets", "skills", "subagents"];
|
|
608
840
|
function invalidParams(detail) {
|
|
609
841
|
return RequestError.invalidParams(void 0, detail);
|
|
610
842
|
}
|
|
@@ -614,9 +846,6 @@ function internalError(detail) {
|
|
|
614
846
|
function authRequired(detail) {
|
|
615
847
|
return RequestError.authRequired(void 0, detail);
|
|
616
848
|
}
|
|
617
|
-
function envCredentialPresent() {
|
|
618
|
-
return Boolean(process.env["DEEPSEEK_API_KEY"] || process.env["DEEPSEEK_BASE_URL"]);
|
|
619
|
-
}
|
|
620
849
|
var MODE_LABELS = {
|
|
621
850
|
"read-only": { name: "Read-only", description: "Bash and file mutations are denied by the sandbox" },
|
|
622
851
|
"workspace-write": {
|
|
@@ -630,6 +859,16 @@ var MODE_LABELS = {
|
|
|
630
859
|
};
|
|
631
860
|
async function apply(ctx, config = {}) {
|
|
632
861
|
const agents = ctx.agents;
|
|
862
|
+
const credentials = ctx.credentials;
|
|
863
|
+
const llm = ctx.llm;
|
|
864
|
+
const agentDefaultModel = ctx.agentDefaultModel;
|
|
865
|
+
const sessionPersistence = ctx.sessionPersistence;
|
|
866
|
+
const approval = ctx.approval;
|
|
867
|
+
const permissionPresets = ctx.permissionPresets;
|
|
868
|
+
const commandRuntime = ctx.commands;
|
|
869
|
+
const agentPresets = ctx.agentPresets;
|
|
870
|
+
const skillRegistry = ctx.skills;
|
|
871
|
+
const subagents = ctx.subagents;
|
|
633
872
|
const harness = config.harness ?? (await Promise.resolve().then(() => (init_self_harness(), self_harness_exports))).selfHarness();
|
|
634
873
|
const { createUserMessage: createUserMessage2, errorChain: errorChain2, sessionId: SessionId2, foldSessionTitle: foldSessionTitle2, setSandboxMode: setSandboxMode2 } = harness;
|
|
635
874
|
const SANDBOX_MODES2 = harness.sandboxModes;
|
|
@@ -644,10 +883,8 @@ async function apply(ctx, config = {}) {
|
|
|
644
883
|
return [...seen];
|
|
645
884
|
};
|
|
646
885
|
const defaultSelection = () => {
|
|
647
|
-
const service = ctx.get("agentDefaultModel");
|
|
648
|
-
if (service === void 0) return {};
|
|
649
886
|
try {
|
|
650
|
-
return
|
|
887
|
+
return agentDefaultModel.currentSelection();
|
|
651
888
|
} catch (error) {
|
|
652
889
|
logDebug(`agentDefaultModel.currentSelection failed: ${String(error)}`);
|
|
653
890
|
return {};
|
|
@@ -664,28 +901,25 @@ async function apply(ctx, config = {}) {
|
|
|
664
901
|
const effortCache = /* @__PURE__ */ new Map();
|
|
665
902
|
const discoverModels = async () => {
|
|
666
903
|
if (liveCatalog !== void 0) return liveCatalog;
|
|
667
|
-
const llm = ctx.get("llm");
|
|
668
904
|
const found = [];
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
905
|
+
try {
|
|
906
|
+
const providers = llm.listProviders();
|
|
907
|
+
const multi = providers.length > 1;
|
|
908
|
+
for (const provider of providers) {
|
|
909
|
+
const models = await llm.listModels(provider.id).catch((error) => {
|
|
910
|
+
logDebug(`listModels(${provider.id}) failed: ${String(error)}`);
|
|
911
|
+
return [];
|
|
912
|
+
});
|
|
913
|
+
for (const model of models) {
|
|
914
|
+
found.push({
|
|
915
|
+
provider: provider.id,
|
|
916
|
+
model: model.id,
|
|
917
|
+
label: multi ? `${model.name} (${provider.name})` : model.name
|
|
677
918
|
});
|
|
678
|
-
for (const model of models) {
|
|
679
|
-
found.push({
|
|
680
|
-
provider: provider.id,
|
|
681
|
-
model: model.id,
|
|
682
|
-
label: multi ? `${model.name} (${provider.name})` : model.name
|
|
683
|
-
});
|
|
684
|
-
}
|
|
685
919
|
}
|
|
686
|
-
} catch (error) {
|
|
687
|
-
logDebug(`model discovery failed: ${String(error)}`);
|
|
688
920
|
}
|
|
921
|
+
} catch (error) {
|
|
922
|
+
logDebug(`model discovery failed: ${String(error)}`);
|
|
689
923
|
}
|
|
690
924
|
liveCatalog = found;
|
|
691
925
|
return found;
|
|
@@ -721,10 +955,9 @@ async function apply(ctx, config = {}) {
|
|
|
721
955
|
if (provider === void 0 || model === void 0) return void 0;
|
|
722
956
|
const key = `${provider}::${model}`;
|
|
723
957
|
if (effortCache.has(key)) return effortCache.get(key);
|
|
724
|
-
const llm = ctx.get("llm");
|
|
725
958
|
let catalog;
|
|
726
959
|
try {
|
|
727
|
-
const reasoning = (await llm
|
|
960
|
+
const reasoning = (await llm.resolveModelInfo(provider, model)).reasoning;
|
|
728
961
|
if (reasoning !== void 0 && reasoning.efforts.length > 0) {
|
|
729
962
|
catalog = {
|
|
730
963
|
efforts: reasoning.efforts.map((effort) => ({
|
|
@@ -827,27 +1060,38 @@ async function apply(ctx, config = {}) {
|
|
|
827
1060
|
}
|
|
828
1061
|
setApprovalPolicy(record, record.approvals);
|
|
829
1062
|
};
|
|
830
|
-
const
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
1063
|
+
const listProviderRoutes = async () => {
|
|
1064
|
+
try {
|
|
1065
|
+
const raw = llm.listProviders();
|
|
1066
|
+
if (!Array.isArray(raw)) return [];
|
|
1067
|
+
return raw.flatMap((entry) => {
|
|
1068
|
+
if (!entry || typeof entry !== "object") return [];
|
|
1069
|
+
const id = entry.id;
|
|
1070
|
+
if (typeof id !== "string" || id.length === 0) return [];
|
|
1071
|
+
const name3 = entry.name;
|
|
1072
|
+
return [{ id, ...typeof name3 === "string" && name3.length > 0 ? { name: name3 } : {} }];
|
|
1073
|
+
});
|
|
1074
|
+
} catch (error) {
|
|
1075
|
+
logDebug(`listProviders failed: ${String(error)}`);
|
|
1076
|
+
return [];
|
|
1077
|
+
}
|
|
1078
|
+
};
|
|
1079
|
+
const credentialPresent = async (provider) => {
|
|
1080
|
+
const names = credentialEnvNames(provider).filter((name3) => !name3.endsWith("_BASE_URL"));
|
|
1081
|
+
for (const name3 of names) {
|
|
1082
|
+
try {
|
|
1083
|
+
if (await credentials.resolve(credentialRef(name3)) !== void 0) return true;
|
|
1084
|
+
} catch (error) {
|
|
1085
|
+
logWarn(`credential lookup failed: ${String(error)}`);
|
|
848
1086
|
}
|
|
849
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
850
1087
|
}
|
|
1088
|
+
return false;
|
|
1089
|
+
};
|
|
1090
|
+
const requireCredential = async (provider) => {
|
|
1091
|
+
if (await credentialPresent(provider)) return;
|
|
1092
|
+
throw authRequired(
|
|
1093
|
+
'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)'
|
|
1094
|
+
);
|
|
851
1095
|
};
|
|
852
1096
|
const agentOptionsFor = (model, provider) => ({
|
|
853
1097
|
...provider !== void 0 ? { provider } : config.provider !== void 0 ? { provider: config.provider } : {},
|
|
@@ -866,7 +1110,6 @@ async function apply(ctx, config = {}) {
|
|
|
866
1110
|
const requireOrRestoreSession = async (sessionId) => {
|
|
867
1111
|
const record = sessions.get(sessionId);
|
|
868
1112
|
if (record !== void 0) return record;
|
|
869
|
-
if (ctx.get("sessionPersistence") === void 0) throw invalidParams(`unknown session: ${sessionId}`);
|
|
870
1113
|
logWarn(`restoring session ${sessionId} from the persisted log`);
|
|
871
1114
|
try {
|
|
872
1115
|
return await restoreSession(sessionId, { replay: false });
|
|
@@ -906,7 +1149,7 @@ async function apply(ctx, config = {}) {
|
|
|
906
1149
|
}
|
|
907
1150
|
const presets = presetsService();
|
|
908
1151
|
const storedPreset = presetFromLog(storedHeader, events);
|
|
909
|
-
const presetId =
|
|
1152
|
+
const presetId = (await presets.resolve(storedPreset)).id;
|
|
910
1153
|
const handle = await agents.resume({
|
|
911
1154
|
resumeSessionId: SessionId2(sessionId),
|
|
912
1155
|
agentOptions: agentOptionsFor(config.model),
|
|
@@ -929,7 +1172,7 @@ async function apply(ctx, config = {}) {
|
|
|
929
1172
|
if (presetId !== void 0) record.preset = presetId;
|
|
930
1173
|
{
|
|
931
1174
|
const permission = permissionService();
|
|
932
|
-
const storedMode = permission
|
|
1175
|
+
const storedMode = permission.current(events);
|
|
933
1176
|
if (storedMode !== void 0) record.modeId = storedMode;
|
|
934
1177
|
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
935
1178
|
const event = events[index];
|
|
@@ -958,9 +1201,9 @@ async function apply(ctx, config = {}) {
|
|
|
958
1201
|
record.inflight = void 0;
|
|
959
1202
|
inflight.resolve(reason);
|
|
960
1203
|
};
|
|
961
|
-
const presetsService = () =>
|
|
1204
|
+
const presetsService = () => agentPresets;
|
|
962
1205
|
const presetSetup = (presets, presetId) => {
|
|
963
|
-
if (
|
|
1206
|
+
if (presetId === void 0) return void 0;
|
|
964
1207
|
return async (agentCtx) => {
|
|
965
1208
|
logDebug(`preset setup: mounting "${presetId}"`);
|
|
966
1209
|
await presets.mount(agentCtx, presetId);
|
|
@@ -979,7 +1222,7 @@ async function apply(ctx, config = {}) {
|
|
|
979
1222
|
const presetLabel = (id) => id.charAt(0).toUpperCase() + id.slice(1);
|
|
980
1223
|
const modeState = (record) => {
|
|
981
1224
|
const permission = permissionService();
|
|
982
|
-
if (permission
|
|
1225
|
+
if (permission.names.length > 0) {
|
|
983
1226
|
return {
|
|
984
1227
|
currentModeId: record.modeId,
|
|
985
1228
|
availableModes: permission.names.map((name3) => {
|
|
@@ -1001,7 +1244,6 @@ async function apply(ctx, config = {}) {
|
|
|
1001
1244
|
throw invalidParams("cannot switch presets while a prompt is running");
|
|
1002
1245
|
}
|
|
1003
1246
|
const presets = presetsService();
|
|
1004
|
-
if (presets === void 0) throw invalidParams(`unknown mode: ${presetId}`);
|
|
1005
1247
|
let resolved;
|
|
1006
1248
|
try {
|
|
1007
1249
|
resolved = (await presets.resolve(presetId)).id;
|
|
@@ -1009,29 +1251,44 @@ async function apply(ctx, config = {}) {
|
|
|
1009
1251
|
throw invalidParams(`unknown preset: ${presetId} (${errorChain2(error)})`);
|
|
1010
1252
|
}
|
|
1011
1253
|
if (resolved === record.preset) return;
|
|
1012
|
-
|
|
1013
|
-
record.agent.session.append("agent-preset/selected", { agentPreset: resolved });
|
|
1014
|
-
} catch (error) {
|
|
1015
|
-
logWarn(`recording preset selection failed: ${String(error)}`);
|
|
1016
|
-
}
|
|
1254
|
+
const previous = record.preset;
|
|
1017
1255
|
const sessionId = record.agent.session.id;
|
|
1256
|
+
const resumeWith = (id) => agents.resume({
|
|
1257
|
+
resumeSessionId: sessionId,
|
|
1258
|
+
agentOptions: agentOptionsFor(record.model ?? config.model, record.provider),
|
|
1259
|
+
...presetSetup(presets, id) !== void 0 ? { setup: presetSetup(presets, id) } : {}
|
|
1260
|
+
});
|
|
1018
1261
|
await record.dispose().catch((error) => {
|
|
1019
1262
|
logWarn(`dispose during preset switch failed: ${String(error)}`);
|
|
1020
1263
|
});
|
|
1021
1264
|
let handle;
|
|
1022
1265
|
try {
|
|
1023
|
-
handle = await
|
|
1024
|
-
resumeSessionId: sessionId,
|
|
1025
|
-
agentOptions: agentOptionsFor(record.model ?? config.model, record.provider),
|
|
1026
|
-
setup: presetSetup(presets, resolved)
|
|
1027
|
-
});
|
|
1266
|
+
handle = await resumeWith(resolved);
|
|
1028
1267
|
} catch (error) {
|
|
1029
|
-
|
|
1030
|
-
|
|
1268
|
+
try {
|
|
1269
|
+
handle = await resumeWith(previous);
|
|
1270
|
+
} catch (restoreError) {
|
|
1271
|
+
sessions.delete(acpSessionId);
|
|
1272
|
+
throw internalError(
|
|
1273
|
+
`preset switch failed: ${errorChain2(error)}; restore also failed: ${errorChain2(restoreError)}`
|
|
1274
|
+
);
|
|
1275
|
+
}
|
|
1276
|
+
record.agent = handle.agent;
|
|
1277
|
+
record.dispose = () => handle.dispose();
|
|
1278
|
+
delete record.selection;
|
|
1279
|
+
ensureSelection(record);
|
|
1280
|
+
setApprovalPolicy(record, record.approvals);
|
|
1281
|
+
publishCommands(acpSessionId, record);
|
|
1282
|
+
throw invalidParams(`preset "${resolved}" failed to mount: ${errorChain2(error)}`);
|
|
1031
1283
|
}
|
|
1032
1284
|
record.agent = handle.agent;
|
|
1033
1285
|
record.dispose = () => handle.dispose();
|
|
1034
1286
|
record.preset = resolved;
|
|
1287
|
+
try {
|
|
1288
|
+
handle.agent.session.append("agent-preset/selected", { agentPreset: resolved });
|
|
1289
|
+
} catch (error) {
|
|
1290
|
+
logWarn(`recording preset selection failed: ${String(error)}`);
|
|
1291
|
+
}
|
|
1035
1292
|
delete record.selection;
|
|
1036
1293
|
ensureSelection(record);
|
|
1037
1294
|
setApprovalPolicy(record, record.approvals);
|
|
@@ -1039,16 +1296,16 @@ async function apply(ctx, config = {}) {
|
|
|
1039
1296
|
};
|
|
1040
1297
|
const setApprovalPolicy = (record, policy) => {
|
|
1041
1298
|
try {
|
|
1042
|
-
|
|
1299
|
+
approval.setPolicy(record.agent, policy);
|
|
1043
1300
|
} catch (error) {
|
|
1044
1301
|
logWarn(`approval policy switch failed: ${String(error)}`);
|
|
1045
1302
|
}
|
|
1046
1303
|
record.approvals = policy;
|
|
1047
1304
|
};
|
|
1048
|
-
const permissionService = () =>
|
|
1305
|
+
const permissionService = () => permissionPresets;
|
|
1049
1306
|
const applyMode = (record, sessionId, modeId) => {
|
|
1050
1307
|
const permission = permissionService();
|
|
1051
|
-
if (permission
|
|
1308
|
+
if (permission.names.includes(modeId)) {
|
|
1052
1309
|
let bundledApproval = record.approvals;
|
|
1053
1310
|
permission.apply(record.agent.session, modeId, (policy) => {
|
|
1054
1311
|
bundledApproval = policy;
|
|
@@ -1090,7 +1347,7 @@ async function apply(ctx, config = {}) {
|
|
|
1090
1347
|
const result = [];
|
|
1091
1348
|
{
|
|
1092
1349
|
const permission = permissionService();
|
|
1093
|
-
const levels = permission
|
|
1350
|
+
const levels = permission.names.length > 0 ? permission.names.map((name3) => {
|
|
1094
1351
|
const spec = permission.resolve(name3);
|
|
1095
1352
|
return { value: name3, name: spec.name, description: spec.description };
|
|
1096
1353
|
}) : SANDBOX_MODES2.map((mode) => {
|
|
@@ -1145,14 +1402,14 @@ async function apply(ctx, config = {}) {
|
|
|
1145
1402
|
});
|
|
1146
1403
|
}
|
|
1147
1404
|
const presets = presetsService();
|
|
1148
|
-
if (
|
|
1405
|
+
if (record.preset !== void 0) {
|
|
1149
1406
|
try {
|
|
1150
1407
|
const roster = await presets.list();
|
|
1151
1408
|
if (roster.length >= 2) {
|
|
1152
1409
|
result.push({
|
|
1153
1410
|
type: "select",
|
|
1154
|
-
id: "
|
|
1155
|
-
name: "
|
|
1411
|
+
id: "agent",
|
|
1412
|
+
name: "Agent",
|
|
1156
1413
|
currentValue: record.preset,
|
|
1157
1414
|
options: roster.map((preset) => ({
|
|
1158
1415
|
value: preset.id,
|
|
@@ -1169,12 +1426,6 @@ async function apply(ctx, config = {}) {
|
|
|
1169
1426
|
};
|
|
1170
1427
|
const BUILTIN_COMMANDS = [
|
|
1171
1428
|
{ name: "status", description: "Show adapter, model, mode, and token status" },
|
|
1172
|
-
{
|
|
1173
|
-
name: "login",
|
|
1174
|
-
description: "Save a DeepSeek API key into the harness credential store (~/.dsh/.credentials.yaml)",
|
|
1175
|
-
input: { hint: "<api-key>" }
|
|
1176
|
-
},
|
|
1177
|
-
{ name: "logout", description: "Remove the API key stored in the harness credential store" },
|
|
1178
1429
|
{
|
|
1179
1430
|
name: "model",
|
|
1180
1431
|
description: "Select the model for this conversation",
|
|
@@ -1184,42 +1435,36 @@ async function apply(ctx, config = {}) {
|
|
|
1184
1435
|
const availableCommandsFor = async (record) => {
|
|
1185
1436
|
const list = [...BUILTIN_COMMANDS];
|
|
1186
1437
|
const seen = new Set(list.map((command) => command.name));
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
...descriptor.input !== void 0 ? { input: { hint: descriptor.input.hint } } : {}
|
|
1197
|
-
});
|
|
1198
|
-
}
|
|
1199
|
-
} catch (error) {
|
|
1200
|
-
logWarn(`command listing failed: ${String(error)}`);
|
|
1438
|
+
try {
|
|
1439
|
+
for (const descriptor of commandRuntime.list(record.agent)) {
|
|
1440
|
+
if (seen.has(descriptor.name)) continue;
|
|
1441
|
+
seen.add(descriptor.name);
|
|
1442
|
+
list.push({
|
|
1443
|
+
name: descriptor.name,
|
|
1444
|
+
description: descriptor.description,
|
|
1445
|
+
...descriptor.input !== void 0 ? { input: { hint: descriptor.input.hint } } : {}
|
|
1446
|
+
});
|
|
1201
1447
|
}
|
|
1448
|
+
} catch (error) {
|
|
1449
|
+
logWarn(`command listing failed: ${String(error)}`);
|
|
1202
1450
|
}
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1451
|
+
try {
|
|
1452
|
+
const skills = await skillRegistry.list({
|
|
1453
|
+
...record.agent.session.header.cwd !== void 0 ? { cwd: record.agent.session.header.cwd } : {},
|
|
1454
|
+
scope: record.agent
|
|
1455
|
+
});
|
|
1456
|
+
for (const skill of skills) {
|
|
1457
|
+
if (skill.invocation.userInvocable !== true) continue;
|
|
1458
|
+
if (seen.has(skill.name)) continue;
|
|
1459
|
+
seen.add(skill.name);
|
|
1460
|
+
list.push({
|
|
1461
|
+
name: skill.name,
|
|
1462
|
+
description: skill.description,
|
|
1463
|
+
input: { hint: "instructions for the skill" }
|
|
1209
1464
|
});
|
|
1210
|
-
for (const skill of skills) {
|
|
1211
|
-
if (skill.invocation.userInvocable !== true) continue;
|
|
1212
|
-
if (seen.has(skill.name)) continue;
|
|
1213
|
-
seen.add(skill.name);
|
|
1214
|
-
list.push({
|
|
1215
|
-
name: skill.name,
|
|
1216
|
-
description: skill.description,
|
|
1217
|
-
input: { hint: "instructions for the skill" }
|
|
1218
|
-
});
|
|
1219
|
-
}
|
|
1220
|
-
} catch (error) {
|
|
1221
|
-
logDebug(`skill listing failed: ${String(error)}`);
|
|
1222
1465
|
}
|
|
1466
|
+
} catch (error) {
|
|
1467
|
+
logDebug(`skill listing failed: ${String(error)}`);
|
|
1223
1468
|
}
|
|
1224
1469
|
return list;
|
|
1225
1470
|
};
|
|
@@ -1246,7 +1491,7 @@ async function apply(ctx, config = {}) {
|
|
|
1246
1491
|
const events = record.agent.session.events;
|
|
1247
1492
|
if (events === void 0) return;
|
|
1248
1493
|
const permission = permissionService();
|
|
1249
|
-
const storedMode = permission
|
|
1494
|
+
const storedMode = permission.current(events);
|
|
1250
1495
|
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
1251
1496
|
const event = events[index];
|
|
1252
1497
|
if (event?.type === "approval/policy") {
|
|
@@ -1263,69 +1508,35 @@ async function apply(ctx, config = {}) {
|
|
|
1263
1508
|
});
|
|
1264
1509
|
}
|
|
1265
1510
|
};
|
|
1266
|
-
const
|
|
1267
|
-
const
|
|
1268
|
-
const
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
const viaEnv = process.env["DEEPSEEK_API_KEY"] !== void 0 ? "DEEPSEEK_API_KEY" : "DEEPSEEK_BASE_URL";
|
|
1276
|
-
return `process environment (${viaEnv})`;
|
|
1277
|
-
}
|
|
1278
|
-
const cs = credentialStore();
|
|
1279
|
-
if (cs === void 0) return "not configured";
|
|
1280
|
-
try {
|
|
1281
|
-
const info = await cs.store.describe(cs.ref);
|
|
1282
|
-
if (!info.configured) return "not configured";
|
|
1283
|
-
return info.source ?? "credential store";
|
|
1284
|
-
} catch (error) {
|
|
1285
|
-
logWarn(`credential describe failed: ${String(error)}`);
|
|
1286
|
-
return "unknown";
|
|
1511
|
+
const describeCredential = async (provider) => {
|
|
1512
|
+
const names = credentialEnvNames(provider).filter((candidate) => !candidate.endsWith("_BASE_URL"));
|
|
1513
|
+
for (const name3 of names) {
|
|
1514
|
+
try {
|
|
1515
|
+
const info = await credentials.describe(credentialRef(name3));
|
|
1516
|
+
if (info.configured) return info.source ?? "credential store";
|
|
1517
|
+
} catch (error) {
|
|
1518
|
+
logWarn(`credential describe failed: ${String(error)}`);
|
|
1519
|
+
}
|
|
1287
1520
|
}
|
|
1521
|
+
return "not configured";
|
|
1288
1522
|
};
|
|
1289
|
-
const
|
|
1290
|
-
|
|
1291
|
-
const cs = credentialStore();
|
|
1292
|
-
if (cs === void 0) {
|
|
1293
|
-
return "This composition has no writable credential store; export DEEPSEEK_API_KEY in the environment that launches the agent instead.";
|
|
1294
|
-
}
|
|
1295
|
-
if (key.length === 0) {
|
|
1296
|
-
return [
|
|
1297
|
-
"Usage: `/login <api-key>` \u2014 stores the key in the harness credential store",
|
|
1298
|
-
"(`~/.dsh/.credentials.yaml`, the same file the dsh Web UI writes; mode 600).",
|
|
1299
|
-
"",
|
|
1300
|
-
`Current credential source: ${await describeCredential()}.`,
|
|
1301
|
-
"",
|
|
1302
|
-
"Note: text entered here also lands in your client's chat history. If you",
|
|
1303
|
-
"prefer to keep the key out of it, save the key in the dsh Web UI instead."
|
|
1304
|
-
].join("\n");
|
|
1305
|
-
}
|
|
1306
|
-
try {
|
|
1307
|
-
await cs.store.set(cs.ref, key);
|
|
1308
|
-
} catch (error) {
|
|
1309
|
-
return [
|
|
1310
|
-
`Could not store the key: ${error instanceof Error ? error.message : String(error)}`,
|
|
1311
|
-
"",
|
|
1312
|
-
"A read-only source (usually the process environment) is currently supplying",
|
|
1313
|
-
"this credential, so a stored value would be shadowed. Unset DEEPSEEK_API_KEY",
|
|
1314
|
-
"in the launching environment, or keep using it as the credential."
|
|
1315
|
-
].join("\n");
|
|
1316
|
-
}
|
|
1317
|
-
return `Saved DEEPSEEK_API_KEY (${maskKey(key)}) to the harness credential store \u2014 source now: ${await describeCredential()}. The dsh Web UI sees the same key.`;
|
|
1523
|
+
const saveCredential = async (provider, key) => {
|
|
1524
|
+
await credentials.set(credentialRef(primaryCredentialName(provider)), key);
|
|
1318
1525
|
};
|
|
1319
|
-
const
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1526
|
+
const saveGateway = async (provider, key, baseUrl) => {
|
|
1527
|
+
await saveCredential(provider, key);
|
|
1528
|
+
await credentials.set(credentialRef(credentialBaseUrlName(provider)), baseUrl);
|
|
1529
|
+
};
|
|
1530
|
+
const logoutStored = async () => {
|
|
1531
|
+
const routes = await listProviderRoutes();
|
|
1532
|
+
const providers = routes.length > 0 ? routes.map((route) => route.id) : [config.provider];
|
|
1533
|
+
for (const provider of providers) {
|
|
1534
|
+
try {
|
|
1535
|
+
await credentials.unset(credentialRef(primaryCredentialName(provider)));
|
|
1536
|
+
} catch (error) {
|
|
1537
|
+
logWarn(`credential unset failed: ${String(error)}`);
|
|
1538
|
+
}
|
|
1326
1539
|
}
|
|
1327
|
-
const remaining = await describeCredential();
|
|
1328
|
-
return remaining === "not configured" ? "Removed the stored DEEPSEEK_API_KEY. No credential is configured now." : `Removed the stored DEEPSEEK_API_KEY; requests now authenticate via: ${remaining}.`;
|
|
1329
1540
|
};
|
|
1330
1541
|
const modelCommandText = async (record, acpSessionId, query) => {
|
|
1331
1542
|
const discovered = await discoverModels();
|
|
@@ -1382,7 +1593,7 @@ async function apply(ctx, config = {}) {
|
|
|
1382
1593
|
`| Provider | ${route.provider ?? "(composition default)"} |`,
|
|
1383
1594
|
`| Model | ${route.model ?? "(composition default)"} |`,
|
|
1384
1595
|
`| Reasoning | ${effort ?? "(adapter default)"} |`,
|
|
1385
|
-
`| Credential | ${await describeCredential()} |`,
|
|
1596
|
+
`| Credential | ${await describeCredential(route.provider)} |`,
|
|
1386
1597
|
...record.preset !== void 0 ? [`| Preset | ${record.preset} |`] : [],
|
|
1387
1598
|
`| Permission mode | ${record.modeId} |`,
|
|
1388
1599
|
`| Approvals | ${record.approvals} |`,
|
|
@@ -1453,11 +1664,7 @@ async function apply(ctx, config = {}) {
|
|
|
1453
1664
|
}).then(({ outcome }) => {
|
|
1454
1665
|
if (outcome.outcome === "cancelled") return "cancelled";
|
|
1455
1666
|
if (outcome.optionId === "allow-always") {
|
|
1456
|
-
|
|
1457
|
-
ctx.get("approval")?.setPolicy?.(record.agent, "never");
|
|
1458
|
-
} catch (error) {
|
|
1459
|
-
logWarn(`approval policy switch failed: ${String(error)}`);
|
|
1460
|
-
}
|
|
1667
|
+
setApprovalPolicy(record, "never");
|
|
1461
1668
|
return "allowed-once";
|
|
1462
1669
|
}
|
|
1463
1670
|
return outcome.optionId === "allow-once" ? "allowed-once" : "rejected";
|
|
@@ -1466,50 +1673,77 @@ async function apply(ctx, config = {}) {
|
|
|
1466
1673
|
const makeAgent = (connection) => {
|
|
1467
1674
|
conn = connection;
|
|
1468
1675
|
return {
|
|
1469
|
-
initialize(params) {
|
|
1470
|
-
const hasPersistence = ctx.get("sessionPersistence") !== void 0;
|
|
1676
|
+
async initialize(params) {
|
|
1471
1677
|
const requested = params.protocolVersion;
|
|
1472
1678
|
const capsMeta = params.clientCapabilities?.["_meta"];
|
|
1473
1679
|
clientTerminalOutput = capsMeta !== null && typeof capsMeta === "object" ? capsMeta["terminal_output"] === true : false;
|
|
1474
|
-
|
|
1680
|
+
const providers = await listProviderRoutes();
|
|
1681
|
+
return {
|
|
1475
1682
|
protocolVersion: typeof requested === "number" && requested >= 1 && requested < PROTOCOL_VERSION ? requested : PROTOCOL_VERSION,
|
|
1476
1683
|
agentInfo: { name: "dsh-acp", title: "DeepSeek Harness", version: VERSION },
|
|
1477
1684
|
agentCapabilities: {
|
|
1478
|
-
loadSession:
|
|
1685
|
+
loadSession: true,
|
|
1479
1686
|
promptCapabilities: { image: false, audio: false, embeddedContext: true },
|
|
1480
1687
|
// Stdio servers always work; streamable HTTP maps onto
|
|
1481
1688
|
// mcp-client's second transport. Legacy SSE does not.
|
|
1482
1689
|
mcpCapabilities: { http: true, sse: false },
|
|
1483
|
-
|
|
1690
|
+
auth: { logout: {} },
|
|
1691
|
+
sessionCapabilities: { list: {} }
|
|
1484
1692
|
},
|
|
1485
|
-
authMethods:
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
{
|
|
1491
|
-
id: "terminal-login",
|
|
1492
|
-
name: "Log in with a DeepSeek API key",
|
|
1493
|
-
description: "Interactive terminal setup \u2014 saves the key to the harness credential store shared with the dsh Web UI",
|
|
1494
|
-
_meta: { "terminal-auth": { args: ["login"], env: {} } }
|
|
1495
|
-
}
|
|
1496
|
-
]
|
|
1497
|
-
});
|
|
1693
|
+
authMethods: advertisedAuthMethods(
|
|
1694
|
+
providers,
|
|
1695
|
+
params.clientCapabilities
|
|
1696
|
+
)
|
|
1697
|
+
};
|
|
1498
1698
|
},
|
|
1499
|
-
async authenticate(
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1699
|
+
async authenticate(params) {
|
|
1700
|
+
const gateway = gatewayFromAuthenticate(params);
|
|
1701
|
+
if (isGatewayAuthMethod(params.methodId) || gateway.baseUrl) {
|
|
1702
|
+
if (!gateway.baseUrl || !gateway.key) {
|
|
1703
|
+
throw authRequired(
|
|
1704
|
+
"authenticate gateway requires `_meta.gateway.baseUrl` and an Authorization header"
|
|
1705
|
+
);
|
|
1706
|
+
}
|
|
1707
|
+
await saveGateway(
|
|
1708
|
+
gateway.providerName ?? config.provider,
|
|
1709
|
+
gateway.key,
|
|
1710
|
+
gateway.baseUrl
|
|
1503
1711
|
);
|
|
1712
|
+
return;
|
|
1713
|
+
}
|
|
1714
|
+
const submitted = apiKeyFromAuthenticate(params);
|
|
1715
|
+
const provider = submitted.provider ?? providerFromAuthMethodId(params.methodId) ?? config.provider;
|
|
1716
|
+
if (submitted.key) {
|
|
1717
|
+
await saveCredential(provider, submitted.key);
|
|
1718
|
+
return;
|
|
1719
|
+
}
|
|
1720
|
+
if (await credentialPresent(provider)) return;
|
|
1721
|
+
if (isBrowserAuthMethod(params.methodId) && shouldOfferLocalAuthPage()) {
|
|
1722
|
+
const page = await startLocalAuthPage({
|
|
1723
|
+
credentialName: primaryCredentialName(provider)
|
|
1724
|
+
});
|
|
1725
|
+
openLocalAuthPage(page.url);
|
|
1726
|
+
try {
|
|
1727
|
+
const key = await page.completed;
|
|
1728
|
+
await saveCredential(provider, key);
|
|
1729
|
+
return;
|
|
1730
|
+
} finally {
|
|
1731
|
+
page.close();
|
|
1732
|
+
}
|
|
1504
1733
|
}
|
|
1734
|
+
await requireCredential(provider);
|
|
1735
|
+
},
|
|
1736
|
+
async logout(_params) {
|
|
1737
|
+
await logoutStored();
|
|
1505
1738
|
},
|
|
1506
1739
|
async newSession(params) {
|
|
1507
1740
|
assertOpen();
|
|
1741
|
+
await requireCredential(config.provider);
|
|
1508
1742
|
validateCwd(params.cwd);
|
|
1509
1743
|
syncMcpServers(params.mcpServers, params.cwd);
|
|
1510
1744
|
const sessionId = SessionId2(randomUUID());
|
|
1511
1745
|
const presets = presetsService();
|
|
1512
|
-
const presetId =
|
|
1746
|
+
const presetId = (await presets.resolve(void 0)).id;
|
|
1513
1747
|
const handle = await agents.create({
|
|
1514
1748
|
sessionId,
|
|
1515
1749
|
meta: { cwd: params.cwd, ...presetId !== void 0 ? { agentPreset: presetId } : {} },
|
|
@@ -1539,6 +1773,7 @@ async function apply(ctx, config = {}) {
|
|
|
1539
1773
|
},
|
|
1540
1774
|
async loadSession(params) {
|
|
1541
1775
|
assertOpen();
|
|
1776
|
+
await requireCredential(config.provider);
|
|
1542
1777
|
validateCwd(params.cwd);
|
|
1543
1778
|
syncMcpServers(params.mcpServers, params.cwd);
|
|
1544
1779
|
requirePersistence();
|
|
@@ -1598,10 +1833,6 @@ async function apply(ctx, config = {}) {
|
|
|
1598
1833
|
return { stopReason: "end_turn" };
|
|
1599
1834
|
};
|
|
1600
1835
|
if (commandMatch?.[1] === "status") return respond(await statusText(record));
|
|
1601
|
-
if (commandMatch?.[1] === "login") {
|
|
1602
|
-
return respond(await loginText(trimmed.slice(commandMatch[0].length)));
|
|
1603
|
-
}
|
|
1604
|
-
if (commandMatch?.[1] === "logout") return respond(await logoutText());
|
|
1605
1836
|
if (commandMatch?.[1] === "model") {
|
|
1606
1837
|
return respond(
|
|
1607
1838
|
await modelCommandText(
|
|
@@ -1612,40 +1843,26 @@ async function apply(ctx, config = {}) {
|
|
|
1612
1843
|
);
|
|
1613
1844
|
}
|
|
1614
1845
|
if (commandMatch !== null && commandMatch[1] !== void 0) {
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
}
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
publishConfigOptions(params.sessionId, record);
|
|
1633
|
-
return respond(result.kind === "error" ? `\u26A0 ${text}` : text);
|
|
1634
|
-
}
|
|
1846
|
+
let execution;
|
|
1847
|
+
try {
|
|
1848
|
+
execution = await commandRuntime.execute(
|
|
1849
|
+
record.agent,
|
|
1850
|
+
trimmed,
|
|
1851
|
+
new AbortController().signal
|
|
1852
|
+
);
|
|
1853
|
+
} catch (error) {
|
|
1854
|
+
return respond(`\u26A0 /${commandMatch[1]} failed: ${errorChain2(error)}`);
|
|
1855
|
+
}
|
|
1856
|
+
if (execution !== void 0) {
|
|
1857
|
+
const { result } = execution;
|
|
1858
|
+
const text = result.text ?? (result.kind === "success" ? `/${commandMatch[1]} \u2713` : `/${commandMatch[1]} failed`);
|
|
1859
|
+
syncPermissionState(record, params.sessionId);
|
|
1860
|
+
publishCommands(params.sessionId, record);
|
|
1861
|
+
publishConfigOptions(params.sessionId, record);
|
|
1862
|
+
return respond(result.kind === "error" ? `\u26A0 ${text}` : text);
|
|
1635
1863
|
}
|
|
1636
1864
|
}
|
|
1637
|
-
|
|
1638
|
-
return respond(
|
|
1639
|
-
[
|
|
1640
|
-
"No DeepSeek credential is configured, so this prompt was not sent to the model.",
|
|
1641
|
-
"",
|
|
1642
|
-
"Configure one of:",
|
|
1643
|
-
"- `/login <api-key>` \u2014 store a key in the harness credential store right here,",
|
|
1644
|
-
"- the dsh Web UI (`dsh web`, Settings \u2192 Models),",
|
|
1645
|
-
"- `DEEPSEEK_API_KEY` in the environment that launches this agent."
|
|
1646
|
-
].join("\n")
|
|
1647
|
-
);
|
|
1648
|
-
}
|
|
1865
|
+
await requireCredential(record.provider ?? config.provider);
|
|
1649
1866
|
if (agents.get(record.agent.id) !== record.agent) {
|
|
1650
1867
|
throw internalError("prompt was not queued: the agent was disposed outside the bridge");
|
|
1651
1868
|
}
|
|
@@ -1697,6 +1914,7 @@ async function apply(ctx, config = {}) {
|
|
|
1697
1914
|
applyMode(record, params.sessionId, value);
|
|
1698
1915
|
break;
|
|
1699
1916
|
}
|
|
1917
|
+
case "agent":
|
|
1700
1918
|
case "preset": {
|
|
1701
1919
|
await switchPreset(record, params.sessionId, value);
|
|
1702
1920
|
break;
|
|
@@ -1749,14 +1967,7 @@ async function apply(ctx, config = {}) {
|
|
|
1749
1967
|
};
|
|
1750
1968
|
};
|
|
1751
1969
|
function requirePersistence() {
|
|
1752
|
-
|
|
1753
|
-
if (persistence === void 0) {
|
|
1754
|
-
throw internalError("session persistence is not composed; session history is unavailable");
|
|
1755
|
-
}
|
|
1756
|
-
return persistence;
|
|
1757
|
-
}
|
|
1758
|
-
function getPersistence() {
|
|
1759
|
-
return ctx.get("sessionPersistence");
|
|
1970
|
+
return sessionPersistence;
|
|
1760
1971
|
}
|
|
1761
1972
|
function validateCwd(cwd) {
|
|
1762
1973
|
if (!isAbsolute(cwd)) throw invalidParams(`cwd must be an absolute path: ${cwd}`);
|
|
@@ -1861,13 +2072,10 @@ async function apply(ctx, config = {}) {
|
|
|
1861
2072
|
settlePrompt(record, "cancelled");
|
|
1862
2073
|
}
|
|
1863
2074
|
quiescing = (async () => {
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
} catch (error) {
|
|
1869
|
-
logWarn(`continuable subagent teardown failed: ${String(error)}`);
|
|
1870
|
-
}
|
|
2075
|
+
try {
|
|
2076
|
+
await subagents.drainContinuableDescendants(records.map((record) => record.agent));
|
|
2077
|
+
} catch (error) {
|
|
2078
|
+
logWarn(`continuable subagent teardown failed: ${String(error)}`);
|
|
1871
2079
|
}
|
|
1872
2080
|
const disposals = await Promise.allSettled(records.map((record) => record.dispose()));
|
|
1873
2081
|
const failures = disposals.filter(
|