@openma/deepseek-harness-acp 0.4.14 → 0.4.16

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
@@ -104,6 +104,28 @@ owns the composition.
104
104
 
105
105
  There are two independent ways to extend an ACP-backed surface.
106
106
 
107
+ To use portable Agent Plugins, Codex plugins, Claude Code plugins, or Pi
108
+ packages through an ACP client, add the
109
+ [Agent Plugins Bridge](https://github.com/openma-ai/dsh-agents-plugins-bridge) to the
110
+ same profile:
111
+
112
+ ```sh
113
+ dsh plugin --profile acp add @openma/dsh-agents-plugins-bridge@latest
114
+ ```
115
+
116
+ The Bridge contributes ordinary Host rows. Imported commands, skills, tools,
117
+ hooks, MCP connections, agents, and Pi extensions therefore reach ACP through
118
+ this adapter's existing projections; there is no ACP-specific plugin import
119
+ runtime. The Bridge's Web management panel and MCP Apps HTML renderer remain
120
+ Web surfaces and are not sent over ACP.
121
+
122
+ For extensions that need a session-owned background lifecycle without an open
123
+ terminal UI, use
124
+ [Martty owner](https://github.com/openma-ai/deepseek-harness-tui/blob/main/README.en.md#martty-owner-long-lived-headless-acp--pi-rpc).
125
+ It is a generic ACP `rpc` client: this package remains the server/transport,
126
+ while Martty owns the long-lived Session and explicit startup/shutdown slash
127
+ commands.
128
+
107
129
  ### Extend the Host composition
108
130
 
109
131
  The ACP adapter rides the Cordis tree that the profile already owns. Add dsh
package/dist/bridge.js CHANGED
@@ -961,6 +961,35 @@ function buildReplay(events) {
961
961
  return { updates, title, contextWindow: projection.contextWindow };
962
962
  }
963
963
 
964
+ // src/bridge/latest-publication.ts
965
+ var LatestPublication = class {
966
+ revisions = /* @__PURE__ */ new Map();
967
+ async run(key, produce, publish) {
968
+ const revision = (this.revisions.get(key) ?? 0) + 1;
969
+ this.revisions.set(key, revision);
970
+ const value = await produce();
971
+ if (this.revisions.get(key) !== revision) return;
972
+ publish(value);
973
+ }
974
+ invalidate(key) {
975
+ this.revisions.set(key, (this.revisions.get(key) ?? 0) + 1);
976
+ }
977
+ };
978
+
979
+ // src/bridge/interaction-mode.ts
980
+ function interactionModeFromClientMeta(meta) {
981
+ if (meta === null || typeof meta !== "object") return void 0;
982
+ const dsh = meta["dsh"];
983
+ if (dsh === null || typeof dsh !== "object") return void 0;
984
+ const interaction = dsh["interaction"];
985
+ if (interaction === null || typeof interaction !== "object") return void 0;
986
+ const mode = interaction["mode"];
987
+ return mode === "interactive" || mode === "rpc" ? mode : void 0;
988
+ }
989
+ function withInteractionMode(options, mode) {
990
+ return mode === void 0 ? options : { ...options, interactionMode: mode };
991
+ }
992
+
964
993
  // src/bridge/prompt.ts
965
994
  function deliverPrompt(target, message, running) {
966
995
  if (running) {
@@ -1721,9 +1750,15 @@ function apply2(ctx, config) {
1721
1750
  }
1722
1751
 
1723
1752
  // src/bridge/presets.ts
1753
+ var BUILT_IN_PRESET_NAMES = {
1754
+ standard: "Standard",
1755
+ code: "Code",
1756
+ minimal: "Minimal",
1757
+ cordis: "Creator"
1758
+ };
1724
1759
  function presetDisplayName(preset) {
1725
- if (preset.id === "cordis") return "Creator";
1726
- if (preset.id === "code") return "Code";
1760
+ const builtInName = BUILT_IN_PRESET_NAMES[preset.id];
1761
+ if (builtInName !== void 0) return builtInName;
1727
1762
  const name5 = preset.name?.trim();
1728
1763
  return name5 !== void 0 && name5.length > 0 ? name5 : preset.id;
1729
1764
  }
@@ -1767,6 +1802,7 @@ async function apply3(ctx, config = {}) {
1767
1802
  const { createUserMessage: createUserMessage2, errorChain: errorChain2, sessionId: SessionId2, foldSessionTitle: foldSessionTitle2, setSandboxMode: setSandboxMode2 } = harness;
1768
1803
  const SANDBOX_MODES2 = harness.sandboxModes;
1769
1804
  const sessions = /* @__PURE__ */ new Map();
1805
+ const commandPublications = new LatestPublication();
1770
1806
  const subagentByChild = /* @__PURE__ */ new Map();
1771
1807
  const subagentByRun = /* @__PURE__ */ new Map();
1772
1808
  const watchedSubagentParents = /* @__PURE__ */ new WeakSet();
@@ -1775,6 +1811,7 @@ async function apply3(ctx, config = {}) {
1775
1811
  let clientTerminalOutput = false;
1776
1812
  let clientSubagentTranscript = false;
1777
1813
  let clientElicitationForm = false;
1814
+ let clientInteractionMode;
1778
1815
  const tuiClient = { advertised: false };
1779
1816
  const rpc = new AcpRpc();
1780
1817
  const findAgent = (agentId) => {
@@ -1970,6 +2007,11 @@ async function apply3(ctx, config = {}) {
1970
2007
  }
1971
2008
  setApprovalPolicy(record, record.approvals);
1972
2009
  };
2010
+ const mutateSession = (record, mutation) => {
2011
+ const run = record.mutationTail.then(mutation);
2012
+ record.mutationTail = run.catch(() => void 0);
2013
+ return run;
2014
+ };
1973
2015
  const listProviderRoutes = async () => {
1974
2016
  try {
1975
2017
  const raw2 = llm.listProviders();
@@ -2003,11 +2045,11 @@ async function apply3(ctx, config = {}) {
2003
2045
  'no credential found for this provider: call authenticate with `_meta["api-key"].apiKey` or `_meta.gateway`, use the browser method, run `dsh-acp login`, or save a key in the dsh Web UI (Settings \u2192 Models)'
2004
2046
  );
2005
2047
  };
2006
- const agentOptionsFor = (model, provider) => ({
2048
+ const agentOptionsFor = (model, provider) => withInteractionMode({
2007
2049
  ...provider !== void 0 ? { provider } : config.provider !== void 0 ? { provider: config.provider } : {},
2008
2050
  ...model !== void 0 ? { model } : {},
2009
2051
  ...config.maxTokens !== void 0 ? { maxTokens: config.maxTokens } : {}
2010
- });
2052
+ }, clientInteractionMode);
2011
2053
  const ownedRecord = (agent) => {
2012
2054
  const record = sessions.get(String(agent.session.id));
2013
2055
  return record?.agent === agent ? record : void 0;
@@ -2481,12 +2523,20 @@ async function apply3(ctx, config = {}) {
2481
2523
  const publishCommands = (sessionId, record) => {
2482
2524
  const target = record ?? sessions.get(sessionId);
2483
2525
  if (target === void 0) return;
2484
- void availableCommandsFor(target).then((availableCommands) => {
2526
+ void commandPublications.run(sessionId, () => availableCommandsFor(target), (availableCommands) => {
2527
+ if (sessions.get(sessionId) !== target) return;
2528
+ logDebug(
2529
+ `available commands ${sessionId}: ${availableCommands.map((command) => command.name).join(", ")}`
2530
+ );
2485
2531
  notify(sessionId, { sessionUpdate: "available_commands_update", availableCommands });
2486
2532
  }).catch((error) => {
2487
2533
  logWarn(`publishing commands failed: ${String(error)}`);
2488
2534
  });
2489
2535
  };
2536
+ ctx.on("commands/change", () => {
2537
+ logDebug(`commands/change: ${sessions.size} live ACP session(s)`);
2538
+ for (const [sessionId, record] of sessions) publishCommands(sessionId, record);
2539
+ });
2490
2540
  const publishConfigOptions = (sessionId, record) => {
2491
2541
  void configOptions(record).then((options) => {
2492
2542
  notify(sessionId, {
@@ -2586,7 +2636,7 @@ async function apply3(ctx, config = {}) {
2586
2636
  }
2587
2637
  const target = matches[0];
2588
2638
  if (target.value === currentValue) return `already on ${target.label}`;
2589
- await switchModel(record, acpSessionId, target.value);
2639
+ await mutateSession(record, () => switchModel(record, acpSessionId, target.value));
2590
2640
  publishCommands(acpSessionId, record);
2591
2641
  publishConfigOptions(acpSessionId, record);
2592
2642
  return `model \u2192 ${target.label}`;
@@ -2625,7 +2675,8 @@ async function apply3(ctx, config = {}) {
2625
2675
  model,
2626
2676
  approvals: modeId === "danger-full-access" ? "never" : "ask",
2627
2677
  cancelled: false,
2628
- inflight: void 0
2678
+ inflight: void 0,
2679
+ mutationTail: Promise.resolve()
2629
2680
  };
2630
2681
  sessions.set(sessionId, record);
2631
2682
  watchSubagentParent(agent);
@@ -2699,6 +2750,7 @@ async function apply3(ctx, config = {}) {
2699
2750
  const capsMeta = params.clientCapabilities?.["_meta"];
2700
2751
  clientTerminalOutput = capsMeta !== null && typeof capsMeta === "object" ? capsMeta["terminal_output"] === true : false;
2701
2752
  clientSubagentTranscript = capsMeta !== null && typeof capsMeta === "object" ? capsMeta["subagent-transcript"] === true : false;
2753
+ clientInteractionMode = interactionModeFromClientMeta(capsMeta);
2702
2754
  clientElicitationForm = params.clientCapabilities?.elicitation?.form !== void 0 && params.clientCapabilities.elicitation.form !== null;
2703
2755
  tuiClient.advertised = advertisesCordis(capsMeta);
2704
2756
  const providers = await listProviderRoutes();
@@ -2985,7 +3037,7 @@ async function apply3(ctx, config = {}) {
2985
3037
  }
2986
3038
  case "agent":
2987
3039
  case "preset": {
2988
- await switchPreset(record, params.sessionId, value);
3040
+ await mutateSession(record, () => switchPreset(record, params.sessionId, value));
2989
3041
  break;
2990
3042
  }
2991
3043
  case "effort": {
@@ -3024,7 +3076,7 @@ async function apply3(ctx, config = {}) {
3024
3076
  break;
3025
3077
  }
3026
3078
  case "model": {
3027
- await switchModel(record, params.sessionId, value);
3079
+ await mutateSession(record, () => switchModel(record, params.sessionId, value));
3028
3080
  break;
3029
3081
  }
3030
3082
  default:
package/dist/index.js CHANGED
@@ -44,6 +44,7 @@ ${probed.map((p) => ` ${p}`).join("\n")}` : ""
44
44
  }
45
45
  };
46
46
  var ANCHOR = "@deepseek-ai/dsh-agent";
47
+ var PROFILE_LAUNCHER = "@deepseek-ai/dsh";
47
48
  function packageDirFrom(base, name) {
48
49
  let dir = resolve(base);
49
50
  for (; ; ) {
@@ -66,7 +67,8 @@ function versionOf(packageDir) {
66
67
  }
67
68
  function hostAt(base, source) {
68
69
  const anchor = packageDirFrom(base, ANCHOR);
69
- if (anchor === void 0) return void 0;
70
+ const launcher = packageDirFrom(base, PROFILE_LAUNCHER);
71
+ if (anchor === void 0 || launcher === void 0) return void 0;
70
72
  return { base: resolve(base), version: versionOf(anchor), source };
71
73
  }
72
74
  function hostFromExplicit(explicit) {
package/dist/plugin.js CHANGED
@@ -962,6 +962,35 @@ function buildReplay(events) {
962
962
  return { updates, title, contextWindow: projection.contextWindow };
963
963
  }
964
964
 
965
+ // src/bridge/latest-publication.ts
966
+ var LatestPublication = class {
967
+ revisions = /* @__PURE__ */ new Map();
968
+ async run(key, produce, publish) {
969
+ const revision = (this.revisions.get(key) ?? 0) + 1;
970
+ this.revisions.set(key, revision);
971
+ const value = await produce();
972
+ if (this.revisions.get(key) !== revision) return;
973
+ publish(value);
974
+ }
975
+ invalidate(key) {
976
+ this.revisions.set(key, (this.revisions.get(key) ?? 0) + 1);
977
+ }
978
+ };
979
+
980
+ // src/bridge/interaction-mode.ts
981
+ function interactionModeFromClientMeta(meta) {
982
+ if (meta === null || typeof meta !== "object") return void 0;
983
+ const dsh = meta["dsh"];
984
+ if (dsh === null || typeof dsh !== "object") return void 0;
985
+ const interaction = dsh["interaction"];
986
+ if (interaction === null || typeof interaction !== "object") return void 0;
987
+ const mode = interaction["mode"];
988
+ return mode === "interactive" || mode === "rpc" ? mode : void 0;
989
+ }
990
+ function withInteractionMode(options, mode) {
991
+ return mode === void 0 ? options : { ...options, interactionMode: mode };
992
+ }
993
+
965
994
  // src/bridge/prompt.ts
966
995
  function deliverPrompt(target, message, running) {
967
996
  if (running) {
@@ -1722,9 +1751,15 @@ function apply2(ctx, config) {
1722
1751
  }
1723
1752
 
1724
1753
  // src/bridge/presets.ts
1754
+ var BUILT_IN_PRESET_NAMES = {
1755
+ standard: "Standard",
1756
+ code: "Code",
1757
+ minimal: "Minimal",
1758
+ cordis: "Creator"
1759
+ };
1725
1760
  function presetDisplayName(preset) {
1726
- if (preset.id === "cordis") return "Creator";
1727
- if (preset.id === "code") return "Code";
1761
+ const builtInName = BUILT_IN_PRESET_NAMES[preset.id];
1762
+ if (builtInName !== void 0) return builtInName;
1728
1763
  const name6 = preset.name?.trim();
1729
1764
  return name6 !== void 0 && name6.length > 0 ? name6 : preset.id;
1730
1765
  }
@@ -1768,6 +1803,7 @@ async function apply3(ctx, config = {}) {
1768
1803
  const { createUserMessage: createUserMessage2, errorChain: errorChain2, sessionId: SessionId2, foldSessionTitle: foldSessionTitle2, setSandboxMode: setSandboxMode2 } = harness;
1769
1804
  const SANDBOX_MODES2 = harness.sandboxModes;
1770
1805
  const sessions = /* @__PURE__ */ new Map();
1806
+ const commandPublications = new LatestPublication();
1771
1807
  const subagentByChild = /* @__PURE__ */ new Map();
1772
1808
  const subagentByRun = /* @__PURE__ */ new Map();
1773
1809
  const watchedSubagentParents = /* @__PURE__ */ new WeakSet();
@@ -1776,6 +1812,7 @@ async function apply3(ctx, config = {}) {
1776
1812
  let clientTerminalOutput = false;
1777
1813
  let clientSubagentTranscript = false;
1778
1814
  let clientElicitationForm = false;
1815
+ let clientInteractionMode;
1779
1816
  const tuiClient = { advertised: false };
1780
1817
  const rpc = new AcpRpc();
1781
1818
  const findAgent = (agentId) => {
@@ -1971,6 +2008,11 @@ async function apply3(ctx, config = {}) {
1971
2008
  }
1972
2009
  setApprovalPolicy(record, record.approvals);
1973
2010
  };
2011
+ const mutateSession = (record, mutation) => {
2012
+ const run = record.mutationTail.then(mutation);
2013
+ record.mutationTail = run.catch(() => void 0);
2014
+ return run;
2015
+ };
1974
2016
  const listProviderRoutes = async () => {
1975
2017
  try {
1976
2018
  const raw2 = llm.listProviders();
@@ -2004,11 +2046,11 @@ async function apply3(ctx, config = {}) {
2004
2046
  'no credential found for this provider: call authenticate with `_meta["api-key"].apiKey` or `_meta.gateway`, use the browser method, run `dsh-acp login`, or save a key in the dsh Web UI (Settings \u2192 Models)'
2005
2047
  );
2006
2048
  };
2007
- const agentOptionsFor = (model, provider) => ({
2049
+ const agentOptionsFor = (model, provider) => withInteractionMode({
2008
2050
  ...provider !== void 0 ? { provider } : config.provider !== void 0 ? { provider: config.provider } : {},
2009
2051
  ...model !== void 0 ? { model } : {},
2010
2052
  ...config.maxTokens !== void 0 ? { maxTokens: config.maxTokens } : {}
2011
- });
2053
+ }, clientInteractionMode);
2012
2054
  const ownedRecord = (agent) => {
2013
2055
  const record = sessions.get(String(agent.session.id));
2014
2056
  return record?.agent === agent ? record : void 0;
@@ -2482,12 +2524,20 @@ async function apply3(ctx, config = {}) {
2482
2524
  const publishCommands = (sessionId, record) => {
2483
2525
  const target = record ?? sessions.get(sessionId);
2484
2526
  if (target === void 0) return;
2485
- void availableCommandsFor(target).then((availableCommands) => {
2527
+ void commandPublications.run(sessionId, () => availableCommandsFor(target), (availableCommands) => {
2528
+ if (sessions.get(sessionId) !== target) return;
2529
+ logDebug(
2530
+ `available commands ${sessionId}: ${availableCommands.map((command) => command.name).join(", ")}`
2531
+ );
2486
2532
  notify(sessionId, { sessionUpdate: "available_commands_update", availableCommands });
2487
2533
  }).catch((error) => {
2488
2534
  logWarn(`publishing commands failed: ${String(error)}`);
2489
2535
  });
2490
2536
  };
2537
+ ctx.on("commands/change", () => {
2538
+ logDebug(`commands/change: ${sessions.size} live ACP session(s)`);
2539
+ for (const [sessionId, record] of sessions) publishCommands(sessionId, record);
2540
+ });
2491
2541
  const publishConfigOptions = (sessionId, record) => {
2492
2542
  void configOptions(record).then((options) => {
2493
2543
  notify(sessionId, {
@@ -2587,7 +2637,7 @@ async function apply3(ctx, config = {}) {
2587
2637
  }
2588
2638
  const target = matches[0];
2589
2639
  if (target.value === currentValue) return `already on ${target.label}`;
2590
- await switchModel(record, acpSessionId, target.value);
2640
+ await mutateSession(record, () => switchModel(record, acpSessionId, target.value));
2591
2641
  publishCommands(acpSessionId, record);
2592
2642
  publishConfigOptions(acpSessionId, record);
2593
2643
  return `model \u2192 ${target.label}`;
@@ -2626,7 +2676,8 @@ async function apply3(ctx, config = {}) {
2626
2676
  model,
2627
2677
  approvals: modeId === "danger-full-access" ? "never" : "ask",
2628
2678
  cancelled: false,
2629
- inflight: void 0
2679
+ inflight: void 0,
2680
+ mutationTail: Promise.resolve()
2630
2681
  };
2631
2682
  sessions.set(sessionId, record);
2632
2683
  watchSubagentParent(agent);
@@ -2700,6 +2751,7 @@ async function apply3(ctx, config = {}) {
2700
2751
  const capsMeta = params.clientCapabilities?.["_meta"];
2701
2752
  clientTerminalOutput = capsMeta !== null && typeof capsMeta === "object" ? capsMeta["terminal_output"] === true : false;
2702
2753
  clientSubagentTranscript = capsMeta !== null && typeof capsMeta === "object" ? capsMeta["subagent-transcript"] === true : false;
2754
+ clientInteractionMode = interactionModeFromClientMeta(capsMeta);
2703
2755
  clientElicitationForm = params.clientCapabilities?.elicitation?.form !== void 0 && params.clientCapabilities.elicitation.form !== null;
2704
2756
  tuiClient.advertised = advertisesCordis(capsMeta);
2705
2757
  const providers = await listProviderRoutes();
@@ -2986,7 +3038,7 @@ async function apply3(ctx, config = {}) {
2986
3038
  }
2987
3039
  case "agent":
2988
3040
  case "preset": {
2989
- await switchPreset(record, params.sessionId, value);
3041
+ await mutateSession(record, () => switchPreset(record, params.sessionId, value));
2990
3042
  break;
2991
3043
  }
2992
3044
  case "effort": {
@@ -3025,7 +3077,7 @@ async function apply3(ctx, config = {}) {
3025
3077
  break;
3026
3078
  }
3027
3079
  case "model": {
3028
- await switchModel(record, params.sessionId, value);
3080
+ await mutateSession(record, () => switchModel(record, params.sessionId, value));
3029
3081
  break;
3030
3082
  }
3031
3083
  default:
package/dist/server.js CHANGED
@@ -957,6 +957,35 @@ function buildReplay(events) {
957
957
  return { updates, title, contextWindow: projection.contextWindow };
958
958
  }
959
959
 
960
+ // src/bridge/latest-publication.ts
961
+ var LatestPublication = class {
962
+ revisions = /* @__PURE__ */ new Map();
963
+ async run(key, produce, publish) {
964
+ const revision = (this.revisions.get(key) ?? 0) + 1;
965
+ this.revisions.set(key, revision);
966
+ const value = await produce();
967
+ if (this.revisions.get(key) !== revision) return;
968
+ publish(value);
969
+ }
970
+ invalidate(key) {
971
+ this.revisions.set(key, (this.revisions.get(key) ?? 0) + 1);
972
+ }
973
+ };
974
+
975
+ // src/bridge/interaction-mode.ts
976
+ function interactionModeFromClientMeta(meta) {
977
+ if (meta === null || typeof meta !== "object") return void 0;
978
+ const dsh = meta["dsh"];
979
+ if (dsh === null || typeof dsh !== "object") return void 0;
980
+ const interaction = dsh["interaction"];
981
+ if (interaction === null || typeof interaction !== "object") return void 0;
982
+ const mode = interaction["mode"];
983
+ return mode === "interactive" || mode === "rpc" ? mode : void 0;
984
+ }
985
+ function withInteractionMode(options, mode) {
986
+ return mode === void 0 ? options : { ...options, interactionMode: mode };
987
+ }
988
+
960
989
  // src/bridge/prompt.ts
961
990
  function deliverPrompt(target, message, running) {
962
991
  if (running) {
@@ -1717,9 +1746,15 @@ function apply2(ctx, config) {
1717
1746
  }
1718
1747
 
1719
1748
  // src/bridge/presets.ts
1749
+ var BUILT_IN_PRESET_NAMES = {
1750
+ standard: "Standard",
1751
+ code: "Code",
1752
+ minimal: "Minimal",
1753
+ cordis: "Creator"
1754
+ };
1720
1755
  function presetDisplayName(preset) {
1721
- if (preset.id === "cordis") return "Creator";
1722
- if (preset.id === "code") return "Code";
1756
+ const builtInName = BUILT_IN_PRESET_NAMES[preset.id];
1757
+ if (builtInName !== void 0) return builtInName;
1723
1758
  const name5 = preset.name?.trim();
1724
1759
  return name5 !== void 0 && name5.length > 0 ? name5 : preset.id;
1725
1760
  }
@@ -1763,6 +1798,7 @@ async function apply3(ctx, config = {}) {
1763
1798
  const { createUserMessage: createUserMessage2, errorChain: errorChain2, sessionId: SessionId2, foldSessionTitle: foldSessionTitle2, setSandboxMode: setSandboxMode2 } = harness;
1764
1799
  const SANDBOX_MODES2 = harness.sandboxModes;
1765
1800
  const sessions = /* @__PURE__ */ new Map();
1801
+ const commandPublications = new LatestPublication();
1766
1802
  const subagentByChild = /* @__PURE__ */ new Map();
1767
1803
  const subagentByRun = /* @__PURE__ */ new Map();
1768
1804
  const watchedSubagentParents = /* @__PURE__ */ new WeakSet();
@@ -1771,6 +1807,7 @@ async function apply3(ctx, config = {}) {
1771
1807
  let clientTerminalOutput = false;
1772
1808
  let clientSubagentTranscript = false;
1773
1809
  let clientElicitationForm = false;
1810
+ let clientInteractionMode;
1774
1811
  const tuiClient = { advertised: false };
1775
1812
  const rpc = new AcpRpc();
1776
1813
  const findAgent = (agentId) => {
@@ -1966,6 +2003,11 @@ async function apply3(ctx, config = {}) {
1966
2003
  }
1967
2004
  setApprovalPolicy(record, record.approvals);
1968
2005
  };
2006
+ const mutateSession = (record, mutation) => {
2007
+ const run = record.mutationTail.then(mutation);
2008
+ record.mutationTail = run.catch(() => void 0);
2009
+ return run;
2010
+ };
1969
2011
  const listProviderRoutes = async () => {
1970
2012
  try {
1971
2013
  const raw2 = llm.listProviders();
@@ -1999,11 +2041,11 @@ async function apply3(ctx, config = {}) {
1999
2041
  'no credential found for this provider: call authenticate with `_meta["api-key"].apiKey` or `_meta.gateway`, use the browser method, run `dsh-acp login`, or save a key in the dsh Web UI (Settings \u2192 Models)'
2000
2042
  );
2001
2043
  };
2002
- const agentOptionsFor = (model, provider) => ({
2044
+ const agentOptionsFor = (model, provider) => withInteractionMode({
2003
2045
  ...provider !== void 0 ? { provider } : config.provider !== void 0 ? { provider: config.provider } : {},
2004
2046
  ...model !== void 0 ? { model } : {},
2005
2047
  ...config.maxTokens !== void 0 ? { maxTokens: config.maxTokens } : {}
2006
- });
2048
+ }, clientInteractionMode);
2007
2049
  const ownedRecord = (agent) => {
2008
2050
  const record = sessions.get(String(agent.session.id));
2009
2051
  return record?.agent === agent ? record : void 0;
@@ -2477,12 +2519,20 @@ async function apply3(ctx, config = {}) {
2477
2519
  const publishCommands = (sessionId, record) => {
2478
2520
  const target = record ?? sessions.get(sessionId);
2479
2521
  if (target === void 0) return;
2480
- void availableCommandsFor(target).then((availableCommands) => {
2522
+ void commandPublications.run(sessionId, () => availableCommandsFor(target), (availableCommands) => {
2523
+ if (sessions.get(sessionId) !== target) return;
2524
+ logDebug(
2525
+ `available commands ${sessionId}: ${availableCommands.map((command) => command.name).join(", ")}`
2526
+ );
2481
2527
  notify(sessionId, { sessionUpdate: "available_commands_update", availableCommands });
2482
2528
  }).catch((error) => {
2483
2529
  logWarn(`publishing commands failed: ${String(error)}`);
2484
2530
  });
2485
2531
  };
2532
+ ctx.on("commands/change", () => {
2533
+ logDebug(`commands/change: ${sessions.size} live ACP session(s)`);
2534
+ for (const [sessionId, record] of sessions) publishCommands(sessionId, record);
2535
+ });
2486
2536
  const publishConfigOptions = (sessionId, record) => {
2487
2537
  void configOptions(record).then((options) => {
2488
2538
  notify(sessionId, {
@@ -2582,7 +2632,7 @@ async function apply3(ctx, config = {}) {
2582
2632
  }
2583
2633
  const target = matches[0];
2584
2634
  if (target.value === currentValue) return `already on ${target.label}`;
2585
- await switchModel(record, acpSessionId, target.value);
2635
+ await mutateSession(record, () => switchModel(record, acpSessionId, target.value));
2586
2636
  publishCommands(acpSessionId, record);
2587
2637
  publishConfigOptions(acpSessionId, record);
2588
2638
  return `model \u2192 ${target.label}`;
@@ -2621,7 +2671,8 @@ async function apply3(ctx, config = {}) {
2621
2671
  model,
2622
2672
  approvals: modeId === "danger-full-access" ? "never" : "ask",
2623
2673
  cancelled: false,
2624
- inflight: void 0
2674
+ inflight: void 0,
2675
+ mutationTail: Promise.resolve()
2625
2676
  };
2626
2677
  sessions.set(sessionId, record);
2627
2678
  watchSubagentParent(agent);
@@ -2695,6 +2746,7 @@ async function apply3(ctx, config = {}) {
2695
2746
  const capsMeta = params.clientCapabilities?.["_meta"];
2696
2747
  clientTerminalOutput = capsMeta !== null && typeof capsMeta === "object" ? capsMeta["terminal_output"] === true : false;
2697
2748
  clientSubagentTranscript = capsMeta !== null && typeof capsMeta === "object" ? capsMeta["subagent-transcript"] === true : false;
2749
+ clientInteractionMode = interactionModeFromClientMeta(capsMeta);
2698
2750
  clientElicitationForm = params.clientCapabilities?.elicitation?.form !== void 0 && params.clientCapabilities.elicitation.form !== null;
2699
2751
  tuiClient.advertised = advertisesCordis(capsMeta);
2700
2752
  const providers = await listProviderRoutes();
@@ -2981,7 +3033,7 @@ async function apply3(ctx, config = {}) {
2981
3033
  }
2982
3034
  case "agent":
2983
3035
  case "preset": {
2984
- await switchPreset(record, params.sessionId, value);
3036
+ await mutateSession(record, () => switchPreset(record, params.sessionId, value));
2985
3037
  break;
2986
3038
  }
2987
3039
  case "effort": {
@@ -3020,7 +3072,7 @@ async function apply3(ctx, config = {}) {
3020
3072
  break;
3021
3073
  }
3022
3074
  case "model": {
3023
- await switchModel(record, params.sessionId, value);
3075
+ await mutateSession(record, () => switchModel(record, params.sessionId, value));
3024
3076
  break;
3025
3077
  }
3026
3078
  default:
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.4.14",
6
+ "version": "0.4.16",
7
7
  "repository": {
8
8
  "type": "git",
9
9
  "url": "git+https://github.com/openma-ai/deepseek-harness-acp.git"
@@ -110,7 +110,8 @@
110
110
  "pnpm": "11.22.0",
111
111
  "tsx": "^4.23.1",
112
112
  "typescript": "^7.0.2",
113
- "vitest": "^4.1.10"
113
+ "vitest": "^4.1.10",
114
+ "yaml": "^2.9.0"
114
115
  },
115
116
  "dependencies": {
116
117
  "@agentclientprotocol/sdk": "^1.3.0",