@openma/deepseek-harness-acp 0.4.24 → 0.4.26

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/dist/bridge.js CHANGED
@@ -71,6 +71,7 @@ import {
71
71
  } from "@agentclientprotocol/sdk";
72
72
  import { credentialRef } from "@deepseek-ai/dsh-credentials";
73
73
  import { ReasoningEffortId } from "@deepseek-ai/dsh-llm";
74
+ import { PERMISSION_SETTINGS_NAMESPACE } from "@deepseek-ai/dsh-permission-presets";
74
75
 
75
76
  // src/version.ts
76
77
  import { createRequire } from "node:module";
@@ -531,7 +532,10 @@ var SessionProjection = class {
531
532
  title;
532
533
  streamedText = /* @__PURE__ */ new Set();
533
534
  streamedReasoning = /* @__PURE__ */ new Set();
535
+ /** One authoritative state per open ACP tool call. Block indexes only
536
+ * route streaming fragments to that state; they never own lifecycle. */
534
537
  toolCalls = /* @__PURE__ */ new Map();
538
+ toolCallBlocks = /* @__PURE__ */ new Map();
535
539
  window = emptyWindow();
536
540
  lastContextUse;
537
541
  /** Client renders `_meta.terminal_output` display terminals (Zed extension). */
@@ -550,6 +554,7 @@ var SessionProjection = class {
550
554
  /** Reset per-prompt accumulators; call when a new `session/prompt` starts. */
551
555
  beginPrompt() {
552
556
  this.window = emptyWindow();
557
+ this.toolCallBlocks.clear();
553
558
  }
554
559
  /** The captured ending for a specific turn, when it already ended. */
555
560
  turnEndFor(turn) {
@@ -589,6 +594,8 @@ var SessionProjection = class {
589
594
  switch (event.type) {
590
595
  case "assistant/chunk":
591
596
  return this.onChunk(data);
597
+ case "tool-call-chunks":
598
+ return this.onToolCallChunks(data);
592
599
  case "assistant/message":
593
600
  return this.onAssistantMessage(data);
594
601
  case "tool/call":
@@ -671,42 +678,31 @@ var SessionProjection = class {
671
678
  onSubagentStart(data) {
672
679
  const subagent = this.subagentMeta(data, "started");
673
680
  if (subagent === void 0) return [];
674
- const runId = subagent["runId"];
675
- const childSessionId = subagent["childSessionId"];
676
681
  return [{
677
- sessionUpdate: "tool_call",
678
- toolCallId: `subagent:${runId}`,
679
- title: `Start subagent ${childSessionId}`,
680
- kind: "other",
681
- status: "in_progress",
682
- rawInput: {
683
- childSessionId,
684
- provider: subagent["provider"],
685
- local: subagent["local"]
686
- },
687
- _meta: { dsh: { subagent } }
682
+ sessionUpdate: "session_info_update",
683
+ _meta: { dsh: { event: "subagent/lifecycle", subagent } }
688
684
  }];
689
685
  }
690
686
  onSubagentEnd(data) {
691
687
  const subagent = this.subagentMeta(data, "finished");
692
688
  if (subagent === void 0) return [];
693
689
  const stopReason = asString(data["stopReason"]) ?? "error";
694
- const lastAssistantMessage = data["lastAssistantMessage"];
695
690
  return [{
696
- sessionUpdate: "tool_call_update",
697
- toolCallId: `subagent:${String(subagent["runId"])}`,
698
- status: stopReason === "completed" ? "completed" : "failed",
699
- rawOutput: {
700
- stopReason,
701
- ...Array.isArray(lastAssistantMessage) ? { lastAssistantMessage } : {}
702
- },
703
- _meta: { dsh: { subagent } }
691
+ sessionUpdate: "session_info_update",
692
+ _meta: {
693
+ dsh: {
694
+ event: "subagent/lifecycle",
695
+ subagent: { ...subagent, stopReason }
696
+ }
697
+ }
704
698
  }];
705
699
  }
706
700
  onChunk(data) {
707
701
  const chunk = data["chunk"];
708
702
  if (chunk === null || typeof chunk === "undefined" || typeof chunk !== "object") return [];
709
703
  const c = chunk;
704
+ if (c["type"] === "block-end") return this.onToolCallBlockEnd(data, c);
705
+ if (c["type"] === "tool-call-delta") return this.onToolCallDelta(data, c);
710
706
  const text = c["text"];
711
707
  if (typeof text !== "string" || text.length === 0) return [];
712
708
  const messageId = this.stepKey(data);
@@ -720,6 +716,97 @@ var SessionProjection = class {
720
716
  }
721
717
  return [];
722
718
  }
719
+ toolBlockKey(data, index) {
720
+ return `${this.stepKey(data)}:${index}`;
721
+ }
722
+ onToolCallDelta(data, chunk) {
723
+ const callId = asString(chunk["id"]);
724
+ const index = chunk["index"];
725
+ const argumentsDelta = chunk["argumentsDelta"];
726
+ if (callId === void 0 || typeof index !== "number" || !Number.isInteger(index) || typeof argumentsDelta !== "string") {
727
+ return [];
728
+ }
729
+ const current = this.toolCalls.get(callId);
730
+ const name5 = asString(chunk["name"]) ?? current?.name;
731
+ if (name5 === void 0) return [];
732
+ const rawArguments = `${current?.arguments ?? ""}${argumentsDelta}`;
733
+ const state = {
734
+ callId,
735
+ name: name5,
736
+ arguments: rawArguments,
737
+ facts: this.toolCallFacts(name5, rawArguments),
738
+ status: current?.status ?? "pending"
739
+ };
740
+ this.toolCalls.set(callId, state);
741
+ this.toolCallBlocks.set(this.toolBlockKey(data, index), callId);
742
+ if (current === void 0) return [this.toolCallCreated(state)];
743
+ return [{
744
+ sessionUpdate: "tool_call_update",
745
+ toolCallId: callId,
746
+ title: state.facts.title,
747
+ name: name5,
748
+ kind: state.facts.kind,
749
+ status: "pending",
750
+ rawInput: state.facts.rawInput,
751
+ ...state.facts.locations.length > 0 ? { locations: state.facts.locations } : {}
752
+ }];
753
+ }
754
+ onToolCallChunks(data) {
755
+ const callId = asString(data["id"]);
756
+ const name5 = asString(data["name"]);
757
+ const index = data["index"];
758
+ const args = data["args"];
759
+ if (callId === void 0 || name5 === void 0 || typeof index !== "number" || !Number.isInteger(index) || !Array.isArray(args)) {
760
+ return [];
761
+ }
762
+ const key = this.toolBlockKey(data, index);
763
+ const current = this.toolCalls.get(callId);
764
+ const fragments = args.filter((fragment) => typeof fragment === "string").join("");
765
+ const rawArguments = `${current?.arguments ?? ""}${fragments}`;
766
+ const state = {
767
+ callId,
768
+ name: name5,
769
+ arguments: rawArguments,
770
+ facts: this.toolCallFacts(name5, rawArguments),
771
+ status: current?.status ?? "pending"
772
+ };
773
+ this.toolCalls.set(callId, state);
774
+ this.toolCallBlocks.set(key, callId);
775
+ return current === void 0 ? [this.toolCallCreated(state)] : [];
776
+ }
777
+ onToolCallBlockEnd(data, chunk) {
778
+ const index = chunk["index"];
779
+ if (typeof index !== "number" || !Number.isInteger(index)) return [];
780
+ const key = this.toolBlockKey(data, index);
781
+ const callId = this.toolCallBlocks.get(key);
782
+ this.toolCallBlocks.delete(key);
783
+ const block = chunk["block"];
784
+ const complete = block !== null && typeof block === "object" ? block : void 0;
785
+ const completeCallId = complete?.["type"] === "tool-call" ? asString(complete["id"]) : void 0;
786
+ const resolvedCallId = callId ?? completeCallId;
787
+ if (resolvedCallId === void 0) return [];
788
+ const current = this.toolCalls.get(resolvedCallId);
789
+ const name5 = (complete?.["type"] === "tool-call" ? asString(complete["name"]) : void 0) ?? current?.name;
790
+ if (name5 === void 0) return [];
791
+ const rawArguments = (complete?.["type"] === "tool-call" && typeof complete["arguments"] === "string" ? complete["arguments"] : void 0) ?? current?.arguments ?? "{}";
792
+ const state = {
793
+ callId: resolvedCallId,
794
+ name: name5,
795
+ arguments: rawArguments,
796
+ facts: this.toolCallFacts(name5, rawArguments),
797
+ status: current?.status ?? "pending"
798
+ };
799
+ this.toolCalls.set(resolvedCallId, state);
800
+ if (current === void 0) return [this.toolCallCreated(state)];
801
+ return [{
802
+ sessionUpdate: "tool_call_update",
803
+ toolCallId: resolvedCallId,
804
+ title: state.facts.title,
805
+ kind: state.facts.kind,
806
+ rawInput: state.facts.rawInput,
807
+ locations: state.facts.locations
808
+ }];
809
+ }
723
810
  onAssistantMessage(data) {
724
811
  const updates = [];
725
812
  const key = this.stepKey(data);
@@ -783,11 +870,7 @@ var SessionProjection = class {
783
870
  }
784
871
  return updates;
785
872
  }
786
- onToolCall(data) {
787
- const callId = asString(data["callId"]);
788
- const name5 = asString(data["name"]) ?? "tool";
789
- if (callId === void 0) return [];
790
- const rawArguments = typeof data["arguments"] === "string" ? data["arguments"] : "{}";
873
+ toolCallFacts(name5, rawArguments) {
791
874
  let parsedArgs = {};
792
875
  try {
793
876
  parsedArgs = JSON.parse(rawArguments);
@@ -795,30 +878,52 @@ var SessionProjection = class {
795
878
  parsedArgs = { arguments: rawArguments };
796
879
  }
797
880
  const presented = this.presentCall?.(name5, parsedArgs);
798
- const facts = presented !== void 0 ? factsFromPresentCall(name5, rawArguments, presented) : classifyToolCall(name5, rawArguments);
799
- this.toolCalls.set(callId, facts);
881
+ return presented !== void 0 ? factsFromPresentCall(name5, rawArguments, presented) : classifyToolCall(name5, rawArguments);
882
+ }
883
+ toolCallCreated(state) {
884
+ const { callId, name: name5, facts, status } = state;
800
885
  const displayTerminal = this.terminalOutput && facts.kind === "execute";
801
- return [
802
- {
803
- sessionUpdate: "tool_call",
804
- toolCallId: callId,
805
- title: facts.title,
806
- name: name5,
807
- kind: facts.kind,
808
- status: "in_progress",
809
- rawInput: facts.rawInput,
810
- ...facts.locations.length > 0 ? { locations: facts.locations } : {},
811
- ...displayTerminal ? {
812
- content: [{ type: "terminal", terminalId: callId }],
813
- _meta: {
814
- terminal_info: {
815
- terminal_id: callId,
816
- ...this.cwd !== void 0 ? { cwd: this.cwd } : {}
817
- }
886
+ return {
887
+ sessionUpdate: "tool_call",
888
+ toolCallId: callId,
889
+ title: facts.title,
890
+ name: name5,
891
+ kind: facts.kind,
892
+ status,
893
+ rawInput: facts.rawInput,
894
+ ...facts.locations.length > 0 ? { locations: facts.locations } : {},
895
+ ...displayTerminal ? {
896
+ content: [{ type: "terminal", terminalId: callId }],
897
+ _meta: {
898
+ terminal_info: {
899
+ terminal_id: callId,
900
+ ...this.cwd !== void 0 ? { cwd: this.cwd } : {}
818
901
  }
819
- } : {}
820
- }
821
- ];
902
+ }
903
+ } : {}
904
+ };
905
+ }
906
+ onToolCall(data) {
907
+ const callId = asString(data["callId"]);
908
+ const name5 = asString(data["name"]) ?? "tool";
909
+ if (callId === void 0) return [];
910
+ const rawArguments = typeof data["arguments"] === "string" ? data["arguments"] : "{}";
911
+ const facts = this.toolCallFacts(name5, rawArguments);
912
+ const current = this.toolCalls.get(callId);
913
+ const state = { callId, name: name5, arguments: rawArguments, facts, status: "in_progress" };
914
+ this.toolCalls.set(callId, state);
915
+ if (current === void 0) return [this.toolCallCreated(state)];
916
+ if (current.status === "in_progress" && current.arguments === rawArguments && current.name === name5) return [];
917
+ return [{
918
+ sessionUpdate: "tool_call_update",
919
+ toolCallId: callId,
920
+ title: facts.title,
921
+ name: name5,
922
+ kind: facts.kind,
923
+ status: "in_progress",
924
+ rawInput: facts.rawInput,
925
+ locations: facts.locations
926
+ }];
822
927
  }
823
928
  onToolResult(data) {
824
929
  const message = data["message"];
@@ -833,7 +938,8 @@ var SessionProjection = class {
833
938
  })() : void 0
834
939
  ) ?? asString(data["callId"]);
835
940
  if (callId === void 0) return [];
836
- const facts = this.toolCalls.get(callId);
941
+ const state = this.toolCalls.get(callId);
942
+ const facts = state?.facts;
837
943
  const { failed, text, diffs } = extractToolResult(data);
838
944
  const content = diffs.map((diff) => ({
839
945
  type: "diff",
@@ -844,6 +950,9 @@ var SessionProjection = class {
844
950
  const isCommand = facts?.kind === "execute";
845
951
  if (this.terminalOutput && isCommand) {
846
952
  this.toolCalls.delete(callId);
953
+ for (const [key, value] of this.toolCallBlocks) {
954
+ if (value === callId) this.toolCallBlocks.delete(key);
955
+ }
847
956
  return [
848
957
  ...text.length > 0 ? [
849
958
  {
@@ -874,6 +983,9 @@ ${text.replace(/\n+$/, "")}
874
983
  content.push({ type: "content", content: block });
875
984
  }
876
985
  this.toolCalls.delete(callId);
986
+ for (const [key, value] of this.toolCallBlocks) {
987
+ if (value === callId) this.toolCallBlocks.delete(key);
988
+ }
877
989
  return [
878
990
  {
879
991
  sessionUpdate: "tool_call_update",
@@ -895,6 +1007,8 @@ ${text.replace(/\n+$/, "")}
895
1007
  this.window.error = { message: r.error?.message ?? "model turn failed" };
896
1008
  }
897
1009
  }
1010
+ this.toolCallBlocks.clear();
1011
+ this.toolCalls.clear();
898
1012
  return [];
899
1013
  }
900
1014
  };
@@ -2094,11 +2208,16 @@ async function apply3(ctx, config = {}) {
2094
2208
  '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)'
2095
2209
  );
2096
2210
  };
2097
- const agentOptionsFor = (model, provider) => withInteractionMode({
2098
- ...provider !== void 0 ? { provider } : config.provider !== void 0 ? { provider: config.provider } : {},
2099
- ...model !== void 0 ? { model } : {},
2100
- ...config.maxTokens !== void 0 ? { maxTokens: config.maxTokens } : {}
2101
- }, clientInteractionMode);
2211
+ const agentOptionsFor = (model, provider) => {
2212
+ const defaults = defaultSelection();
2213
+ const resolvedProvider = provider ?? config.provider ?? defaults.provider;
2214
+ const resolvedModel = model ?? config.model ?? defaults.model;
2215
+ return withInteractionMode({
2216
+ ...resolvedProvider !== void 0 ? { provider: resolvedProvider } : {},
2217
+ ...resolvedModel !== void 0 ? { model: resolvedModel } : {},
2218
+ ...config.maxTokens !== void 0 ? { maxTokens: config.maxTokens } : {}
2219
+ }, clientInteractionMode);
2220
+ };
2102
2221
  const ownedRecord = (agent) => {
2103
2222
  const record = sessions.get(String(agent.session.id));
2104
2223
  return record?.agent === agent ? record : void 0;
@@ -2291,8 +2410,8 @@ async function apply3(ctx, config = {}) {
2291
2410
  return {
2292
2411
  currentModeId: record.modeId,
2293
2412
  availableModes: permission.names.map((name5) => {
2294
- const spec = permission.resolve(name5);
2295
- return { id: name5, name: spec.name, description: spec.description };
2413
+ const presentation = permissionPresentation(name5);
2414
+ return { id: name5, ...presentation };
2296
2415
  })
2297
2416
  };
2298
2417
  }
@@ -2368,6 +2487,25 @@ async function apply3(ctx, config = {}) {
2368
2487
  record.approvals = policy;
2369
2488
  };
2370
2489
  const permissionService = () => permissionPresets;
2490
+ const permissionPresentation = (id) => {
2491
+ const spec = permissionService().resolve(id);
2492
+ const stock = MODE_LABELS[id];
2493
+ const fallbackName = stock?.name ?? id.split("-").map((part) => part.length > 0 ? `${part[0].toUpperCase()}${part.slice(1)}` : part).join(" ");
2494
+ return {
2495
+ name: typeof spec.name === "string" && spec.name.trim().length > 0 ? spec.name : fallbackName,
2496
+ description: typeof spec.description === "string" ? spec.description : stock?.description ?? ""
2497
+ };
2498
+ };
2499
+ const saveDefaultPermission = async (modeId) => {
2500
+ if (!permissionService().names.includes(modeId)) return;
2501
+ try {
2502
+ await ctx.get("settings")?.update(PERMISSION_SETTINGS_NAMESPACE, {
2503
+ defaultPreset: modeId
2504
+ });
2505
+ } catch (error) {
2506
+ logWarn(`permission switch applied to the session but was not saved as the default: ${String(error)}`);
2507
+ }
2508
+ };
2371
2509
  const applyMode = (record, sessionId, modeId) => {
2372
2510
  const permission = permissionService();
2373
2511
  if (permission.names.includes(modeId)) {
@@ -2418,8 +2556,8 @@ async function apply3(ctx, config = {}) {
2418
2556
  {
2419
2557
  const permission = permissionService();
2420
2558
  const levels = permission.names.length > 0 ? permission.names.map((name5) => {
2421
- const spec = permission.resolve(name5);
2422
- return { value: name5, name: spec.name, description: spec.description };
2559
+ const presentation = permissionPresentation(name5);
2560
+ return { value: name5, ...presentation };
2423
2561
  }) : SANDBOX_MODES2.map((mode) => {
2424
2562
  const label = MODE_LABELS[mode] ?? { name: mode, description: "" };
2425
2563
  return { value: mode, name: label.name, description: label.description };
@@ -3081,6 +3219,7 @@ async function apply3(ctx, config = {}) {
3081
3219
  async setSessionMode(params) {
3082
3220
  const record = await requireOrRestoreSession(params.sessionId);
3083
3221
  applyMode(record, params.sessionId, params.modeId);
3222
+ await saveDefaultPermission(params.modeId);
3084
3223
  notify(params.sessionId, {
3085
3224
  sessionUpdate: "config_option_update",
3086
3225
  configOptions: await configOptions(record)
@@ -3094,6 +3233,7 @@ async function apply3(ctx, config = {}) {
3094
3233
  switch (params.configId) {
3095
3234
  case "mode": {
3096
3235
  applyMode(record, params.sessionId, value);
3236
+ await saveDefaultPermission(value);
3097
3237
  break;
3098
3238
  }
3099
3239
  case "agent":
package/dist/index.js CHANGED
@@ -295,11 +295,8 @@ async function bootAcpProfile(host, overrides) {
295
295
  const appBoot = await import(pathToFileURL(req.resolve("@deepseek-ai/dsh-app-boot")).href);
296
296
  const homePaths = await import(pathToFileURL(req.resolve("@deepseek-ai/dsh-home-paths")).href);
297
297
  const home = homePaths.resolveDshHome();
298
- let bundlePatches;
299
- let profilePatches = [];
300
- let profilePatchPath;
301
- let rootConfigPath = join3(ownRoot(), "profile-root.cordis.yml");
302
- let bareModuleBaseUrl = pathToFileURL(dshDir + sep2).href;
298
+ const rootConfigPath = join3(ownRoot(), "profile-root.cordis.yml");
299
+ const bareModuleBaseUrl = pathToFileURL(dshDir + sep2).href;
303
300
  const distBridge = join3(ownRoot(), "dist", "bridge.js");
304
301
  const bridgeEntry = import.meta.url.endsWith(".ts") || !existsSync3(distBridge) ? join3(ownRoot(), "src", "bundle.ts") : distBridge;
305
302
  const distServer = join3(ownRoot(), "dist", "server.js");
@@ -308,26 +305,13 @@ async function bootAcpProfile(host, overrides) {
308
305
  const pluginEntry = import.meta.url.endsWith(".ts") || !existsSync3(distPlugin) ? join3(ownRoot(), "src", "plugin.ts") : distPlugin;
309
306
  const distStdio = join3(ownRoot(), "dist", "stdio.js");
310
307
  const stdioEntry = import.meta.url.endsWith(".ts") || !existsSync3(distStdio) ? join3(ownRoot(), "src", "stdio.ts") : distStdio;
311
- const realProfileDir = join3(home, "profiles", "acp");
312
- if (existsSync3(join3(realProfileDir, "package.json"))) {
313
- appBoot.healProfilesModuleFallback(dshPkgPath);
314
- const profile = appBoot.loadProfile(BIN, "acp", dshPkgPath);
315
- bundlePatches = profile.layers.flatMap((layer) => layer.patches);
316
- profilePatches = profile.patches;
317
- profilePatchPath = join3(profile.dir, "cordis.patch.yml");
318
- const realRoot = join3(profile.dir, "cordis.yml");
319
- if (existsSync3(realRoot)) rootConfigPath = realRoot;
320
- bareModuleBaseUrl = void 0;
321
- logDebug(`profile: real (${profile.dir})`);
322
- } else {
323
- const basePatch = bundlePatchPath(req.resolve("@deepseek-ai/dsh-base/package.json"));
324
- const ownPatch = bundlePatchPath(join3(ownRoot(), "package.json"));
325
- bundlePatches = [
326
- ...appBoot.loadOverlayPatches(BIN, basePatch),
327
- ...appBoot.loadOverlayPatches(BIN, ownPatch)
328
- ];
329
- logDebug(`profile: virtual (dsh-base + ${ownRoot()})`);
330
- }
308
+ const basePatch = bundlePatchPath(req.resolve("@deepseek-ai/dsh-base/package.json"));
309
+ const ownPatch = bundlePatchPath(join3(ownRoot(), "package.json"));
310
+ const bundlePatches = [
311
+ ...appBoot.loadOverlayPatches(BIN, basePatch),
312
+ ...appBoot.loadOverlayPatches(BIN, ownPatch)
313
+ ];
314
+ logDebug(`profile: standalone (dsh-base + ${ownRoot()})`);
331
315
  for (const bundle of overrides?.bundles ?? []) {
332
316
  bundlePatches.push(...loadBundleLayer(appBoot, bundle));
333
317
  logDebug(`profile: added bundle ${bundle}`);
@@ -343,7 +327,7 @@ async function bootAcpProfile(host, overrides) {
343
327
  const homePatchPath = join3(home, "cordis.patch.yml");
344
328
  const homePatches = appBoot.loadOptionalPatches(BIN, homePatchPath) ?? [];
345
329
  const rows = /* @__PURE__ */ new Map();
346
- for (const row of appBoot.composeEntries([bundlePatches, profilePatches, homePatches])) {
330
+ for (const row of appBoot.composeEntries([bundlePatches, homePatches])) {
347
331
  if (typeof row.id === "string") rows.set(row.id, row);
348
332
  }
349
333
  const overlays = [];
@@ -376,7 +360,7 @@ async function bootAcpProfile(host, overrides) {
376
360
  overlays.push({ id: configRowId, config: { ...configRow.config ?? {}, ...patch } });
377
361
  }
378
362
  }
379
- const allPatches = [...bundlePatches, ...profilePatches, ...homePatches, ...overlays];
363
+ const allPatches = [...bundlePatches, ...homePatches, ...overlays];
380
364
  const launchEnvironment = appBoot.loadLayeredEnv("dsh");
381
365
  const launchEnvKey = (await import(pathToFileURL(req.resolve("@deepseek-ai/dsh-launch-environment")).href)).DSH_LAUNCH_ENVIRONMENT_KEY;
382
366
  const ctx = await appBoot.boot(
@@ -396,9 +380,6 @@ async function bootAcpProfile(host, overrides) {
396
380
  });
397
381
  throw error;
398
382
  }
399
- if (profilePatchPath !== void 0) {
400
- logDebug(`user patch layers are read at boot; edit ${profilePatchPath} and restart to apply`);
401
- }
402
383
  return ctx;
403
384
  }
404
385
 
package/dist/plugin.js CHANGED
@@ -73,6 +73,7 @@ import {
73
73
  } from "@agentclientprotocol/sdk";
74
74
  import { credentialRef } from "@deepseek-ai/dsh-credentials";
75
75
  import { ReasoningEffortId } from "@deepseek-ai/dsh-llm";
76
+ import { PERMISSION_SETTINGS_NAMESPACE } from "@deepseek-ai/dsh-permission-presets";
76
77
 
77
78
  // src/version.ts
78
79
  import { createRequire } from "node:module";
@@ -533,7 +534,10 @@ var SessionProjection = class {
533
534
  title;
534
535
  streamedText = /* @__PURE__ */ new Set();
535
536
  streamedReasoning = /* @__PURE__ */ new Set();
537
+ /** One authoritative state per open ACP tool call. Block indexes only
538
+ * route streaming fragments to that state; they never own lifecycle. */
536
539
  toolCalls = /* @__PURE__ */ new Map();
540
+ toolCallBlocks = /* @__PURE__ */ new Map();
537
541
  window = emptyWindow();
538
542
  lastContextUse;
539
543
  /** Client renders `_meta.terminal_output` display terminals (Zed extension). */
@@ -552,6 +556,7 @@ var SessionProjection = class {
552
556
  /** Reset per-prompt accumulators; call when a new `session/prompt` starts. */
553
557
  beginPrompt() {
554
558
  this.window = emptyWindow();
559
+ this.toolCallBlocks.clear();
555
560
  }
556
561
  /** The captured ending for a specific turn, when it already ended. */
557
562
  turnEndFor(turn) {
@@ -591,6 +596,8 @@ var SessionProjection = class {
591
596
  switch (event.type) {
592
597
  case "assistant/chunk":
593
598
  return this.onChunk(data);
599
+ case "tool-call-chunks":
600
+ return this.onToolCallChunks(data);
594
601
  case "assistant/message":
595
602
  return this.onAssistantMessage(data);
596
603
  case "tool/call":
@@ -673,42 +680,31 @@ var SessionProjection = class {
673
680
  onSubagentStart(data) {
674
681
  const subagent = this.subagentMeta(data, "started");
675
682
  if (subagent === void 0) return [];
676
- const runId = subagent["runId"];
677
- const childSessionId = subagent["childSessionId"];
678
683
  return [{
679
- sessionUpdate: "tool_call",
680
- toolCallId: `subagent:${runId}`,
681
- title: `Start subagent ${childSessionId}`,
682
- kind: "other",
683
- status: "in_progress",
684
- rawInput: {
685
- childSessionId,
686
- provider: subagent["provider"],
687
- local: subagent["local"]
688
- },
689
- _meta: { dsh: { subagent } }
684
+ sessionUpdate: "session_info_update",
685
+ _meta: { dsh: { event: "subagent/lifecycle", subagent } }
690
686
  }];
691
687
  }
692
688
  onSubagentEnd(data) {
693
689
  const subagent = this.subagentMeta(data, "finished");
694
690
  if (subagent === void 0) return [];
695
691
  const stopReason = asString(data["stopReason"]) ?? "error";
696
- const lastAssistantMessage = data["lastAssistantMessage"];
697
692
  return [{
698
- sessionUpdate: "tool_call_update",
699
- toolCallId: `subagent:${String(subagent["runId"])}`,
700
- status: stopReason === "completed" ? "completed" : "failed",
701
- rawOutput: {
702
- stopReason,
703
- ...Array.isArray(lastAssistantMessage) ? { lastAssistantMessage } : {}
704
- },
705
- _meta: { dsh: { subagent } }
693
+ sessionUpdate: "session_info_update",
694
+ _meta: {
695
+ dsh: {
696
+ event: "subagent/lifecycle",
697
+ subagent: { ...subagent, stopReason }
698
+ }
699
+ }
706
700
  }];
707
701
  }
708
702
  onChunk(data) {
709
703
  const chunk = data["chunk"];
710
704
  if (chunk === null || typeof chunk === "undefined" || typeof chunk !== "object") return [];
711
705
  const c = chunk;
706
+ if (c["type"] === "block-end") return this.onToolCallBlockEnd(data, c);
707
+ if (c["type"] === "tool-call-delta") return this.onToolCallDelta(data, c);
712
708
  const text = c["text"];
713
709
  if (typeof text !== "string" || text.length === 0) return [];
714
710
  const messageId = this.stepKey(data);
@@ -722,6 +718,97 @@ var SessionProjection = class {
722
718
  }
723
719
  return [];
724
720
  }
721
+ toolBlockKey(data, index) {
722
+ return `${this.stepKey(data)}:${index}`;
723
+ }
724
+ onToolCallDelta(data, chunk) {
725
+ const callId = asString(chunk["id"]);
726
+ const index = chunk["index"];
727
+ const argumentsDelta = chunk["argumentsDelta"];
728
+ if (callId === void 0 || typeof index !== "number" || !Number.isInteger(index) || typeof argumentsDelta !== "string") {
729
+ return [];
730
+ }
731
+ const current = this.toolCalls.get(callId);
732
+ const name6 = asString(chunk["name"]) ?? current?.name;
733
+ if (name6 === void 0) return [];
734
+ const rawArguments = `${current?.arguments ?? ""}${argumentsDelta}`;
735
+ const state = {
736
+ callId,
737
+ name: name6,
738
+ arguments: rawArguments,
739
+ facts: this.toolCallFacts(name6, rawArguments),
740
+ status: current?.status ?? "pending"
741
+ };
742
+ this.toolCalls.set(callId, state);
743
+ this.toolCallBlocks.set(this.toolBlockKey(data, index), callId);
744
+ if (current === void 0) return [this.toolCallCreated(state)];
745
+ return [{
746
+ sessionUpdate: "tool_call_update",
747
+ toolCallId: callId,
748
+ title: state.facts.title,
749
+ name: name6,
750
+ kind: state.facts.kind,
751
+ status: "pending",
752
+ rawInput: state.facts.rawInput,
753
+ ...state.facts.locations.length > 0 ? { locations: state.facts.locations } : {}
754
+ }];
755
+ }
756
+ onToolCallChunks(data) {
757
+ const callId = asString(data["id"]);
758
+ const name6 = asString(data["name"]);
759
+ const index = data["index"];
760
+ const args = data["args"];
761
+ if (callId === void 0 || name6 === void 0 || typeof index !== "number" || !Number.isInteger(index) || !Array.isArray(args)) {
762
+ return [];
763
+ }
764
+ const key = this.toolBlockKey(data, index);
765
+ const current = this.toolCalls.get(callId);
766
+ const fragments = args.filter((fragment) => typeof fragment === "string").join("");
767
+ const rawArguments = `${current?.arguments ?? ""}${fragments}`;
768
+ const state = {
769
+ callId,
770
+ name: name6,
771
+ arguments: rawArguments,
772
+ facts: this.toolCallFacts(name6, rawArguments),
773
+ status: current?.status ?? "pending"
774
+ };
775
+ this.toolCalls.set(callId, state);
776
+ this.toolCallBlocks.set(key, callId);
777
+ return current === void 0 ? [this.toolCallCreated(state)] : [];
778
+ }
779
+ onToolCallBlockEnd(data, chunk) {
780
+ const index = chunk["index"];
781
+ if (typeof index !== "number" || !Number.isInteger(index)) return [];
782
+ const key = this.toolBlockKey(data, index);
783
+ const callId = this.toolCallBlocks.get(key);
784
+ this.toolCallBlocks.delete(key);
785
+ const block = chunk["block"];
786
+ const complete = block !== null && typeof block === "object" ? block : void 0;
787
+ const completeCallId = complete?.["type"] === "tool-call" ? asString(complete["id"]) : void 0;
788
+ const resolvedCallId = callId ?? completeCallId;
789
+ if (resolvedCallId === void 0) return [];
790
+ const current = this.toolCalls.get(resolvedCallId);
791
+ const name6 = (complete?.["type"] === "tool-call" ? asString(complete["name"]) : void 0) ?? current?.name;
792
+ if (name6 === void 0) return [];
793
+ const rawArguments = (complete?.["type"] === "tool-call" && typeof complete["arguments"] === "string" ? complete["arguments"] : void 0) ?? current?.arguments ?? "{}";
794
+ const state = {
795
+ callId: resolvedCallId,
796
+ name: name6,
797
+ arguments: rawArguments,
798
+ facts: this.toolCallFacts(name6, rawArguments),
799
+ status: current?.status ?? "pending"
800
+ };
801
+ this.toolCalls.set(resolvedCallId, state);
802
+ if (current === void 0) return [this.toolCallCreated(state)];
803
+ return [{
804
+ sessionUpdate: "tool_call_update",
805
+ toolCallId: resolvedCallId,
806
+ title: state.facts.title,
807
+ kind: state.facts.kind,
808
+ rawInput: state.facts.rawInput,
809
+ locations: state.facts.locations
810
+ }];
811
+ }
725
812
  onAssistantMessage(data) {
726
813
  const updates = [];
727
814
  const key = this.stepKey(data);
@@ -785,11 +872,7 @@ var SessionProjection = class {
785
872
  }
786
873
  return updates;
787
874
  }
788
- onToolCall(data) {
789
- const callId = asString(data["callId"]);
790
- const name6 = asString(data["name"]) ?? "tool";
791
- if (callId === void 0) return [];
792
- const rawArguments = typeof data["arguments"] === "string" ? data["arguments"] : "{}";
875
+ toolCallFacts(name6, rawArguments) {
793
876
  let parsedArgs = {};
794
877
  try {
795
878
  parsedArgs = JSON.parse(rawArguments);
@@ -797,30 +880,52 @@ var SessionProjection = class {
797
880
  parsedArgs = { arguments: rawArguments };
798
881
  }
799
882
  const presented = this.presentCall?.(name6, parsedArgs);
800
- const facts = presented !== void 0 ? factsFromPresentCall(name6, rawArguments, presented) : classifyToolCall(name6, rawArguments);
801
- this.toolCalls.set(callId, facts);
883
+ return presented !== void 0 ? factsFromPresentCall(name6, rawArguments, presented) : classifyToolCall(name6, rawArguments);
884
+ }
885
+ toolCallCreated(state) {
886
+ const { callId, name: name6, facts, status } = state;
802
887
  const displayTerminal = this.terminalOutput && facts.kind === "execute";
803
- return [
804
- {
805
- sessionUpdate: "tool_call",
806
- toolCallId: callId,
807
- title: facts.title,
808
- name: name6,
809
- kind: facts.kind,
810
- status: "in_progress",
811
- rawInput: facts.rawInput,
812
- ...facts.locations.length > 0 ? { locations: facts.locations } : {},
813
- ...displayTerminal ? {
814
- content: [{ type: "terminal", terminalId: callId }],
815
- _meta: {
816
- terminal_info: {
817
- terminal_id: callId,
818
- ...this.cwd !== void 0 ? { cwd: this.cwd } : {}
819
- }
888
+ return {
889
+ sessionUpdate: "tool_call",
890
+ toolCallId: callId,
891
+ title: facts.title,
892
+ name: name6,
893
+ kind: facts.kind,
894
+ status,
895
+ rawInput: facts.rawInput,
896
+ ...facts.locations.length > 0 ? { locations: facts.locations } : {},
897
+ ...displayTerminal ? {
898
+ content: [{ type: "terminal", terminalId: callId }],
899
+ _meta: {
900
+ terminal_info: {
901
+ terminal_id: callId,
902
+ ...this.cwd !== void 0 ? { cwd: this.cwd } : {}
820
903
  }
821
- } : {}
822
- }
823
- ];
904
+ }
905
+ } : {}
906
+ };
907
+ }
908
+ onToolCall(data) {
909
+ const callId = asString(data["callId"]);
910
+ const name6 = asString(data["name"]) ?? "tool";
911
+ if (callId === void 0) return [];
912
+ const rawArguments = typeof data["arguments"] === "string" ? data["arguments"] : "{}";
913
+ const facts = this.toolCallFacts(name6, rawArguments);
914
+ const current = this.toolCalls.get(callId);
915
+ const state = { callId, name: name6, arguments: rawArguments, facts, status: "in_progress" };
916
+ this.toolCalls.set(callId, state);
917
+ if (current === void 0) return [this.toolCallCreated(state)];
918
+ if (current.status === "in_progress" && current.arguments === rawArguments && current.name === name6) return [];
919
+ return [{
920
+ sessionUpdate: "tool_call_update",
921
+ toolCallId: callId,
922
+ title: facts.title,
923
+ name: name6,
924
+ kind: facts.kind,
925
+ status: "in_progress",
926
+ rawInput: facts.rawInput,
927
+ locations: facts.locations
928
+ }];
824
929
  }
825
930
  onToolResult(data) {
826
931
  const message = data["message"];
@@ -835,7 +940,8 @@ var SessionProjection = class {
835
940
  })() : void 0
836
941
  ) ?? asString(data["callId"]);
837
942
  if (callId === void 0) return [];
838
- const facts = this.toolCalls.get(callId);
943
+ const state = this.toolCalls.get(callId);
944
+ const facts = state?.facts;
839
945
  const { failed, text, diffs } = extractToolResult(data);
840
946
  const content = diffs.map((diff) => ({
841
947
  type: "diff",
@@ -846,6 +952,9 @@ var SessionProjection = class {
846
952
  const isCommand = facts?.kind === "execute";
847
953
  if (this.terminalOutput && isCommand) {
848
954
  this.toolCalls.delete(callId);
955
+ for (const [key, value] of this.toolCallBlocks) {
956
+ if (value === callId) this.toolCallBlocks.delete(key);
957
+ }
849
958
  return [
850
959
  ...text.length > 0 ? [
851
960
  {
@@ -876,6 +985,9 @@ ${text.replace(/\n+$/, "")}
876
985
  content.push({ type: "content", content: block });
877
986
  }
878
987
  this.toolCalls.delete(callId);
988
+ for (const [key, value] of this.toolCallBlocks) {
989
+ if (value === callId) this.toolCallBlocks.delete(key);
990
+ }
879
991
  return [
880
992
  {
881
993
  sessionUpdate: "tool_call_update",
@@ -897,6 +1009,8 @@ ${text.replace(/\n+$/, "")}
897
1009
  this.window.error = { message: r.error?.message ?? "model turn failed" };
898
1010
  }
899
1011
  }
1012
+ this.toolCallBlocks.clear();
1013
+ this.toolCalls.clear();
900
1014
  return [];
901
1015
  }
902
1016
  };
@@ -2096,11 +2210,16 @@ async function apply3(ctx, config = {}) {
2096
2210
  '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)'
2097
2211
  );
2098
2212
  };
2099
- const agentOptionsFor = (model, provider) => withInteractionMode({
2100
- ...provider !== void 0 ? { provider } : config.provider !== void 0 ? { provider: config.provider } : {},
2101
- ...model !== void 0 ? { model } : {},
2102
- ...config.maxTokens !== void 0 ? { maxTokens: config.maxTokens } : {}
2103
- }, clientInteractionMode);
2213
+ const agentOptionsFor = (model, provider) => {
2214
+ const defaults = defaultSelection();
2215
+ const resolvedProvider = provider ?? config.provider ?? defaults.provider;
2216
+ const resolvedModel = model ?? config.model ?? defaults.model;
2217
+ return withInteractionMode({
2218
+ ...resolvedProvider !== void 0 ? { provider: resolvedProvider } : {},
2219
+ ...resolvedModel !== void 0 ? { model: resolvedModel } : {},
2220
+ ...config.maxTokens !== void 0 ? { maxTokens: config.maxTokens } : {}
2221
+ }, clientInteractionMode);
2222
+ };
2104
2223
  const ownedRecord = (agent) => {
2105
2224
  const record = sessions.get(String(agent.session.id));
2106
2225
  return record?.agent === agent ? record : void 0;
@@ -2293,8 +2412,8 @@ async function apply3(ctx, config = {}) {
2293
2412
  return {
2294
2413
  currentModeId: record.modeId,
2295
2414
  availableModes: permission.names.map((name6) => {
2296
- const spec = permission.resolve(name6);
2297
- return { id: name6, name: spec.name, description: spec.description };
2415
+ const presentation = permissionPresentation(name6);
2416
+ return { id: name6, ...presentation };
2298
2417
  })
2299
2418
  };
2300
2419
  }
@@ -2370,6 +2489,25 @@ async function apply3(ctx, config = {}) {
2370
2489
  record.approvals = policy;
2371
2490
  };
2372
2491
  const permissionService = () => permissionPresets;
2492
+ const permissionPresentation = (id) => {
2493
+ const spec = permissionService().resolve(id);
2494
+ const stock = MODE_LABELS[id];
2495
+ const fallbackName = stock?.name ?? id.split("-").map((part) => part.length > 0 ? `${part[0].toUpperCase()}${part.slice(1)}` : part).join(" ");
2496
+ return {
2497
+ name: typeof spec.name === "string" && spec.name.trim().length > 0 ? spec.name : fallbackName,
2498
+ description: typeof spec.description === "string" ? spec.description : stock?.description ?? ""
2499
+ };
2500
+ };
2501
+ const saveDefaultPermission = async (modeId) => {
2502
+ if (!permissionService().names.includes(modeId)) return;
2503
+ try {
2504
+ await ctx.get("settings")?.update(PERMISSION_SETTINGS_NAMESPACE, {
2505
+ defaultPreset: modeId
2506
+ });
2507
+ } catch (error) {
2508
+ logWarn(`permission switch applied to the session but was not saved as the default: ${String(error)}`);
2509
+ }
2510
+ };
2373
2511
  const applyMode = (record, sessionId, modeId) => {
2374
2512
  const permission = permissionService();
2375
2513
  if (permission.names.includes(modeId)) {
@@ -2420,8 +2558,8 @@ async function apply3(ctx, config = {}) {
2420
2558
  {
2421
2559
  const permission = permissionService();
2422
2560
  const levels = permission.names.length > 0 ? permission.names.map((name6) => {
2423
- const spec = permission.resolve(name6);
2424
- return { value: name6, name: spec.name, description: spec.description };
2561
+ const presentation = permissionPresentation(name6);
2562
+ return { value: name6, ...presentation };
2425
2563
  }) : SANDBOX_MODES2.map((mode) => {
2426
2564
  const label = MODE_LABELS[mode] ?? { name: mode, description: "" };
2427
2565
  return { value: mode, name: label.name, description: label.description };
@@ -3083,6 +3221,7 @@ async function apply3(ctx, config = {}) {
3083
3221
  async setSessionMode(params) {
3084
3222
  const record = await requireOrRestoreSession(params.sessionId);
3085
3223
  applyMode(record, params.sessionId, params.modeId);
3224
+ await saveDefaultPermission(params.modeId);
3086
3225
  notify(params.sessionId, {
3087
3226
  sessionUpdate: "config_option_update",
3088
3227
  configOptions: await configOptions(record)
@@ -3096,6 +3235,7 @@ async function apply3(ctx, config = {}) {
3096
3235
  switch (params.configId) {
3097
3236
  case "mode": {
3098
3237
  applyMode(record, params.sessionId, value);
3238
+ await saveDefaultPermission(value);
3099
3239
  break;
3100
3240
  }
3101
3241
  case "agent":
package/dist/server.js CHANGED
@@ -67,6 +67,7 @@ import {
67
67
  } from "@agentclientprotocol/sdk";
68
68
  import { credentialRef } from "@deepseek-ai/dsh-credentials";
69
69
  import { ReasoningEffortId } from "@deepseek-ai/dsh-llm";
70
+ import { PERMISSION_SETTINGS_NAMESPACE } from "@deepseek-ai/dsh-permission-presets";
70
71
 
71
72
  // src/version.ts
72
73
  import { createRequire } from "node:module";
@@ -527,7 +528,10 @@ var SessionProjection = class {
527
528
  title;
528
529
  streamedText = /* @__PURE__ */ new Set();
529
530
  streamedReasoning = /* @__PURE__ */ new Set();
531
+ /** One authoritative state per open ACP tool call. Block indexes only
532
+ * route streaming fragments to that state; they never own lifecycle. */
530
533
  toolCalls = /* @__PURE__ */ new Map();
534
+ toolCallBlocks = /* @__PURE__ */ new Map();
531
535
  window = emptyWindow();
532
536
  lastContextUse;
533
537
  /** Client renders `_meta.terminal_output` display terminals (Zed extension). */
@@ -546,6 +550,7 @@ var SessionProjection = class {
546
550
  /** Reset per-prompt accumulators; call when a new `session/prompt` starts. */
547
551
  beginPrompt() {
548
552
  this.window = emptyWindow();
553
+ this.toolCallBlocks.clear();
549
554
  }
550
555
  /** The captured ending for a specific turn, when it already ended. */
551
556
  turnEndFor(turn) {
@@ -585,6 +590,8 @@ var SessionProjection = class {
585
590
  switch (event.type) {
586
591
  case "assistant/chunk":
587
592
  return this.onChunk(data);
593
+ case "tool-call-chunks":
594
+ return this.onToolCallChunks(data);
588
595
  case "assistant/message":
589
596
  return this.onAssistantMessage(data);
590
597
  case "tool/call":
@@ -667,42 +674,31 @@ var SessionProjection = class {
667
674
  onSubagentStart(data) {
668
675
  const subagent = this.subagentMeta(data, "started");
669
676
  if (subagent === void 0) return [];
670
- const runId = subagent["runId"];
671
- const childSessionId = subagent["childSessionId"];
672
677
  return [{
673
- sessionUpdate: "tool_call",
674
- toolCallId: `subagent:${runId}`,
675
- title: `Start subagent ${childSessionId}`,
676
- kind: "other",
677
- status: "in_progress",
678
- rawInput: {
679
- childSessionId,
680
- provider: subagent["provider"],
681
- local: subagent["local"]
682
- },
683
- _meta: { dsh: { subagent } }
678
+ sessionUpdate: "session_info_update",
679
+ _meta: { dsh: { event: "subagent/lifecycle", subagent } }
684
680
  }];
685
681
  }
686
682
  onSubagentEnd(data) {
687
683
  const subagent = this.subagentMeta(data, "finished");
688
684
  if (subagent === void 0) return [];
689
685
  const stopReason = asString(data["stopReason"]) ?? "error";
690
- const lastAssistantMessage = data["lastAssistantMessage"];
691
686
  return [{
692
- sessionUpdate: "tool_call_update",
693
- toolCallId: `subagent:${String(subagent["runId"])}`,
694
- status: stopReason === "completed" ? "completed" : "failed",
695
- rawOutput: {
696
- stopReason,
697
- ...Array.isArray(lastAssistantMessage) ? { lastAssistantMessage } : {}
698
- },
699
- _meta: { dsh: { subagent } }
687
+ sessionUpdate: "session_info_update",
688
+ _meta: {
689
+ dsh: {
690
+ event: "subagent/lifecycle",
691
+ subagent: { ...subagent, stopReason }
692
+ }
693
+ }
700
694
  }];
701
695
  }
702
696
  onChunk(data) {
703
697
  const chunk = data["chunk"];
704
698
  if (chunk === null || typeof chunk === "undefined" || typeof chunk !== "object") return [];
705
699
  const c = chunk;
700
+ if (c["type"] === "block-end") return this.onToolCallBlockEnd(data, c);
701
+ if (c["type"] === "tool-call-delta") return this.onToolCallDelta(data, c);
706
702
  const text = c["text"];
707
703
  if (typeof text !== "string" || text.length === 0) return [];
708
704
  const messageId = this.stepKey(data);
@@ -716,6 +712,97 @@ var SessionProjection = class {
716
712
  }
717
713
  return [];
718
714
  }
715
+ toolBlockKey(data, index) {
716
+ return `${this.stepKey(data)}:${index}`;
717
+ }
718
+ onToolCallDelta(data, chunk) {
719
+ const callId = asString(chunk["id"]);
720
+ const index = chunk["index"];
721
+ const argumentsDelta = chunk["argumentsDelta"];
722
+ if (callId === void 0 || typeof index !== "number" || !Number.isInteger(index) || typeof argumentsDelta !== "string") {
723
+ return [];
724
+ }
725
+ const current = this.toolCalls.get(callId);
726
+ const name5 = asString(chunk["name"]) ?? current?.name;
727
+ if (name5 === void 0) return [];
728
+ const rawArguments = `${current?.arguments ?? ""}${argumentsDelta}`;
729
+ const state = {
730
+ callId,
731
+ name: name5,
732
+ arguments: rawArguments,
733
+ facts: this.toolCallFacts(name5, rawArguments),
734
+ status: current?.status ?? "pending"
735
+ };
736
+ this.toolCalls.set(callId, state);
737
+ this.toolCallBlocks.set(this.toolBlockKey(data, index), callId);
738
+ if (current === void 0) return [this.toolCallCreated(state)];
739
+ return [{
740
+ sessionUpdate: "tool_call_update",
741
+ toolCallId: callId,
742
+ title: state.facts.title,
743
+ name: name5,
744
+ kind: state.facts.kind,
745
+ status: "pending",
746
+ rawInput: state.facts.rawInput,
747
+ ...state.facts.locations.length > 0 ? { locations: state.facts.locations } : {}
748
+ }];
749
+ }
750
+ onToolCallChunks(data) {
751
+ const callId = asString(data["id"]);
752
+ const name5 = asString(data["name"]);
753
+ const index = data["index"];
754
+ const args = data["args"];
755
+ if (callId === void 0 || name5 === void 0 || typeof index !== "number" || !Number.isInteger(index) || !Array.isArray(args)) {
756
+ return [];
757
+ }
758
+ const key = this.toolBlockKey(data, index);
759
+ const current = this.toolCalls.get(callId);
760
+ const fragments = args.filter((fragment) => typeof fragment === "string").join("");
761
+ const rawArguments = `${current?.arguments ?? ""}${fragments}`;
762
+ const state = {
763
+ callId,
764
+ name: name5,
765
+ arguments: rawArguments,
766
+ facts: this.toolCallFacts(name5, rawArguments),
767
+ status: current?.status ?? "pending"
768
+ };
769
+ this.toolCalls.set(callId, state);
770
+ this.toolCallBlocks.set(key, callId);
771
+ return current === void 0 ? [this.toolCallCreated(state)] : [];
772
+ }
773
+ onToolCallBlockEnd(data, chunk) {
774
+ const index = chunk["index"];
775
+ if (typeof index !== "number" || !Number.isInteger(index)) return [];
776
+ const key = this.toolBlockKey(data, index);
777
+ const callId = this.toolCallBlocks.get(key);
778
+ this.toolCallBlocks.delete(key);
779
+ const block = chunk["block"];
780
+ const complete = block !== null && typeof block === "object" ? block : void 0;
781
+ const completeCallId = complete?.["type"] === "tool-call" ? asString(complete["id"]) : void 0;
782
+ const resolvedCallId = callId ?? completeCallId;
783
+ if (resolvedCallId === void 0) return [];
784
+ const current = this.toolCalls.get(resolvedCallId);
785
+ const name5 = (complete?.["type"] === "tool-call" ? asString(complete["name"]) : void 0) ?? current?.name;
786
+ if (name5 === void 0) return [];
787
+ const rawArguments = (complete?.["type"] === "tool-call" && typeof complete["arguments"] === "string" ? complete["arguments"] : void 0) ?? current?.arguments ?? "{}";
788
+ const state = {
789
+ callId: resolvedCallId,
790
+ name: name5,
791
+ arguments: rawArguments,
792
+ facts: this.toolCallFacts(name5, rawArguments),
793
+ status: current?.status ?? "pending"
794
+ };
795
+ this.toolCalls.set(resolvedCallId, state);
796
+ if (current === void 0) return [this.toolCallCreated(state)];
797
+ return [{
798
+ sessionUpdate: "tool_call_update",
799
+ toolCallId: resolvedCallId,
800
+ title: state.facts.title,
801
+ kind: state.facts.kind,
802
+ rawInput: state.facts.rawInput,
803
+ locations: state.facts.locations
804
+ }];
805
+ }
719
806
  onAssistantMessage(data) {
720
807
  const updates = [];
721
808
  const key = this.stepKey(data);
@@ -779,11 +866,7 @@ var SessionProjection = class {
779
866
  }
780
867
  return updates;
781
868
  }
782
- onToolCall(data) {
783
- const callId = asString(data["callId"]);
784
- const name5 = asString(data["name"]) ?? "tool";
785
- if (callId === void 0) return [];
786
- const rawArguments = typeof data["arguments"] === "string" ? data["arguments"] : "{}";
869
+ toolCallFacts(name5, rawArguments) {
787
870
  let parsedArgs = {};
788
871
  try {
789
872
  parsedArgs = JSON.parse(rawArguments);
@@ -791,30 +874,52 @@ var SessionProjection = class {
791
874
  parsedArgs = { arguments: rawArguments };
792
875
  }
793
876
  const presented = this.presentCall?.(name5, parsedArgs);
794
- const facts = presented !== void 0 ? factsFromPresentCall(name5, rawArguments, presented) : classifyToolCall(name5, rawArguments);
795
- this.toolCalls.set(callId, facts);
877
+ return presented !== void 0 ? factsFromPresentCall(name5, rawArguments, presented) : classifyToolCall(name5, rawArguments);
878
+ }
879
+ toolCallCreated(state) {
880
+ const { callId, name: name5, facts, status } = state;
796
881
  const displayTerminal = this.terminalOutput && facts.kind === "execute";
797
- return [
798
- {
799
- sessionUpdate: "tool_call",
800
- toolCallId: callId,
801
- title: facts.title,
802
- name: name5,
803
- kind: facts.kind,
804
- status: "in_progress",
805
- rawInput: facts.rawInput,
806
- ...facts.locations.length > 0 ? { locations: facts.locations } : {},
807
- ...displayTerminal ? {
808
- content: [{ type: "terminal", terminalId: callId }],
809
- _meta: {
810
- terminal_info: {
811
- terminal_id: callId,
812
- ...this.cwd !== void 0 ? { cwd: this.cwd } : {}
813
- }
882
+ return {
883
+ sessionUpdate: "tool_call",
884
+ toolCallId: callId,
885
+ title: facts.title,
886
+ name: name5,
887
+ kind: facts.kind,
888
+ status,
889
+ rawInput: facts.rawInput,
890
+ ...facts.locations.length > 0 ? { locations: facts.locations } : {},
891
+ ...displayTerminal ? {
892
+ content: [{ type: "terminal", terminalId: callId }],
893
+ _meta: {
894
+ terminal_info: {
895
+ terminal_id: callId,
896
+ ...this.cwd !== void 0 ? { cwd: this.cwd } : {}
814
897
  }
815
- } : {}
816
- }
817
- ];
898
+ }
899
+ } : {}
900
+ };
901
+ }
902
+ onToolCall(data) {
903
+ const callId = asString(data["callId"]);
904
+ const name5 = asString(data["name"]) ?? "tool";
905
+ if (callId === void 0) return [];
906
+ const rawArguments = typeof data["arguments"] === "string" ? data["arguments"] : "{}";
907
+ const facts = this.toolCallFacts(name5, rawArguments);
908
+ const current = this.toolCalls.get(callId);
909
+ const state = { callId, name: name5, arguments: rawArguments, facts, status: "in_progress" };
910
+ this.toolCalls.set(callId, state);
911
+ if (current === void 0) return [this.toolCallCreated(state)];
912
+ if (current.status === "in_progress" && current.arguments === rawArguments && current.name === name5) return [];
913
+ return [{
914
+ sessionUpdate: "tool_call_update",
915
+ toolCallId: callId,
916
+ title: facts.title,
917
+ name: name5,
918
+ kind: facts.kind,
919
+ status: "in_progress",
920
+ rawInput: facts.rawInput,
921
+ locations: facts.locations
922
+ }];
818
923
  }
819
924
  onToolResult(data) {
820
925
  const message = data["message"];
@@ -829,7 +934,8 @@ var SessionProjection = class {
829
934
  })() : void 0
830
935
  ) ?? asString(data["callId"]);
831
936
  if (callId === void 0) return [];
832
- const facts = this.toolCalls.get(callId);
937
+ const state = this.toolCalls.get(callId);
938
+ const facts = state?.facts;
833
939
  const { failed, text, diffs } = extractToolResult(data);
834
940
  const content = diffs.map((diff) => ({
835
941
  type: "diff",
@@ -840,6 +946,9 @@ var SessionProjection = class {
840
946
  const isCommand = facts?.kind === "execute";
841
947
  if (this.terminalOutput && isCommand) {
842
948
  this.toolCalls.delete(callId);
949
+ for (const [key, value] of this.toolCallBlocks) {
950
+ if (value === callId) this.toolCallBlocks.delete(key);
951
+ }
843
952
  return [
844
953
  ...text.length > 0 ? [
845
954
  {
@@ -870,6 +979,9 @@ ${text.replace(/\n+$/, "")}
870
979
  content.push({ type: "content", content: block });
871
980
  }
872
981
  this.toolCalls.delete(callId);
982
+ for (const [key, value] of this.toolCallBlocks) {
983
+ if (value === callId) this.toolCallBlocks.delete(key);
984
+ }
873
985
  return [
874
986
  {
875
987
  sessionUpdate: "tool_call_update",
@@ -891,6 +1003,8 @@ ${text.replace(/\n+$/, "")}
891
1003
  this.window.error = { message: r.error?.message ?? "model turn failed" };
892
1004
  }
893
1005
  }
1006
+ this.toolCallBlocks.clear();
1007
+ this.toolCalls.clear();
894
1008
  return [];
895
1009
  }
896
1010
  };
@@ -2090,11 +2204,16 @@ async function apply3(ctx, config = {}) {
2090
2204
  '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)'
2091
2205
  );
2092
2206
  };
2093
- const agentOptionsFor = (model, provider) => withInteractionMode({
2094
- ...provider !== void 0 ? { provider } : config.provider !== void 0 ? { provider: config.provider } : {},
2095
- ...model !== void 0 ? { model } : {},
2096
- ...config.maxTokens !== void 0 ? { maxTokens: config.maxTokens } : {}
2097
- }, clientInteractionMode);
2207
+ const agentOptionsFor = (model, provider) => {
2208
+ const defaults = defaultSelection();
2209
+ const resolvedProvider = provider ?? config.provider ?? defaults.provider;
2210
+ const resolvedModel = model ?? config.model ?? defaults.model;
2211
+ return withInteractionMode({
2212
+ ...resolvedProvider !== void 0 ? { provider: resolvedProvider } : {},
2213
+ ...resolvedModel !== void 0 ? { model: resolvedModel } : {},
2214
+ ...config.maxTokens !== void 0 ? { maxTokens: config.maxTokens } : {}
2215
+ }, clientInteractionMode);
2216
+ };
2098
2217
  const ownedRecord = (agent) => {
2099
2218
  const record = sessions.get(String(agent.session.id));
2100
2219
  return record?.agent === agent ? record : void 0;
@@ -2287,8 +2406,8 @@ async function apply3(ctx, config = {}) {
2287
2406
  return {
2288
2407
  currentModeId: record.modeId,
2289
2408
  availableModes: permission.names.map((name5) => {
2290
- const spec = permission.resolve(name5);
2291
- return { id: name5, name: spec.name, description: spec.description };
2409
+ const presentation = permissionPresentation(name5);
2410
+ return { id: name5, ...presentation };
2292
2411
  })
2293
2412
  };
2294
2413
  }
@@ -2364,6 +2483,25 @@ async function apply3(ctx, config = {}) {
2364
2483
  record.approvals = policy;
2365
2484
  };
2366
2485
  const permissionService = () => permissionPresets;
2486
+ const permissionPresentation = (id) => {
2487
+ const spec = permissionService().resolve(id);
2488
+ const stock = MODE_LABELS[id];
2489
+ const fallbackName = stock?.name ?? id.split("-").map((part) => part.length > 0 ? `${part[0].toUpperCase()}${part.slice(1)}` : part).join(" ");
2490
+ return {
2491
+ name: typeof spec.name === "string" && spec.name.trim().length > 0 ? spec.name : fallbackName,
2492
+ description: typeof spec.description === "string" ? spec.description : stock?.description ?? ""
2493
+ };
2494
+ };
2495
+ const saveDefaultPermission = async (modeId) => {
2496
+ if (!permissionService().names.includes(modeId)) return;
2497
+ try {
2498
+ await ctx.get("settings")?.update(PERMISSION_SETTINGS_NAMESPACE, {
2499
+ defaultPreset: modeId
2500
+ });
2501
+ } catch (error) {
2502
+ logWarn(`permission switch applied to the session but was not saved as the default: ${String(error)}`);
2503
+ }
2504
+ };
2367
2505
  const applyMode = (record, sessionId, modeId) => {
2368
2506
  const permission = permissionService();
2369
2507
  if (permission.names.includes(modeId)) {
@@ -2414,8 +2552,8 @@ async function apply3(ctx, config = {}) {
2414
2552
  {
2415
2553
  const permission = permissionService();
2416
2554
  const levels = permission.names.length > 0 ? permission.names.map((name5) => {
2417
- const spec = permission.resolve(name5);
2418
- return { value: name5, name: spec.name, description: spec.description };
2555
+ const presentation = permissionPresentation(name5);
2556
+ return { value: name5, ...presentation };
2419
2557
  }) : SANDBOX_MODES2.map((mode) => {
2420
2558
  const label = MODE_LABELS[mode] ?? { name: mode, description: "" };
2421
2559
  return { value: mode, name: label.name, description: label.description };
@@ -3077,6 +3215,7 @@ async function apply3(ctx, config = {}) {
3077
3215
  async setSessionMode(params) {
3078
3216
  const record = await requireOrRestoreSession(params.sessionId);
3079
3217
  applyMode(record, params.sessionId, params.modeId);
3218
+ await saveDefaultPermission(params.modeId);
3080
3219
  notify(params.sessionId, {
3081
3220
  sessionUpdate: "config_option_update",
3082
3221
  configOptions: await configOptions(record)
@@ -3090,6 +3229,7 @@ async function apply3(ctx, config = {}) {
3090
3229
  switch (params.configId) {
3091
3230
  case "mode": {
3092
3231
  applyMode(record, params.sessionId, value);
3232
+ await saveDefaultPermission(value);
3093
3233
  break;
3094
3234
  }
3095
3235
  case "agent":
package/dist/stdio.js CHANGED
@@ -34,6 +34,7 @@ import {
34
34
  } from "@agentclientprotocol/sdk";
35
35
  import { credentialRef } from "@deepseek-ai/dsh-credentials";
36
36
  import { ReasoningEffortId } from "@deepseek-ai/dsh-llm";
37
+ import { PERMISSION_SETTINGS_NAMESPACE } from "@deepseek-ai/dsh-permission-presets";
37
38
 
38
39
  // src/version.ts
39
40
  import { createRequire } from "node:module";
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.4.24",
6
+ "version": "0.4.26",
7
7
  "repository": {
8
8
  "type": "git",
9
9
  "url": "git+https://github.com/openma-ai/deepseek-harness-acp.git"
Binary file
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "archive": "dsh-runtime.tgz",
3
3
  "dsh": "0.1.1-rc.2",
4
- "sha256": "bb94c7ba7f09bd7d890dd068bedc2f5f68542d7746ca1b3410c17ac9cf9a17f3"
4
+ "sha256": "f9f9276ec14fdd907146175d5c3b98d1e680eb27b9caaa37fd609908f197e70e"
5
5
  }