@openma/deepseek-harness-acp 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -161,6 +161,33 @@ npm install --prefix /tmp/dsh-host @deepseek-ai/dsh
161
161
  DSH_ACP_TEST_HOST=/tmp/dsh-host npm test
162
162
  ```
163
163
 
164
+ ### Live iteration: paired profiles
165
+
166
+ Keep the profile you use in your editor on the published package, and point a
167
+ second profile at this worktree via a pnpm symlink:
168
+
169
+ ```bash
170
+ # stable — what Zed uses day to day
171
+ dsh plugin --profile acp add -w @openma/deepseek-harness-acp
172
+
173
+ # dev — a symlink to this checkout; no packing, no version bumps
174
+ dsh plugin --profile acp-test add -w "link:$PWD"
175
+ ```
176
+
177
+ The dev loop is then just `npm run build` and a process restart (`dist/` and
178
+ `cordis.patch.yml` are read through the link). Note pnpm treats `file:` as a
179
+ copy install and caches same-version tarballs — `link:` avoids both. In Zed,
180
+ register both entries:
181
+
182
+ ```jsonc
183
+ {
184
+ "agent_servers": {
185
+ "DeepSeek Harness": { "command": "dsh", "args": ["--profile", "acp"] },
186
+ "DeepSeek Harness (dev)": { "command": "dsh", "args": ["--profile", "acp-test"] }
187
+ }
188
+ }
189
+ ```
190
+
164
191
  ## License
165
192
 
166
193
  Apache-2.0.
package/cordis.patch.yml CHANGED
@@ -20,7 +20,91 @@
20
20
  - id: hmr
21
21
  disabled: true
22
22
 
23
+ # Model-facing tool rows move off the host plane and into each agent preset's
24
+ # own composition (standard/code/minimal/cordis ship complete agent.cordis.yml
25
+ # files) — the same split the web-app bundle makes, and what gives "minimal"
26
+ # its two-tool surface. Host-plane services (job/skill/goal registries,
27
+ # shell-env, sandbox, approval) stay in dsh-base and serve every preset.
28
+
29
+ - id: tool-bash
30
+ disabled: true
31
+
32
+ - id: tool-pwsh
33
+ disabled: true
34
+
35
+ - id: tool-jobs
36
+ disabled: true
37
+
38
+ - id: tool-fs
39
+ disabled: true
40
+
41
+ - id: tool-fs-search
42
+ disabled: true
43
+
44
+ - id: tool-str-replace-editor
45
+ disabled: true
46
+
47
+ - id: skill-filesystem
48
+ disabled: true
49
+
50
+ - id: tool-skill
51
+ disabled: true
52
+
53
+ - id: tool-goal
54
+ disabled: true
55
+
56
+ - id: plan-mode
57
+ disabled: true
58
+
59
+ - id: compaction-basic
60
+ disabled: true
61
+
62
+ - id: command-compact
63
+ disabled: true
64
+
65
+ - id: tool-result-pruner
66
+ disabled: true
67
+
68
+ - id: tool-subagent-control
69
+ disabled: true
70
+
71
+ - id: tool-subagent-list-agents
72
+ disabled: true
73
+
74
+ - id: tool-subagent
75
+ disabled: true
76
+
77
+ - id: tool-subagent-fork
78
+ disabled: true
79
+
80
+ - id: workflow-worker-thread
81
+ disabled: true
82
+
83
+ - id: tool-workflow
84
+ disabled: true
85
+
86
+ - id: tool-ralph
87
+ disabled: true
88
+
89
+ - id: agent-instructions
90
+ disabled: true
91
+
92
+ - id: tool-todo
93
+ disabled: true
94
+
95
+ - id: tool-web
96
+ disabled: true
97
+
23
98
  - insert:
99
+ # Agent presets: per-session model-facing compositions (persona, tools,
100
+ # compaction). The dsh CLI injects its shipped root into any row with this
101
+ # id, so standard/code/minimal/creator are available and ACP session modes
102
+ # surface them (~/.dsh/agent-presets adds user-authored ones).
103
+ - id: agent-presets
104
+ name: '@deepseek-ai/dsh-agent-presets'
105
+ config:
106
+ default: standard
107
+
24
108
  - id: acp-bridge
25
109
  name: '@openma/deepseek-harness-acp/bridge'
26
110
  config:
package/dist/bridge.js CHANGED
@@ -58,7 +58,8 @@ import {
58
58
  } from "@agentclientprotocol/sdk";
59
59
 
60
60
  // src/version.ts
61
- var VERSION = "0.1.0";
61
+ import { createRequire } from "node:module";
62
+ var VERSION = createRequire(import.meta.url)("../package.json").version;
62
63
 
63
64
  // src/log.ts
64
65
  var PREFIX = "[dsh-acp]";
@@ -706,7 +707,9 @@ async function apply(ctx, config = {}) {
706
707
  if (logged !== void 0) return logged;
707
708
  const { provider, model } = routeOf(record);
708
709
  if (provider === void 0 || model === void 0) return void 0;
709
- return { provider, model };
710
+ const defaults = defaultSelection();
711
+ const effort = defaults.model === void 0 || defaults.model === model ? defaults.reasoningEffort : void 0;
712
+ return { provider, model, ...effort !== void 0 ? { reasoningEffort: effort } : {} };
710
713
  },
711
714
  set current(next) {
712
715
  picked = next;
@@ -740,9 +743,11 @@ async function apply(ctx, config = {}) {
740
743
  });
741
744
  let handle;
742
745
  try {
746
+ const presets = presetsService();
743
747
  handle = await agents.resume({
744
748
  resumeSessionId: sessionId,
745
- agentOptions: agentOptionsFor(choice.model, choice.provider)
749
+ agentOptions: agentOptionsFor(choice.model, choice.provider),
750
+ ...presetSetup(presets, record.preset) !== void 0 ? { setup: presetSetup(presets, record.preset) } : {}
746
751
  });
747
752
  } catch (error) {
748
753
  sessions.delete(acpSessionId);
@@ -757,6 +762,7 @@ async function apply(ctx, config = {}) {
757
762
  delete record.provider;
758
763
  }
759
764
  delete record.selection;
765
+ ensureSelection(record);
760
766
  if (record.effort !== void 0) {
761
767
  const route = routeOf(record);
762
768
  const catalog = await effortCatalog(route.provider, route.model);
@@ -825,13 +831,84 @@ async function apply(ctx, config = {}) {
825
831
  record.inflight = void 0;
826
832
  inflight.resolve(reason);
827
833
  };
828
- const modeState = (record) => ({
829
- currentModeId: record.modeId,
830
- availableModes: SANDBOX_MODES2.map((mode) => {
831
- const label = MODE_LABELS[mode] ?? { name: mode, description: "" };
832
- return { id: mode, name: label.name, description: label.description };
833
- })
834
- });
834
+ const presetsService = () => ctx.get("agentPresets");
835
+ const presetSetup = (presets, presetId) => {
836
+ if (presets === void 0 || presetId === void 0) return void 0;
837
+ return async (agentCtx) => {
838
+ logDebug(`preset setup: mounting "${presetId}"`);
839
+ await presets.mount(agentCtx, presetId);
840
+ logDebug(`preset setup: mounted "${presetId}"`);
841
+ };
842
+ };
843
+ const presetFromLog = (header, events) => {
844
+ for (let index = events.length - 1; index >= 0; index -= 1) {
845
+ const event = events[index];
846
+ if (event?.type === "agent-preset/selected") {
847
+ return event.data?.agentPreset;
848
+ }
849
+ }
850
+ return header?.agentPreset;
851
+ };
852
+ const presetLabel = (id) => id.charAt(0).toUpperCase() + id.slice(1);
853
+ const modeState = (record) => {
854
+ const permission = permissionService();
855
+ if (permission !== void 0 && permission.names.length > 0) {
856
+ return {
857
+ currentModeId: record.modeId,
858
+ availableModes: permission.names.map((name3) => {
859
+ const spec = permission.resolve(name3);
860
+ return { id: name3, name: spec.name, description: spec.description };
861
+ })
862
+ };
863
+ }
864
+ return {
865
+ currentModeId: record.modeId,
866
+ availableModes: SANDBOX_MODES2.map((mode) => {
867
+ const label = MODE_LABELS[mode] ?? { name: mode, description: "" };
868
+ return { id: mode, name: label.name, description: label.description };
869
+ })
870
+ };
871
+ };
872
+ const switchPreset = async (record, acpSessionId, presetId) => {
873
+ if (record.inflight !== void 0) {
874
+ throw invalidParams("cannot switch presets while a prompt is running");
875
+ }
876
+ const presets = presetsService();
877
+ if (presets === void 0) throw invalidParams(`unknown mode: ${presetId}`);
878
+ let resolved;
879
+ try {
880
+ resolved = (await presets.resolve(presetId)).id;
881
+ } catch (error) {
882
+ throw invalidParams(`unknown preset: ${presetId} (${errorChain2(error)})`);
883
+ }
884
+ if (resolved === record.preset) return;
885
+ try {
886
+ record.agent.session.append("agent-preset/selected", { agentPreset: resolved });
887
+ } catch (error) {
888
+ logWarn(`recording preset selection failed: ${String(error)}`);
889
+ }
890
+ const sessionId = record.agent.session.id;
891
+ await record.dispose().catch((error) => {
892
+ logWarn(`dispose during preset switch failed: ${String(error)}`);
893
+ });
894
+ let handle;
895
+ try {
896
+ handle = await agents.resume({
897
+ resumeSessionId: sessionId,
898
+ agentOptions: agentOptionsFor(record.model ?? config.model, record.provider),
899
+ setup: presetSetup(presets, resolved)
900
+ });
901
+ } catch (error) {
902
+ sessions.delete(acpSessionId);
903
+ throw internalError(`preset switch failed: ${errorChain2(error)}`);
904
+ }
905
+ record.agent = handle.agent;
906
+ record.dispose = () => handle.dispose();
907
+ record.preset = resolved;
908
+ delete record.selection;
909
+ ensureSelection(record);
910
+ setApprovalPolicy(record, record.approvals);
911
+ };
835
912
  const setApprovalPolicy = (record, policy) => {
836
913
  try {
837
914
  ctx.get("approval")?.setPolicy?.(record.agent, policy);
@@ -840,7 +917,19 @@ async function apply(ctx, config = {}) {
840
917
  }
841
918
  record.approvals = policy;
842
919
  };
920
+ const permissionService = () => ctx.get("permission");
843
921
  const applyMode = (record, sessionId, modeId) => {
922
+ const permission = permissionService();
923
+ if (permission !== void 0 && permission.names.includes(modeId)) {
924
+ let bundledApproval = record.approvals;
925
+ permission.apply(record.agent.session, modeId, (policy) => {
926
+ bundledApproval = policy;
927
+ });
928
+ setApprovalPolicy(record, bundledApproval);
929
+ record.modeId = permission.resolve(modeId).sandbox;
930
+ notify(sessionId, { sessionUpdate: "current_mode_update", currentModeId: modeId });
931
+ return;
932
+ }
844
933
  const mode = SANDBOX_MODES2.find((candidate) => candidate === modeId);
845
934
  if (mode === void 0) throw invalidParams(`unknown mode: ${modeId}`);
846
935
  setSandboxMode2(record.agent.session, mode);
@@ -871,21 +960,6 @@ async function apply(ctx, config = {}) {
871
960
  };
872
961
  const configOptions = async (record) => {
873
962
  const result = [];
874
- result.push({
875
- type: "select",
876
- id: "mode",
877
- name: "Mode",
878
- category: "mode",
879
- currentValue: record.modeId,
880
- options: SANDBOX_MODES2.map((mode) => {
881
- const label = MODE_LABELS[mode] ?? { name: mode, description: "" };
882
- return {
883
- value: mode,
884
- name: label.name,
885
- ...label.description.length > 0 ? { description: label.description } : {}
886
- };
887
- })
888
- });
889
963
  const models = await modelChoices(record);
890
964
  if (models.length >= 2) {
891
965
  result.push({
@@ -901,7 +975,14 @@ async function apply(ctx, config = {}) {
901
975
  const efforts = await effortCatalog(route.provider, route.model);
902
976
  if (efforts !== void 0 && efforts.efforts.length >= 2) {
903
977
  const known = new Set(efforts.efforts.map((effort) => effort.id));
904
- const preferred = [record.effort, loggedConfig(record)?.reasoningEffort, efforts.defaultEffort].find(
978
+ const preferred = [
979
+ record.effort,
980
+ loggedConfig(record)?.reasoningEffort,
981
+ // The user's saved product default (Web UI → settings.yaml),
982
+ // e.g. reasoningEffort: max, outranks the adapter's default.
983
+ defaultSelection().reasoningEffort,
984
+ efforts.defaultEffort
985
+ ].find(
905
986
  (candidate) => candidate !== void 0 && known.has(candidate)
906
987
  );
907
988
  result.push({
@@ -917,16 +998,27 @@ async function apply(ctx, config = {}) {
917
998
  }))
918
999
  });
919
1000
  }
920
- result.push({
921
- type: "select",
922
- id: "approvals",
923
- name: "Approvals",
924
- currentValue: record.approvals,
925
- options: [
926
- { value: "ask", name: "Ask", description: "Ask before actions beyond the sandbox mode" },
927
- { value: "never", name: "Never ask", description: "Auto-approve every permission request" }
928
- ]
929
- });
1001
+ const presets = presetsService();
1002
+ if (presets !== void 0 && record.preset !== void 0) {
1003
+ try {
1004
+ const roster = await presets.list();
1005
+ if (roster.length >= 2) {
1006
+ result.push({
1007
+ type: "select",
1008
+ id: "preset",
1009
+ name: "Preset",
1010
+ currentValue: record.preset,
1011
+ options: roster.map((preset) => ({
1012
+ value: preset.id,
1013
+ name: presetLabel(preset.id),
1014
+ description: `Agent preset \u201C${preset.id}\u201D`
1015
+ }))
1016
+ });
1017
+ }
1018
+ } catch (error) {
1019
+ logWarn(`preset roster unavailable: ${String(error)}`);
1020
+ }
1021
+ }
930
1022
  return result;
931
1023
  };
932
1024
  const publishCommands = (sessionId) => {
@@ -1020,6 +1112,7 @@ async function apply(ctx, config = {}) {
1020
1112
  `| Model | ${route.model ?? "(composition default)"} |`,
1021
1113
  `| Reasoning | ${effort ?? "(adapter default)"} |`,
1022
1114
  `| Credential | ${await describeCredential()} |`,
1115
+ ...record.preset !== void 0 ? [`| Preset | ${record.preset} |`] : [],
1023
1116
  `| Permission mode | ${record.modeId} |`,
1024
1117
  `| Approvals | ${record.approvals} |`,
1025
1118
  `| Workspace | ${record.agent.session.header.cwd ?? process.cwd()} |`,
@@ -1128,10 +1221,13 @@ async function apply(ctx, config = {}) {
1128
1221
  validateCwd(params.cwd);
1129
1222
  syncMcpServers(params.mcpServers, params.cwd);
1130
1223
  const sessionId = SessionId2(randomUUID());
1224
+ const presets = presetsService();
1225
+ const presetId = presets === void 0 ? void 0 : (await presets.resolve(void 0)).id;
1131
1226
  const handle = await agents.create({
1132
1227
  sessionId,
1133
- meta: { cwd: params.cwd },
1134
- agentOptions: agentOptionsFor(config.model)
1228
+ meta: { cwd: params.cwd, ...presetId !== void 0 ? { agentPreset: presetId } : {} },
1229
+ agentOptions: agentOptionsFor(config.model),
1230
+ ...presetSetup(presets, presetId) !== void 0 ? { setup: presetSetup(presets, presetId) } : {}
1135
1231
  });
1136
1232
  if (closed) {
1137
1233
  await handle.dispose();
@@ -1144,6 +1240,8 @@ async function apply(ctx, config = {}) {
1144
1240
  config.model,
1145
1241
  config.permissionMode ?? "workspace-write"
1146
1242
  );
1243
+ if (presetId !== void 0) record.preset = presetId;
1244
+ ensureSelection(record);
1147
1245
  queueMicrotask(() => publishCommands(String(sessionId)));
1148
1246
  const options = await configOptions(record);
1149
1247
  return {
@@ -1168,8 +1266,11 @@ async function apply(ctx, config = {}) {
1168
1266
  });
1169
1267
  }
1170
1268
  let events;
1269
+ let storedHeader;
1171
1270
  try {
1172
- ({ events } = await persistence.inspect(SessionId2(sessionId)));
1271
+ const inspected = await persistence.inspect(SessionId2(sessionId));
1272
+ events = inspected.events;
1273
+ storedHeader = inspected.header;
1173
1274
  } catch (error) {
1174
1275
  throw invalidParams(`session not found: ${sessionId} (${errorChain2(error)})`);
1175
1276
  }
@@ -1181,9 +1282,13 @@ async function apply(ctx, config = {}) {
1181
1282
  title: replay.title
1182
1283
  });
1183
1284
  }
1285
+ const presets = presetsService();
1286
+ const storedPreset = presetFromLog(storedHeader, events);
1287
+ const presetId = presets === void 0 ? void 0 : (await presets.resolve(storedPreset)).id;
1184
1288
  const handle = await agents.resume({
1185
1289
  resumeSessionId: SessionId2(sessionId),
1186
- agentOptions: agentOptionsFor(config.model)
1290
+ agentOptions: agentOptionsFor(config.model),
1291
+ ...presetSetup(presets, presetId) !== void 0 ? { setup: presetSetup(presets, presetId) } : {}
1187
1292
  });
1188
1293
  const projection = new SessionProjection(replay.contextWindow);
1189
1294
  projection.title = replay.title;
@@ -1195,6 +1300,21 @@ async function apply(ctx, config = {}) {
1195
1300
  config.permissionMode ?? "workspace-write",
1196
1301
  projection
1197
1302
  );
1303
+ if (presetId !== void 0) record.preset = presetId;
1304
+ {
1305
+ const permission = permissionService();
1306
+ const storedMode = permission?.current(events);
1307
+ if (storedMode !== void 0) record.modeId = storedMode;
1308
+ for (let index = events.length - 1; index >= 0; index -= 1) {
1309
+ const event = events[index];
1310
+ if (event?.type === "approval/policy") {
1311
+ const policy = event.data?.policy;
1312
+ if (policy === "ask" || policy === "never") record.approvals = policy;
1313
+ break;
1314
+ }
1315
+ }
1316
+ }
1317
+ ensureSelection(record);
1198
1318
  queueMicrotask(() => publishCommands(sessionId));
1199
1319
  const options = await configOptions(record);
1200
1320
  return {
@@ -1314,13 +1434,8 @@ async function apply(ctx, config = {}) {
1314
1434
  const value = typeof params.value === "string" ? params.value : void 0;
1315
1435
  if (value === void 0) throw invalidParams(`invalid value: ${String(params.value)}`);
1316
1436
  switch (params.configId) {
1317
- case "mode": {
1318
- applyMode(record, params.sessionId, value);
1319
- break;
1320
- }
1321
- case "approvals": {
1322
- if (value !== "ask" && value !== "never") throw invalidParams(`unknown approvals: ${value}`);
1323
- setApprovalPolicy(record, value);
1437
+ case "preset": {
1438
+ await switchPreset(record, params.sessionId, value);
1324
1439
  break;
1325
1440
  }
1326
1441
  case "effort": {
package/dist/index.js CHANGED
@@ -72,7 +72,8 @@ import {
72
72
  } from "@agentclientprotocol/sdk";
73
73
 
74
74
  // src/version.ts
75
- var VERSION = "0.1.0";
75
+ import { createRequire } from "node:module";
76
+ var VERSION = createRequire(import.meta.url)("../package.json").version;
76
77
 
77
78
  // src/log.ts
78
79
  var PREFIX = "[dsh-acp]";
@@ -724,7 +725,9 @@ async function apply(ctx, config = {}) {
724
725
  if (logged !== void 0) return logged;
725
726
  const { provider, model } = routeOf(record);
726
727
  if (provider === void 0 || model === void 0) return void 0;
727
- return { provider, model };
728
+ const defaults = defaultSelection();
729
+ const effort = defaults.model === void 0 || defaults.model === model ? defaults.reasoningEffort : void 0;
730
+ return { provider, model, ...effort !== void 0 ? { reasoningEffort: effort } : {} };
728
731
  },
729
732
  set current(next) {
730
733
  picked = next;
@@ -758,9 +761,11 @@ async function apply(ctx, config = {}) {
758
761
  });
759
762
  let handle;
760
763
  try {
764
+ const presets = presetsService();
761
765
  handle = await agents.resume({
762
766
  resumeSessionId: sessionId,
763
- agentOptions: agentOptionsFor(choice.model, choice.provider)
767
+ agentOptions: agentOptionsFor(choice.model, choice.provider),
768
+ ...presetSetup(presets, record.preset) !== void 0 ? { setup: presetSetup(presets, record.preset) } : {}
764
769
  });
765
770
  } catch (error) {
766
771
  sessions.delete(acpSessionId);
@@ -775,6 +780,7 @@ async function apply(ctx, config = {}) {
775
780
  delete record.provider;
776
781
  }
777
782
  delete record.selection;
783
+ ensureSelection(record);
778
784
  if (record.effort !== void 0) {
779
785
  const route = routeOf(record);
780
786
  const catalog = await effortCatalog(route.provider, route.model);
@@ -843,13 +849,84 @@ async function apply(ctx, config = {}) {
843
849
  record.inflight = void 0;
844
850
  inflight.resolve(reason);
845
851
  };
846
- const modeState = (record) => ({
847
- currentModeId: record.modeId,
848
- availableModes: SANDBOX_MODES2.map((mode) => {
849
- const label = MODE_LABELS[mode] ?? { name: mode, description: "" };
850
- return { id: mode, name: label.name, description: label.description };
851
- })
852
- });
852
+ const presetsService = () => ctx.get("agentPresets");
853
+ const presetSetup = (presets, presetId) => {
854
+ if (presets === void 0 || presetId === void 0) return void 0;
855
+ return async (agentCtx) => {
856
+ logDebug(`preset setup: mounting "${presetId}"`);
857
+ await presets.mount(agentCtx, presetId);
858
+ logDebug(`preset setup: mounted "${presetId}"`);
859
+ };
860
+ };
861
+ const presetFromLog = (header, events) => {
862
+ for (let index = events.length - 1; index >= 0; index -= 1) {
863
+ const event = events[index];
864
+ if (event?.type === "agent-preset/selected") {
865
+ return event.data?.agentPreset;
866
+ }
867
+ }
868
+ return header?.agentPreset;
869
+ };
870
+ const presetLabel = (id) => id.charAt(0).toUpperCase() + id.slice(1);
871
+ const modeState = (record) => {
872
+ const permission = permissionService();
873
+ if (permission !== void 0 && permission.names.length > 0) {
874
+ return {
875
+ currentModeId: record.modeId,
876
+ availableModes: permission.names.map((name3) => {
877
+ const spec = permission.resolve(name3);
878
+ return { id: name3, name: spec.name, description: spec.description };
879
+ })
880
+ };
881
+ }
882
+ return {
883
+ currentModeId: record.modeId,
884
+ availableModes: SANDBOX_MODES2.map((mode) => {
885
+ const label = MODE_LABELS[mode] ?? { name: mode, description: "" };
886
+ return { id: mode, name: label.name, description: label.description };
887
+ })
888
+ };
889
+ };
890
+ const switchPreset = async (record, acpSessionId, presetId) => {
891
+ if (record.inflight !== void 0) {
892
+ throw invalidParams("cannot switch presets while a prompt is running");
893
+ }
894
+ const presets = presetsService();
895
+ if (presets === void 0) throw invalidParams(`unknown mode: ${presetId}`);
896
+ let resolved;
897
+ try {
898
+ resolved = (await presets.resolve(presetId)).id;
899
+ } catch (error) {
900
+ throw invalidParams(`unknown preset: ${presetId} (${errorChain2(error)})`);
901
+ }
902
+ if (resolved === record.preset) return;
903
+ try {
904
+ record.agent.session.append("agent-preset/selected", { agentPreset: resolved });
905
+ } catch (error) {
906
+ logWarn(`recording preset selection failed: ${String(error)}`);
907
+ }
908
+ const sessionId = record.agent.session.id;
909
+ await record.dispose().catch((error) => {
910
+ logWarn(`dispose during preset switch failed: ${String(error)}`);
911
+ });
912
+ let handle;
913
+ try {
914
+ handle = await agents.resume({
915
+ resumeSessionId: sessionId,
916
+ agentOptions: agentOptionsFor(record.model ?? config.model, record.provider),
917
+ setup: presetSetup(presets, resolved)
918
+ });
919
+ } catch (error) {
920
+ sessions.delete(acpSessionId);
921
+ throw internalError(`preset switch failed: ${errorChain2(error)}`);
922
+ }
923
+ record.agent = handle.agent;
924
+ record.dispose = () => handle.dispose();
925
+ record.preset = resolved;
926
+ delete record.selection;
927
+ ensureSelection(record);
928
+ setApprovalPolicy(record, record.approvals);
929
+ };
853
930
  const setApprovalPolicy = (record, policy) => {
854
931
  try {
855
932
  ctx.get("approval")?.setPolicy?.(record.agent, policy);
@@ -858,7 +935,19 @@ async function apply(ctx, config = {}) {
858
935
  }
859
936
  record.approvals = policy;
860
937
  };
938
+ const permissionService = () => ctx.get("permission");
861
939
  const applyMode = (record, sessionId, modeId) => {
940
+ const permission = permissionService();
941
+ if (permission !== void 0 && permission.names.includes(modeId)) {
942
+ let bundledApproval = record.approvals;
943
+ permission.apply(record.agent.session, modeId, (policy) => {
944
+ bundledApproval = policy;
945
+ });
946
+ setApprovalPolicy(record, bundledApproval);
947
+ record.modeId = permission.resolve(modeId).sandbox;
948
+ notify(sessionId, { sessionUpdate: "current_mode_update", currentModeId: modeId });
949
+ return;
950
+ }
862
951
  const mode = SANDBOX_MODES2.find((candidate) => candidate === modeId);
863
952
  if (mode === void 0) throw invalidParams(`unknown mode: ${modeId}`);
864
953
  setSandboxMode2(record.agent.session, mode);
@@ -889,21 +978,6 @@ async function apply(ctx, config = {}) {
889
978
  };
890
979
  const configOptions = async (record) => {
891
980
  const result = [];
892
- result.push({
893
- type: "select",
894
- id: "mode",
895
- name: "Mode",
896
- category: "mode",
897
- currentValue: record.modeId,
898
- options: SANDBOX_MODES2.map((mode) => {
899
- const label = MODE_LABELS[mode] ?? { name: mode, description: "" };
900
- return {
901
- value: mode,
902
- name: label.name,
903
- ...label.description.length > 0 ? { description: label.description } : {}
904
- };
905
- })
906
- });
907
981
  const models = await modelChoices(record);
908
982
  if (models.length >= 2) {
909
983
  result.push({
@@ -919,7 +993,14 @@ async function apply(ctx, config = {}) {
919
993
  const efforts = await effortCatalog(route.provider, route.model);
920
994
  if (efforts !== void 0 && efforts.efforts.length >= 2) {
921
995
  const known = new Set(efforts.efforts.map((effort) => effort.id));
922
- const preferred = [record.effort, loggedConfig(record)?.reasoningEffort, efforts.defaultEffort].find(
996
+ const preferred = [
997
+ record.effort,
998
+ loggedConfig(record)?.reasoningEffort,
999
+ // The user's saved product default (Web UI → settings.yaml),
1000
+ // e.g. reasoningEffort: max, outranks the adapter's default.
1001
+ defaultSelection().reasoningEffort,
1002
+ efforts.defaultEffort
1003
+ ].find(
923
1004
  (candidate) => candidate !== void 0 && known.has(candidate)
924
1005
  );
925
1006
  result.push({
@@ -935,16 +1016,27 @@ async function apply(ctx, config = {}) {
935
1016
  }))
936
1017
  });
937
1018
  }
938
- result.push({
939
- type: "select",
940
- id: "approvals",
941
- name: "Approvals",
942
- currentValue: record.approvals,
943
- options: [
944
- { value: "ask", name: "Ask", description: "Ask before actions beyond the sandbox mode" },
945
- { value: "never", name: "Never ask", description: "Auto-approve every permission request" }
946
- ]
947
- });
1019
+ const presets = presetsService();
1020
+ if (presets !== void 0 && record.preset !== void 0) {
1021
+ try {
1022
+ const roster = await presets.list();
1023
+ if (roster.length >= 2) {
1024
+ result.push({
1025
+ type: "select",
1026
+ id: "preset",
1027
+ name: "Preset",
1028
+ currentValue: record.preset,
1029
+ options: roster.map((preset) => ({
1030
+ value: preset.id,
1031
+ name: presetLabel(preset.id),
1032
+ description: `Agent preset \u201C${preset.id}\u201D`
1033
+ }))
1034
+ });
1035
+ }
1036
+ } catch (error) {
1037
+ logWarn(`preset roster unavailable: ${String(error)}`);
1038
+ }
1039
+ }
948
1040
  return result;
949
1041
  };
950
1042
  const publishCommands = (sessionId) => {
@@ -1038,6 +1130,7 @@ async function apply(ctx, config = {}) {
1038
1130
  `| Model | ${route.model ?? "(composition default)"} |`,
1039
1131
  `| Reasoning | ${effort ?? "(adapter default)"} |`,
1040
1132
  `| Credential | ${await describeCredential()} |`,
1133
+ ...record.preset !== void 0 ? [`| Preset | ${record.preset} |`] : [],
1041
1134
  `| Permission mode | ${record.modeId} |`,
1042
1135
  `| Approvals | ${record.approvals} |`,
1043
1136
  `| Workspace | ${record.agent.session.header.cwd ?? process.cwd()} |`,
@@ -1146,10 +1239,13 @@ async function apply(ctx, config = {}) {
1146
1239
  validateCwd(params.cwd);
1147
1240
  syncMcpServers(params.mcpServers, params.cwd);
1148
1241
  const sessionId = SessionId2(randomUUID());
1242
+ const presets = presetsService();
1243
+ const presetId = presets === void 0 ? void 0 : (await presets.resolve(void 0)).id;
1149
1244
  const handle = await agents.create({
1150
1245
  sessionId,
1151
- meta: { cwd: params.cwd },
1152
- agentOptions: agentOptionsFor(config.model)
1246
+ meta: { cwd: params.cwd, ...presetId !== void 0 ? { agentPreset: presetId } : {} },
1247
+ agentOptions: agentOptionsFor(config.model),
1248
+ ...presetSetup(presets, presetId) !== void 0 ? { setup: presetSetup(presets, presetId) } : {}
1153
1249
  });
1154
1250
  if (closed) {
1155
1251
  await handle.dispose();
@@ -1162,6 +1258,8 @@ async function apply(ctx, config = {}) {
1162
1258
  config.model,
1163
1259
  config.permissionMode ?? "workspace-write"
1164
1260
  );
1261
+ if (presetId !== void 0) record.preset = presetId;
1262
+ ensureSelection(record);
1165
1263
  queueMicrotask(() => publishCommands(String(sessionId)));
1166
1264
  const options = await configOptions(record);
1167
1265
  return {
@@ -1186,8 +1284,11 @@ async function apply(ctx, config = {}) {
1186
1284
  });
1187
1285
  }
1188
1286
  let events;
1287
+ let storedHeader;
1189
1288
  try {
1190
- ({ events } = await persistence.inspect(SessionId2(sessionId)));
1289
+ const inspected = await persistence.inspect(SessionId2(sessionId));
1290
+ events = inspected.events;
1291
+ storedHeader = inspected.header;
1191
1292
  } catch (error) {
1192
1293
  throw invalidParams(`session not found: ${sessionId} (${errorChain2(error)})`);
1193
1294
  }
@@ -1199,9 +1300,13 @@ async function apply(ctx, config = {}) {
1199
1300
  title: replay.title
1200
1301
  });
1201
1302
  }
1303
+ const presets = presetsService();
1304
+ const storedPreset = presetFromLog(storedHeader, events);
1305
+ const presetId = presets === void 0 ? void 0 : (await presets.resolve(storedPreset)).id;
1202
1306
  const handle = await agents.resume({
1203
1307
  resumeSessionId: SessionId2(sessionId),
1204
- agentOptions: agentOptionsFor(config.model)
1308
+ agentOptions: agentOptionsFor(config.model),
1309
+ ...presetSetup(presets, presetId) !== void 0 ? { setup: presetSetup(presets, presetId) } : {}
1205
1310
  });
1206
1311
  const projection = new SessionProjection(replay.contextWindow);
1207
1312
  projection.title = replay.title;
@@ -1213,6 +1318,21 @@ async function apply(ctx, config = {}) {
1213
1318
  config.permissionMode ?? "workspace-write",
1214
1319
  projection
1215
1320
  );
1321
+ if (presetId !== void 0) record.preset = presetId;
1322
+ {
1323
+ const permission = permissionService();
1324
+ const storedMode = permission?.current(events);
1325
+ if (storedMode !== void 0) record.modeId = storedMode;
1326
+ for (let index = events.length - 1; index >= 0; index -= 1) {
1327
+ const event = events[index];
1328
+ if (event?.type === "approval/policy") {
1329
+ const policy = event.data?.policy;
1330
+ if (policy === "ask" || policy === "never") record.approvals = policy;
1331
+ break;
1332
+ }
1333
+ }
1334
+ }
1335
+ ensureSelection(record);
1216
1336
  queueMicrotask(() => publishCommands(sessionId));
1217
1337
  const options = await configOptions(record);
1218
1338
  return {
@@ -1332,13 +1452,8 @@ async function apply(ctx, config = {}) {
1332
1452
  const value = typeof params.value === "string" ? params.value : void 0;
1333
1453
  if (value === void 0) throw invalidParams(`invalid value: ${String(params.value)}`);
1334
1454
  switch (params.configId) {
1335
- case "mode": {
1336
- applyMode(record, params.sessionId, value);
1337
- break;
1338
- }
1339
- case "approvals": {
1340
- if (value !== "ask" && value !== "never") throw invalidParams(`unknown approvals: ${value}`);
1341
- setApprovalPolicy(record, value);
1455
+ case "preset": {
1456
+ await switchPreset(record, params.sessionId, value);
1342
1457
  break;
1343
1458
  }
1344
1459
  case "effort": {
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.2.0",
6
+ "version": "0.3.0",
7
7
  "repository": {
8
8
  "type": "git",
9
9
  "url": "git+https://github.com/openma-ai/deepseek-harness-acp.git"
@@ -12,7 +12,7 @@
12
12
  "bugs": {
13
13
  "url": "https://github.com/openma-ai/deepseek-harness-acp/issues"
14
14
  },
15
- "description": "Agent Client Protocol (ACP) adapter for DeepSeek Harness — use DeepSeek Harness from ACP clients such as Zed.",
15
+ "description": "Agent Client Protocol (ACP) adapter for DeepSeek Harness \u2014 use DeepSeek Harness from ACP clients such as Zed.",
16
16
  "main": "dist/index.js",
17
17
  "bin": {
18
18
  "dsh-acp": "dist/index.js"
@@ -123,4 +123,4 @@
123
123
  "patch": "./cordis.patch.yml"
124
124
  }
125
125
  }
126
- }
126
+ }