@openma/deepseek-harness-acp 0.4.3 → 0.4.5
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/dist/bridge.js +191 -69
- package/dist/index.js +308 -71
- package/package.json +1 -1
package/dist/bridge.js
CHANGED
|
@@ -863,6 +863,87 @@ async function apply(ctx, config = {}) {
|
|
|
863
863
|
if (record === void 0) throw invalidParams(`unknown session: ${sessionId}`);
|
|
864
864
|
return record;
|
|
865
865
|
};
|
|
866
|
+
const requireOrRestoreSession = async (sessionId) => {
|
|
867
|
+
const record = sessions.get(sessionId);
|
|
868
|
+
if (record !== void 0) return record;
|
|
869
|
+
if (ctx.get("sessionPersistence") === void 0) throw invalidParams(`unknown session: ${sessionId}`);
|
|
870
|
+
logWarn(`restoring session ${sessionId} from the persisted log`);
|
|
871
|
+
try {
|
|
872
|
+
return await restoreSession(sessionId, { replay: false });
|
|
873
|
+
} catch (error) {
|
|
874
|
+
throw invalidParams(`unknown session: ${sessionId} (${errorChain2(error)})`);
|
|
875
|
+
}
|
|
876
|
+
};
|
|
877
|
+
const restoreSession = async (sessionId, options) => {
|
|
878
|
+
const persistence = requirePersistence();
|
|
879
|
+
const existing = sessions.get(sessionId);
|
|
880
|
+
if (existing !== void 0) {
|
|
881
|
+
sessions.delete(sessionId);
|
|
882
|
+
existing.agent.cancel({ kind: "user" });
|
|
883
|
+
settlePrompt(existing, "cancelled");
|
|
884
|
+
await existing.dispose().catch((error) => {
|
|
885
|
+
logWarn(`dispose before reload failed: ${String(error)}`);
|
|
886
|
+
});
|
|
887
|
+
}
|
|
888
|
+
let events;
|
|
889
|
+
let storedHeader;
|
|
890
|
+
try {
|
|
891
|
+
const inspected = await persistence.inspect(SessionId2(sessionId));
|
|
892
|
+
events = inspected.events;
|
|
893
|
+
storedHeader = inspected.header;
|
|
894
|
+
} catch (error) {
|
|
895
|
+
throw invalidParams(`session not found: ${sessionId} (${errorChain2(error)})`);
|
|
896
|
+
}
|
|
897
|
+
const replay = buildReplay(events);
|
|
898
|
+
if (options.replay) {
|
|
899
|
+
for (const update of replay.updates) notify(sessionId, update);
|
|
900
|
+
if (replay.title !== void 0) {
|
|
901
|
+
notify(sessionId, {
|
|
902
|
+
sessionUpdate: "session_info_update",
|
|
903
|
+
title: replay.title
|
|
904
|
+
});
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
const presets = presetsService();
|
|
908
|
+
const storedPreset = presetFromLog(storedHeader, events);
|
|
909
|
+
const presetId = presets === void 0 ? void 0 : (await presets.resolve(storedPreset)).id;
|
|
910
|
+
const handle = await agents.resume({
|
|
911
|
+
resumeSessionId: SessionId2(sessionId),
|
|
912
|
+
agentOptions: agentOptionsFor(config.model),
|
|
913
|
+
...presetSetup(presets, presetId) !== void 0 ? { setup: presetSetup(presets, presetId) } : {}
|
|
914
|
+
});
|
|
915
|
+
const cwd = options.cwd ?? storedHeader?.cwd;
|
|
916
|
+
const projection = new SessionProjection(replay.contextWindow, {
|
|
917
|
+
terminalOutput: clientTerminalOutput,
|
|
918
|
+
...cwd !== void 0 ? { cwd } : {}
|
|
919
|
+
});
|
|
920
|
+
projection.title = replay.title;
|
|
921
|
+
const record = registerRecord(
|
|
922
|
+
sessionId,
|
|
923
|
+
handle.agent,
|
|
924
|
+
() => handle.dispose(),
|
|
925
|
+
config.model,
|
|
926
|
+
config.permissionMode ?? "workspace-write",
|
|
927
|
+
projection
|
|
928
|
+
);
|
|
929
|
+
if (presetId !== void 0) record.preset = presetId;
|
|
930
|
+
{
|
|
931
|
+
const permission = permissionService();
|
|
932
|
+
const storedMode = permission?.current(events);
|
|
933
|
+
if (storedMode !== void 0) record.modeId = storedMode;
|
|
934
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
935
|
+
const event = events[index];
|
|
936
|
+
if (event?.type === "approval/policy") {
|
|
937
|
+
const policy = event.data?.policy;
|
|
938
|
+
if (policy === "ask" || policy === "never") record.approvals = policy;
|
|
939
|
+
break;
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
ensureSelection(record);
|
|
944
|
+
queueMicrotask(() => publishCommands(sessionId));
|
|
945
|
+
return record;
|
|
946
|
+
};
|
|
866
947
|
const assertOpen = () => {
|
|
867
948
|
if (closed) throw internalError("the ACP bridge has been disposed");
|
|
868
949
|
};
|
|
@@ -964,7 +1045,7 @@ async function apply(ctx, config = {}) {
|
|
|
964
1045
|
}
|
|
965
1046
|
record.approvals = policy;
|
|
966
1047
|
};
|
|
967
|
-
const permissionService = () => ctx.get("permission");
|
|
1048
|
+
const permissionService = () => ctx.get("permissionPresets") ?? ctx.get("permission");
|
|
968
1049
|
const applyMode = (record, sessionId, modeId) => {
|
|
969
1050
|
const permission = permissionService();
|
|
970
1051
|
if (permission !== void 0 && permission.names.includes(modeId)) {
|
|
@@ -1093,7 +1174,12 @@ async function apply(ctx, config = {}) {
|
|
|
1093
1174
|
description: "Save a DeepSeek API key into the harness credential store (~/.dsh/.credentials.yaml)",
|
|
1094
1175
|
input: { hint: "<api-key>" }
|
|
1095
1176
|
},
|
|
1096
|
-
{ name: "logout", description: "Remove the API key stored in the harness credential store" }
|
|
1177
|
+
{ name: "logout", description: "Remove the API key stored in the harness credential store" },
|
|
1178
|
+
{
|
|
1179
|
+
name: "model",
|
|
1180
|
+
description: "Select the model for this conversation",
|
|
1181
|
+
input: { hint: "[model] \u2014 blank lists available models" }
|
|
1182
|
+
}
|
|
1097
1183
|
];
|
|
1098
1184
|
const availableCommandsFor = async (record) => {
|
|
1099
1185
|
const list = [...BUILTIN_COMMANDS];
|
|
@@ -1146,6 +1232,37 @@ async function apply(ctx, config = {}) {
|
|
|
1146
1232
|
logWarn(`publishing commands failed: ${String(error)}`);
|
|
1147
1233
|
});
|
|
1148
1234
|
};
|
|
1235
|
+
const publishConfigOptions = (sessionId, record) => {
|
|
1236
|
+
void configOptions(record).then((options) => {
|
|
1237
|
+
notify(sessionId, {
|
|
1238
|
+
sessionUpdate: "config_option_update",
|
|
1239
|
+
configOptions: options
|
|
1240
|
+
});
|
|
1241
|
+
}).catch((error) => {
|
|
1242
|
+
logWarn(`publishing config options failed: ${String(error)}`);
|
|
1243
|
+
});
|
|
1244
|
+
};
|
|
1245
|
+
const syncPermissionState = (record, sessionId) => {
|
|
1246
|
+
const events = record.agent.session.events;
|
|
1247
|
+
if (events === void 0) return;
|
|
1248
|
+
const permission = permissionService();
|
|
1249
|
+
const storedMode = permission?.current(events);
|
|
1250
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
1251
|
+
const event = events[index];
|
|
1252
|
+
if (event?.type === "approval/policy") {
|
|
1253
|
+
const policy = event.data?.policy;
|
|
1254
|
+
if (policy === "ask" || policy === "never") record.approvals = policy;
|
|
1255
|
+
break;
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
if (storedMode !== void 0 && storedMode !== record.modeId) {
|
|
1259
|
+
record.modeId = storedMode;
|
|
1260
|
+
notify(sessionId, {
|
|
1261
|
+
sessionUpdate: "current_mode_update",
|
|
1262
|
+
currentModeId: record.modeId
|
|
1263
|
+
});
|
|
1264
|
+
}
|
|
1265
|
+
};
|
|
1149
1266
|
const credentialStore = () => {
|
|
1150
1267
|
const brand = harness.credentialRef;
|
|
1151
1268
|
const store = ctx.get("credentials");
|
|
@@ -1210,6 +1327,49 @@ async function apply(ctx, config = {}) {
|
|
|
1210
1327
|
const remaining = await describeCredential();
|
|
1211
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}.`;
|
|
1212
1329
|
};
|
|
1330
|
+
const modelCommandText = async (record, acpSessionId, query) => {
|
|
1331
|
+
const discovered = await discoverModels();
|
|
1332
|
+
const catalog = [
|
|
1333
|
+
...discovered.map((entry) => ({
|
|
1334
|
+
value: encodeChoice(entry.provider, entry.model),
|
|
1335
|
+
label: entry.label
|
|
1336
|
+
})),
|
|
1337
|
+
...modelCandidates().filter((model) => !discovered.some((entry) => entry.model === model)).map((model) => ({ value: encodeChoice(void 0, model), label: model }))
|
|
1338
|
+
];
|
|
1339
|
+
const route = routeOf(record);
|
|
1340
|
+
const currentValue = encodeChoice(record.provider, record.model ?? route.model ?? "");
|
|
1341
|
+
if (query === "") {
|
|
1342
|
+
const lines = catalog.map(
|
|
1343
|
+
(entry) => `${entry.value === currentValue ? "\u2192" : " "} ${entry.label} \u2014 ${entry.value}`
|
|
1344
|
+
);
|
|
1345
|
+
return [
|
|
1346
|
+
`model: ${route.model ?? "(product default)"}${record.effort !== void 0 ? ` (effort ${record.effort})` : ""}`,
|
|
1347
|
+
"",
|
|
1348
|
+
...lines.length > 0 ? lines : ["no models discovered \u2014 check credentials with /status"],
|
|
1349
|
+
"",
|
|
1350
|
+
"switch with /model <name>"
|
|
1351
|
+
].join("\n");
|
|
1352
|
+
}
|
|
1353
|
+
const lowered = query.toLowerCase();
|
|
1354
|
+
const exact = catalog.filter(
|
|
1355
|
+
(entry) => entry.value.toLowerCase() === lowered || decodeChoice(entry.value).model.toLowerCase() === lowered
|
|
1356
|
+
);
|
|
1357
|
+
const matches = exact.length > 0 ? exact : catalog.filter(
|
|
1358
|
+
(entry) => entry.value.toLowerCase().includes(lowered) || entry.label.toLowerCase().includes(lowered)
|
|
1359
|
+
);
|
|
1360
|
+
if (matches.length === 0) return `no model matches "${query}" \u2014 see /model`;
|
|
1361
|
+
if (matches.length > 1) {
|
|
1362
|
+
return [`"${query}" is ambiguous:`, ...matches.map((entry) => ` ${entry.label} \u2014 ${entry.value}`)].join(
|
|
1363
|
+
"\n"
|
|
1364
|
+
);
|
|
1365
|
+
}
|
|
1366
|
+
const target = matches[0];
|
|
1367
|
+
if (target.value === currentValue) return `already on ${target.label}`;
|
|
1368
|
+
await switchModel(record, acpSessionId, target.value);
|
|
1369
|
+
publishCommands(acpSessionId, record);
|
|
1370
|
+
publishConfigOptions(acpSessionId, record);
|
|
1371
|
+
return `model \u2192 ${target.label}`;
|
|
1372
|
+
};
|
|
1213
1373
|
const statusText = async (record) => {
|
|
1214
1374
|
const used = record.projection.contextWindow;
|
|
1215
1375
|
const route = routeOf(record);
|
|
@@ -1322,13 +1482,24 @@ async function apply(ctx, config = {}) {
|
|
|
1322
1482
|
mcpCapabilities: { http: true, sse: false },
|
|
1323
1483
|
...hasPersistence ? { sessionCapabilities: { list: {} } } : {}
|
|
1324
1484
|
},
|
|
1325
|
-
authMethods: [
|
|
1485
|
+
authMethods: [
|
|
1486
|
+
// ACP Terminal Auth (see the registry's AUTHENTICATION.md):
|
|
1487
|
+
// the client runs `dsh-acp login` in an interactive
|
|
1488
|
+
// terminal; the key lands in the shared harness
|
|
1489
|
+
// credential store (~/.dsh/.credentials.yaml).
|
|
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
|
+
]
|
|
1326
1497
|
});
|
|
1327
1498
|
},
|
|
1328
1499
|
async authenticate(_params) {
|
|
1329
1500
|
if (!await credentialPresent()) {
|
|
1330
1501
|
throw authRequired(
|
|
1331
|
-
"no DeepSeek credential found: save one in the dsh Web UI (Settings \u2192 Models) or set DEEPSEEK_API_KEY"
|
|
1502
|
+
"no DeepSeek credential found: run `dsh-acp login`, save one in the dsh Web UI (Settings \u2192 Models), or set DEEPSEEK_API_KEY"
|
|
1332
1503
|
);
|
|
1333
1504
|
}
|
|
1334
1505
|
},
|
|
@@ -1370,68 +1541,8 @@ async function apply(ctx, config = {}) {
|
|
|
1370
1541
|
assertOpen();
|
|
1371
1542
|
validateCwd(params.cwd);
|
|
1372
1543
|
syncMcpServers(params.mcpServers, params.cwd);
|
|
1373
|
-
|
|
1374
|
-
const
|
|
1375
|
-
const existing = sessions.get(sessionId);
|
|
1376
|
-
if (existing !== void 0) {
|
|
1377
|
-
sessions.delete(sessionId);
|
|
1378
|
-
existing.agent.cancel({ kind: "user" });
|
|
1379
|
-
settlePrompt(existing, "cancelled");
|
|
1380
|
-
await existing.dispose().catch((error) => {
|
|
1381
|
-
logWarn(`dispose before reload failed: ${String(error)}`);
|
|
1382
|
-
});
|
|
1383
|
-
}
|
|
1384
|
-
let events;
|
|
1385
|
-
let storedHeader;
|
|
1386
|
-
try {
|
|
1387
|
-
const inspected = await persistence.inspect(SessionId2(sessionId));
|
|
1388
|
-
events = inspected.events;
|
|
1389
|
-
storedHeader = inspected.header;
|
|
1390
|
-
} catch (error) {
|
|
1391
|
-
throw invalidParams(`session not found: ${sessionId} (${errorChain2(error)})`);
|
|
1392
|
-
}
|
|
1393
|
-
const replay = buildReplay(events);
|
|
1394
|
-
for (const update of replay.updates) notify(sessionId, update);
|
|
1395
|
-
if (replay.title !== void 0) {
|
|
1396
|
-
notify(sessionId, {
|
|
1397
|
-
sessionUpdate: "session_info_update",
|
|
1398
|
-
title: replay.title
|
|
1399
|
-
});
|
|
1400
|
-
}
|
|
1401
|
-
const presets = presetsService();
|
|
1402
|
-
const storedPreset = presetFromLog(storedHeader, events);
|
|
1403
|
-
const presetId = presets === void 0 ? void 0 : (await presets.resolve(storedPreset)).id;
|
|
1404
|
-
const handle = await agents.resume({
|
|
1405
|
-
resumeSessionId: SessionId2(sessionId),
|
|
1406
|
-
agentOptions: agentOptionsFor(config.model),
|
|
1407
|
-
...presetSetup(presets, presetId) !== void 0 ? { setup: presetSetup(presets, presetId) } : {}
|
|
1408
|
-
});
|
|
1409
|
-
const projection = new SessionProjection(replay.contextWindow);
|
|
1410
|
-
projection.title = replay.title;
|
|
1411
|
-
const record = registerRecord(
|
|
1412
|
-
sessionId,
|
|
1413
|
-
handle.agent,
|
|
1414
|
-
() => handle.dispose(),
|
|
1415
|
-
config.model,
|
|
1416
|
-
config.permissionMode ?? "workspace-write",
|
|
1417
|
-
projection
|
|
1418
|
-
);
|
|
1419
|
-
if (presetId !== void 0) record.preset = presetId;
|
|
1420
|
-
{
|
|
1421
|
-
const permission = permissionService();
|
|
1422
|
-
const storedMode = permission?.current(events);
|
|
1423
|
-
if (storedMode !== void 0) record.modeId = storedMode;
|
|
1424
|
-
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
1425
|
-
const event = events[index];
|
|
1426
|
-
if (event?.type === "approval/policy") {
|
|
1427
|
-
const policy = event.data?.policy;
|
|
1428
|
-
if (policy === "ask" || policy === "never") record.approvals = policy;
|
|
1429
|
-
break;
|
|
1430
|
-
}
|
|
1431
|
-
}
|
|
1432
|
-
}
|
|
1433
|
-
ensureSelection(record);
|
|
1434
|
-
queueMicrotask(() => publishCommands(sessionId));
|
|
1544
|
+
requirePersistence();
|
|
1545
|
+
const record = await restoreSession(params.sessionId, { replay: true, cwd: params.cwd });
|
|
1435
1546
|
const options = await configOptions(record);
|
|
1436
1547
|
return {
|
|
1437
1548
|
modes: modeState(record),
|
|
@@ -1465,7 +1576,7 @@ async function apply(ctx, config = {}) {
|
|
|
1465
1576
|
},
|
|
1466
1577
|
async prompt(params) {
|
|
1467
1578
|
assertOpen();
|
|
1468
|
-
const record =
|
|
1579
|
+
const record = await requireOrRestoreSession(params.sessionId);
|
|
1469
1580
|
if (record.inflight !== void 0) {
|
|
1470
1581
|
throw invalidParams("a prompt is already in flight for this session");
|
|
1471
1582
|
}
|
|
@@ -1491,6 +1602,15 @@ async function apply(ctx, config = {}) {
|
|
|
1491
1602
|
return respond(await loginText(trimmed.slice(commandMatch[0].length)));
|
|
1492
1603
|
}
|
|
1493
1604
|
if (commandMatch?.[1] === "logout") return respond(await logoutText());
|
|
1605
|
+
if (commandMatch?.[1] === "model") {
|
|
1606
|
+
return respond(
|
|
1607
|
+
await modelCommandText(
|
|
1608
|
+
record,
|
|
1609
|
+
params.sessionId,
|
|
1610
|
+
trimmed.slice(commandMatch[0].length).trim()
|
|
1611
|
+
)
|
|
1612
|
+
);
|
|
1613
|
+
}
|
|
1494
1614
|
if (commandMatch !== null && commandMatch[1] !== void 0) {
|
|
1495
1615
|
const commandsSvc = ctx.get("commands");
|
|
1496
1616
|
if (commandsSvc !== void 0) {
|
|
@@ -1507,7 +1627,9 @@ async function apply(ctx, config = {}) {
|
|
|
1507
1627
|
if (execution !== void 0) {
|
|
1508
1628
|
const { result } = execution;
|
|
1509
1629
|
const text = result.text ?? (result.kind === "success" ? `/${commandMatch[1]} \u2713` : `/${commandMatch[1]} failed`);
|
|
1630
|
+
syncPermissionState(record, params.sessionId);
|
|
1510
1631
|
publishCommands(params.sessionId, record);
|
|
1632
|
+
publishConfigOptions(params.sessionId, record);
|
|
1511
1633
|
return respond(result.kind === "error" ? `\u26A0 ${text}` : text);
|
|
1512
1634
|
}
|
|
1513
1635
|
}
|
|
@@ -1558,7 +1680,7 @@ async function apply(ctx, config = {}) {
|
|
|
1558
1680
|
return { stopReason, ...usage !== void 0 ? { usage } : {} };
|
|
1559
1681
|
},
|
|
1560
1682
|
async setSessionMode(params) {
|
|
1561
|
-
const record =
|
|
1683
|
+
const record = await requireOrRestoreSession(params.sessionId);
|
|
1562
1684
|
applyMode(record, params.sessionId, params.modeId);
|
|
1563
1685
|
notify(params.sessionId, {
|
|
1564
1686
|
sessionUpdate: "config_option_update",
|
|
@@ -1567,7 +1689,7 @@ async function apply(ctx, config = {}) {
|
|
|
1567
1689
|
return {};
|
|
1568
1690
|
},
|
|
1569
1691
|
async setSessionConfigOption(params) {
|
|
1570
|
-
const record =
|
|
1692
|
+
const record = await requireOrRestoreSession(params.sessionId);
|
|
1571
1693
|
const value = typeof params.value === "string" ? params.value : void 0;
|
|
1572
1694
|
if (value === void 0) throw invalidParams(`invalid value: ${String(params.value)}`);
|
|
1573
1695
|
switch (params.configId) {
|
package/dist/index.js
CHANGED
|
@@ -888,6 +888,87 @@ async function apply(ctx, config = {}) {
|
|
|
888
888
|
if (record === void 0) throw invalidParams(`unknown session: ${sessionId}`);
|
|
889
889
|
return record;
|
|
890
890
|
};
|
|
891
|
+
const requireOrRestoreSession = async (sessionId) => {
|
|
892
|
+
const record = sessions.get(sessionId);
|
|
893
|
+
if (record !== void 0) return record;
|
|
894
|
+
if (ctx.get("sessionPersistence") === void 0) throw invalidParams(`unknown session: ${sessionId}`);
|
|
895
|
+
logWarn(`restoring session ${sessionId} from the persisted log`);
|
|
896
|
+
try {
|
|
897
|
+
return await restoreSession(sessionId, { replay: false });
|
|
898
|
+
} catch (error) {
|
|
899
|
+
throw invalidParams(`unknown session: ${sessionId} (${errorChain2(error)})`);
|
|
900
|
+
}
|
|
901
|
+
};
|
|
902
|
+
const restoreSession = async (sessionId, options) => {
|
|
903
|
+
const persistence = requirePersistence();
|
|
904
|
+
const existing = sessions.get(sessionId);
|
|
905
|
+
if (existing !== void 0) {
|
|
906
|
+
sessions.delete(sessionId);
|
|
907
|
+
existing.agent.cancel({ kind: "user" });
|
|
908
|
+
settlePrompt(existing, "cancelled");
|
|
909
|
+
await existing.dispose().catch((error) => {
|
|
910
|
+
logWarn(`dispose before reload failed: ${String(error)}`);
|
|
911
|
+
});
|
|
912
|
+
}
|
|
913
|
+
let events;
|
|
914
|
+
let storedHeader;
|
|
915
|
+
try {
|
|
916
|
+
const inspected = await persistence.inspect(SessionId2(sessionId));
|
|
917
|
+
events = inspected.events;
|
|
918
|
+
storedHeader = inspected.header;
|
|
919
|
+
} catch (error) {
|
|
920
|
+
throw invalidParams(`session not found: ${sessionId} (${errorChain2(error)})`);
|
|
921
|
+
}
|
|
922
|
+
const replay = buildReplay(events);
|
|
923
|
+
if (options.replay) {
|
|
924
|
+
for (const update of replay.updates) notify(sessionId, update);
|
|
925
|
+
if (replay.title !== void 0) {
|
|
926
|
+
notify(sessionId, {
|
|
927
|
+
sessionUpdate: "session_info_update",
|
|
928
|
+
title: replay.title
|
|
929
|
+
});
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
const presets = presetsService();
|
|
933
|
+
const storedPreset = presetFromLog(storedHeader, events);
|
|
934
|
+
const presetId = presets === void 0 ? void 0 : (await presets.resolve(storedPreset)).id;
|
|
935
|
+
const handle = await agents.resume({
|
|
936
|
+
resumeSessionId: SessionId2(sessionId),
|
|
937
|
+
agentOptions: agentOptionsFor(config.model),
|
|
938
|
+
...presetSetup(presets, presetId) !== void 0 ? { setup: presetSetup(presets, presetId) } : {}
|
|
939
|
+
});
|
|
940
|
+
const cwd = options.cwd ?? storedHeader?.cwd;
|
|
941
|
+
const projection = new SessionProjection(replay.contextWindow, {
|
|
942
|
+
terminalOutput: clientTerminalOutput,
|
|
943
|
+
...cwd !== void 0 ? { cwd } : {}
|
|
944
|
+
});
|
|
945
|
+
projection.title = replay.title;
|
|
946
|
+
const record = registerRecord(
|
|
947
|
+
sessionId,
|
|
948
|
+
handle.agent,
|
|
949
|
+
() => handle.dispose(),
|
|
950
|
+
config.model,
|
|
951
|
+
config.permissionMode ?? "workspace-write",
|
|
952
|
+
projection
|
|
953
|
+
);
|
|
954
|
+
if (presetId !== void 0) record.preset = presetId;
|
|
955
|
+
{
|
|
956
|
+
const permission = permissionService();
|
|
957
|
+
const storedMode = permission?.current(events);
|
|
958
|
+
if (storedMode !== void 0) record.modeId = storedMode;
|
|
959
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
960
|
+
const event = events[index];
|
|
961
|
+
if (event?.type === "approval/policy") {
|
|
962
|
+
const policy = event.data?.policy;
|
|
963
|
+
if (policy === "ask" || policy === "never") record.approvals = policy;
|
|
964
|
+
break;
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
ensureSelection(record);
|
|
969
|
+
queueMicrotask(() => publishCommands(sessionId));
|
|
970
|
+
return record;
|
|
971
|
+
};
|
|
891
972
|
const assertOpen = () => {
|
|
892
973
|
if (closed) throw internalError("the ACP bridge has been disposed");
|
|
893
974
|
};
|
|
@@ -989,7 +1070,7 @@ async function apply(ctx, config = {}) {
|
|
|
989
1070
|
}
|
|
990
1071
|
record.approvals = policy;
|
|
991
1072
|
};
|
|
992
|
-
const permissionService = () => ctx.get("permission");
|
|
1073
|
+
const permissionService = () => ctx.get("permissionPresets") ?? ctx.get("permission");
|
|
993
1074
|
const applyMode = (record, sessionId, modeId) => {
|
|
994
1075
|
const permission = permissionService();
|
|
995
1076
|
if (permission !== void 0 && permission.names.includes(modeId)) {
|
|
@@ -1118,7 +1199,12 @@ async function apply(ctx, config = {}) {
|
|
|
1118
1199
|
description: "Save a DeepSeek API key into the harness credential store (~/.dsh/.credentials.yaml)",
|
|
1119
1200
|
input: { hint: "<api-key>" }
|
|
1120
1201
|
},
|
|
1121
|
-
{ name: "logout", description: "Remove the API key stored in the harness credential store" }
|
|
1202
|
+
{ name: "logout", description: "Remove the API key stored in the harness credential store" },
|
|
1203
|
+
{
|
|
1204
|
+
name: "model",
|
|
1205
|
+
description: "Select the model for this conversation",
|
|
1206
|
+
input: { hint: "[model] \u2014 blank lists available models" }
|
|
1207
|
+
}
|
|
1122
1208
|
];
|
|
1123
1209
|
const availableCommandsFor = async (record) => {
|
|
1124
1210
|
const list = [...BUILTIN_COMMANDS];
|
|
@@ -1171,6 +1257,37 @@ async function apply(ctx, config = {}) {
|
|
|
1171
1257
|
logWarn(`publishing commands failed: ${String(error)}`);
|
|
1172
1258
|
});
|
|
1173
1259
|
};
|
|
1260
|
+
const publishConfigOptions = (sessionId, record) => {
|
|
1261
|
+
void configOptions(record).then((options) => {
|
|
1262
|
+
notify(sessionId, {
|
|
1263
|
+
sessionUpdate: "config_option_update",
|
|
1264
|
+
configOptions: options
|
|
1265
|
+
});
|
|
1266
|
+
}).catch((error) => {
|
|
1267
|
+
logWarn(`publishing config options failed: ${String(error)}`);
|
|
1268
|
+
});
|
|
1269
|
+
};
|
|
1270
|
+
const syncPermissionState = (record, sessionId) => {
|
|
1271
|
+
const events = record.agent.session.events;
|
|
1272
|
+
if (events === void 0) return;
|
|
1273
|
+
const permission = permissionService();
|
|
1274
|
+
const storedMode = permission?.current(events);
|
|
1275
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
1276
|
+
const event = events[index];
|
|
1277
|
+
if (event?.type === "approval/policy") {
|
|
1278
|
+
const policy = event.data?.policy;
|
|
1279
|
+
if (policy === "ask" || policy === "never") record.approvals = policy;
|
|
1280
|
+
break;
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
if (storedMode !== void 0 && storedMode !== record.modeId) {
|
|
1284
|
+
record.modeId = storedMode;
|
|
1285
|
+
notify(sessionId, {
|
|
1286
|
+
sessionUpdate: "current_mode_update",
|
|
1287
|
+
currentModeId: record.modeId
|
|
1288
|
+
});
|
|
1289
|
+
}
|
|
1290
|
+
};
|
|
1174
1291
|
const credentialStore = () => {
|
|
1175
1292
|
const brand = harness.credentialRef;
|
|
1176
1293
|
const store = ctx.get("credentials");
|
|
@@ -1235,6 +1352,49 @@ async function apply(ctx, config = {}) {
|
|
|
1235
1352
|
const remaining = await describeCredential();
|
|
1236
1353
|
return remaining === "not configured" ? "Removed the stored DEEPSEEK_API_KEY. No credential is configured now." : `Removed the stored DEEPSEEK_API_KEY; requests now authenticate via: ${remaining}.`;
|
|
1237
1354
|
};
|
|
1355
|
+
const modelCommandText = async (record, acpSessionId, query) => {
|
|
1356
|
+
const discovered = await discoverModels();
|
|
1357
|
+
const catalog = [
|
|
1358
|
+
...discovered.map((entry) => ({
|
|
1359
|
+
value: encodeChoice(entry.provider, entry.model),
|
|
1360
|
+
label: entry.label
|
|
1361
|
+
})),
|
|
1362
|
+
...modelCandidates().filter((model) => !discovered.some((entry) => entry.model === model)).map((model) => ({ value: encodeChoice(void 0, model), label: model }))
|
|
1363
|
+
];
|
|
1364
|
+
const route = routeOf(record);
|
|
1365
|
+
const currentValue = encodeChoice(record.provider, record.model ?? route.model ?? "");
|
|
1366
|
+
if (query === "") {
|
|
1367
|
+
const lines = catalog.map(
|
|
1368
|
+
(entry) => `${entry.value === currentValue ? "\u2192" : " "} ${entry.label} \u2014 ${entry.value}`
|
|
1369
|
+
);
|
|
1370
|
+
return [
|
|
1371
|
+
`model: ${route.model ?? "(product default)"}${record.effort !== void 0 ? ` (effort ${record.effort})` : ""}`,
|
|
1372
|
+
"",
|
|
1373
|
+
...lines.length > 0 ? lines : ["no models discovered \u2014 check credentials with /status"],
|
|
1374
|
+
"",
|
|
1375
|
+
"switch with /model <name>"
|
|
1376
|
+
].join("\n");
|
|
1377
|
+
}
|
|
1378
|
+
const lowered = query.toLowerCase();
|
|
1379
|
+
const exact = catalog.filter(
|
|
1380
|
+
(entry) => entry.value.toLowerCase() === lowered || decodeChoice(entry.value).model.toLowerCase() === lowered
|
|
1381
|
+
);
|
|
1382
|
+
const matches = exact.length > 0 ? exact : catalog.filter(
|
|
1383
|
+
(entry) => entry.value.toLowerCase().includes(lowered) || entry.label.toLowerCase().includes(lowered)
|
|
1384
|
+
);
|
|
1385
|
+
if (matches.length === 0) return `no model matches "${query}" \u2014 see /model`;
|
|
1386
|
+
if (matches.length > 1) {
|
|
1387
|
+
return [`"${query}" is ambiguous:`, ...matches.map((entry) => ` ${entry.label} \u2014 ${entry.value}`)].join(
|
|
1388
|
+
"\n"
|
|
1389
|
+
);
|
|
1390
|
+
}
|
|
1391
|
+
const target = matches[0];
|
|
1392
|
+
if (target.value === currentValue) return `already on ${target.label}`;
|
|
1393
|
+
await switchModel(record, acpSessionId, target.value);
|
|
1394
|
+
publishCommands(acpSessionId, record);
|
|
1395
|
+
publishConfigOptions(acpSessionId, record);
|
|
1396
|
+
return `model \u2192 ${target.label}`;
|
|
1397
|
+
};
|
|
1238
1398
|
const statusText = async (record) => {
|
|
1239
1399
|
const used = record.projection.contextWindow;
|
|
1240
1400
|
const route = routeOf(record);
|
|
@@ -1347,13 +1507,24 @@ async function apply(ctx, config = {}) {
|
|
|
1347
1507
|
mcpCapabilities: { http: true, sse: false },
|
|
1348
1508
|
...hasPersistence ? { sessionCapabilities: { list: {} } } : {}
|
|
1349
1509
|
},
|
|
1350
|
-
authMethods: [
|
|
1510
|
+
authMethods: [
|
|
1511
|
+
// ACP Terminal Auth (see the registry's AUTHENTICATION.md):
|
|
1512
|
+
// the client runs `dsh-acp login` in an interactive
|
|
1513
|
+
// terminal; the key lands in the shared harness
|
|
1514
|
+
// credential store (~/.dsh/.credentials.yaml).
|
|
1515
|
+
{
|
|
1516
|
+
id: "terminal-login",
|
|
1517
|
+
name: "Log in with a DeepSeek API key",
|
|
1518
|
+
description: "Interactive terminal setup \u2014 saves the key to the harness credential store shared with the dsh Web UI",
|
|
1519
|
+
_meta: { "terminal-auth": { args: ["login"], env: {} } }
|
|
1520
|
+
}
|
|
1521
|
+
]
|
|
1351
1522
|
});
|
|
1352
1523
|
},
|
|
1353
1524
|
async authenticate(_params) {
|
|
1354
1525
|
if (!await credentialPresent()) {
|
|
1355
1526
|
throw authRequired(
|
|
1356
|
-
"no DeepSeek credential found: save one in the dsh Web UI (Settings \u2192 Models) or set DEEPSEEK_API_KEY"
|
|
1527
|
+
"no DeepSeek credential found: run `dsh-acp login`, save one in the dsh Web UI (Settings \u2192 Models), or set DEEPSEEK_API_KEY"
|
|
1357
1528
|
);
|
|
1358
1529
|
}
|
|
1359
1530
|
},
|
|
@@ -1395,68 +1566,8 @@ async function apply(ctx, config = {}) {
|
|
|
1395
1566
|
assertOpen();
|
|
1396
1567
|
validateCwd(params.cwd);
|
|
1397
1568
|
syncMcpServers(params.mcpServers, params.cwd);
|
|
1398
|
-
|
|
1399
|
-
const
|
|
1400
|
-
const existing = sessions.get(sessionId);
|
|
1401
|
-
if (existing !== void 0) {
|
|
1402
|
-
sessions.delete(sessionId);
|
|
1403
|
-
existing.agent.cancel({ kind: "user" });
|
|
1404
|
-
settlePrompt(existing, "cancelled");
|
|
1405
|
-
await existing.dispose().catch((error) => {
|
|
1406
|
-
logWarn(`dispose before reload failed: ${String(error)}`);
|
|
1407
|
-
});
|
|
1408
|
-
}
|
|
1409
|
-
let events;
|
|
1410
|
-
let storedHeader;
|
|
1411
|
-
try {
|
|
1412
|
-
const inspected = await persistence.inspect(SessionId2(sessionId));
|
|
1413
|
-
events = inspected.events;
|
|
1414
|
-
storedHeader = inspected.header;
|
|
1415
|
-
} catch (error) {
|
|
1416
|
-
throw invalidParams(`session not found: ${sessionId} (${errorChain2(error)})`);
|
|
1417
|
-
}
|
|
1418
|
-
const replay = buildReplay(events);
|
|
1419
|
-
for (const update of replay.updates) notify(sessionId, update);
|
|
1420
|
-
if (replay.title !== void 0) {
|
|
1421
|
-
notify(sessionId, {
|
|
1422
|
-
sessionUpdate: "session_info_update",
|
|
1423
|
-
title: replay.title
|
|
1424
|
-
});
|
|
1425
|
-
}
|
|
1426
|
-
const presets = presetsService();
|
|
1427
|
-
const storedPreset = presetFromLog(storedHeader, events);
|
|
1428
|
-
const presetId = presets === void 0 ? void 0 : (await presets.resolve(storedPreset)).id;
|
|
1429
|
-
const handle = await agents.resume({
|
|
1430
|
-
resumeSessionId: SessionId2(sessionId),
|
|
1431
|
-
agentOptions: agentOptionsFor(config.model),
|
|
1432
|
-
...presetSetup(presets, presetId) !== void 0 ? { setup: presetSetup(presets, presetId) } : {}
|
|
1433
|
-
});
|
|
1434
|
-
const projection = new SessionProjection(replay.contextWindow);
|
|
1435
|
-
projection.title = replay.title;
|
|
1436
|
-
const record = registerRecord(
|
|
1437
|
-
sessionId,
|
|
1438
|
-
handle.agent,
|
|
1439
|
-
() => handle.dispose(),
|
|
1440
|
-
config.model,
|
|
1441
|
-
config.permissionMode ?? "workspace-write",
|
|
1442
|
-
projection
|
|
1443
|
-
);
|
|
1444
|
-
if (presetId !== void 0) record.preset = presetId;
|
|
1445
|
-
{
|
|
1446
|
-
const permission = permissionService();
|
|
1447
|
-
const storedMode = permission?.current(events);
|
|
1448
|
-
if (storedMode !== void 0) record.modeId = storedMode;
|
|
1449
|
-
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
1450
|
-
const event = events[index];
|
|
1451
|
-
if (event?.type === "approval/policy") {
|
|
1452
|
-
const policy = event.data?.policy;
|
|
1453
|
-
if (policy === "ask" || policy === "never") record.approvals = policy;
|
|
1454
|
-
break;
|
|
1455
|
-
}
|
|
1456
|
-
}
|
|
1457
|
-
}
|
|
1458
|
-
ensureSelection(record);
|
|
1459
|
-
queueMicrotask(() => publishCommands(sessionId));
|
|
1569
|
+
requirePersistence();
|
|
1570
|
+
const record = await restoreSession(params.sessionId, { replay: true, cwd: params.cwd });
|
|
1460
1571
|
const options = await configOptions(record);
|
|
1461
1572
|
return {
|
|
1462
1573
|
modes: modeState(record),
|
|
@@ -1490,7 +1601,7 @@ async function apply(ctx, config = {}) {
|
|
|
1490
1601
|
},
|
|
1491
1602
|
async prompt(params) {
|
|
1492
1603
|
assertOpen();
|
|
1493
|
-
const record =
|
|
1604
|
+
const record = await requireOrRestoreSession(params.sessionId);
|
|
1494
1605
|
if (record.inflight !== void 0) {
|
|
1495
1606
|
throw invalidParams("a prompt is already in flight for this session");
|
|
1496
1607
|
}
|
|
@@ -1516,6 +1627,15 @@ async function apply(ctx, config = {}) {
|
|
|
1516
1627
|
return respond(await loginText(trimmed.slice(commandMatch[0].length)));
|
|
1517
1628
|
}
|
|
1518
1629
|
if (commandMatch?.[1] === "logout") return respond(await logoutText());
|
|
1630
|
+
if (commandMatch?.[1] === "model") {
|
|
1631
|
+
return respond(
|
|
1632
|
+
await modelCommandText(
|
|
1633
|
+
record,
|
|
1634
|
+
params.sessionId,
|
|
1635
|
+
trimmed.slice(commandMatch[0].length).trim()
|
|
1636
|
+
)
|
|
1637
|
+
);
|
|
1638
|
+
}
|
|
1519
1639
|
if (commandMatch !== null && commandMatch[1] !== void 0) {
|
|
1520
1640
|
const commandsSvc = ctx.get("commands");
|
|
1521
1641
|
if (commandsSvc !== void 0) {
|
|
@@ -1532,7 +1652,9 @@ async function apply(ctx, config = {}) {
|
|
|
1532
1652
|
if (execution !== void 0) {
|
|
1533
1653
|
const { result } = execution;
|
|
1534
1654
|
const text = result.text ?? (result.kind === "success" ? `/${commandMatch[1]} \u2713` : `/${commandMatch[1]} failed`);
|
|
1655
|
+
syncPermissionState(record, params.sessionId);
|
|
1535
1656
|
publishCommands(params.sessionId, record);
|
|
1657
|
+
publishConfigOptions(params.sessionId, record);
|
|
1536
1658
|
return respond(result.kind === "error" ? `\u26A0 ${text}` : text);
|
|
1537
1659
|
}
|
|
1538
1660
|
}
|
|
@@ -1583,7 +1705,7 @@ async function apply(ctx, config = {}) {
|
|
|
1583
1705
|
return { stopReason, ...usage !== void 0 ? { usage } : {} };
|
|
1584
1706
|
},
|
|
1585
1707
|
async setSessionMode(params) {
|
|
1586
|
-
const record =
|
|
1708
|
+
const record = await requireOrRestoreSession(params.sessionId);
|
|
1587
1709
|
applyMode(record, params.sessionId, params.modeId);
|
|
1588
1710
|
notify(params.sessionId, {
|
|
1589
1711
|
sessionUpdate: "config_option_update",
|
|
@@ -1592,7 +1714,7 @@ async function apply(ctx, config = {}) {
|
|
|
1592
1714
|
return {};
|
|
1593
1715
|
},
|
|
1594
1716
|
async setSessionConfigOption(params) {
|
|
1595
|
-
const record =
|
|
1717
|
+
const record = await requireOrRestoreSession(params.sessionId);
|
|
1596
1718
|
const value = typeof params.value === "string" ? params.value : void 0;
|
|
1597
1719
|
if (value === void 0) throw invalidParams(`invalid value: ${String(params.value)}`);
|
|
1598
1720
|
switch (params.configId) {
|
|
@@ -2245,6 +2367,9 @@ async function bootAcpProfile(host, overrides) {
|
|
|
2245
2367
|
overlays.push({ id: "session-telemetry-otel", disabled: true });
|
|
2246
2368
|
}
|
|
2247
2369
|
const bridgeRow = rows.get("acp-bridge");
|
|
2370
|
+
if (overrides?.serve === false && bridgeRow !== void 0) {
|
|
2371
|
+
overlays.push({ id: "acp-bridge", disabled: true });
|
|
2372
|
+
}
|
|
2248
2373
|
if (overrides !== void 0 && bridgeRow !== void 0) {
|
|
2249
2374
|
const patch = {};
|
|
2250
2375
|
if (overrides.provider !== void 0) patch["provider"] = overrides.provider;
|
|
@@ -2281,9 +2406,93 @@ async function bootAcpProfile(host, overrides) {
|
|
|
2281
2406
|
return ctx;
|
|
2282
2407
|
}
|
|
2283
2408
|
|
|
2409
|
+
// src/login.ts
|
|
2410
|
+
import { createRequire as createRequire3 } from "node:module";
|
|
2411
|
+
import { join as join3 } from "node:path";
|
|
2412
|
+
import { pathToFileURL as pathToFileURL3 } from "node:url";
|
|
2413
|
+
async function readSecret(promptText) {
|
|
2414
|
+
const stdin = process.stdin;
|
|
2415
|
+
if (!stdin.isTTY) {
|
|
2416
|
+
const chunks = [];
|
|
2417
|
+
for await (const chunk of stdin) {
|
|
2418
|
+
chunks.push(Buffer.from(chunk));
|
|
2419
|
+
const text = Buffer.concat(chunks).toString("utf8");
|
|
2420
|
+
const newline = text.indexOf("\n");
|
|
2421
|
+
if (newline >= 0) return text.slice(0, newline).trim();
|
|
2422
|
+
}
|
|
2423
|
+
return Buffer.concat(chunks).toString("utf8").trim();
|
|
2424
|
+
}
|
|
2425
|
+
process.stderr.write(promptText);
|
|
2426
|
+
return await new Promise((resolve2) => {
|
|
2427
|
+
stdin.setRawMode(true);
|
|
2428
|
+
stdin.resume();
|
|
2429
|
+
stdin.setEncoding("utf8");
|
|
2430
|
+
let buffer = "";
|
|
2431
|
+
const onData = (chunk) => {
|
|
2432
|
+
for (const char of chunk) {
|
|
2433
|
+
if (char === "\n" || char === "\r" || char === "") {
|
|
2434
|
+
stdin.setRawMode(false);
|
|
2435
|
+
stdin.pause();
|
|
2436
|
+
stdin.off("data", onData);
|
|
2437
|
+
process.stderr.write("\n");
|
|
2438
|
+
resolve2(buffer.trim());
|
|
2439
|
+
return;
|
|
2440
|
+
}
|
|
2441
|
+
if (char === "") {
|
|
2442
|
+
stdin.setRawMode(false);
|
|
2443
|
+
process.stderr.write("^C\n");
|
|
2444
|
+
process.exit(130);
|
|
2445
|
+
}
|
|
2446
|
+
if (char === "\x7F" || char === "\b") {
|
|
2447
|
+
buffer = buffer.slice(0, -1);
|
|
2448
|
+
continue;
|
|
2449
|
+
}
|
|
2450
|
+
buffer += char;
|
|
2451
|
+
}
|
|
2452
|
+
};
|
|
2453
|
+
stdin.on("data", onData);
|
|
2454
|
+
});
|
|
2455
|
+
}
|
|
2456
|
+
async function runLogin(ctx, hostBase, argv) {
|
|
2457
|
+
const req = createRequire3(join3(hostBase, "noop.js"));
|
|
2458
|
+
let credentialRef2;
|
|
2459
|
+
try {
|
|
2460
|
+
const mod = await import(pathToFileURL3(req.resolve("@deepseek-ai/dsh-credentials")).href);
|
|
2461
|
+
credentialRef2 = mod.credentialRef;
|
|
2462
|
+
} catch {
|
|
2463
|
+
credentialRef2 = void 0;
|
|
2464
|
+
}
|
|
2465
|
+
const store = ctx.get("credentials");
|
|
2466
|
+
if (store === void 0 || credentialRef2 === void 0) {
|
|
2467
|
+
process.stderr.write(
|
|
2468
|
+
"This composition has no writable credential store; export DEEPSEEK_API_KEY instead.\n"
|
|
2469
|
+
);
|
|
2470
|
+
return 1;
|
|
2471
|
+
}
|
|
2472
|
+
const ref = credentialRef2("DEEPSEEK_API_KEY");
|
|
2473
|
+
const before = await store.describe(ref);
|
|
2474
|
+
if (before.configured && before.source !== void 0) {
|
|
2475
|
+
process.stderr.write(`A key is already configured (source: ${before.source}).
|
|
2476
|
+
`);
|
|
2477
|
+
}
|
|
2478
|
+
const key = argv[0] ?? await readSecret("DeepSeek API key (input hidden): ");
|
|
2479
|
+
if (key.length === 0) {
|
|
2480
|
+
process.stderr.write("No key entered; nothing saved.\n");
|
|
2481
|
+
return 1;
|
|
2482
|
+
}
|
|
2483
|
+
await store.set(ref, key);
|
|
2484
|
+
const masked = key.length <= 8 ? "\u2026" : `${key.slice(0, 4)}\u2026${key.slice(-4)}`;
|
|
2485
|
+
process.stderr.write(
|
|
2486
|
+
`Saved ${masked} to the harness credential store (~/.dsh/.credentials.yaml, mode 600).
|
|
2487
|
+
The dsh Web UI and every dsh surface share this credential.
|
|
2488
|
+
`
|
|
2489
|
+
);
|
|
2490
|
+
return 0;
|
|
2491
|
+
}
|
|
2492
|
+
|
|
2284
2493
|
// src/settings.ts
|
|
2285
2494
|
import { homedir } from "node:os";
|
|
2286
|
-
import { join as
|
|
2495
|
+
import { join as join4 } from "node:path";
|
|
2287
2496
|
var DEFAULT_PERSONA = `You are a coding assistant powered by the {{model}} model, working inside an editor via the Agent Client Protocol. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox \u2014 a \`[sandbox: file access denied \u2026]\` result is policy, not a command bug.
|
|
2288
2497
|
|
|
2289
2498
|
Verify your work by running the code or tests. Keep answers brief and factual.`;
|
|
@@ -2376,7 +2585,7 @@ function resolveSettings(argv) {
|
|
|
2376
2585
|
models,
|
|
2377
2586
|
maxTokens,
|
|
2378
2587
|
permissionMode,
|
|
2379
|
-
sessionRoot: stringFlag(parsed, "session-root") ?? envString("DSH_SESSION_ROOT") ??
|
|
2588
|
+
sessionRoot: stringFlag(parsed, "session-root") ?? envString("DSH_SESSION_ROOT") ?? join4(homedir(), ".dsh-acp", "sessions"),
|
|
2380
2589
|
persona: stringFlag(parsed, "persona") ?? envString("DSH_SYSTEM_PROMPT") ?? DEFAULT_PERSONA,
|
|
2381
2590
|
thinking: !parsed.flags.has("no-thinking"),
|
|
2382
2591
|
reasoningEffort: reasoningRaw,
|
|
@@ -2384,6 +2593,8 @@ function resolveSettings(argv) {
|
|
|
2384
2593
|
};
|
|
2385
2594
|
}
|
|
2386
2595
|
var HELP_TEXT = `Usage: dsh-acp [options]
|
|
2596
|
+
dsh-acp login [api-key] Save a DeepSeek API key (interactive when omitted)
|
|
2597
|
+
dsh-acp update Self-update via npm
|
|
2387
2598
|
|
|
2388
2599
|
Agent Client Protocol (ACP) stdio server for DeepSeek Harness. Speak ACP on
|
|
2389
2600
|
stdin/stdout from an ACP client such as Zed; diagnostics go to stderr.
|
|
@@ -2427,6 +2638,32 @@ async function main() {
|
|
|
2427
2638
|
console.log(HELP_TEXT.trimEnd());
|
|
2428
2639
|
return;
|
|
2429
2640
|
}
|
|
2641
|
+
if (argv[0] === "login") {
|
|
2642
|
+
let booted;
|
|
2643
|
+
let hostBase;
|
|
2644
|
+
try {
|
|
2645
|
+
const host = resolveHost(void 0);
|
|
2646
|
+
hostBase = host.base;
|
|
2647
|
+
booted = await bootAcpProfile(host, { serve: false });
|
|
2648
|
+
} catch (error) {
|
|
2649
|
+
if (error instanceof HarnessNotFoundError) {
|
|
2650
|
+
logError(error.message);
|
|
2651
|
+
process.exitCode = 1;
|
|
2652
|
+
return;
|
|
2653
|
+
}
|
|
2654
|
+
throw error;
|
|
2655
|
+
}
|
|
2656
|
+
try {
|
|
2657
|
+
process.exitCode = await runLogin(
|
|
2658
|
+
booted,
|
|
2659
|
+
hostBase,
|
|
2660
|
+
argv.slice(1)
|
|
2661
|
+
);
|
|
2662
|
+
} finally {
|
|
2663
|
+
await booted.fiber.dispose().catch(() => void 0);
|
|
2664
|
+
}
|
|
2665
|
+
return;
|
|
2666
|
+
}
|
|
2430
2667
|
if (argv[0] === "update") {
|
|
2431
2668
|
logInfo(`updating ${NAME} (npm install -g ${NAME}@latest)\u2026`);
|
|
2432
2669
|
const result = spawnSync("npm", ["install", "-g", `${NAME}@latest`], { stdio: ["ignore", 2, 2] });
|