@openma/deepseek-harness-acp 0.4.2 → 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 +257 -78
- package/dist/index.js +257 -78
- 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
|
};
|
|
@@ -954,6 +1035,7 @@ async function apply(ctx, config = {}) {
|
|
|
954
1035
|
delete record.selection;
|
|
955
1036
|
ensureSelection(record);
|
|
956
1037
|
setApprovalPolicy(record, record.approvals);
|
|
1038
|
+
publishCommands(acpSessionId, record);
|
|
957
1039
|
};
|
|
958
1040
|
const setApprovalPolicy = (record, policy) => {
|
|
959
1041
|
try {
|
|
@@ -963,7 +1045,7 @@ async function apply(ctx, config = {}) {
|
|
|
963
1045
|
}
|
|
964
1046
|
record.approvals = policy;
|
|
965
1047
|
};
|
|
966
|
-
const permissionService = () => ctx.get("permission");
|
|
1048
|
+
const permissionService = () => ctx.get("permissionPresets") ?? ctx.get("permission");
|
|
967
1049
|
const applyMode = (record, sessionId, modeId) => {
|
|
968
1050
|
const permission = permissionService();
|
|
969
1051
|
if (permission !== void 0 && permission.names.includes(modeId)) {
|
|
@@ -1085,20 +1167,102 @@ async function apply(ctx, config = {}) {
|
|
|
1085
1167
|
}
|
|
1086
1168
|
return result;
|
|
1087
1169
|
};
|
|
1088
|
-
const
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
]
|
|
1170
|
+
const BUILTIN_COMMANDS = [
|
|
1171
|
+
{ 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
|
+
{
|
|
1179
|
+
name: "model",
|
|
1180
|
+
description: "Select the model for this conversation",
|
|
1181
|
+
input: { hint: "[model] \u2014 blank lists available models" }
|
|
1182
|
+
}
|
|
1183
|
+
];
|
|
1184
|
+
const availableCommandsFor = async (record) => {
|
|
1185
|
+
const list = [...BUILTIN_COMMANDS];
|
|
1186
|
+
const seen = new Set(list.map((command) => command.name));
|
|
1187
|
+
const commandsSvc = ctx.get("commands");
|
|
1188
|
+
if (commandsSvc !== void 0) {
|
|
1189
|
+
try {
|
|
1190
|
+
for (const descriptor of commandsSvc.list(record.agent)) {
|
|
1191
|
+
if (seen.has(descriptor.name)) continue;
|
|
1192
|
+
seen.add(descriptor.name);
|
|
1193
|
+
list.push({
|
|
1194
|
+
name: descriptor.name,
|
|
1195
|
+
description: descriptor.description,
|
|
1196
|
+
...descriptor.input !== void 0 ? { input: { hint: descriptor.input.hint } } : {}
|
|
1197
|
+
});
|
|
1198
|
+
}
|
|
1199
|
+
} catch (error) {
|
|
1200
|
+
logWarn(`command listing failed: ${String(error)}`);
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
const skillsSvc = ctx.get("skills");
|
|
1204
|
+
if (skillsSvc !== void 0) {
|
|
1205
|
+
try {
|
|
1206
|
+
const skills = await skillsSvc.list({
|
|
1207
|
+
...record.agent.session.header.cwd !== void 0 ? { cwd: record.agent.session.header.cwd } : {},
|
|
1208
|
+
scope: record.agent
|
|
1209
|
+
});
|
|
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
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
return list;
|
|
1225
|
+
};
|
|
1226
|
+
const publishCommands = (sessionId, record) => {
|
|
1227
|
+
const target = record ?? sessions.get(sessionId);
|
|
1228
|
+
if (target === void 0) return;
|
|
1229
|
+
void availableCommandsFor(target).then((availableCommands) => {
|
|
1230
|
+
notify(sessionId, { sessionUpdate: "available_commands_update", availableCommands });
|
|
1231
|
+
}).catch((error) => {
|
|
1232
|
+
logWarn(`publishing commands failed: ${String(error)}`);
|
|
1100
1233
|
});
|
|
1101
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
|
+
};
|
|
1102
1266
|
const credentialStore = () => {
|
|
1103
1267
|
const brand = harness.credentialRef;
|
|
1104
1268
|
const store = ctx.get("credentials");
|
|
@@ -1163,6 +1327,49 @@ async function apply(ctx, config = {}) {
|
|
|
1163
1327
|
const remaining = await describeCredential();
|
|
1164
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}.`;
|
|
1165
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
|
+
};
|
|
1166
1373
|
const statusText = async (record) => {
|
|
1167
1374
|
const used = record.projection.contextWindow;
|
|
1168
1375
|
const route = routeOf(record);
|
|
@@ -1323,68 +1530,8 @@ async function apply(ctx, config = {}) {
|
|
|
1323
1530
|
assertOpen();
|
|
1324
1531
|
validateCwd(params.cwd);
|
|
1325
1532
|
syncMcpServers(params.mcpServers, params.cwd);
|
|
1326
|
-
|
|
1327
|
-
const
|
|
1328
|
-
const existing = sessions.get(sessionId);
|
|
1329
|
-
if (existing !== void 0) {
|
|
1330
|
-
sessions.delete(sessionId);
|
|
1331
|
-
existing.agent.cancel({ kind: "user" });
|
|
1332
|
-
settlePrompt(existing, "cancelled");
|
|
1333
|
-
await existing.dispose().catch((error) => {
|
|
1334
|
-
logWarn(`dispose before reload failed: ${String(error)}`);
|
|
1335
|
-
});
|
|
1336
|
-
}
|
|
1337
|
-
let events;
|
|
1338
|
-
let storedHeader;
|
|
1339
|
-
try {
|
|
1340
|
-
const inspected = await persistence.inspect(SessionId2(sessionId));
|
|
1341
|
-
events = inspected.events;
|
|
1342
|
-
storedHeader = inspected.header;
|
|
1343
|
-
} catch (error) {
|
|
1344
|
-
throw invalidParams(`session not found: ${sessionId} (${errorChain2(error)})`);
|
|
1345
|
-
}
|
|
1346
|
-
const replay = buildReplay(events);
|
|
1347
|
-
for (const update of replay.updates) notify(sessionId, update);
|
|
1348
|
-
if (replay.title !== void 0) {
|
|
1349
|
-
notify(sessionId, {
|
|
1350
|
-
sessionUpdate: "session_info_update",
|
|
1351
|
-
title: replay.title
|
|
1352
|
-
});
|
|
1353
|
-
}
|
|
1354
|
-
const presets = presetsService();
|
|
1355
|
-
const storedPreset = presetFromLog(storedHeader, events);
|
|
1356
|
-
const presetId = presets === void 0 ? void 0 : (await presets.resolve(storedPreset)).id;
|
|
1357
|
-
const handle = await agents.resume({
|
|
1358
|
-
resumeSessionId: SessionId2(sessionId),
|
|
1359
|
-
agentOptions: agentOptionsFor(config.model),
|
|
1360
|
-
...presetSetup(presets, presetId) !== void 0 ? { setup: presetSetup(presets, presetId) } : {}
|
|
1361
|
-
});
|
|
1362
|
-
const projection = new SessionProjection(replay.contextWindow);
|
|
1363
|
-
projection.title = replay.title;
|
|
1364
|
-
const record = registerRecord(
|
|
1365
|
-
sessionId,
|
|
1366
|
-
handle.agent,
|
|
1367
|
-
() => handle.dispose(),
|
|
1368
|
-
config.model,
|
|
1369
|
-
config.permissionMode ?? "workspace-write",
|
|
1370
|
-
projection
|
|
1371
|
-
);
|
|
1372
|
-
if (presetId !== void 0) record.preset = presetId;
|
|
1373
|
-
{
|
|
1374
|
-
const permission = permissionService();
|
|
1375
|
-
const storedMode = permission?.current(events);
|
|
1376
|
-
if (storedMode !== void 0) record.modeId = storedMode;
|
|
1377
|
-
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
1378
|
-
const event = events[index];
|
|
1379
|
-
if (event?.type === "approval/policy") {
|
|
1380
|
-
const policy = event.data?.policy;
|
|
1381
|
-
if (policy === "ask" || policy === "never") record.approvals = policy;
|
|
1382
|
-
break;
|
|
1383
|
-
}
|
|
1384
|
-
}
|
|
1385
|
-
}
|
|
1386
|
-
ensureSelection(record);
|
|
1387
|
-
queueMicrotask(() => publishCommands(sessionId));
|
|
1533
|
+
requirePersistence();
|
|
1534
|
+
const record = await restoreSession(params.sessionId, { replay: true, cwd: params.cwd });
|
|
1388
1535
|
const options = await configOptions(record);
|
|
1389
1536
|
return {
|
|
1390
1537
|
modes: modeState(record),
|
|
@@ -1418,7 +1565,7 @@ async function apply(ctx, config = {}) {
|
|
|
1418
1565
|
},
|
|
1419
1566
|
async prompt(params) {
|
|
1420
1567
|
assertOpen();
|
|
1421
|
-
const record =
|
|
1568
|
+
const record = await requireOrRestoreSession(params.sessionId);
|
|
1422
1569
|
if (record.inflight !== void 0) {
|
|
1423
1570
|
throw invalidParams("a prompt is already in flight for this session");
|
|
1424
1571
|
}
|
|
@@ -1444,6 +1591,38 @@ async function apply(ctx, config = {}) {
|
|
|
1444
1591
|
return respond(await loginText(trimmed.slice(commandMatch[0].length)));
|
|
1445
1592
|
}
|
|
1446
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
|
+
}
|
|
1603
|
+
if (commandMatch !== null && commandMatch[1] !== void 0) {
|
|
1604
|
+
const commandsSvc = ctx.get("commands");
|
|
1605
|
+
if (commandsSvc !== void 0) {
|
|
1606
|
+
let execution;
|
|
1607
|
+
try {
|
|
1608
|
+
execution = await commandsSvc.execute(
|
|
1609
|
+
record.agent,
|
|
1610
|
+
trimmed,
|
|
1611
|
+
new AbortController().signal
|
|
1612
|
+
);
|
|
1613
|
+
} catch (error) {
|
|
1614
|
+
return respond(`\u26A0 /${commandMatch[1]} failed: ${errorChain2(error)}`);
|
|
1615
|
+
}
|
|
1616
|
+
if (execution !== void 0) {
|
|
1617
|
+
const { result } = execution;
|
|
1618
|
+
const text = result.text ?? (result.kind === "success" ? `/${commandMatch[1]} \u2713` : `/${commandMatch[1]} failed`);
|
|
1619
|
+
syncPermissionState(record, params.sessionId);
|
|
1620
|
+
publishCommands(params.sessionId, record);
|
|
1621
|
+
publishConfigOptions(params.sessionId, record);
|
|
1622
|
+
return respond(result.kind === "error" ? `\u26A0 ${text}` : text);
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1447
1626
|
if (!await credentialPresent()) {
|
|
1448
1627
|
return respond(
|
|
1449
1628
|
[
|
|
@@ -1490,7 +1669,7 @@ async function apply(ctx, config = {}) {
|
|
|
1490
1669
|
return { stopReason, ...usage !== void 0 ? { usage } : {} };
|
|
1491
1670
|
},
|
|
1492
1671
|
async setSessionMode(params) {
|
|
1493
|
-
const record =
|
|
1672
|
+
const record = await requireOrRestoreSession(params.sessionId);
|
|
1494
1673
|
applyMode(record, params.sessionId, params.modeId);
|
|
1495
1674
|
notify(params.sessionId, {
|
|
1496
1675
|
sessionUpdate: "config_option_update",
|
|
@@ -1499,7 +1678,7 @@ async function apply(ctx, config = {}) {
|
|
|
1499
1678
|
return {};
|
|
1500
1679
|
},
|
|
1501
1680
|
async setSessionConfigOption(params) {
|
|
1502
|
-
const record =
|
|
1681
|
+
const record = await requireOrRestoreSession(params.sessionId);
|
|
1503
1682
|
const value = typeof params.value === "string" ? params.value : void 0;
|
|
1504
1683
|
if (value === void 0) throw invalidParams(`invalid value: ${String(params.value)}`);
|
|
1505
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
|
};
|
|
@@ -979,6 +1060,7 @@ async function apply(ctx, config = {}) {
|
|
|
979
1060
|
delete record.selection;
|
|
980
1061
|
ensureSelection(record);
|
|
981
1062
|
setApprovalPolicy(record, record.approvals);
|
|
1063
|
+
publishCommands(acpSessionId, record);
|
|
982
1064
|
};
|
|
983
1065
|
const setApprovalPolicy = (record, policy) => {
|
|
984
1066
|
try {
|
|
@@ -988,7 +1070,7 @@ async function apply(ctx, config = {}) {
|
|
|
988
1070
|
}
|
|
989
1071
|
record.approvals = policy;
|
|
990
1072
|
};
|
|
991
|
-
const permissionService = () => ctx.get("permission");
|
|
1073
|
+
const permissionService = () => ctx.get("permissionPresets") ?? ctx.get("permission");
|
|
992
1074
|
const applyMode = (record, sessionId, modeId) => {
|
|
993
1075
|
const permission = permissionService();
|
|
994
1076
|
if (permission !== void 0 && permission.names.includes(modeId)) {
|
|
@@ -1110,20 +1192,102 @@ async function apply(ctx, config = {}) {
|
|
|
1110
1192
|
}
|
|
1111
1193
|
return result;
|
|
1112
1194
|
};
|
|
1113
|
-
const
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
]
|
|
1195
|
+
const BUILTIN_COMMANDS = [
|
|
1196
|
+
{ name: "status", description: "Show adapter, model, mode, and token status" },
|
|
1197
|
+
{
|
|
1198
|
+
name: "login",
|
|
1199
|
+
description: "Save a DeepSeek API key into the harness credential store (~/.dsh/.credentials.yaml)",
|
|
1200
|
+
input: { hint: "<api-key>" }
|
|
1201
|
+
},
|
|
1202
|
+
{ name: "logout", description: "Remove the API key stored in the harness credential store" },
|
|
1203
|
+
{
|
|
1204
|
+
name: "model",
|
|
1205
|
+
description: "Select the model for this conversation",
|
|
1206
|
+
input: { hint: "[model] \u2014 blank lists available models" }
|
|
1207
|
+
}
|
|
1208
|
+
];
|
|
1209
|
+
const availableCommandsFor = async (record) => {
|
|
1210
|
+
const list = [...BUILTIN_COMMANDS];
|
|
1211
|
+
const seen = new Set(list.map((command) => command.name));
|
|
1212
|
+
const commandsSvc = ctx.get("commands");
|
|
1213
|
+
if (commandsSvc !== void 0) {
|
|
1214
|
+
try {
|
|
1215
|
+
for (const descriptor of commandsSvc.list(record.agent)) {
|
|
1216
|
+
if (seen.has(descriptor.name)) continue;
|
|
1217
|
+
seen.add(descriptor.name);
|
|
1218
|
+
list.push({
|
|
1219
|
+
name: descriptor.name,
|
|
1220
|
+
description: descriptor.description,
|
|
1221
|
+
...descriptor.input !== void 0 ? { input: { hint: descriptor.input.hint } } : {}
|
|
1222
|
+
});
|
|
1223
|
+
}
|
|
1224
|
+
} catch (error) {
|
|
1225
|
+
logWarn(`command listing failed: ${String(error)}`);
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
const skillsSvc = ctx.get("skills");
|
|
1229
|
+
if (skillsSvc !== void 0) {
|
|
1230
|
+
try {
|
|
1231
|
+
const skills = await skillsSvc.list({
|
|
1232
|
+
...record.agent.session.header.cwd !== void 0 ? { cwd: record.agent.session.header.cwd } : {},
|
|
1233
|
+
scope: record.agent
|
|
1234
|
+
});
|
|
1235
|
+
for (const skill of skills) {
|
|
1236
|
+
if (skill.invocation.userInvocable !== true) continue;
|
|
1237
|
+
if (seen.has(skill.name)) continue;
|
|
1238
|
+
seen.add(skill.name);
|
|
1239
|
+
list.push({
|
|
1240
|
+
name: skill.name,
|
|
1241
|
+
description: skill.description,
|
|
1242
|
+
input: { hint: "instructions for the skill" }
|
|
1243
|
+
});
|
|
1244
|
+
}
|
|
1245
|
+
} catch (error) {
|
|
1246
|
+
logDebug(`skill listing failed: ${String(error)}`);
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
return list;
|
|
1250
|
+
};
|
|
1251
|
+
const publishCommands = (sessionId, record) => {
|
|
1252
|
+
const target = record ?? sessions.get(sessionId);
|
|
1253
|
+
if (target === void 0) return;
|
|
1254
|
+
void availableCommandsFor(target).then((availableCommands) => {
|
|
1255
|
+
notify(sessionId, { sessionUpdate: "available_commands_update", availableCommands });
|
|
1256
|
+
}).catch((error) => {
|
|
1257
|
+
logWarn(`publishing commands failed: ${String(error)}`);
|
|
1258
|
+
});
|
|
1259
|
+
};
|
|
1260
|
+
const publishConfigOptions = (sessionId, record) => {
|
|
1261
|
+
void configOptions(record).then((options) => {
|
|
1262
|
+
notify(sessionId, {
|
|
1263
|
+
sessionUpdate: "config_option_update",
|
|
1264
|
+
configOptions: options
|
|
1265
|
+
});
|
|
1266
|
+
}).catch((error) => {
|
|
1267
|
+
logWarn(`publishing config options failed: ${String(error)}`);
|
|
1125
1268
|
});
|
|
1126
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
|
+
};
|
|
1127
1291
|
const credentialStore = () => {
|
|
1128
1292
|
const brand = harness.credentialRef;
|
|
1129
1293
|
const store = ctx.get("credentials");
|
|
@@ -1188,6 +1352,49 @@ async function apply(ctx, config = {}) {
|
|
|
1188
1352
|
const remaining = await describeCredential();
|
|
1189
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}.`;
|
|
1190
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
|
+
};
|
|
1191
1398
|
const statusText = async (record) => {
|
|
1192
1399
|
const used = record.projection.contextWindow;
|
|
1193
1400
|
const route = routeOf(record);
|
|
@@ -1348,68 +1555,8 @@ async function apply(ctx, config = {}) {
|
|
|
1348
1555
|
assertOpen();
|
|
1349
1556
|
validateCwd(params.cwd);
|
|
1350
1557
|
syncMcpServers(params.mcpServers, params.cwd);
|
|
1351
|
-
|
|
1352
|
-
const
|
|
1353
|
-
const existing = sessions.get(sessionId);
|
|
1354
|
-
if (existing !== void 0) {
|
|
1355
|
-
sessions.delete(sessionId);
|
|
1356
|
-
existing.agent.cancel({ kind: "user" });
|
|
1357
|
-
settlePrompt(existing, "cancelled");
|
|
1358
|
-
await existing.dispose().catch((error) => {
|
|
1359
|
-
logWarn(`dispose before reload failed: ${String(error)}`);
|
|
1360
|
-
});
|
|
1361
|
-
}
|
|
1362
|
-
let events;
|
|
1363
|
-
let storedHeader;
|
|
1364
|
-
try {
|
|
1365
|
-
const inspected = await persistence.inspect(SessionId2(sessionId));
|
|
1366
|
-
events = inspected.events;
|
|
1367
|
-
storedHeader = inspected.header;
|
|
1368
|
-
} catch (error) {
|
|
1369
|
-
throw invalidParams(`session not found: ${sessionId} (${errorChain2(error)})`);
|
|
1370
|
-
}
|
|
1371
|
-
const replay = buildReplay(events);
|
|
1372
|
-
for (const update of replay.updates) notify(sessionId, update);
|
|
1373
|
-
if (replay.title !== void 0) {
|
|
1374
|
-
notify(sessionId, {
|
|
1375
|
-
sessionUpdate: "session_info_update",
|
|
1376
|
-
title: replay.title
|
|
1377
|
-
});
|
|
1378
|
-
}
|
|
1379
|
-
const presets = presetsService();
|
|
1380
|
-
const storedPreset = presetFromLog(storedHeader, events);
|
|
1381
|
-
const presetId = presets === void 0 ? void 0 : (await presets.resolve(storedPreset)).id;
|
|
1382
|
-
const handle = await agents.resume({
|
|
1383
|
-
resumeSessionId: SessionId2(sessionId),
|
|
1384
|
-
agentOptions: agentOptionsFor(config.model),
|
|
1385
|
-
...presetSetup(presets, presetId) !== void 0 ? { setup: presetSetup(presets, presetId) } : {}
|
|
1386
|
-
});
|
|
1387
|
-
const projection = new SessionProjection(replay.contextWindow);
|
|
1388
|
-
projection.title = replay.title;
|
|
1389
|
-
const record = registerRecord(
|
|
1390
|
-
sessionId,
|
|
1391
|
-
handle.agent,
|
|
1392
|
-
() => handle.dispose(),
|
|
1393
|
-
config.model,
|
|
1394
|
-
config.permissionMode ?? "workspace-write",
|
|
1395
|
-
projection
|
|
1396
|
-
);
|
|
1397
|
-
if (presetId !== void 0) record.preset = presetId;
|
|
1398
|
-
{
|
|
1399
|
-
const permission = permissionService();
|
|
1400
|
-
const storedMode = permission?.current(events);
|
|
1401
|
-
if (storedMode !== void 0) record.modeId = storedMode;
|
|
1402
|
-
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
1403
|
-
const event = events[index];
|
|
1404
|
-
if (event?.type === "approval/policy") {
|
|
1405
|
-
const policy = event.data?.policy;
|
|
1406
|
-
if (policy === "ask" || policy === "never") record.approvals = policy;
|
|
1407
|
-
break;
|
|
1408
|
-
}
|
|
1409
|
-
}
|
|
1410
|
-
}
|
|
1411
|
-
ensureSelection(record);
|
|
1412
|
-
queueMicrotask(() => publishCommands(sessionId));
|
|
1558
|
+
requirePersistence();
|
|
1559
|
+
const record = await restoreSession(params.sessionId, { replay: true, cwd: params.cwd });
|
|
1413
1560
|
const options = await configOptions(record);
|
|
1414
1561
|
return {
|
|
1415
1562
|
modes: modeState(record),
|
|
@@ -1443,7 +1590,7 @@ async function apply(ctx, config = {}) {
|
|
|
1443
1590
|
},
|
|
1444
1591
|
async prompt(params) {
|
|
1445
1592
|
assertOpen();
|
|
1446
|
-
const record =
|
|
1593
|
+
const record = await requireOrRestoreSession(params.sessionId);
|
|
1447
1594
|
if (record.inflight !== void 0) {
|
|
1448
1595
|
throw invalidParams("a prompt is already in flight for this session");
|
|
1449
1596
|
}
|
|
@@ -1469,6 +1616,38 @@ async function apply(ctx, config = {}) {
|
|
|
1469
1616
|
return respond(await loginText(trimmed.slice(commandMatch[0].length)));
|
|
1470
1617
|
}
|
|
1471
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
|
+
}
|
|
1628
|
+
if (commandMatch !== null && commandMatch[1] !== void 0) {
|
|
1629
|
+
const commandsSvc = ctx.get("commands");
|
|
1630
|
+
if (commandsSvc !== void 0) {
|
|
1631
|
+
let execution;
|
|
1632
|
+
try {
|
|
1633
|
+
execution = await commandsSvc.execute(
|
|
1634
|
+
record.agent,
|
|
1635
|
+
trimmed,
|
|
1636
|
+
new AbortController().signal
|
|
1637
|
+
);
|
|
1638
|
+
} catch (error) {
|
|
1639
|
+
return respond(`\u26A0 /${commandMatch[1]} failed: ${errorChain2(error)}`);
|
|
1640
|
+
}
|
|
1641
|
+
if (execution !== void 0) {
|
|
1642
|
+
const { result } = execution;
|
|
1643
|
+
const text = result.text ?? (result.kind === "success" ? `/${commandMatch[1]} \u2713` : `/${commandMatch[1]} failed`);
|
|
1644
|
+
syncPermissionState(record, params.sessionId);
|
|
1645
|
+
publishCommands(params.sessionId, record);
|
|
1646
|
+
publishConfigOptions(params.sessionId, record);
|
|
1647
|
+
return respond(result.kind === "error" ? `\u26A0 ${text}` : text);
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1472
1651
|
if (!await credentialPresent()) {
|
|
1473
1652
|
return respond(
|
|
1474
1653
|
[
|
|
@@ -1515,7 +1694,7 @@ async function apply(ctx, config = {}) {
|
|
|
1515
1694
|
return { stopReason, ...usage !== void 0 ? { usage } : {} };
|
|
1516
1695
|
},
|
|
1517
1696
|
async setSessionMode(params) {
|
|
1518
|
-
const record =
|
|
1697
|
+
const record = await requireOrRestoreSession(params.sessionId);
|
|
1519
1698
|
applyMode(record, params.sessionId, params.modeId);
|
|
1520
1699
|
notify(params.sessionId, {
|
|
1521
1700
|
sessionUpdate: "config_option_update",
|
|
@@ -1524,7 +1703,7 @@ async function apply(ctx, config = {}) {
|
|
|
1524
1703
|
return {};
|
|
1525
1704
|
},
|
|
1526
1705
|
async setSessionConfigOption(params) {
|
|
1527
|
-
const record =
|
|
1706
|
+
const record = await requireOrRestoreSession(params.sessionId);
|
|
1528
1707
|
const value = typeof params.value === "string" ? params.value : void 0;
|
|
1529
1708
|
if (value === void 0) throw invalidParams(`invalid value: ${String(params.value)}`);
|
|
1530
1709
|
switch (params.configId) {
|