@openma/deepseek-harness-acp 0.4.3 → 0.4.4
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 +178 -67
- package/dist/index.js +178 -67
- 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);
|
|
@@ -1370,68 +1530,8 @@ async function apply(ctx, config = {}) {
|
|
|
1370
1530
|
assertOpen();
|
|
1371
1531
|
validateCwd(params.cwd);
|
|
1372
1532
|
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));
|
|
1533
|
+
requirePersistence();
|
|
1534
|
+
const record = await restoreSession(params.sessionId, { replay: true, cwd: params.cwd });
|
|
1435
1535
|
const options = await configOptions(record);
|
|
1436
1536
|
return {
|
|
1437
1537
|
modes: modeState(record),
|
|
@@ -1465,7 +1565,7 @@ async function apply(ctx, config = {}) {
|
|
|
1465
1565
|
},
|
|
1466
1566
|
async prompt(params) {
|
|
1467
1567
|
assertOpen();
|
|
1468
|
-
const record =
|
|
1568
|
+
const record = await requireOrRestoreSession(params.sessionId);
|
|
1469
1569
|
if (record.inflight !== void 0) {
|
|
1470
1570
|
throw invalidParams("a prompt is already in flight for this session");
|
|
1471
1571
|
}
|
|
@@ -1491,6 +1591,15 @@ async function apply(ctx, config = {}) {
|
|
|
1491
1591
|
return respond(await loginText(trimmed.slice(commandMatch[0].length)));
|
|
1492
1592
|
}
|
|
1493
1593
|
if (commandMatch?.[1] === "logout") return respond(await logoutText());
|
|
1594
|
+
if (commandMatch?.[1] === "model") {
|
|
1595
|
+
return respond(
|
|
1596
|
+
await modelCommandText(
|
|
1597
|
+
record,
|
|
1598
|
+
params.sessionId,
|
|
1599
|
+
trimmed.slice(commandMatch[0].length).trim()
|
|
1600
|
+
)
|
|
1601
|
+
);
|
|
1602
|
+
}
|
|
1494
1603
|
if (commandMatch !== null && commandMatch[1] !== void 0) {
|
|
1495
1604
|
const commandsSvc = ctx.get("commands");
|
|
1496
1605
|
if (commandsSvc !== void 0) {
|
|
@@ -1507,7 +1616,9 @@ async function apply(ctx, config = {}) {
|
|
|
1507
1616
|
if (execution !== void 0) {
|
|
1508
1617
|
const { result } = execution;
|
|
1509
1618
|
const text = result.text ?? (result.kind === "success" ? `/${commandMatch[1]} \u2713` : `/${commandMatch[1]} failed`);
|
|
1619
|
+
syncPermissionState(record, params.sessionId);
|
|
1510
1620
|
publishCommands(params.sessionId, record);
|
|
1621
|
+
publishConfigOptions(params.sessionId, record);
|
|
1511
1622
|
return respond(result.kind === "error" ? `\u26A0 ${text}` : text);
|
|
1512
1623
|
}
|
|
1513
1624
|
}
|
|
@@ -1558,7 +1669,7 @@ async function apply(ctx, config = {}) {
|
|
|
1558
1669
|
return { stopReason, ...usage !== void 0 ? { usage } : {} };
|
|
1559
1670
|
},
|
|
1560
1671
|
async setSessionMode(params) {
|
|
1561
|
-
const record =
|
|
1672
|
+
const record = await requireOrRestoreSession(params.sessionId);
|
|
1562
1673
|
applyMode(record, params.sessionId, params.modeId);
|
|
1563
1674
|
notify(params.sessionId, {
|
|
1564
1675
|
sessionUpdate: "config_option_update",
|
|
@@ -1567,7 +1678,7 @@ async function apply(ctx, config = {}) {
|
|
|
1567
1678
|
return {};
|
|
1568
1679
|
},
|
|
1569
1680
|
async setSessionConfigOption(params) {
|
|
1570
|
-
const record =
|
|
1681
|
+
const record = await requireOrRestoreSession(params.sessionId);
|
|
1571
1682
|
const value = typeof params.value === "string" ? params.value : void 0;
|
|
1572
1683
|
if (value === void 0) throw invalidParams(`invalid value: ${String(params.value)}`);
|
|
1573
1684
|
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);
|
|
@@ -1395,68 +1555,8 @@ async function apply(ctx, config = {}) {
|
|
|
1395
1555
|
assertOpen();
|
|
1396
1556
|
validateCwd(params.cwd);
|
|
1397
1557
|
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));
|
|
1558
|
+
requirePersistence();
|
|
1559
|
+
const record = await restoreSession(params.sessionId, { replay: true, cwd: params.cwd });
|
|
1460
1560
|
const options = await configOptions(record);
|
|
1461
1561
|
return {
|
|
1462
1562
|
modes: modeState(record),
|
|
@@ -1490,7 +1590,7 @@ async function apply(ctx, config = {}) {
|
|
|
1490
1590
|
},
|
|
1491
1591
|
async prompt(params) {
|
|
1492
1592
|
assertOpen();
|
|
1493
|
-
const record =
|
|
1593
|
+
const record = await requireOrRestoreSession(params.sessionId);
|
|
1494
1594
|
if (record.inflight !== void 0) {
|
|
1495
1595
|
throw invalidParams("a prompt is already in flight for this session");
|
|
1496
1596
|
}
|
|
@@ -1516,6 +1616,15 @@ async function apply(ctx, config = {}) {
|
|
|
1516
1616
|
return respond(await loginText(trimmed.slice(commandMatch[0].length)));
|
|
1517
1617
|
}
|
|
1518
1618
|
if (commandMatch?.[1] === "logout") return respond(await logoutText());
|
|
1619
|
+
if (commandMatch?.[1] === "model") {
|
|
1620
|
+
return respond(
|
|
1621
|
+
await modelCommandText(
|
|
1622
|
+
record,
|
|
1623
|
+
params.sessionId,
|
|
1624
|
+
trimmed.slice(commandMatch[0].length).trim()
|
|
1625
|
+
)
|
|
1626
|
+
);
|
|
1627
|
+
}
|
|
1519
1628
|
if (commandMatch !== null && commandMatch[1] !== void 0) {
|
|
1520
1629
|
const commandsSvc = ctx.get("commands");
|
|
1521
1630
|
if (commandsSvc !== void 0) {
|
|
@@ -1532,7 +1641,9 @@ async function apply(ctx, config = {}) {
|
|
|
1532
1641
|
if (execution !== void 0) {
|
|
1533
1642
|
const { result } = execution;
|
|
1534
1643
|
const text = result.text ?? (result.kind === "success" ? `/${commandMatch[1]} \u2713` : `/${commandMatch[1]} failed`);
|
|
1644
|
+
syncPermissionState(record, params.sessionId);
|
|
1535
1645
|
publishCommands(params.sessionId, record);
|
|
1646
|
+
publishConfigOptions(params.sessionId, record);
|
|
1536
1647
|
return respond(result.kind === "error" ? `\u26A0 ${text}` : text);
|
|
1537
1648
|
}
|
|
1538
1649
|
}
|
|
@@ -1583,7 +1694,7 @@ async function apply(ctx, config = {}) {
|
|
|
1583
1694
|
return { stopReason, ...usage !== void 0 ? { usage } : {} };
|
|
1584
1695
|
},
|
|
1585
1696
|
async setSessionMode(params) {
|
|
1586
|
-
const record =
|
|
1697
|
+
const record = await requireOrRestoreSession(params.sessionId);
|
|
1587
1698
|
applyMode(record, params.sessionId, params.modeId);
|
|
1588
1699
|
notify(params.sessionId, {
|
|
1589
1700
|
sessionUpdate: "config_option_update",
|
|
@@ -1592,7 +1703,7 @@ async function apply(ctx, config = {}) {
|
|
|
1592
1703
|
return {};
|
|
1593
1704
|
},
|
|
1594
1705
|
async setSessionConfigOption(params) {
|
|
1595
|
-
const record =
|
|
1706
|
+
const record = await requireOrRestoreSession(params.sessionId);
|
|
1596
1707
|
const value = typeof params.value === "string" ? params.value : void 0;
|
|
1597
1708
|
if (value === void 0) throw invalidParams(`invalid value: ${String(params.value)}`);
|
|
1598
1709
|
switch (params.configId) {
|