@openma/deepseek-harness-acp 0.1.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
@@ -23,8 +23,11 @@ import { SessionId } from "@deepseek-ai/dsh-session";
23
23
  import { foldSessionTitle } from "@deepseek-ai/dsh-session-title";
24
24
  import { SANDBOX_MODES, setSandboxMode } from "@deepseek-ai/dsh-sandbox-policy";
25
25
  import { credentialRef } from "@deepseek-ai/dsh-credentials";
26
+ import * as agentModule from "@deepseek-ai/dsh-agent";
26
27
  import * as mcpClient from "@deepseek-ai/dsh-mcp-client";
27
28
  function selfHarness() {
29
+ const agents = agentModule;
30
+ const installModelSelection = agents.installModelSelection ?? agents.ln;
28
31
  return {
29
32
  createUserMessage,
30
33
  errorChain,
@@ -33,6 +36,7 @@ function selfHarness() {
33
36
  setSandboxMode,
34
37
  sandboxModes: SANDBOX_MODES,
35
38
  credentialRef,
39
+ ...installModelSelection !== void 0 ? { installModelSelection } : {},
36
40
  mcpClient
37
41
  };
38
42
  }
@@ -54,7 +58,8 @@ import {
54
58
  } from "@agentclientprotocol/sdk";
55
59
 
56
60
  // src/version.ts
57
- var VERSION = "0.1.0";
61
+ import { createRequire } from "node:module";
62
+ var VERSION = createRequire(import.meta.url)("../package.json").version;
58
63
 
59
64
  // src/log.ts
60
65
  var PREFIX = "[dsh-acp]";
@@ -610,6 +615,7 @@ async function apply(ctx, config = {}) {
610
615
  return { provider: value.slice(0, i), model: value.slice(i + 2) };
611
616
  };
612
617
  let liveCatalog;
618
+ const effortCache = /* @__PURE__ */ new Map();
613
619
  const discoverModels = async () => {
614
620
  if (liveCatalog !== void 0) return liveCatalog;
615
621
  const llm = ctx.get("llm");
@@ -640,7 +646,141 @@ async function apply(ctx, config = {}) {
640
646
  };
641
647
  ctx.on("llm/adapters-updated", () => {
642
648
  liveCatalog = void 0;
649
+ effortCache.clear();
643
650
  });
651
+ const routeOf = (record) => {
652
+ const logged = loggedConfig(record);
653
+ const provider = record.provider ?? defaultProvider() ?? logged?.provider;
654
+ const model = record.model ?? config.model ?? defaultSelection().model ?? logged?.model ?? record.agent.options.model;
655
+ return {
656
+ ...provider !== void 0 ? { provider } : {},
657
+ ...model !== void 0 ? { model } : {}
658
+ };
659
+ };
660
+ const loggedConfig = (record) => {
661
+ try {
662
+ const header = record.agent.session.requestHeader?.();
663
+ const cfg = header?.config;
664
+ if (cfg === void 0) return void 0;
665
+ return {
666
+ provider: cfg.provider,
667
+ model: cfg.model,
668
+ ...cfg.reasoningEffort !== void 0 ? { reasoningEffort: String(cfg.reasoningEffort) } : {}
669
+ };
670
+ } catch {
671
+ return void 0;
672
+ }
673
+ };
674
+ const effortCatalog = async (provider, model) => {
675
+ if (provider === void 0 || model === void 0) return void 0;
676
+ const key = `${provider}::${model}`;
677
+ if (effortCache.has(key)) return effortCache.get(key);
678
+ const llm = ctx.get("llm");
679
+ let catalog;
680
+ try {
681
+ const reasoning = (await llm?.resolveModelInfo?.(provider, model))?.reasoning;
682
+ if (reasoning !== void 0 && reasoning.efforts.length > 0) {
683
+ catalog = {
684
+ efforts: reasoning.efforts.map((effort) => ({
685
+ id: String(effort.id),
686
+ name: effort.name,
687
+ ...effort.description !== void 0 ? { description: effort.description } : {}
688
+ })),
689
+ ...reasoning.defaultEffort !== void 0 ? { defaultEffort: String(reasoning.defaultEffort) } : {}
690
+ };
691
+ }
692
+ } catch (error) {
693
+ logDebug(`resolveModelInfo(${key}) failed: ${String(error)}`);
694
+ }
695
+ effortCache.set(key, catalog);
696
+ return catalog;
697
+ };
698
+ const ensureSelection = (record) => {
699
+ const install = harness.installModelSelection;
700
+ if (install === void 0) return void 0;
701
+ if (record.selection !== void 0) return record.selection;
702
+ let picked;
703
+ const selection = {
704
+ get current() {
705
+ if (picked !== void 0) return picked;
706
+ const logged = loggedConfig(record);
707
+ if (logged !== void 0) return logged;
708
+ const { provider, model } = routeOf(record);
709
+ if (provider === void 0 || model === void 0) return void 0;
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 } : {} };
713
+ },
714
+ set current(next) {
715
+ picked = next;
716
+ },
717
+ assembled: void 0
718
+ };
719
+ try {
720
+ install(record.agent.ctx, selection);
721
+ } catch (error) {
722
+ logWarn(`installModelSelection failed: ${String(error)}`);
723
+ return void 0;
724
+ }
725
+ record.selection = selection;
726
+ return selection;
727
+ };
728
+ const switchModel = async (record, acpSessionId, value) => {
729
+ if (record.inflight !== void 0) {
730
+ throw invalidParams("cannot switch models while a prompt is running");
731
+ }
732
+ const choice = decodeChoice(value);
733
+ const known = /* @__PURE__ */ new Set([
734
+ ...modelCandidates().map((model) => encodeChoice(void 0, model)),
735
+ ...(await discoverModels()).map((entry) => encodeChoice(entry.provider, entry.model)),
736
+ encodeChoice(record.provider, record.model ?? "")
737
+ ]);
738
+ if (!known.has(value)) throw invalidParams(`unknown model: ${value}`);
739
+ if (choice.model === record.model && choice.provider === (record.provider ?? defaultProvider())) return;
740
+ const sessionId = record.agent.session.id;
741
+ await record.dispose().catch((error) => {
742
+ logWarn(`dispose during model switch failed: ${String(error)}`);
743
+ });
744
+ let handle;
745
+ try {
746
+ const presets = presetsService();
747
+ handle = await agents.resume({
748
+ resumeSessionId: sessionId,
749
+ agentOptions: agentOptionsFor(choice.model, choice.provider),
750
+ ...presetSetup(presets, record.preset) !== void 0 ? { setup: presetSetup(presets, record.preset) } : {}
751
+ });
752
+ } catch (error) {
753
+ sessions.delete(acpSessionId);
754
+ throw internalError(`model switch failed: ${errorChain2(error)}`);
755
+ }
756
+ record.agent = handle.agent;
757
+ record.dispose = () => handle.dispose();
758
+ record.model = choice.model;
759
+ if (choice.provider !== void 0 && choice.provider !== defaultProvider()) {
760
+ record.provider = choice.provider;
761
+ } else {
762
+ delete record.provider;
763
+ }
764
+ delete record.selection;
765
+ ensureSelection(record);
766
+ if (record.effort !== void 0) {
767
+ const route = routeOf(record);
768
+ const catalog = await effortCatalog(route.provider, route.model);
769
+ if (catalog?.efforts.some((effort) => effort.id === record.effort) === true && route.provider !== void 0 && route.model !== void 0) {
770
+ const selection = ensureSelection(record);
771
+ if (selection !== void 0) {
772
+ selection.current = {
773
+ provider: route.provider,
774
+ model: route.model,
775
+ reasoningEffort: record.effort
776
+ };
777
+ }
778
+ } else {
779
+ delete record.effort;
780
+ }
781
+ }
782
+ setApprovalPolicy(record, record.approvals);
783
+ };
644
784
  const credentialPresent = async () => {
645
785
  if (envCredentialPresent()) return true;
646
786
  const brand = harness.credentialRef;
@@ -691,14 +831,113 @@ async function apply(ctx, config = {}) {
691
831
  record.inflight = void 0;
692
832
  inflight.resolve(reason);
693
833
  };
694
- const modeState = (record) => ({
695
- currentModeId: record.modeId,
696
- availableModes: SANDBOX_MODES2.map((mode) => {
697
- const label = MODE_LABELS[mode] ?? { name: mode, description: "" };
698
- return { id: mode, name: label.name, description: label.description };
699
- })
700
- });
701
- const configOptions = async (record) => {
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
+ };
912
+ const setApprovalPolicy = (record, policy) => {
913
+ try {
914
+ ctx.get("approval")?.setPolicy?.(record.agent, policy);
915
+ } catch (error) {
916
+ logWarn(`approval policy switch failed: ${String(error)}`);
917
+ }
918
+ record.approvals = policy;
919
+ };
920
+ const permissionService = () => ctx.get("permission");
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
+ }
933
+ const mode = SANDBOX_MODES2.find((candidate) => candidate === modeId);
934
+ if (mode === void 0) throw invalidParams(`unknown mode: ${modeId}`);
935
+ setSandboxMode2(record.agent.session, mode);
936
+ setApprovalPolicy(record, mode === "danger-full-access" ? "never" : "ask");
937
+ record.modeId = mode;
938
+ notify(sessionId, { sessionUpdate: "current_mode_update", currentModeId: mode });
939
+ };
940
+ const modelChoices = async (record) => {
702
941
  const seen = /* @__PURE__ */ new Set();
703
942
  const options = [];
704
943
  const push = (value, name3) => {
@@ -717,17 +956,70 @@ async function apply(ctx, config = {}) {
717
956
  push(encodeChoice(choice.provider, choice.model), choice.label);
718
957
  }
719
958
  for (const model of modelCandidates()) push(encodeChoice(void 0, model), model);
720
- if (options.length < 2) return [];
721
- return [
722
- {
959
+ return options;
960
+ };
961
+ const configOptions = async (record) => {
962
+ const result = [];
963
+ const models = await modelChoices(record);
964
+ if (models.length >= 2) {
965
+ result.push({
723
966
  type: "select",
724
967
  id: "model",
725
968
  name: "Model",
726
969
  category: "model",
727
- currentValue: current,
728
- options
970
+ currentValue: models[0].value,
971
+ options: models
972
+ });
973
+ }
974
+ const route = routeOf(record);
975
+ const efforts = await effortCatalog(route.provider, route.model);
976
+ if (efforts !== void 0 && efforts.efforts.length >= 2) {
977
+ const known = new Set(efforts.efforts.map((effort) => effort.id));
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(
986
+ (candidate) => candidate !== void 0 && known.has(candidate)
987
+ );
988
+ result.push({
989
+ type: "select",
990
+ id: "effort",
991
+ name: "Reasoning",
992
+ category: "thought_level",
993
+ currentValue: preferred ?? efforts.efforts[0].id,
994
+ options: efforts.efforts.map((effort) => ({
995
+ value: effort.id,
996
+ name: effort.name,
997
+ ...effort.description !== void 0 ? { description: effort.description } : {}
998
+ }))
999
+ });
1000
+ }
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)}`);
729
1020
  }
730
- ];
1021
+ }
1022
+ return result;
731
1023
  };
732
1024
  const publishCommands = (sessionId) => {
733
1025
  notify(sessionId, {
@@ -809,15 +1101,20 @@ async function apply(ctx, config = {}) {
809
1101
  };
810
1102
  const statusText = async (record) => {
811
1103
  const used = record.projection.contextWindow;
1104
+ const route = routeOf(record);
1105
+ const effort = record.effort ?? loggedConfig(record)?.reasoningEffort;
812
1106
  const lines = [
813
1107
  `**dsh-acp** ${VERSION} \u2014 DeepSeek Harness ACP bridge`,
814
1108
  "",
815
1109
  `| | |`,
816
1110
  `|---|---|`,
817
- `| Provider | ${record.provider ?? defaultProvider() ?? "(composition default)"} |`,
818
- `| Model | ${record.model ?? config.model ?? defaultSelection().model ?? record.agent.options.model ?? "(composition default)"} |`,
1111
+ `| Provider | ${route.provider ?? "(composition default)"} |`,
1112
+ `| Model | ${route.model ?? "(composition default)"} |`,
1113
+ `| Reasoning | ${effort ?? "(adapter default)"} |`,
819
1114
  `| Credential | ${await describeCredential()} |`,
1115
+ ...record.preset !== void 0 ? [`| Preset | ${record.preset} |`] : [],
820
1116
  `| Permission mode | ${record.modeId} |`,
1117
+ `| Approvals | ${record.approvals} |`,
821
1118
  `| Workspace | ${record.agent.session.header.cwd ?? process.cwd()} |`,
822
1119
  `| Session | ${String(record.agent.session.id)} |`,
823
1120
  ...used !== void 0 ? [`| Context window | ${used.toLocaleString()} tokens |`] : []
@@ -831,6 +1128,7 @@ async function apply(ctx, config = {}) {
831
1128
  projection: projection ?? new SessionProjection(),
832
1129
  modeId,
833
1130
  model,
1131
+ approvals: modeId === "danger-full-access" ? "never" : "ask",
834
1132
  cancelled: false,
835
1133
  inflight: void 0
836
1134
  };
@@ -923,10 +1221,13 @@ async function apply(ctx, config = {}) {
923
1221
  validateCwd(params.cwd);
924
1222
  syncMcpServers(params.mcpServers, params.cwd);
925
1223
  const sessionId = SessionId2(randomUUID());
1224
+ const presets = presetsService();
1225
+ const presetId = presets === void 0 ? void 0 : (await presets.resolve(void 0)).id;
926
1226
  const handle = await agents.create({
927
1227
  sessionId,
928
- meta: { cwd: params.cwd },
929
- 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) } : {}
930
1231
  });
931
1232
  if (closed) {
932
1233
  await handle.dispose();
@@ -939,6 +1240,8 @@ async function apply(ctx, config = {}) {
939
1240
  config.model,
940
1241
  config.permissionMode ?? "workspace-write"
941
1242
  );
1243
+ if (presetId !== void 0) record.preset = presetId;
1244
+ ensureSelection(record);
942
1245
  queueMicrotask(() => publishCommands(String(sessionId)));
943
1246
  const options = await configOptions(record);
944
1247
  return {
@@ -963,8 +1266,11 @@ async function apply(ctx, config = {}) {
963
1266
  });
964
1267
  }
965
1268
  let events;
1269
+ let storedHeader;
966
1270
  try {
967
- ({ events } = await persistence.inspect(SessionId2(sessionId)));
1271
+ const inspected = await persistence.inspect(SessionId2(sessionId));
1272
+ events = inspected.events;
1273
+ storedHeader = inspected.header;
968
1274
  } catch (error) {
969
1275
  throw invalidParams(`session not found: ${sessionId} (${errorChain2(error)})`);
970
1276
  }
@@ -976,9 +1282,13 @@ async function apply(ctx, config = {}) {
976
1282
  title: replay.title
977
1283
  });
978
1284
  }
1285
+ const presets = presetsService();
1286
+ const storedPreset = presetFromLog(storedHeader, events);
1287
+ const presetId = presets === void 0 ? void 0 : (await presets.resolve(storedPreset)).id;
979
1288
  const handle = await agents.resume({
980
1289
  resumeSessionId: SessionId2(sessionId),
981
- agentOptions: agentOptionsFor(config.model)
1290
+ agentOptions: agentOptionsFor(config.model),
1291
+ ...presetSetup(presets, presetId) !== void 0 ? { setup: presetSetup(presets, presetId) } : {}
982
1292
  });
983
1293
  const projection = new SessionProjection(replay.contextWindow);
984
1294
  projection.title = replay.title;
@@ -990,6 +1300,21 @@ async function apply(ctx, config = {}) {
990
1300
  config.permissionMode ?? "workspace-write",
991
1301
  projection
992
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);
993
1318
  queueMicrotask(() => publishCommands(sessionId));
994
1319
  const options = await configOptions(record);
995
1320
  return {
@@ -1095,58 +1420,48 @@ async function apply(ctx, config = {}) {
1095
1420
  const usage = record.projection.promptUsage();
1096
1421
  return { stopReason, ...usage !== void 0 ? { usage } : {} };
1097
1422
  },
1098
- setSessionMode(params) {
1423
+ async setSessionMode(params) {
1099
1424
  const record = requireSession(params.sessionId);
1100
- const mode = SANDBOX_MODES2.find((candidate) => candidate === params.modeId);
1101
- if (mode === void 0) throw invalidParams(`unknown mode: ${params.modeId}`);
1102
- setSandboxMode2(record.agent.session, mode);
1103
- try {
1104
- ctx.get("approval")?.setPolicy?.(record.agent, mode === "danger-full-access" ? "never" : "ask");
1105
- } catch (error) {
1106
- logWarn(`approval policy switch failed: ${String(error)}`);
1107
- }
1108
- record.modeId = mode;
1109
- notify(params.sessionId, { sessionUpdate: "current_mode_update", currentModeId: mode });
1110
- return Promise.resolve({});
1425
+ applyMode(record, params.sessionId, params.modeId);
1426
+ notify(params.sessionId, {
1427
+ sessionUpdate: "config_option_update",
1428
+ configOptions: await configOptions(record)
1429
+ });
1430
+ return {};
1111
1431
  },
1112
1432
  async setSessionConfigOption(params) {
1113
1433
  const record = requireSession(params.sessionId);
1114
- if (params.configId !== "model") throw invalidParams(`unknown config option: ${params.configId}`);
1115
- if (record.inflight !== void 0) {
1116
- throw invalidParams("cannot switch models while a prompt is running");
1117
- }
1118
1434
  const value = typeof params.value === "string" ? params.value : void 0;
1119
- if (value === void 0) throw invalidParams(`unknown model: ${String(params.value)}`);
1120
- const choice = decodeChoice(value);
1121
- const known = /* @__PURE__ */ new Set([
1122
- ...modelCandidates().map((model) => encodeChoice(void 0, model)),
1123
- ...(await discoverModels()).map((entry) => encodeChoice(entry.provider, entry.model)),
1124
- encodeChoice(record.provider, record.model ?? "")
1125
- ]);
1126
- if (!known.has(value)) throw invalidParams(`unknown model: ${value}`);
1127
- if (choice.model !== record.model || choice.provider !== (record.provider ?? defaultProvider())) {
1128
- const sessionId = record.agent.session.id;
1129
- await record.dispose().catch((error) => {
1130
- logWarn(`dispose during model switch failed: ${String(error)}`);
1131
- });
1132
- let handle;
1133
- try {
1134
- handle = await agents.resume({
1135
- resumeSessionId: sessionId,
1136
- agentOptions: agentOptionsFor(choice.model, choice.provider)
1137
- });
1138
- } catch (error) {
1139
- sessions.delete(params.sessionId);
1140
- throw internalError(`model switch failed: ${errorChain2(error)}`);
1435
+ if (value === void 0) throw invalidParams(`invalid value: ${String(params.value)}`);
1436
+ switch (params.configId) {
1437
+ case "preset": {
1438
+ await switchPreset(record, params.sessionId, value);
1439
+ break;
1440
+ }
1441
+ case "effort": {
1442
+ const route = routeOf(record);
1443
+ const catalog = await effortCatalog(route.provider, route.model);
1444
+ if (catalog === void 0 || !catalog.efforts.some((effort) => effort.id === value)) {
1445
+ throw invalidParams(`unknown effort: ${value}`);
1446
+ }
1447
+ const selection = ensureSelection(record);
1448
+ if (selection === void 0 || route.provider === void 0 || route.model === void 0) {
1449
+ throw invalidParams("reasoning effort switching is unavailable on this host");
1450
+ }
1451
+ selection.current = {
1452
+ provider: route.provider,
1453
+ model: route.model,
1454
+ reasoningEffort: value
1455
+ };
1456
+ record.effort = value;
1457
+ break;
1141
1458
  }
1142
- record.agent = handle.agent;
1143
- record.dispose = () => handle.dispose();
1144
- record.model = choice.model;
1145
- if (choice.provider !== void 0 && choice.provider !== defaultProvider()) {
1146
- record.provider = choice.provider;
1147
- } else {
1148
- delete record.provider;
1459
+ case "model": {
1460
+ await switchModel(record, params.sessionId, value);
1461
+ break;
1149
1462
  }
1463
+ default:
1464
+ throw invalidParams(`unknown config option: ${params.configId}`);
1150
1465
  }
1151
1466
  return { configOptions: await configOptions(record) };
1152
1467
  },
package/dist/index.js CHANGED
@@ -24,8 +24,11 @@ import { SessionId } from "@deepseek-ai/dsh-session";
24
24
  import { foldSessionTitle } from "@deepseek-ai/dsh-session-title";
25
25
  import { SANDBOX_MODES, setSandboxMode } from "@deepseek-ai/dsh-sandbox-policy";
26
26
  import { credentialRef } from "@deepseek-ai/dsh-credentials";
27
+ import * as agentModule from "@deepseek-ai/dsh-agent";
27
28
  import * as mcpClient from "@deepseek-ai/dsh-mcp-client";
28
29
  function selfHarness() {
30
+ const agents = agentModule;
31
+ const installModelSelection = agents.installModelSelection ?? agents.ln;
29
32
  return {
30
33
  createUserMessage,
31
34
  errorChain,
@@ -34,6 +37,7 @@ function selfHarness() {
34
37
  setSandboxMode,
35
38
  sandboxModes: SANDBOX_MODES,
36
39
  credentialRef,
40
+ ...installModelSelection !== void 0 ? { installModelSelection } : {},
37
41
  mcpClient
38
42
  };
39
43
  }
@@ -68,7 +72,8 @@ import {
68
72
  } from "@agentclientprotocol/sdk";
69
73
 
70
74
  // src/version.ts
71
- var VERSION = "0.1.0";
75
+ import { createRequire } from "node:module";
76
+ var VERSION = createRequire(import.meta.url)("../package.json").version;
72
77
 
73
78
  // src/log.ts
74
79
  var PREFIX = "[dsh-acp]";
@@ -628,6 +633,7 @@ async function apply(ctx, config = {}) {
628
633
  return { provider: value.slice(0, i), model: value.slice(i + 2) };
629
634
  };
630
635
  let liveCatalog;
636
+ const effortCache = /* @__PURE__ */ new Map();
631
637
  const discoverModels = async () => {
632
638
  if (liveCatalog !== void 0) return liveCatalog;
633
639
  const llm = ctx.get("llm");
@@ -658,7 +664,141 @@ async function apply(ctx, config = {}) {
658
664
  };
659
665
  ctx.on("llm/adapters-updated", () => {
660
666
  liveCatalog = void 0;
667
+ effortCache.clear();
661
668
  });
669
+ const routeOf = (record) => {
670
+ const logged = loggedConfig(record);
671
+ const provider = record.provider ?? defaultProvider() ?? logged?.provider;
672
+ const model = record.model ?? config.model ?? defaultSelection().model ?? logged?.model ?? record.agent.options.model;
673
+ return {
674
+ ...provider !== void 0 ? { provider } : {},
675
+ ...model !== void 0 ? { model } : {}
676
+ };
677
+ };
678
+ const loggedConfig = (record) => {
679
+ try {
680
+ const header = record.agent.session.requestHeader?.();
681
+ const cfg = header?.config;
682
+ if (cfg === void 0) return void 0;
683
+ return {
684
+ provider: cfg.provider,
685
+ model: cfg.model,
686
+ ...cfg.reasoningEffort !== void 0 ? { reasoningEffort: String(cfg.reasoningEffort) } : {}
687
+ };
688
+ } catch {
689
+ return void 0;
690
+ }
691
+ };
692
+ const effortCatalog = async (provider, model) => {
693
+ if (provider === void 0 || model === void 0) return void 0;
694
+ const key = `${provider}::${model}`;
695
+ if (effortCache.has(key)) return effortCache.get(key);
696
+ const llm = ctx.get("llm");
697
+ let catalog;
698
+ try {
699
+ const reasoning = (await llm?.resolveModelInfo?.(provider, model))?.reasoning;
700
+ if (reasoning !== void 0 && reasoning.efforts.length > 0) {
701
+ catalog = {
702
+ efforts: reasoning.efforts.map((effort) => ({
703
+ id: String(effort.id),
704
+ name: effort.name,
705
+ ...effort.description !== void 0 ? { description: effort.description } : {}
706
+ })),
707
+ ...reasoning.defaultEffort !== void 0 ? { defaultEffort: String(reasoning.defaultEffort) } : {}
708
+ };
709
+ }
710
+ } catch (error) {
711
+ logDebug(`resolveModelInfo(${key}) failed: ${String(error)}`);
712
+ }
713
+ effortCache.set(key, catalog);
714
+ return catalog;
715
+ };
716
+ const ensureSelection = (record) => {
717
+ const install = harness.installModelSelection;
718
+ if (install === void 0) return void 0;
719
+ if (record.selection !== void 0) return record.selection;
720
+ let picked;
721
+ const selection = {
722
+ get current() {
723
+ if (picked !== void 0) return picked;
724
+ const logged = loggedConfig(record);
725
+ if (logged !== void 0) return logged;
726
+ const { provider, model } = routeOf(record);
727
+ if (provider === void 0 || model === void 0) return void 0;
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 } : {} };
731
+ },
732
+ set current(next) {
733
+ picked = next;
734
+ },
735
+ assembled: void 0
736
+ };
737
+ try {
738
+ install(record.agent.ctx, selection);
739
+ } catch (error) {
740
+ logWarn(`installModelSelection failed: ${String(error)}`);
741
+ return void 0;
742
+ }
743
+ record.selection = selection;
744
+ return selection;
745
+ };
746
+ const switchModel = async (record, acpSessionId, value) => {
747
+ if (record.inflight !== void 0) {
748
+ throw invalidParams("cannot switch models while a prompt is running");
749
+ }
750
+ const choice = decodeChoice(value);
751
+ const known = /* @__PURE__ */ new Set([
752
+ ...modelCandidates().map((model) => encodeChoice(void 0, model)),
753
+ ...(await discoverModels()).map((entry) => encodeChoice(entry.provider, entry.model)),
754
+ encodeChoice(record.provider, record.model ?? "")
755
+ ]);
756
+ if (!known.has(value)) throw invalidParams(`unknown model: ${value}`);
757
+ if (choice.model === record.model && choice.provider === (record.provider ?? defaultProvider())) return;
758
+ const sessionId = record.agent.session.id;
759
+ await record.dispose().catch((error) => {
760
+ logWarn(`dispose during model switch failed: ${String(error)}`);
761
+ });
762
+ let handle;
763
+ try {
764
+ const presets = presetsService();
765
+ handle = await agents.resume({
766
+ resumeSessionId: sessionId,
767
+ agentOptions: agentOptionsFor(choice.model, choice.provider),
768
+ ...presetSetup(presets, record.preset) !== void 0 ? { setup: presetSetup(presets, record.preset) } : {}
769
+ });
770
+ } catch (error) {
771
+ sessions.delete(acpSessionId);
772
+ throw internalError(`model switch failed: ${errorChain2(error)}`);
773
+ }
774
+ record.agent = handle.agent;
775
+ record.dispose = () => handle.dispose();
776
+ record.model = choice.model;
777
+ if (choice.provider !== void 0 && choice.provider !== defaultProvider()) {
778
+ record.provider = choice.provider;
779
+ } else {
780
+ delete record.provider;
781
+ }
782
+ delete record.selection;
783
+ ensureSelection(record);
784
+ if (record.effort !== void 0) {
785
+ const route = routeOf(record);
786
+ const catalog = await effortCatalog(route.provider, route.model);
787
+ if (catalog?.efforts.some((effort) => effort.id === record.effort) === true && route.provider !== void 0 && route.model !== void 0) {
788
+ const selection = ensureSelection(record);
789
+ if (selection !== void 0) {
790
+ selection.current = {
791
+ provider: route.provider,
792
+ model: route.model,
793
+ reasoningEffort: record.effort
794
+ };
795
+ }
796
+ } else {
797
+ delete record.effort;
798
+ }
799
+ }
800
+ setApprovalPolicy(record, record.approvals);
801
+ };
662
802
  const credentialPresent = async () => {
663
803
  if (envCredentialPresent()) return true;
664
804
  const brand = harness.credentialRef;
@@ -709,14 +849,113 @@ async function apply(ctx, config = {}) {
709
849
  record.inflight = void 0;
710
850
  inflight.resolve(reason);
711
851
  };
712
- const modeState = (record) => ({
713
- currentModeId: record.modeId,
714
- availableModes: SANDBOX_MODES2.map((mode) => {
715
- const label = MODE_LABELS[mode] ?? { name: mode, description: "" };
716
- return { id: mode, name: label.name, description: label.description };
717
- })
718
- });
719
- const configOptions = async (record) => {
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
+ };
930
+ const setApprovalPolicy = (record, policy) => {
931
+ try {
932
+ ctx.get("approval")?.setPolicy?.(record.agent, policy);
933
+ } catch (error) {
934
+ logWarn(`approval policy switch failed: ${String(error)}`);
935
+ }
936
+ record.approvals = policy;
937
+ };
938
+ const permissionService = () => ctx.get("permission");
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
+ }
951
+ const mode = SANDBOX_MODES2.find((candidate) => candidate === modeId);
952
+ if (mode === void 0) throw invalidParams(`unknown mode: ${modeId}`);
953
+ setSandboxMode2(record.agent.session, mode);
954
+ setApprovalPolicy(record, mode === "danger-full-access" ? "never" : "ask");
955
+ record.modeId = mode;
956
+ notify(sessionId, { sessionUpdate: "current_mode_update", currentModeId: mode });
957
+ };
958
+ const modelChoices = async (record) => {
720
959
  const seen = /* @__PURE__ */ new Set();
721
960
  const options = [];
722
961
  const push = (value, name3) => {
@@ -735,17 +974,70 @@ async function apply(ctx, config = {}) {
735
974
  push(encodeChoice(choice.provider, choice.model), choice.label);
736
975
  }
737
976
  for (const model of modelCandidates()) push(encodeChoice(void 0, model), model);
738
- if (options.length < 2) return [];
739
- return [
740
- {
977
+ return options;
978
+ };
979
+ const configOptions = async (record) => {
980
+ const result = [];
981
+ const models = await modelChoices(record);
982
+ if (models.length >= 2) {
983
+ result.push({
741
984
  type: "select",
742
985
  id: "model",
743
986
  name: "Model",
744
987
  category: "model",
745
- currentValue: current,
746
- options
988
+ currentValue: models[0].value,
989
+ options: models
990
+ });
991
+ }
992
+ const route = routeOf(record);
993
+ const efforts = await effortCatalog(route.provider, route.model);
994
+ if (efforts !== void 0 && efforts.efforts.length >= 2) {
995
+ const known = new Set(efforts.efforts.map((effort) => effort.id));
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(
1004
+ (candidate) => candidate !== void 0 && known.has(candidate)
1005
+ );
1006
+ result.push({
1007
+ type: "select",
1008
+ id: "effort",
1009
+ name: "Reasoning",
1010
+ category: "thought_level",
1011
+ currentValue: preferred ?? efforts.efforts[0].id,
1012
+ options: efforts.efforts.map((effort) => ({
1013
+ value: effort.id,
1014
+ name: effort.name,
1015
+ ...effort.description !== void 0 ? { description: effort.description } : {}
1016
+ }))
1017
+ });
1018
+ }
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)}`);
747
1038
  }
