@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.
Files changed (3) hide show
  1. package/dist/bridge.js +178 -67
  2. package/dist/index.js +178 -67
  3. 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
- const persistence = requirePersistence();
1374
- const sessionId = params.sessionId;
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 = requireSession(params.sessionId);
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 = requireSession(params.sessionId);
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 = requireSession(params.sessionId);
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
- const persistence = requirePersistence();
1399
- const sessionId = params.sessionId;
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 = requireSession(params.sessionId);
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 = requireSession(params.sessionId);
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 = requireSession(params.sessionId);
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) {
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.4.3",
6
+ "version": "0.4.4",
7
7
  "repository": {
8
8
  "type": "git",
9
9
  "url": "git+https://github.com/openma-ai/deepseek-harness-acp.git"