@openma/deepseek-harness-acp 0.4.26 → 0.4.28

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
@@ -171,6 +171,15 @@ Optional wire behavior follows ACP's extension conventions:
171
171
  extension must still get normal sessions, prompts, updates, cancellation,
172
172
  auth, and config options.
173
173
 
174
+ #### DSH tool result values
175
+
176
+ The [ACP metadata registry][metadata-registry] is the authoritative list of
177
+ every `_meta` field this adapter reads or emits. It records direction, carrier,
178
+ shape, negotiation requirements, and replay behavior, including native DSH
179
+ tool values at `_meta.dsh.toolResult.value`.
180
+
181
+ [metadata-registry]: https://github.com/openma-ai/deepseek-harness-acp/blob/main/docs/metadata.md
182
+
174
183
  The current package applies this pattern to the built-in `_dsh/cordis/*`
175
184
  family used by the TUI for Client capability discovery, dynamic Package
176
185
  lifecycle, and package-private Host/Client RPC. It is an explicit, versioned
@@ -217,7 +226,7 @@ another provider.
217
226
  - **Live model catalog** — providers × models from the running composition (third-party providers added in the Web UI appear immediately), plus reasoning-effort selection that follows your product default.
218
227
  - **Slash commands** — adapter built-ins (`/status`, `/model`) plus the harness command registry (`/compact`, `/goal`, `/permission`, `/plan`, …) executed without a model turn, plus **skills** (`/skill-name` — the harness's own invocation gesture). Login and logout are ACP methods, not chat commands.
219
228
  - **Plans & usage** — `todo_write` snapshots as ACP plans; token accounting as `usage_update` and per-turn usage.
220
- - **Sessions** — `session/load` with full history replay, `session/list`, silent restore when a client prompts an old session after an agent restart, titles as `session_info_update`. Multi-root sessions are not advertised: non-empty `additionalDirectories` on `session/new` or `session/load` return `Invalid params` until [dsh supports multiple workspace roots](https://github.com/deepseek-ai/deepseek-harness/discussions/2474).
229
+ - **Sessions** — `session/resume` restores a durable session without replaying its transcript; `session/load` remains the full-history path. Also supports `session/list`, silent restore when a client prompts an old session after an agent restart, and titles as `session_info_update`. Multi-root sessions are not advertised: non-empty `additionalDirectories` on `session/new`, `session/resume`, or `session/load` return `Invalid params` until [dsh supports multiple workspace roots](https://github.com/deepseek-ai/deepseek-harness/discussions/2474).
221
230
  - **MCP servers** — per-session `mcpServers` mount `@deepseek-ai/dsh-mcp-client` instances (stdio + streamable HTTP); tools join as `mcp__<server>__<tool>`; a failing server never takes the session down.
222
231
  - **Real cancellation** — `session/cancel` interrupts the live turn through the harness agent.
223
232
 
package/cordis.patch.yml CHANGED
@@ -20,6 +20,14 @@
20
20
  - id: hmr
21
21
  disabled: true
22
22
 
23
+ # Newer dsh versions seed the acp profile with their own ACP transport.
24
+ # This bundle owns stdout and must replace that transport.
25
+ - id: acp
26
+ disabled: true
27
+
28
+ - id: acp-app-startup
29
+ disabled: true
30
+
23
31
  # Model-facing tool rows move off the host plane and into each agent preset's
24
32
  # own composition (standard/code/minimal/cordis ship complete agent.cordis.yml
25
33
  # files) — the same split the web-app bundle makes, and what gives "minimal"
package/dist/bridge.js CHANGED
@@ -419,6 +419,28 @@ function diffsFromToolMeta(meta) {
419
419
  }
420
420
  return result;
421
421
  }
422
+ function acknowledgementId(text, prefix) {
423
+ if (!text.startsWith(prefix)) return void 0;
424
+ const id = text.slice(prefix.length);
425
+ if (id.length === 0 || id.trim() !== id || id.includes(" ") || id.includes(" ") || id.includes("\n")) {
426
+ return void 0;
427
+ }
428
+ return id;
429
+ }
430
+ function typedToolAcknowledgement(state, text, failed) {
431
+ if (state === void 0 || failed) return void 0;
432
+ if (state.name === "bash" || state.name === "pwsh") {
433
+ const jobId = acknowledgementId(text, "started background job ");
434
+ return jobId === void 0 ? void 0 : { kind: "background", jobId };
435
+ }
436
+ if (state.name === "subagent") {
437
+ const jobId = acknowledgementId(text, "started background subagent job ");
438
+ if (jobId !== void 0) return { kind: "background", jobId };
439
+ const subagentId = acknowledgementId(text, "started subagent ");
440
+ if (subagentId !== void 0) return { kind: "continuable", subagentId };
441
+ }
442
+ return void 0;
443
+ }
422
444
  function extractToolResult(data) {
423
445
  let failed = data["error"] !== void 0 && data["error"] !== null;
424
446
  const parts = [];
@@ -532,6 +554,8 @@ var SessionProjection = class {
532
554
  title;
533
555
  streamedText = /* @__PURE__ */ new Set();
534
556
  streamedReasoning = /* @__PURE__ */ new Set();
557
+ /** Structured tool values observed live before DSH omits them from its durable event. */
558
+ toolResultValues = /* @__PURE__ */ new Map();
535
559
  /** One authoritative state per open ACP tool call. Block indexes only
536
560
  * route streaming fragments to that state; they never own lifecycle. */
537
561
  toolCalls = /* @__PURE__ */ new Map();
@@ -551,6 +575,10 @@ var SessionProjection = class {
551
575
  this.presentCall = options?.presentCall;
552
576
  this.subagent = options?.subagent;
553
577
  }
578
+ /** Stage one live `tools/result` value for the matching durable `tool/result` event. */
579
+ recordToolResult(callId, value) {
580
+ this.toolResultValues.set(callId, value);
581
+ }
554
582
  /** Reset per-prompt accumulators; call when a new `session/prompt` starts. */
555
583
  beginPrompt() {
556
584
  this.window = emptyWindow();
@@ -941,6 +969,18 @@ var SessionProjection = class {
941
969
  const state = this.toolCalls.get(callId);
942
970
  const facts = state?.facts;
943
971
  const { failed, text, diffs } = extractToolResult(data);
972
+ const hasLiveValue = this.toolResultValues.has(callId);
973
+ const liveValue = this.toolResultValues.get(callId);
974
+ this.toolResultValues.delete(callId);
975
+ const toolResultValue = hasLiveValue ? liveValue : typedToolAcknowledgement(state, text, failed);
976
+ const toolResultMeta = toolResultValue === void 0 ? void 0 : {
977
+ dsh: {
978
+ toolResult: {
979
+ value: toolResultValue
980
+ }
981
+ }
982
+ };
983
+ const rawOutput = text.length > 0 ? { output: text, isError: failed } : void 0;
944
984
  const content = diffs.map((diff) => ({
945
985
  type: "diff",
946
986
  path: diff.path,
@@ -965,8 +1005,9 @@ var SessionProjection = class {
965
1005
  sessionUpdate: "tool_call_update",
966
1006
  toolCallId: callId,
967
1007
  status: failed ? "failed" : "completed",
968
- ...text.length > 0 ? { rawOutput: { output: text, isError: failed } } : {},
1008
+ ...rawOutput !== void 0 ? { rawOutput } : {},
969
1009
  _meta: {
1010
+ ...toolResultMeta ?? {},
970
1011
  terminal_exit: { terminal_id: callId, exit_code: failed ? 1 : 0, signal: null }
971
1012
  }
972
1013
  }
@@ -992,7 +1033,8 @@ ${text.replace(/\n+$/, "")}
992
1033
  toolCallId: callId,
993
1034
  status: failed ? "failed" : "completed",
994
1035
  ...content.length > 0 ? { content } : {},
995
- ...text.length > 0 ? { rawOutput: { output: text, isError: failed } } : {}
1036
+ ...rawOutput !== void 0 ? { rawOutput } : {},
1037
+ ...toolResultMeta !== void 0 ? { _meta: toolResultMeta } : {}
996
1038
  }
997
1039
  ];
998
1040
  }
@@ -1009,11 +1051,28 @@ ${text.replace(/\n+$/, "")}
1009
1051
  }
1010
1052
  this.toolCallBlocks.clear();
1011
1053
  this.toolCalls.clear();
1054
+ this.toolResultValues.clear();
1012
1055
  return [];
1013
1056
  }
1014
1057
  };
1015
1058
 
1016
1059
  // src/bridge/history.ts
1060
+ function buildResumeMetadata(events) {
1061
+ let title;
1062
+ let contextWindow;
1063
+ for (const event of events) {
1064
+ if (event.type === "session/title") {
1065
+ const value = event.data?.["title"];
1066
+ if (typeof value === "string" && value.length > 0) title = value;
1067
+ continue;
1068
+ }
1069
+ if (event.type === "request/context") {
1070
+ const value = event.data?.["contextWindow"];
1071
+ if (typeof value === "number" && value > 0) contextWindow = value;
1072
+ }
1073
+ }
1074
+ return { title, contextWindow };
1075
+ }
1017
1076
  function userMessage(data) {
1018
1077
  if (data === void 0) return void 0;
1019
1078
  const source = data["source"];
@@ -1852,26 +1911,27 @@ function installAcpUserQuestionProvider(service, options) {
1852
1911
  let router = routers.get(owner);
1853
1912
  if (router === void 0) {
1854
1913
  const routes = [];
1855
- const disposeProvider = service.registerProvider({
1856
- async ask(request) {
1857
- for (let index = routes.length - 1; index >= 0; index -= 1) {
1858
- const route = routes[index];
1859
- const sessionId = route?.sessionIdForRequest(request);
1860
- if (route === void 0 || sessionId === void 0) continue;
1861
- if (!route.formSupported()) {
1862
- throw new UserQuestionError(
1863
- "the ACP client does not support form elicitation",
1864
- "CLIENT_UNSUPPORTED"
1865
- );
1866
- }
1867
- return askUserQuestionsOverAcp(request, sessionId, route.create);
1914
+ const ask = async (request, next) => {
1915
+ for (let index = routes.length - 1; index >= 0; index -= 1) {
1916
+ const route = routes[index];
1917
+ const sessionId = route?.sessionIdForRequest(request);
1918
+ if (route === void 0 || sessionId === void 0) continue;
1919
+ if (!route.formSupported()) {
1920
+ throw new UserQuestionError(
1921
+ "the ACP client does not support form elicitation",
1922
+ "CLIENT_UNSUPPORTED"
1923
+ );
1868
1924
  }
1869
- throw new UserQuestionError(
1870
- "ACP user interaction requires a live root session",
1871
- "ASK_MISSING_AGENT"
1872
- );
1925
+ return askUserQuestionsOverAcp(request, sessionId, route.create);
1873
1926
  }
1874
- });
1927
+ if (next !== void 0) return next();
1928
+ throw new UserQuestionError(
1929
+ "ACP user interaction requires a live root session",
1930
+ "ASK_MISSING_AGENT"
1931
+ );
1932
+ };
1933
+ const legacy = service;
1934
+ const disposeProvider = typeof legacy.registerProvider === "function" ? legacy.registerProvider({ ask }) : service.ctx.root.on("user-questions/request", ask);
1875
1935
  router = { routes, disposeProvider };
1876
1936
  routers.set(owner, router);
1877
1937
  }
@@ -1914,6 +1974,9 @@ function presetDisplayName(preset) {
1914
1974
  // src/bridge/index.ts
1915
1975
  var name3 = "acp-bridge";
1916
1976
  var inject3 = ["agents", "credentials", "llm", "agentDefaultModel", "sessionPersistence", "approval", "permissionPresets", "commands", "agentPresets", "skills", "subagents", "userQuestions"];
1977
+ function readSessionEvents(session) {
1978
+ return session.snapshotEvents?.() ?? session.events ?? [];
1979
+ }
1917
1980
  function invalidParams(detail) {
1918
1981
  return RequestError.invalidParams(void 0, detail);
1919
1982
  }
@@ -2257,8 +2320,10 @@ async function apply3(ctx, config = {}) {
2257
2320
  } catch (error) {
2258
2321
  throw invalidParams(`session not found: ${sessionId} (${errorChain2(error)})`);
2259
2322
  }
2260
- const replay = buildReplay(events);
2323
+ let restored;
2261
2324
  if (options.replay) {
2325
+ const replay = buildReplay(events);
2326
+ restored = replay;
2262
2327
  for (const update of replay.updates) notify(sessionId, update);
2263
2328
  if (replay.title !== void 0) {
2264
2329
  notify(sessionId, {
@@ -2266,6 +2331,8 @@ async function apply3(ctx, config = {}) {
2266
2331
  title: replay.title
2267
2332
  });
2268
2333
  }
2334
+ } else {
2335
+ restored = buildResumeMetadata(events);
2269
2336
  }
2270
2337
  const presets = presetsService();
2271
2338
  const storedPreset = presetFromLog(storedHeader, events);
@@ -2276,11 +2343,11 @@ async function apply3(ctx, config = {}) {
2276
2343
  ...presetSetup(presets, presetId) !== void 0 ? { setup: presetSetup(presets, presetId) } : {}
2277
2344
  });
2278
2345
  const cwd = options.cwd ?? storedHeader?.cwd;
2279
- const projection = new SessionProjection(replay.contextWindow, {
2346
+ const projection = new SessionProjection(restored.contextWindow, {
2280
2347
  terminalOutput: clientTerminalOutput,
2281
2348
  ...cwd !== void 0 ? { cwd } : {}
2282
2349
  });
2283
- projection.title = replay.title;
2350
+ projection.title = restored.title;
2284
2351
  const record = registerRecord(
2285
2352
  sessionId,
2286
2353
  handle.agent,
@@ -2291,8 +2358,7 @@ async function apply3(ctx, config = {}) {
2291
2358
  );
2292
2359
  if (presetId !== void 0) record.preset = presetId;
2293
2360
  {
2294
- const permission = permissionService();
2295
- const storedMode = permission.current(events);
2361
+ const storedMode = currentPermission(record);
2296
2362
  if (storedMode !== void 0) record.modeId = storedMode;
2297
2363
  for (let index = events.length - 1; index >= 0; index -= 1) {
2298
2364
  const event = events[index];
@@ -2487,6 +2553,10 @@ async function apply3(ctx, config = {}) {
2487
2553
  record.approvals = policy;
2488
2554
  };
2489
2555
  const permissionService = () => permissionPresets;
2556
+ const currentPermission = (record) => {
2557
+ const session = record.agent.session;
2558
+ return permissionService().current(typeof session.snapshotEvents === "function" ? session : readSessionEvents(session));
2559
+ };
2490
2560
  const permissionPresentation = (id) => {
2491
2561
  const spec = permissionService().resolve(id);
2492
2562
  const stock = MODE_LABELS[id];
@@ -2572,7 +2642,7 @@ async function apply3(ctx, config = {}) {
2572
2642
  });
2573
2643
  }
2574
2644
  if (commandRuntime.list(record.agent).some((command) => command.name === "plan")) {
2575
- const active = [...record.agent.session.events].reverse().map((event) => event).find((event) => event.type === "plan/mode")?.data?.["active"] === true;
2645
+ const active = [...readSessionEvents(record.agent.session)].reverse().map((event) => event).find((event) => event.type === "plan/mode")?.data?.["active"] === true;
2576
2646
  result.push({
2577
2647
  type: "select",
2578
2648
  id: "collaboration_mode",
@@ -2740,10 +2810,8 @@ async function apply3(ctx, config = {}) {
2740
2810
  });
2741
2811
  };
2742
2812
  const syncPermissionState = (record, sessionId) => {
2743
- const events = record.agent.session.events;
2744
- if (events === void 0) return;
2745
- const permission = permissionService();
2746
- const storedMode = permission.current(events);
2813
+ const events = readSessionEvents(record.agent.session);
2814
+ const storedMode = currentPermission(record);
2747
2815
  for (let index = events.length - 1; index >= 0; index -= 1) {
2748
2816
  const event = events[index];
2749
2817
  if (event?.type === "approval/policy") {
@@ -2874,6 +2942,11 @@ async function apply3(ctx, config = {}) {
2874
2942
  watchSubagentParent(agent);
2875
2943
  return record;
2876
2944
  };
2945
+ ctx.on("tools/result", (exec, result) => {
2946
+ if (exec.parent !== void 0 || exec.agent === void 0 || result.isError) return;
2947
+ const projection = ownedRecord(exec.agent)?.projection ?? subagentByChild.get(String(exec.agent.session.id))?.projection;
2948
+ projection?.recordToolResult(String(exec.callId), result.value);
2949
+ });
2877
2950
  ctx.on("session/event", (session, event) => {
2878
2951
  const sessionId = String(session.header.id);
2879
2952
  const record = sessions.get(sessionId);
@@ -2961,7 +3034,7 @@ async function apply3(ctx, config = {}) {
2961
3034
  // mcp-client's second transport. Legacy SSE does not.
2962
3035
  mcpCapabilities: { http: true, sse: false },
2963
3036
  auth: { logout: {} },
2964
- sessionCapabilities: { list: {} }
3037
+ sessionCapabilities: { list: {}, resume: {} }
2965
3038
  },
2966
3039
  authMethods: advertisedAuthMethods(
2967
3040
  providers,
@@ -3076,6 +3149,20 @@ async function apply3(ctx, config = {}) {
3076
3149
  ...options.length > 0 ? { configOptions: options } : {}
3077
3150
  };
3078
3151
  },
3152
+ async resumeSession(params) {
3153
+ assertOpen();
3154
+ await requireCredential(config.provider);
3155
+ validateCwd(params.cwd);
3156
+ validateAdditionalDirectories(params.additionalDirectories);
3157
+ syncMcpServers(params.mcpServers, params.cwd);
3158
+ requirePersistence();
3159
+ const record = await restoreSession(params.sessionId, { replay: false, cwd: params.cwd });
3160
+ const options = await configOptions(record);
3161
+ return {
3162
+ modes: modeState(record),
3163
+ ...options.length > 0 ? { configOptions: options } : {}
3164
+ };
3165
+ },
3079
3166
  async listSessions(params) {
3080
3167
  assertOpen();
3081
3168
  const persistence = requirePersistence();
package/dist/plugin.js CHANGED
@@ -421,6 +421,28 @@ function diffsFromToolMeta(meta) {
421
421
  }
422
422
  return result;
423
423
  }
424
+ function acknowledgementId(text, prefix) {
425
+ if (!text.startsWith(prefix)) return void 0;
426
+ const id = text.slice(prefix.length);
427
+ if (id.length === 0 || id.trim() !== id || id.includes(" ") || id.includes(" ") || id.includes("\n")) {
428
+ return void 0;
429
+ }
430
+ return id;
431
+ }
432
+ function typedToolAcknowledgement(state, text, failed) {
433
+ if (state === void 0 || failed) return void 0;
434
+ if (state.name === "bash" || state.name === "pwsh") {
435
+ const jobId = acknowledgementId(text, "started background job ");
436
+ return jobId === void 0 ? void 0 : { kind: "background", jobId };
437
+ }
438
+ if (state.name === "subagent") {
439
+ const jobId = acknowledgementId(text, "started background subagent job ");
440
+ if (jobId !== void 0) return { kind: "background", jobId };
441
+ const subagentId = acknowledgementId(text, "started subagent ");
442
+ if (subagentId !== void 0) return { kind: "continuable", subagentId };
443
+ }
444
+ return void 0;
445
+ }
424
446
  function extractToolResult(data) {
425
447
  let failed = data["error"] !== void 0 && data["error"] !== null;
426
448
  const parts = [];
@@ -534,6 +556,8 @@ var SessionProjection = class {
534
556
  title;
535
557
  streamedText = /* @__PURE__ */ new Set();
536
558
  streamedReasoning = /* @__PURE__ */ new Set();
559
+ /** Structured tool values observed live before DSH omits them from its durable event. */
560
+ toolResultValues = /* @__PURE__ */ new Map();
537
561
  /** One authoritative state per open ACP tool call. Block indexes only
538
562
  * route streaming fragments to that state; they never own lifecycle. */
539
563
  toolCalls = /* @__PURE__ */ new Map();
@@ -553,6 +577,10 @@ var SessionProjection = class {
553
577
  this.presentCall = options?.presentCall;
554
578
  this.subagent = options?.subagent;
555
579
  }
580
+ /** Stage one live `tools/result` value for the matching durable `tool/result` event. */
581
+ recordToolResult(callId, value) {
582
+ this.toolResultValues.set(callId, value);
583
+ }
556
584
  /** Reset per-prompt accumulators; call when a new `session/prompt` starts. */
557
585
  beginPrompt() {
558
586
  this.window = emptyWindow();
@@ -943,6 +971,18 @@ var SessionProjection = class {
943
971
  const state = this.toolCalls.get(callId);
944
972
  const facts = state?.facts;
945
973
  const { failed, text, diffs } = extractToolResult(data);
974
+ const hasLiveValue = this.toolResultValues.has(callId);
975
+ const liveValue = this.toolResultValues.get(callId);
976
+ this.toolResultValues.delete(callId);
977
+ const toolResultValue = hasLiveValue ? liveValue : typedToolAcknowledgement(state, text, failed);
978
+ const toolResultMeta = toolResultValue === void 0 ? void 0 : {
979
+ dsh: {
980
+ toolResult: {
981
+ value: toolResultValue
982
+ }
983
+ }
984
+ };
985
+ const rawOutput = text.length > 0 ? { output: text, isError: failed } : void 0;
946
986
  const content = diffs.map((diff) => ({
947
987
  type: "diff",
948
988
  path: diff.path,
@@ -967,8 +1007,9 @@ var SessionProjection = class {
967
1007
  sessionUpdate: "tool_call_update",
968
1008
  toolCallId: callId,
969
1009
  status: failed ? "failed" : "completed",
970
- ...text.length > 0 ? { rawOutput: { output: text, isError: failed } } : {},
1010
+ ...rawOutput !== void 0 ? { rawOutput } : {},
971
1011
  _meta: {
1012
+ ...toolResultMeta ?? {},
972
1013
  terminal_exit: { terminal_id: callId, exit_code: failed ? 1 : 0, signal: null }
973
1014
  }
974
1015
  }
@@ -994,7 +1035,8 @@ ${text.replace(/\n+$/, "")}
994
1035
  toolCallId: callId,
995
1036
  status: failed ? "failed" : "completed",
996
1037
  ...content.length > 0 ? { content } : {},
997
- ...text.length > 0 ? { rawOutput: { output: text, isError: failed } } : {}
1038
+ ...rawOutput !== void 0 ? { rawOutput } : {},
1039
+ ...toolResultMeta !== void 0 ? { _meta: toolResultMeta } : {}
998
1040
  }
999
1041
  ];
1000
1042
  }
@@ -1011,11 +1053,28 @@ ${text.replace(/\n+$/, "")}
1011
1053
  }
1012
1054
  this.toolCallBlocks.clear();
1013
1055
  this.toolCalls.clear();
1056
+ this.toolResultValues.clear();
1014
1057
  return [];
1015
1058
  }
1016
1059
  };
1017
1060
 
1018
1061
  // src/bridge/history.ts
1062
+ function buildResumeMetadata(events) {
1063
+ let title;
1064
+ let contextWindow;
1065
+ for (const event of events) {
1066
+ if (event.type === "session/title") {
1067
+ const value = event.data?.["title"];
1068
+ if (typeof value === "string" && value.length > 0) title = value;
1069
+ continue;
1070
+ }
1071
+ if (event.type === "request/context") {
1072
+ const value = event.data?.["contextWindow"];
1073
+ if (typeof value === "number" && value > 0) contextWindow = value;
1074
+ }
1075
+ }
1076
+ return { title, contextWindow };
1077
+ }
1019
1078
  function userMessage(data) {
1020
1079
  if (data === void 0) return void 0;
1021
1080
  const source = data["source"];
@@ -1854,26 +1913,27 @@ function installAcpUserQuestionProvider(service, options) {
1854
1913
  let router = routers.get(owner);
1855
1914
  if (router === void 0) {
1856
1915
  const routes = [];
1857
- const disposeProvider = service.registerProvider({
1858
- async ask(request) {
1859
- for (let index = routes.length - 1; index >= 0; index -= 1) {
1860
- const route = routes[index];
1861
- const sessionId = route?.sessionIdForRequest(request);
1862
- if (route === void 0 || sessionId === void 0) continue;
1863
- if (!route.formSupported()) {
1864
- throw new UserQuestionError(
1865
- "the ACP client does not support form elicitation",
1866
- "CLIENT_UNSUPPORTED"
1867
- );
1868
- }
1869
- return askUserQuestionsOverAcp(request, sessionId, route.create);
1916
+ const ask = async (request, next) => {
1917
+ for (let index = routes.length - 1; index >= 0; index -= 1) {
1918
+ const route = routes[index];
1919
+ const sessionId = route?.sessionIdForRequest(request);
1920
+ if (route === void 0 || sessionId === void 0) continue;
1921
+ if (!route.formSupported()) {
1922
+ throw new UserQuestionError(
1923
+ "the ACP client does not support form elicitation",
1924
+ "CLIENT_UNSUPPORTED"
1925
+ );
1870
1926
  }
1871
- throw new UserQuestionError(
1872
- "ACP user interaction requires a live root session",
1873
- "ASK_MISSING_AGENT"
1874
- );
1927
+ return askUserQuestionsOverAcp(request, sessionId, route.create);
1875
1928
  }
1876
- });
1929
+ if (next !== void 0) return next();
1930
+ throw new UserQuestionError(
1931
+ "ACP user interaction requires a live root session",
1932
+ "ASK_MISSING_AGENT"
1933
+ );
1934
+ };
1935
+ const legacy = service;
1936
+ const disposeProvider = typeof legacy.registerProvider === "function" ? legacy.registerProvider({ ask }) : service.ctx.root.on("user-questions/request", ask);
1877
1937
  router = { routes, disposeProvider };
1878
1938
  routers.set(owner, router);
1879
1939
  }
@@ -1916,6 +1976,9 @@ function presetDisplayName(preset) {
1916
1976
  // src/bridge/index.ts
1917
1977
  var name3 = "acp-bridge";
1918
1978
  var inject3 = ["agents", "credentials", "llm", "agentDefaultModel", "sessionPersistence", "approval", "permissionPresets", "commands", "agentPresets", "skills", "subagents", "userQuestions"];
1979
+ function readSessionEvents(session) {
1980
+ return session.snapshotEvents?.() ?? session.events ?? [];
1981
+ }
1919
1982
  function invalidParams(detail) {
1920
1983
  return RequestError.invalidParams(void 0, detail);
1921
1984
  }
@@ -2259,8 +2322,10 @@ async function apply3(ctx, config = {}) {
2259
2322
  } catch (error) {
2260
2323
  throw invalidParams(`session not found: ${sessionId} (${errorChain2(error)})`);
2261
2324
  }
2262
- const replay = buildReplay(events);
2325
+ let restored;
2263
2326
  if (options.replay) {
2327
+ const replay = buildReplay(events);
2328
+ restored = replay;
2264
2329
  for (const update of replay.updates) notify(sessionId, update);
2265
2330
  if (replay.title !== void 0) {
2266
2331
  notify(sessionId, {
@@ -2268,6 +2333,8 @@ async function apply3(ctx, config = {}) {
2268
2333
  title: replay.title
2269
2334
  });
2270
2335
  }
2336
+ } else {
2337
+ restored = buildResumeMetadata(events);
2271
2338
  }
2272
2339
  const presets = presetsService();
2273
2340
  const storedPreset = presetFromLog(storedHeader, events);
@@ -2278,11 +2345,11 @@ async function apply3(ctx, config = {}) {
2278
2345
  ...presetSetup(presets, presetId) !== void 0 ? { setup: presetSetup(presets, presetId) } : {}
2279
2346
  });
2280
2347
  const cwd = options.cwd ?? storedHeader?.cwd;
2281
- const projection = new SessionProjection(replay.contextWindow, {
2348
+ const projection = new SessionProjection(restored.contextWindow, {
2282
2349
  terminalOutput: clientTerminalOutput,
2283
2350
  ...cwd !== void 0 ? { cwd } : {}
2284
2351
  });
2285
- projection.title = replay.title;
2352
+ projection.title = restored.title;
2286
2353
  const record = registerRecord(
2287
2354
  sessionId,
2288
2355
  handle.agent,
@@ -2293,8 +2360,7 @@ async function apply3(ctx, config = {}) {
2293
2360
  );
2294
2361
  if (presetId !== void 0) record.preset = presetId;
2295
2362
  {
2296
- const permission = permissionService();
2297
- const storedMode = permission.current(events);
2363
+ const storedMode = currentPermission(record);
2298
2364
  if (storedMode !== void 0) record.modeId = storedMode;
2299
2365
  for (let index = events.length - 1; index >= 0; index -= 1) {
2300
2366
  const event = events[index];
@@ -2489,6 +2555,10 @@ async function apply3(ctx, config = {}) {
2489
2555
  record.approvals = policy;
2490
2556
  };
2491
2557
  const permissionService = () => permissionPresets;
2558
+ const currentPermission = (record) => {
2559
+ const session = record.agent.session;
2560
+ return permissionService().current(typeof session.snapshotEvents === "function" ? session : readSessionEvents(session));
2561
+ };
2492
2562
  const permissionPresentation = (id) => {
2493
2563
  const spec = permissionService().resolve(id);
2494
2564
  const stock = MODE_LABELS[id];
@@ -2574,7 +2644,7 @@ async function apply3(ctx, config = {}) {
2574
2644
  });
2575
2645
  }
2576
2646
  if (commandRuntime.list(record.agent).some((command) => command.name === "plan")) {
2577
- const active = [...record.agent.session.events].reverse().map((event) => event).find((event) => event.type === "plan/mode")?.data?.["active"] === true;
2647
+ const active = [...readSessionEvents(record.agent.session)].reverse().map((event) => event).find((event) => event.type === "plan/mode")?.data?.["active"] === true;
2578
2648
  result.push({
2579
2649
  type: "select",
2580
2650
  id: "collaboration_mode",
@@ -2742,10 +2812,8 @@ async function apply3(ctx, config = {}) {
2742
2812
  });
2743
2813
  };
2744
2814
  const syncPermissionState = (record, sessionId) => {
2745
- const events = record.agent.session.events;
2746
- if (events === void 0) return;
2747
- const permission = permissionService();
2748
- const storedMode = permission.current(events);
2815
+ const events = readSessionEvents(record.agent.session);
2816
+ const storedMode = currentPermission(record);
2749
2817
  for (let index = events.length - 1; index >= 0; index -= 1) {
2750
2818
  const event = events[index];
2751
2819
  if (event?.type === "approval/policy") {
@@ -2876,6 +2944,11 @@ async function apply3(ctx, config = {}) {
2876
2944
  watchSubagentParent(agent);
2877
2945
  return record;
2878
2946
  };
2947
+ ctx.on("tools/result", (exec, result) => {
2948
+ if (exec.parent !== void 0 || exec.agent === void 0 || result.isError) return;
2949
+ const projection = ownedRecord(exec.agent)?.projection ?? subagentByChild.get(String(exec.agent.session.id))?.projection;
2950
+ projection?.recordToolResult(String(exec.callId), result.value);
2951
+ });
2879
2952
  ctx.on("session/event", (session, event) => {
2880
2953
  const sessionId = String(session.header.id);
2881
2954
  const record = sessions.get(sessionId);
@@ -2963,7 +3036,7 @@ async function apply3(ctx, config = {}) {
2963
3036
  // mcp-client's second transport. Legacy SSE does not.
2964
3037
  mcpCapabilities: { http: true, sse: false },
2965
3038
  auth: { logout: {} },
2966
- sessionCapabilities: { list: {} }
3039
+ sessionCapabilities: { list: {}, resume: {} }
2967
3040
  },
2968
3041
  authMethods: advertisedAuthMethods(
2969
3042
  providers,
@@ -3078,6 +3151,20 @@ async function apply3(ctx, config = {}) {
3078
3151
  ...options.length > 0 ? { configOptions: options } : {}
3079
3152
  };
3080
3153
  },
3154
+ async resumeSession(params) {
3155
+ assertOpen();
3156
+ await requireCredential(config.provider);
3157
+ validateCwd(params.cwd);
3158
+ validateAdditionalDirectories(params.additionalDirectories);
3159
+ syncMcpServers(params.mcpServers, params.cwd);
3160
+ requirePersistence();
3161
+ const record = await restoreSession(params.sessionId, { replay: false, cwd: params.cwd });
3162
+ const options = await configOptions(record);
3163
+ return {
3164
+ modes: modeState(record),
3165
+ ...options.length > 0 ? { configOptions: options } : {}
3166
+ };
3167
+ },
3081
3168
  async listSessions(params) {
3082
3169
  assertOpen();
3083
3170
  const persistence = requirePersistence();
@@ -3487,39 +3574,59 @@ function resolvedHostModule(ctx, specifier) {
3487
3574
  for (const anchor of anchors) {
3488
3575
  try {
3489
3576
  return pathToFileURL(createRequire2(anchor).resolve(specifier)).href;
3490
- } catch {
3577
+ } catch (error) {
3578
+ if (error.code === "ERR_PACKAGE_PATH_NOT_EXPORTED") return specifier;
3491
3579
  }
3492
3580
  }
3493
3581
  return specifier;
3494
3582
  }
3495
- function shippedPresetRoot(ctx) {
3583
+ function shippedPresetRoots(ctx) {
3496
3584
  const anchors = [ctx.baseUrl, import.meta.url].filter((value) => typeof value === "string");
3497
3585
  for (const anchor of anchors) {
3498
3586
  try {
3499
3587
  const manifest = createRequire2(anchor).resolve("@deepseek-ai/dsh/package.json");
3500
3588
  const root = join(dirname(manifest), "config", "agent-presets");
3501
- if (existsSync(root)) return root;
3589
+ if (existsSync(root)) return [{ path: root, trust: "system" }];
3590
+ } catch {
3591
+ }
3592
+ }
3593
+ for (const anchor of anchors) {
3594
+ try {
3595
+ const manifest = createRequire2(anchor).resolve("@deepseek-ai/dsh-agent-presets/package.json");
3596
+ if (existsSync(join(dirname(manifest), "presets"))) return [];
3502
3597
  } catch {
3503
3598
  }
3504
3599
  }
3505
- throw new Error("dsh-acp-plugin: cannot resolve the dsh shipped agent presets");
3600
+ throw new Error(
3601
+ "dsh-acp-plugin: cannot resolve the dsh shipped agent presets (looked for @deepseek-ai/dsh/config/agent-presets and a self-shipping @deepseek-ai/dsh-agent-presets)"
3602
+ );
3506
3603
  }
3507
3604
  async function mountService(ctx, service, specifier, config) {
3508
3605
  if (ctx.get(service) !== void 0) return;
3606
+ const resolved = typeof config === "function" ? config() : config;
3509
3607
  const exports = await ctx.loader.import(resolvedHostModule(ctx, specifier));
3510
3608
  const plugin = ctx.loader.unwrapExports(exports);
3511
- await ctx.plugin(plugin, config);
3609
+ await ctx.plugin(plugin, resolved);
3512
3610
  if (ctx.get(service) === void 0) {
3513
3611
  throw new Error(`dsh-acp-plugin: ${specifier} did not provide ${service}`);
3514
3612
  }
3515
3613
  }
3516
3614
  async function apply5(ctx, config = {}) {
3517
3615
  if (ctx.get("acpServer") !== void 0) return;
3518
- await mountService(ctx, "agentPresets", "@deepseek-ai/dsh-agent-presets", {
3519
- default: "standard",
3520
- roots: [{ path: shippedPresetRoot(ctx), trust: "system" }]
3616
+ await mountService(ctx, "agentPresets", "@deepseek-ai/dsh-agent-presets", () => {
3617
+ const roots = shippedPresetRoots(ctx);
3618
+ return {
3619
+ default: "standard",
3620
+ // dsh 0.1.2's roster self-ships its presets (includeShippedRoot);
3621
+ // passing the legacy root there would duplicate the roster.
3622
+ ...roots.length > 0 ? { roots } : {}
3623
+ };
3521
3624
  });
3522
3625
  await mountService(ctx, "dynamicCordisRunner", "@deepseek-ai/dsh-cordis-host-runner");
3626
+ const modelSettings = "@deepseek-ai/dsh-tool-subagent/model-selection-settings";
3627
+ if (ctx.get("subagentModelSelection") === void 0 && resolvedHostModule(ctx, modelSettings) !== modelSettings) {
3628
+ await mountService(ctx, "subagentModelSelection", modelSettings);
3629
+ }
3523
3630
  await ctx.plugin(server_exports, config);
3524
3631
  if (ctx.get("acpServer") === void 0) {
3525
3632
  throw new Error("dsh-acp-plugin: ACP server did not activate");
package/dist/server.js CHANGED
@@ -415,6 +415,28 @@ function diffsFromToolMeta(meta) {
415
415
  }
416
416
  return result;
417
417
  }
418
+ function acknowledgementId(text, prefix) {
419
+ if (!text.startsWith(prefix)) return void 0;
420
+ const id = text.slice(prefix.length);
421
+ if (id.length === 0 || id.trim() !== id || id.includes(" ") || id.includes(" ") || id.includes("\n")) {
422
+ return void 0;
423
+ }
424
+ return id;
425
+ }
426
+ function typedToolAcknowledgement(state, text, failed) {
427
+ if (state === void 0 || failed) return void 0;
428
+ if (state.name === "bash" || state.name === "pwsh") {
429
+ const jobId = acknowledgementId(text, "started background job ");
430
+ return jobId === void 0 ? void 0 : { kind: "background", jobId };
431
+ }
432
+ if (state.name === "subagent") {
433
+ const jobId = acknowledgementId(text, "started background subagent job ");
434
+ if (jobId !== void 0) return { kind: "background", jobId };
435
+ const subagentId = acknowledgementId(text, "started subagent ");
436
+ if (subagentId !== void 0) return { kind: "continuable", subagentId };
437
+ }
438
+ return void 0;
439
+ }
418
440
  function extractToolResult(data) {
419
441
  let failed = data["error"] !== void 0 && data["error"] !== null;
420
442
  const parts = [];
@@ -528,6 +550,8 @@ var SessionProjection = class {
528
550
  title;
529
551
  streamedText = /* @__PURE__ */ new Set();
530
552
  streamedReasoning = /* @__PURE__ */ new Set();
553
+ /** Structured tool values observed live before DSH omits them from its durable event. */
554
+ toolResultValues = /* @__PURE__ */ new Map();
531
555
  /** One authoritative state per open ACP tool call. Block indexes only
532
556
  * route streaming fragments to that state; they never own lifecycle. */
533
557
  toolCalls = /* @__PURE__ */ new Map();
@@ -547,6 +571,10 @@ var SessionProjection = class {
547
571
  this.presentCall = options?.presentCall;
548
572
  this.subagent = options?.subagent;
549
573
  }
574
+ /** Stage one live `tools/result` value for the matching durable `tool/result` event. */
575
+ recordToolResult(callId, value) {
576
+ this.toolResultValues.set(callId, value);
577
+ }
550
578
  /** Reset per-prompt accumulators; call when a new `session/prompt` starts. */
551
579
  beginPrompt() {
552
580
  this.window = emptyWindow();
@@ -937,6 +965,18 @@ var SessionProjection = class {
937
965
  const state = this.toolCalls.get(callId);
938
966
  const facts = state?.facts;
939
967
  const { failed, text, diffs } = extractToolResult(data);
968
+ const hasLiveValue = this.toolResultValues.has(callId);
969
+ const liveValue = this.toolResultValues.get(callId);
970
+ this.toolResultValues.delete(callId);
971
+ const toolResultValue = hasLiveValue ? liveValue : typedToolAcknowledgement(state, text, failed);
972
+ const toolResultMeta = toolResultValue === void 0 ? void 0 : {
973
+ dsh: {
974
+ toolResult: {
975
+ value: toolResultValue
976
+ }
977
+ }
978
+ };
979
+ const rawOutput = text.length > 0 ? { output: text, isError: failed } : void 0;
940
980
  const content = diffs.map((diff) => ({
941
981
  type: "diff",
942
982
  path: diff.path,
@@ -961,8 +1001,9 @@ var SessionProjection = class {
961
1001
  sessionUpdate: "tool_call_update",
962
1002
  toolCallId: callId,
963
1003
  status: failed ? "failed" : "completed",
964
- ...text.length > 0 ? { rawOutput: { output: text, isError: failed } } : {},
1004
+ ...rawOutput !== void 0 ? { rawOutput } : {},
965
1005
  _meta: {
1006
+ ...toolResultMeta ?? {},
966
1007
  terminal_exit: { terminal_id: callId, exit_code: failed ? 1 : 0, signal: null }
967
1008
  }
968
1009
  }
@@ -988,7 +1029,8 @@ ${text.replace(/\n+$/, "")}
988
1029
  toolCallId: callId,
989
1030
  status: failed ? "failed" : "completed",
990
1031
  ...content.length > 0 ? { content } : {},
991
- ...text.length > 0 ? { rawOutput: { output: text, isError: failed } } : {}
1032
+ ...rawOutput !== void 0 ? { rawOutput } : {},
1033
+ ...toolResultMeta !== void 0 ? { _meta: toolResultMeta } : {}
992
1034
  }
993
1035
  ];
994
1036
  }
@@ -1005,11 +1047,28 @@ ${text.replace(/\n+$/, "")}
1005
1047
  }
1006
1048
  this.toolCallBlocks.clear();
1007
1049
  this.toolCalls.clear();
1050
+ this.toolResultValues.clear();
1008
1051
  return [];
1009
1052
  }
1010
1053
  };
1011
1054
 
1012
1055
  // src/bridge/history.ts
1056
+ function buildResumeMetadata(events) {
1057
+ let title;
1058
+ let contextWindow;
1059
+ for (const event of events) {
1060
+ if (event.type === "session/title") {
1061
+ const value = event.data?.["title"];
1062
+ if (typeof value === "string" && value.length > 0) title = value;
1063
+ continue;
1064
+ }
1065
+ if (event.type === "request/context") {
1066
+ const value = event.data?.["contextWindow"];
1067
+ if (typeof value === "number" && value > 0) contextWindow = value;
1068
+ }
1069
+ }
1070
+ return { title, contextWindow };
1071
+ }
1013
1072
  function userMessage(data) {
1014
1073
  if (data === void 0) return void 0;
1015
1074
  const source = data["source"];
@@ -1848,26 +1907,27 @@ function installAcpUserQuestionProvider(service, options) {
1848
1907
  let router = routers.get(owner);
1849
1908
  if (router === void 0) {
1850
1909
  const routes = [];
1851
- const disposeProvider = service.registerProvider({
1852
- async ask(request) {
1853
- for (let index = routes.length - 1; index >= 0; index -= 1) {
1854
- const route = routes[index];
1855
- const sessionId = route?.sessionIdForRequest(request);
1856
- if (route === void 0 || sessionId === void 0) continue;
1857
- if (!route.formSupported()) {
1858
- throw new UserQuestionError(
1859
- "the ACP client does not support form elicitation",
1860
- "CLIENT_UNSUPPORTED"
1861
- );
1862
- }
1863
- return askUserQuestionsOverAcp(request, sessionId, route.create);
1910
+ const ask = async (request, next) => {
1911
+ for (let index = routes.length - 1; index >= 0; index -= 1) {
1912
+ const route = routes[index];
1913
+ const sessionId = route?.sessionIdForRequest(request);
1914
+ if (route === void 0 || sessionId === void 0) continue;
1915
+ if (!route.formSupported()) {
1916
+ throw new UserQuestionError(
1917
+ "the ACP client does not support form elicitation",
1918
+ "CLIENT_UNSUPPORTED"
1919
+ );
1864
1920
  }
1865
- throw new UserQuestionError(
1866
- "ACP user interaction requires a live root session",
1867
- "ASK_MISSING_AGENT"
1868
- );
1921
+ return askUserQuestionsOverAcp(request, sessionId, route.create);
1869
1922
  }
1870
- });
1923
+ if (next !== void 0) return next();
1924
+ throw new UserQuestionError(
1925
+ "ACP user interaction requires a live root session",
1926
+ "ASK_MISSING_AGENT"
1927
+ );
1928
+ };
1929
+ const legacy = service;
1930
+ const disposeProvider = typeof legacy.registerProvider === "function" ? legacy.registerProvider({ ask }) : service.ctx.root.on("user-questions/request", ask);
1871
1931
  router = { routes, disposeProvider };
1872
1932
  routers.set(owner, router);
1873
1933
  }
@@ -1910,6 +1970,9 @@ function presetDisplayName(preset) {
1910
1970
  // src/bridge/index.ts
1911
1971
  var name3 = "acp-bridge";
1912
1972
  var inject3 = ["agents", "credentials", "llm", "agentDefaultModel", "sessionPersistence", "approval", "permissionPresets", "commands", "agentPresets", "skills", "subagents", "userQuestions"];
1973
+ function readSessionEvents(session) {
1974
+ return session.snapshotEvents?.() ?? session.events ?? [];
1975
+ }
1913
1976
  function invalidParams(detail) {
1914
1977
  return RequestError.invalidParams(void 0, detail);
1915
1978
  }
@@ -2253,8 +2316,10 @@ async function apply3(ctx, config = {}) {
2253
2316
  } catch (error) {
2254
2317
  throw invalidParams(`session not found: ${sessionId} (${errorChain2(error)})`);
2255
2318
  }
2256
- const replay = buildReplay(events);
2319
+ let restored;
2257
2320
  if (options.replay) {
2321
+ const replay = buildReplay(events);
2322
+ restored = replay;
2258
2323
  for (const update of replay.updates) notify(sessionId, update);
2259
2324
  if (replay.title !== void 0) {
2260
2325
  notify(sessionId, {
@@ -2262,6 +2327,8 @@ async function apply3(ctx, config = {}) {
2262
2327
  title: replay.title
2263
2328
  });
2264
2329
  }
2330
+ } else {
2331
+ restored = buildResumeMetadata(events);
2265
2332
  }
2266
2333
  const presets = presetsService();
2267
2334
  const storedPreset = presetFromLog(storedHeader, events);
@@ -2272,11 +2339,11 @@ async function apply3(ctx, config = {}) {
2272
2339
  ...presetSetup(presets, presetId) !== void 0 ? { setup: presetSetup(presets, presetId) } : {}
2273
2340
  });
2274
2341
  const cwd = options.cwd ?? storedHeader?.cwd;
2275
- const projection = new SessionProjection(replay.contextWindow, {
2342
+ const projection = new SessionProjection(restored.contextWindow, {
2276
2343
  terminalOutput: clientTerminalOutput,
2277
2344
  ...cwd !== void 0 ? { cwd } : {}
2278
2345
  });
2279
- projection.title = replay.title;
2346
+ projection.title = restored.title;
2280
2347
  const record = registerRecord(
2281
2348
  sessionId,
2282
2349
  handle.agent,
@@ -2287,8 +2354,7 @@ async function apply3(ctx, config = {}) {
2287
2354
  );
2288
2355
  if (presetId !== void 0) record.preset = presetId;
2289
2356
  {
2290
- const permission = permissionService();
2291
- const storedMode = permission.current(events);
2357
+ const storedMode = currentPermission(record);
2292
2358
  if (storedMode !== void 0) record.modeId = storedMode;
2293
2359
  for (let index = events.length - 1; index >= 0; index -= 1) {
2294
2360
  const event = events[index];
@@ -2483,6 +2549,10 @@ async function apply3(ctx, config = {}) {
2483
2549
  record.approvals = policy;
2484
2550
  };
2485
2551
  const permissionService = () => permissionPresets;
2552
+ const currentPermission = (record) => {
2553
+ const session = record.agent.session;
2554
+ return permissionService().current(typeof session.snapshotEvents === "function" ? session : readSessionEvents(session));
2555
+ };
2486
2556
  const permissionPresentation = (id) => {
2487
2557
  const spec = permissionService().resolve(id);
2488
2558
  const stock = MODE_LABELS[id];
@@ -2568,7 +2638,7 @@ async function apply3(ctx, config = {}) {
2568
2638
  });
2569
2639
  }
2570
2640
  if (commandRuntime.list(record.agent).some((command) => command.name === "plan")) {
2571
- const active = [...record.agent.session.events].reverse().map((event) => event).find((event) => event.type === "plan/mode")?.data?.["active"] === true;
2641
+ const active = [...readSessionEvents(record.agent.session)].reverse().map((event) => event).find((event) => event.type === "plan/mode")?.data?.["active"] === true;
2572
2642
  result.push({
2573
2643
  type: "select",
2574
2644
  id: "collaboration_mode",
@@ -2736,10 +2806,8 @@ async function apply3(ctx, config = {}) {
2736
2806
  });
2737
2807
  };
2738
2808
  const syncPermissionState = (record, sessionId) => {
2739
- const events = record.agent.session.events;
2740
- if (events === void 0) return;
2741
- const permission = permissionService();
2742
- const storedMode = permission.current(events);
2809
+ const events = readSessionEvents(record.agent.session);
2810
+ const storedMode = currentPermission(record);
2743
2811
  for (let index = events.length - 1; index >= 0; index -= 1) {
2744
2812
  const event = events[index];
2745
2813
  if (event?.type === "approval/policy") {
@@ -2870,6 +2938,11 @@ async function apply3(ctx, config = {}) {
2870
2938
  watchSubagentParent(agent);
2871
2939
  return record;
2872
2940
  };
2941
+ ctx.on("tools/result", (exec, result) => {
2942
+ if (exec.parent !== void 0 || exec.agent === void 0 || result.isError) return;
2943
+ const projection = ownedRecord(exec.agent)?.projection ?? subagentByChild.get(String(exec.agent.session.id))?.projection;
2944
+ projection?.recordToolResult(String(exec.callId), result.value);
2945
+ });
2873
2946
  ctx.on("session/event", (session, event) => {
2874
2947
  const sessionId = String(session.header.id);
2875
2948
  const record = sessions.get(sessionId);
@@ -2957,7 +3030,7 @@ async function apply3(ctx, config = {}) {
2957
3030
  // mcp-client's second transport. Legacy SSE does not.
2958
3031
  mcpCapabilities: { http: true, sse: false },
2959
3032
  auth: { logout: {} },
2960
- sessionCapabilities: { list: {} }
3033
+ sessionCapabilities: { list: {}, resume: {} }
2961
3034
  },
2962
3035
  authMethods: advertisedAuthMethods(
2963
3036
  providers,
@@ -3072,6 +3145,20 @@ async function apply3(ctx, config = {}) {
3072
3145
  ...options.length > 0 ? { configOptions: options } : {}
3073
3146
  };
3074
3147
  },
3148
+ async resumeSession(params) {
3149
+ assertOpen();
3150
+ await requireCredential(config.provider);
3151
+ validateCwd(params.cwd);
3152
+ validateAdditionalDirectories(params.additionalDirectories);
3153
+ syncMcpServers(params.mcpServers, params.cwd);
3154
+ requirePersistence();
3155
+ const record = await restoreSession(params.sessionId, { replay: false, cwd: params.cwd });
3156
+ const options = await configOptions(record);
3157
+ return {
3158
+ modes: modeState(record),
3159
+ ...options.length > 0 ? { configOptions: options } : {}
3160
+ };
3161
+ },
3075
3162
  async listSessions(params) {
3076
3163
  assertOpen();
3077
3164
  const persistence = requirePersistence();
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.4.26",
6
+ "version": "0.4.28",
7
7
  "repository": {
8
8
  "type": "git",
9
9
  "url": "git+https://github.com/openma-ai/deepseek-harness-acp.git"
@@ -53,62 +53,61 @@
53
53
  "devDependencies": {
54
54
  "@deepseek-ai/cordis": "^4.0.1",
55
55
  "@deepseek-ai/cordis-plugin-timer": "^1.1.3",
56
- "@deepseek-ai/dsh": "0.1.1-rc.2",
57
- "@deepseek-ai/dsh-agent": "0.1.1-rc.2",
58
- "@deepseek-ai/dsh-agent-instructions": "0.1.1-rc.2",
59
- "@deepseek-ai/dsh-agent-loop": "0.1.1-rc.2",
60
- "@deepseek-ai/dsh-agent-spine-demo": "0.1.1-rc.2",
61
- "@deepseek-ai/dsh-anonymous-user-id": "0.1.1-rc.2",
62
- "@deepseek-ai/dsh-attachment": "0.1.1-rc.2",
63
- "@deepseek-ai/dsh-bash-sandbox": "0.1.1-rc.2",
64
- "@deepseek-ai/dsh-brand": "0.1.1-rc.2",
65
- "@deepseek-ai/dsh-commands": "0.1.1-rc.2",
66
- "@deepseek-ai/dsh-compaction": "0.1.1-rc.2",
67
- "@deepseek-ai/dsh-compaction-basic": "0.1.1-rc.2",
68
- "@deepseek-ai/dsh-compaction-tool-result-pruner": "0.1.1-rc.2",
69
- "@deepseek-ai/dsh-credentials": "0.1.1-rc.2",
70
- "@deepseek-ai/dsh-credentials-local": "0.1.1-rc.2",
71
- "@deepseek-ai/dsh-fs": "0.1.1-rc.2",
72
- "@deepseek-ai/dsh-fs-local": "0.1.1-rc.2",
73
- "@deepseek-ai/dsh-fs-observation-policy": "0.1.1-rc.2",
74
- "@deepseek-ai/dsh-fs-sandbox": "0.1.1-rc.2",
75
- "@deepseek-ai/dsh-goal": "0.1.1-rc.2",
76
- "@deepseek-ai/dsh-goal-round-driver": "0.1.1-rc.2",
77
- "@deepseek-ai/dsh-home-paths": "0.1.1-rc.2",
78
- "@deepseek-ai/dsh-invariants": "0.1.1-rc.2",
79
- "@deepseek-ai/dsh-jobs-local": "0.1.1-rc.2",
80
- "@deepseek-ai/dsh-launch-environment": "0.1.1-rc.2",
81
- "@deepseek-ai/dsh-llm": "0.1.1-rc.2",
82
- "@deepseek-ai/dsh-llm-deepseek": "0.1.1-rc.2",
83
- "@deepseek-ai/dsh-llm-retry": "0.1.1-rc.2",
84
- "@deepseek-ai/dsh-sandbox": "0.1.1-rc.2",
85
- "@deepseek-ai/dsh-sandbox-local": "0.1.1-rc.2",
86
- "@deepseek-ai/dsh-sandbox-policy": "0.1.1-rc.2",
87
- "@deepseek-ai/dsh-scope": "0.1.1-rc.2",
88
- "@deepseek-ai/dsh-session": "0.1.1-rc.2",
89
- "@deepseek-ai/dsh-session-checkpoint-policy": "0.1.1-rc.2",
90
- "@deepseek-ai/dsh-session-persistence": "0.1.1-rc.2",
91
- "@deepseek-ai/dsh-session-persistence-jsonl": "0.1.1-rc.2",
92
- "@deepseek-ai/dsh-session-projection": "0.1.1-rc.2",
93
- "@deepseek-ai/dsh-session-title": "0.1.1-rc.2",
94
- "@deepseek-ai/dsh-settings": "0.1.1-rc.2",
95
- "@deepseek-ai/dsh-shell": "0.1.1-rc.2",
96
- "@deepseek-ai/dsh-shell-env": "0.1.1-rc.2",
97
- "@deepseek-ai/dsh-skill": "0.1.1-rc.2",
98
- "@deepseek-ai/dsh-skill-filesystem": "0.1.1-rc.2",
99
- "@deepseek-ai/dsh-subprocess": "0.1.1-rc.2",
100
- "@deepseek-ai/dsh-subprocess-local": "0.1.1-rc.2",
101
- "@deepseek-ai/dsh-system-prompt": "0.1.1-rc.2",
102
- "@deepseek-ai/dsh-timeout": "0.1.1-rc.2",
103
- "@deepseek-ai/dsh-token-meter": "0.1.1-rc.2",
104
- "@deepseek-ai/dsh-tool-bash": "0.1.1-rc.2",
105
- "@deepseek-ai/dsh-tool-fs": "0.1.1-rc.2",
106
- "@deepseek-ai/dsh-tool-goal": "0.1.1-rc.2",
107
- "@deepseek-ai/dsh-tool-jobs": "0.1.1-rc.2",
108
- "@deepseek-ai/dsh-tool-skill": "0.1.1-rc.2",
109
- "@deepseek-ai/dsh-tool-todo": "0.1.1-rc.2",
110
- "@deepseek-ai/dsh-tools": "0.1.1-rc.2",
111
- "@deepseek-ai/dsh-user-approval": "0.1.1-rc.2",
56
+ "@deepseek-ai/dsh": "0.1.2-rc.1",
57
+ "@deepseek-ai/dsh-agent": "0.1.2-rc.1",
58
+ "@deepseek-ai/dsh-agent-instructions": "0.1.2-rc.1",
59
+ "@deepseek-ai/dsh-agent-loop": "0.1.2-rc.1",
60
+ "@deepseek-ai/dsh-anonymous-user-id": "0.1.2-rc.1",
61
+ "@deepseek-ai/dsh-attachment": "0.1.2-rc.1",
62
+ "@deepseek-ai/dsh-bash-sandbox": "0.1.2-rc.1",
63
+ "@deepseek-ai/dsh-brand": "0.1.2-rc.1",
64
+ "@deepseek-ai/dsh-commands": "0.1.2-rc.1",
65
+ "@deepseek-ai/dsh-compaction": "0.1.2-rc.1",
66
+ "@deepseek-ai/dsh-compaction-basic": "0.1.2-rc.1",
67
+ "@deepseek-ai/dsh-compaction-tool-result-pruner": "0.1.2-rc.1",
68
+ "@deepseek-ai/dsh-credentials": "0.1.2-rc.1",
69
+ "@deepseek-ai/dsh-credentials-local": "0.1.2-rc.1",
70
+ "@deepseek-ai/dsh-fs": "0.1.2-rc.1",
71
+ "@deepseek-ai/dsh-fs-local": "0.1.2-rc.1",
72
+ "@deepseek-ai/dsh-fs-observation-policy": "0.1.2-rc.1",
73
+ "@deepseek-ai/dsh-fs-sandbox": "0.1.2-rc.1",
74
+ "@deepseek-ai/dsh-goal": "0.1.2-rc.1",
75
+ "@deepseek-ai/dsh-goal-round-driver": "0.1.2-rc.1",
76
+ "@deepseek-ai/dsh-home-paths": "0.1.2-rc.1",
77
+ "@deepseek-ai/dsh-invariants": "0.1.2-rc.1",
78
+ "@deepseek-ai/dsh-jobs-local": "0.1.2-rc.1",
79
+ "@deepseek-ai/dsh-launch-environment": "0.1.2-rc.1",
80
+ "@deepseek-ai/dsh-llm": "0.1.2-rc.1",
81
+ "@deepseek-ai/dsh-llm-deepseek": "0.1.2-rc.1",
82
+ "@deepseek-ai/dsh-llm-retry": "0.1.2-rc.1",
83
+ "@deepseek-ai/dsh-sandbox": "0.1.2-rc.1",
84
+ "@deepseek-ai/dsh-sandbox-local": "0.1.2-rc.1",
85
+ "@deepseek-ai/dsh-sandbox-policy": "0.1.2-rc.1",
86
+ "@deepseek-ai/dsh-scope": "0.1.2-rc.1",
87
+ "@deepseek-ai/dsh-session": "0.1.2-rc.1",
88
+ "@deepseek-ai/dsh-session-checkpoint-policy": "0.1.2-rc.1",
89
+ "@deepseek-ai/dsh-session-persistence": "0.1.2-rc.1",
90
+ "@deepseek-ai/dsh-session-persistence-jsonl": "0.1.2-rc.1",
91
+ "@deepseek-ai/dsh-session-projection": "0.1.2-rc.1",
92
+ "@deepseek-ai/dsh-session-title": "0.1.2-rc.1",
93
+ "@deepseek-ai/dsh-settings": "0.1.2-rc.1",
94
+ "@deepseek-ai/dsh-shell": "0.1.2-rc.1",
95
+ "@deepseek-ai/dsh-shell-env": "0.1.2-rc.1",
96
+ "@deepseek-ai/dsh-skill": "0.1.2-rc.1",
97
+ "@deepseek-ai/dsh-skill-filesystem": "0.1.2-rc.1",
98
+ "@deepseek-ai/dsh-subprocess": "0.1.2-rc.1",
99
+ "@deepseek-ai/dsh-subprocess-local": "0.1.2-rc.1",
100
+ "@deepseek-ai/dsh-system-prompt": "0.1.2-rc.1",
101
+ "@deepseek-ai/dsh-timeout": "0.1.2-rc.1",
102
+ "@deepseek-ai/dsh-token-meter": "0.1.2-rc.1",
103
+ "@deepseek-ai/dsh-tool-bash": "0.1.2-rc.1",
104
+ "@deepseek-ai/dsh-tool-fs": "0.1.2-rc.1",
105
+ "@deepseek-ai/dsh-tool-goal": "0.1.2-rc.1",
106
+ "@deepseek-ai/dsh-tool-jobs": "0.1.2-rc.1",
107
+ "@deepseek-ai/dsh-tool-skill": "0.1.2-rc.1",
108
+ "@deepseek-ai/dsh-tool-todo": "0.1.2-rc.1",
109
+ "@deepseek-ai/dsh-tools": "0.1.2-rc.1",
110
+ "@deepseek-ai/dsh-user-approval": "0.1.2-rc.1",
112
111
  "@types/node": "^26.1.0",
113
112
  "esbuild": "^0.28.1",
114
113
  "pnpm": "11.22.0",
@@ -145,7 +144,7 @@
145
144
  }
146
145
  },
147
146
  "dshAcp": {
148
- "standaloneDsh": "0.1.1-rc.2",
147
+ "standaloneDsh": "0.1.2-rc.1",
149
148
  "hostCompatibility": {
150
149
  "cordisProtocol": 0,
151
150
  "policy": "capability-negotiated"
Binary file
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "archive": "dsh-runtime.tgz",
3
- "dsh": "0.1.1-rc.2",
4
- "sha256": "f9f9276ec14fdd907146175d5c3b98d1e680eb27b9caaa37fd609908f197e70e"
3
+ "dsh": "0.1.2-rc.1",
4
+ "sha256": "3c6bc3b858fddfe1b8f5d547721977699ce69e68feca19620a1df9dcde642a1b"
5
5
  }