748
- ];
1039
+ }
1040
+ return result;
749
1041
  };
750
1042
  const publishCommands = (sessionId) => {
751
1043
  notify(sessionId, {
@@ -827,15 +1119,20 @@ async function apply(ctx, config = {}) {
827
1119
  };
828
1120
  const statusText = async (record) => {
829
1121
  const used = record.projection.contextWindow;
1122
+ const route = routeOf(record);
1123
+ const effort = record.effort ?? loggedConfig(record)?.reasoningEffort;
830
1124
  const lines = [
831
1125
  `**dsh-acp** ${VERSION} \u2014 DeepSeek Harness ACP bridge`,
832
1126
  "",
833
1127
  `| | |`,
834
1128
  `|---|---|`,
835
- `| Provider | ${record.provider ?? defaultProvider() ?? "(composition default)"} |`,
836
- `| Model | ${record.model ?? config.model ?? defaultSelection().model ?? record.agent.options.model ?? "(composition default)"} |`,
1129
+ `| Provider | ${route.provider ?? "(composition default)"} |`,
1130
+ `| Model | ${route.model ?? "(composition default)"} |`,
1131
+ `| Reasoning | ${effort ?? "(adapter default)"} |`,
837
1132
  `| Credential | ${await describeCredential()} |`,
1133
+ ...record.preset !== void 0 ? [`| Preset | ${record.preset} |`] : [],
838
1134
  `| Permission mode | ${record.modeId} |`,
1135
+ `| Approvals | ${record.approvals} |`,
839
1136
  `| Workspace | ${record.agent.session.header.cwd ?? process.cwd()} |`,
840
1137
  `| Session | ${String(record.agent.session.id)} |`,
841
1138
  ...used !== void 0 ? [`| Context window | ${used.toLocaleString()} tokens |`] : []
@@ -849,6 +1146,7 @@ async function apply(ctx, config = {}) {
849
1146
  projection: projection ?? new SessionProjection(),
850
1147
  modeId,
851
1148
  model,
1149
+ approvals: modeId === "danger-full-access" ? "never" : "ask",
852
1150
  cancelled: false,
853
1151
  inflight: void 0
854
1152
  };
@@ -941,10 +1239,13 @@ async function apply(ctx, config = {}) {
941
1239
  validateCwd(params.cwd);
942
1240
  syncMcpServers(params.mcpServers, params.cwd);
943
1241
  const sessionId = SessionId2(randomUUID());
1242
+ const presets = presetsService();
1243
+ const presetId = presets === void 0 ? void 0 : (await presets.resolve(void 0)).id;
944
1244
  const handle = await agents.create({
945
1245
  sessionId,
946
- meta: { cwd: params.cwd },
947
- 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) } : {}
948
1249
  });
949
1250
  if (closed) {
950
1251
  await handle.dispose();
@@ -957,6 +1258,8 @@ async function apply(ctx, config = {}) {
957
1258
  config.model,
958
1259
  config.permissionMode ?? "workspace-write"
959
1260
  );
1261
+ if (presetId !== void 0) record.preset = presetId;
1262
+ ensureSelection(record);
960
1263
  queueMicrotask(() => publishCommands(String(sessionId)));
961
1264
  const options = await configOptions(record);
962
1265
  return {
@@ -981,8 +1284,11 @@ async function apply(ctx, config = {}) {
981
1284
  });
982
1285
  }
983
1286
  let events;
1287
+ let storedHeader;
984
1288
  try {
985
- ({ events } = await persistence.inspect(SessionId2(sessionId)));
1289
+ const inspected = await persistence.inspect(SessionId2(sessionId));
1290
+ events = inspected.events;
1291
+ storedHeader = inspected.header;
986
1292
  } catch (error) {
987
1293
  throw invalidParams(`session not found: ${sessionId} (${errorChain2(error)})`);
988
1294
  }
@@ -994,9 +1300,13 @@ async function apply(ctx, config = {}) {
994
1300
  title: replay.title
995
1301
  });
996
1302
  }
1303
+ const presets = presetsService();
1304
+ const storedPreset = presetFromLog(storedHeader, events);
1305
+ const presetId = presets === void 0 ? void 0 : (await presets.resolve(storedPreset)).id;
997
1306
  const handle = await agents.resume({
998
1307
  resumeSessionId: SessionId2(sessionId),
999
- agentOptions: agentOptionsFor(config.model)
1308
+ agentOptions: agentOptionsFor(config.model),
1309
+ ...presetSetup(presets, presetId) !== void 0 ? { setup: presetSetup(presets, presetId) } : {}
1000
1310
  });
1001
1311
  const projection = new SessionProjection(replay.contextWindow);
1002
1312
  projection.title = replay.title;
@@ -1008,6 +1318,21 @@ async function apply(ctx, config = {}) {
1008
1318
  config.permissionMode ?? "workspace-write",
1009
1319
  projection
1010
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);
1011
1336
  queueMicrotask(() => publishCommands(sessionId));
1012
1337
  const options = await configOptions(record);
1013
1338
  return {
@@ -1113,58 +1438,48 @@ async function apply(ctx, config = {}) {
1113
1438
  const usage = record.projection.promptUsage();
1114
1439
  return { stopReason, ...usage !== void 0 ? { usage } : {} };
1115
1440
  },
1116
- setSessionMode(params) {
1441
+ async setSessionMode(params) {
1117
1442
  const record = requireSession(params.sessionId);
1118
- const mode = SANDBOX_MODES2.find((candidate) => candidate === params.modeId);
1119
- if (mode === void 0) throw invalidParams(`unknown mode: ${params.modeId}`);
1120
- setSandboxMode2(record.agent.session, mode);
1121
- try {
1122
- ctx.get("approval")?.setPolicy?.(record.agent, mode === "danger-full-access" ? "never" : "ask");
1123
- } catch (error) {
1124
- logWarn(`approval policy switch failed: ${String(error)}`);
1125
- }
1126
- record.modeId = mode;
1127
- notify(params.sessionId, { sessionUpdate: "current_mode_update", currentModeId: mode });
1128
- return Promise.resolve({});
1443
+ applyMode(record, params.sessionId, params.modeId);
1444
+ notify(params.sessionId, {
1445
+ sessionUpdate: "config_option_update",
1446
+ configOptions: await configOptions(record)
1447
+ });
1448
+ return {};
1129
1449
  },
1130
1450
  async setSessionConfigOption(params) {
1131
1451
  const record = requireSession(params.sessionId);
1132
- if (params.configId !== "model") throw invalidParams(`unknown config option: ${params.configId}`);
1133
- if (record.inflight !== void 0) {
1134
- throw invalidParams("cannot switch models while a prompt is running");
1135
- }
1136
1452
  const value = typeof params.value === "string" ? params.value : void 0;
1137
- if (value === void 0) throw invalidParams(`unknown model: ${String(params.value)}`);
1138
- const choice = decodeChoice(value);
1139
- const known = /* @__PURE__ */ new Set([
1140
- ...modelCandidates().map((model) => encodeChoice(void 0, model)),
1141
- ...(await discoverModels()).map((entry) => encodeChoice(entry.provider, entry.model)),
1142
- encodeChoice(record.provider, record.model ?? "")
1143
- ]);
1144
- if (!known.has(value)) throw invalidParams(`unknown model: ${value}`);
1145
- if (choice.model !== record.model || choice.provider !== (record.provider ?? defaultProvider())) {
1146
- const sessionId = record.agent.session.id;
1147
- await record.dispose().catch((error) => {
1148
- logWarn(`dispose during model switch failed: ${String(error)}`);
1149
- });
1150
- let handle;
1151
- try {
1152
- handle = await agents.resume({
1153
- resumeSessionId: sessionId,
1154
- agentOptions: agentOptionsFor(choice.model, choice.provider)
1155
- });
1156
- } catch (error) {
1157
- sessions.delete(params.sessionId);
1158
- throw internalError(`model switch failed: ${errorChain2(error)}`);
1453
+ if (value === void 0) throw invalidParams(`invalid value: ${String(params.value)}`);
1454
+ switch (params.configId) {
1455
+ case "preset": {
1456
+ await switchPreset(record, params.sessionId, value);
1457
+ break;
1458
+ }
1459
+ case "effort": {
1460
+ const route = routeOf(record);
1461
+ const catalog = await effortCatalog(route.provider, route.model);
1462
+ if (catalog === void 0 || !catalog.efforts.some((effort) => effort.id === value)) {
1463
+ throw invalidParams(`unknown effort: ${value}`);
1464
+ }
1465
+ const selection = ensureSelection(record);
1466
+ if (selection === void 0 || route.provider === void 0 || route.model === void 0) {
1467
+ throw invalidParams("reasoning effort switching is unavailable on this host");
1468
+ }
1469
+ selection.current = {
1470
+ provider: route.provider,
1471
+ model: route.model,
1472
+ reasoningEffort: value
1473
+ };
1474
+ record.effort = value;
1475
+ break;
1159
1476
  }
1160
- record.agent = handle.agent;
1161
- record.dispose = () => handle.dispose();
1162
- record.model = choice.model;
1163
- if (choice.provider !== void 0 && choice.provider !== defaultProvider()) {
1164
- record.provider = choice.provider;
1165
- } else {
1166
- delete record.provider;
1477
+ case "model": {
1478
+ await switchModel(record, params.sessionId, value);
1479
+ break;
1167
1480
  }
1481
+ default:
1482
+ throw invalidParams(`unknown config option: ${params.configId}`);
1168
1483
  }
1169
1484
  return { configOptions: await configOptions(record) };
1170
1485
  },
@@ -1600,6 +1915,8 @@ async function apply2(ctx, config) {
1600
1915
  const p = kit.plugins;
1601
1916
  const anyCtx = ctx;
1602
1917
  const credentialsSeam = p["credentialsSeam"];
1918
+ const agentsModule = p["agents"];
1919
+ const installModelSelection = agentsModule.installModelSelection ?? agentsModule.ln;
1603
1920
  const harness = {
1604
1921
  createUserMessage: kit.llm.createUserMessage,
1605
1922
  errorChain: kit.llm.errorChain,
@@ -1607,6 +1924,7 @@ async function apply2(ctx, config) {
1607
1924
  foldSessionTitle: kit.sessionTitle.foldSessionTitle,
1608
1925
  setSandboxMode: kit.sandboxPolicy.setSandboxMode,
1609
1926
  sandboxModes: kit.sandboxPolicy.SANDBOX_MODES,
1927
+ ...installModelSelection !== void 0 ? { installModelSelection } : {},
1610
1928
  ...credentialsSeam?.credentialRef !== void 0 ? { credentialRef: credentialsSeam.credentialRef } : {},
1611
1929
  ...p["mcpClient"] !== void 0 ? { mcpClient: p["mcpClient"] } : {}
1612
1930
  };
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.1.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
+ }