lody 0.93.2 → 0.94.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,65 @@
1
+ import { S as SessionUsageAccumulator } from "./chunks/usage-DY4Eg1Mf.js";
1
2
  import { randomUUID, createHash } from "node:crypto";
2
3
  import { isAbsolute } from "node:path";
3
4
  import { Readable, Writable } from "node:stream";
4
5
  import { n as ndJsonStream, A as AgentSideConnection, R as RequestError, P as PROTOCOL_VERSION } from "./chunks/acp-DCoAUZc7.js";
5
- import { j as DEEPSEEK_HARNESS_BASE_URL_ENV, k as DEEPSEEK_HARNESS_API_KEY_ENV, a as DEEPSEEK_HARNESS_AGENT_PRESETS, b as ACP_EXTENSION_DSH_VERSION } from "./chunks/profile-Dv5auBPt.js";
6
+ import { i as DEEPSEEK_HARNESS_BASE_URL_ENV, j as DEEPSEEK_HARNESS_API_KEY_ENV, a as DEEPSEEK_HARNESS_AGENT_PRESETS, k as ACP_EXTENSION_DSH_VERSION, l as DEEPSEEK_HARNESS_DEFAULT_MODEL, m as DEEPSEEK_HARNESS_PROVIDER } from "./chunks/profile-6mKAZyTP.js";
7
+ import { L as LODY_EXTENSION_METHODS } from "./chunks/methods-CvEDMahD.js";
6
8
  import { c as createPlanModeConfigOption, L as LODY_PLAN_MODE_CONFIG_ID } from "./chunks/plan-mode-BLOER-fv.js";
9
+ import "node:url";
7
10
  import "./chunks/schemas-C-NzK2uQ.js";
11
+ const prices = {
12
+ "deepseek-flash": [0.15, 3e-3, 0.6],
13
+ // Official aliases now served and billed as DeepSeek-V4.1-Flash.
14
+ "deepseek-v4-flash": [0.15, 3e-3, 0.6],
15
+ "deepseek-v4-pro": [0.66, 0.022, 1.98],
16
+ "deepseek-v4-flash-vision-exp": [0.15, 3e-3, 0.6]
17
+ };
18
+ function deepSeekCostUSD(model, usage, epochMs) {
19
+ const price = Object.hasOwn(prices, model) ? prices[model] : void 0;
20
+ if (!price || !Number.isFinite(epochMs))
21
+ return void 0;
22
+ const date = new Date(epochMs);
23
+ const weekday = date.getUTCDay() >= 1 && date.getUTCDay() <= 5;
24
+ const hour = date.getUTCHours();
25
+ const peak = weekday && (hour >= 1 && hour < 4 || hour >= 6 && hour < 10);
26
+ return ((usage.inputTokens + (usage.cacheCreationInputTokens ?? 0)) * price[0] + usage.cacheReadInputTokens * price[1] + (usage.outputTokens + (usage.reasoningOutputTokens ?? 0)) * price[2]) * (peak ? 2 : 1) / 1e6;
27
+ }
28
+ class HarnessUsageTracker {
29
+ officialEndpoint;
30
+ accumulator = new SessionUsageAccumulator();
31
+ route;
32
+ seen = /* @__PURE__ */ new Set();
33
+ constructor(officialEndpoint) {
34
+ this.officialEndpoint = officialEndpoint;
35
+ }
36
+ setRoute(provider, model) {
37
+ this.route = { provider, model };
38
+ }
39
+ record(sessionId, seq, time, raw) {
40
+ if (!this.route || !Number.isSafeInteger(seq) || seq < 0)
41
+ return void 0;
42
+ if (this.seen.has(seq))
43
+ return void 0;
44
+ if ([raw.inputTokens, raw.outputTokens, ...Object.values(raw)].some((value) => !Number.isFinite(value) || value < 0))
45
+ return void 0;
46
+ const usage = {
47
+ // Pinned dsh-llm-deepseek already subtracts cache hits from inputTokens.
48
+ inputTokens: raw.inputTokens,
49
+ outputTokens: Math.max(0, raw.outputTokens - (raw.reasoningTokens ?? 0)),
50
+ cacheReadInputTokens: raw.cacheReadTokens ?? 0,
51
+ cacheCreationInputTokens: raw.cacheWriteTokens ?? 0,
52
+ reasoningOutputTokens: raw.reasoningTokens ?? 0
53
+ };
54
+ if (this.officialEndpoint && this.route.provider === "deepseek-official") {
55
+ const costUSD = deepSeekCostUSD(this.route.model, usage, time);
56
+ if (costUSD !== void 0)
57
+ usage.costUSD = costUSD;
58
+ }
59
+ this.seen.add(seq);
60
+ return this.accumulator.update(sessionId, String(seq), { [this.route.model]: usage });
61
+ }
62
+ }
8
63
  const name = "acp-extension-dsh";
9
64
  const inject = [
10
65
  "agents",
@@ -17,7 +72,8 @@ const inject = [
17
72
  "sessionQuery"
18
73
  ];
19
74
  const LODY_CAPABILITIES = {
20
- compaction: { version: 1 }
75
+ compaction: { version: 1 },
76
+ usage: { version: 1 }
21
77
  };
22
78
  const MODEL_CONFIG_ID = "model";
23
79
  const MODE_CONFIG_ID = "mode";
@@ -61,8 +117,8 @@ function nonEmptyString(value, fallback) {
61
117
  return typeof value === "string" && value.trim() ? value.trim() : fallback;
62
118
  }
63
119
  function resolveAdapterConfig(config) {
64
- const provider = nonEmptyString(config?.provider, "deepseek-official");
65
- const model = nonEmptyString(config?.model, "deepseek-v4-pro");
120
+ const provider = nonEmptyString(config?.provider, DEEPSEEK_HARNESS_PROVIDER);
121
+ const model = nonEmptyString(config?.model, DEEPSEEK_HARNESS_DEFAULT_MODEL);
66
122
  return {
67
123
  provider,
68
124
  model,
@@ -658,7 +714,7 @@ function apply(ctx, rawConfig) {
658
714
  });
659
715
  };
660
716
  const refreshPermissionState = (record) => {
661
- const permissionMode = ctx.permissionPresets.current(record.agent.session.events);
717
+ const permissionMode = ctx.permissionPresets.current(record.agent.session);
662
718
  if (permissionMode === record.permissionMode)
663
719
  return false;
664
720
  record.permissionMode = permissionMode;
@@ -715,6 +771,31 @@ function apply(ctx, rawConfig) {
715
771
  throw new AggregateError(failures, `DeepSeek ACP teardown failed for ${failures.length} session(s): ${failures.map(errorChain).join("; ")}`);
716
772
  }
717
773
  };
774
+ ctx.on("agent/assistant-stream", ({ agent, frame }) => {
775
+ const record = ownedRecord(agent);
776
+ if (!record || frame.type !== "chunk")
777
+ return;
778
+ const chunk = frame.chunk;
779
+ if (!chunk)
780
+ return;
781
+ if (chunk.type === "reasoning-delta" && typeof chunk.text === "string" && chunk.text.length > 0) {
782
+ enqueueNotification(record, {
783
+ sessionId: record.agent.session.id,
784
+ update: {
785
+ sessionUpdate: "agent_thought_chunk",
786
+ content: { type: "text", text: chunk.text }
787
+ }
788
+ });
789
+ } else if (chunk.type === "block-end" && chunk.block?.type === "reasoning") {
790
+ enqueueNotification(record, {
791
+ sessionId: record.agent.session.id,
792
+ update: {
793
+ sessionUpdate: "agent_thought_chunk",
794
+ content: { type: "text", text: "\n\n" }
795
+ }
796
+ });
797
+ }
798
+ });
718
799
  ctx.on("session/event", (session, event) => {
719
800
  const record = sessions.get(session.header.id);
720
801
  if (!record || record.agent.session !== session)
@@ -728,23 +809,15 @@ function apply(ctx, rawConfig) {
728
809
  if (PERMISSION_EVENT_TYPES.has(event.type))
729
810
  schedulePermissionSync(record);
730
811
  try {
731
- if (event.type === "assistant/chunk" && event.data.chunk?.type === "reasoning-delta" && typeof event.data.chunk.text === "string" && event.data.chunk.text.length > 0) {
732
- enqueueNotification(record, {
733
- sessionId: record.agent.session.id,
734
- update: {
735
- sessionUpdate: "agent_thought_chunk",
736
- content: { type: "text", text: event.data.chunk.text }
737
- }
738
- });
739
- } else if (event.type === "assistant/chunk" && event.data.chunk?.type === "block-end" && event.data.chunk.block?.type === "reasoning") {
740
- enqueueNotification(record, {
741
- sessionId: record.agent.session.id,
742
- update: {
743
- sessionUpdate: "agent_thought_chunk",
744
- content: { type: "text", text: "\n\n" }
745
- }
746
- });
747
- } else if (event.type === "assistant/message") {
812
+ if (event.type === "request/context" && event.data.provider && event.data.model) {
813
+ record.usage.setRoute(event.data.provider, event.data.model);
814
+ }
815
+ if (event.type === "assistant/message" && event.data.usage && event.seq !== void 0) {
816
+ const update = record.usage.record(session.id, event.seq, event.time ?? NaN, event.data.usage);
817
+ if (update)
818
+ enqueueOutput(record, () => conn.extNotification(LODY_EXTENSION_METHODS.sessionUsageUpdate, update), record.inflight);
819
+ }
820
+ if (event.type === "assistant/message") {
748
821
  const inflight = record.inflight?.turn === event.data.turn ? record.inflight : void 0;
749
822
  enqueueOutput(record, async () => {
750
823
  for (const block of event.data.message?.content ?? []) {
@@ -834,7 +907,7 @@ function apply(ctx, rawConfig) {
834
907
  return;
835
908
  assertAllowed(modeId, new Set(ctx.permissionPresets.names), "permission mode");
836
909
  ctx.permissionPresets.set(record.agent.session, modeId);
837
- const permissionMode = ctx.permissionPresets.current(record.agent.session.events);
910
+ const permissionMode = ctx.permissionPresets.current(record.agent.session);
838
911
  if (permissionMode !== modeId) {
839
912
  throw internalError(`permission preset ${JSON.stringify(modeId)} did not become the effective mode`);
840
913
  }
@@ -861,9 +934,8 @@ function apply(ctx, rawConfig) {
861
934
  if (record.started) {
862
935
  throw invalidParams("agent preset is fixed after the session has started");
863
936
  }
864
- return ctx.agentPresets.recompose(record.agent.ctx, value).then((preset) => {
865
- record.agent.session.append("agent-preset/selected", { agentPreset: preset.id });
866
- record.agentPreset = preset.id;
937
+ return ctx.agentPresets.select(record.agent, value).then((selectedPreset) => {
938
+ record.agentPreset = selectedPreset;
867
939
  return { configOptions: configOptions(record) };
868
940
  }).catch((error) => {
869
941
  if (error instanceof RequestError)
@@ -992,13 +1064,14 @@ function apply(ctx, rawConfig) {
992
1064
  let permissionMode;
993
1065
  let permissionOptions;
994
1066
  try {
995
- permissionMode = ctx.permissionPresets.current(handle.agent.session.events);
1067
+ permissionMode = ctx.permissionPresets.current(handle.agent.session);
996
1068
  permissionOptions = permissionState(ctx, permissionMode);
997
1069
  } catch (error) {
998
1070
  await dispose();
999
1071
  throw error;
1000
1072
  }
1001
1073
  const record = {
1074
+ usage: new HarnessUsageTracker(!baseUrl || /^https:\/\/api\.deepseek\.com(?:\/v1)?\/?$/.test(baseUrl)),
1002
1075
  agent: handle.agent,
1003
1076
  dispose,
1004
1077
  selection,
@@ -17,8 +17,9 @@
17
17
  - id: persona
18
18
  name: '@deepseek-ai/dsh-persona'
19
19
  config:
20
- text: |-
21
- You are a coding agent powered by the {{model}} model, running on the DeepSeek Harness. Your working directory is {{cwd}}.
20
+ suffix: Your working directory is {{cwd}}.
21
+ prefix: |-
22
+ You are a coding agent powered by the {{model}} model, running on the DeepSeek Harness.
22
23
 
23
24
  You can read and modify the harness you run on. Its composition is Cordis: every capability is a plugin row in a `cordis.yml`, and an agent preset is one such file mounted for a single session.
24
25
 
@@ -76,12 +77,12 @@
76
77
 
77
78
  # ── goals ───────────────────────────────────────────────────────────────────
78
79
 
79
- # Only the model-facing tool. The goal SERVICE, its session driver, and the
80
- # `/goal` command stay on the host plane: the Gateway serves the goal domain as
81
- # Remote endpoints whose receiver comes from a generated descriptor, so it
82
- # resolves `goals` on the host and an entry-local realm here would hide it. The
83
- # registry is keyed by session anyway, so one host instance serves every
84
- # session. What a preset chooses is whether its agent can call the goal tool.
80
+ # The goal service and session driver stay on the host plane, where the Gateway
81
+ # can resolve them. The human command and model-facing tool register into this
82
+ # preset's scoped layers.
83
+ - id: command-goal
84
+ name: '@deepseek-ai/dsh-command-goal'
85
+
85
86
  - id: tool-goal
86
87
  name: '@deepseek-ai/dsh-tool-goal'
87
88
 
@@ -153,12 +154,6 @@
153
154
  # `workflows` is different — nothing outside an agent reads it — so every row
154
155
  # that reaches it shares one entry-local realm here, and a consumer left
155
156
  # outside would resolve a host registry this preset does not populate.
156
- #
157
- # `tool-subagent-report` is host-plane for the same reason as the registry,
158
- # not because a preset may not want it: it registers a CONTINUABLE SETUP on
159
- # that singleton rather than a tool this agent calls, and the setup list is
160
- # not scope-aware — one copy per mounted preset means every child gets
161
- # `report` registered once per live session, which throws on the second.
162
157
  - id: delegation
163
158
  name: cordis:group
164
159
  group: true
@@ -176,8 +171,13 @@
176
171
  config:
177
172
  provider: spawn
178
173
  toolName: subagent
174
+ modelSelectionSettings: true
179
175
  backgroundMode: continuable
180
176
 
177
+ # Fork omits model selection so provider/model stay equal to the parent and
178
+ # the inherited history remains eligible for KV Cache reuse. This preset
179
+ # keeps fork continuable; parent and child inherit the same messaging tool,
180
+ # while the parent id and return guidance follow the inherited history.
181
181
  - id: tool-subagent-fork
182
182
  name: '@deepseek-ai/dsh-tool-subagent'
183
183
  config:
@@ -236,7 +236,7 @@
236
236
  - id: tool-web
237
237
  name: '@deepseek-ai/dsh-tool-web'
238
238
  config:
239
- fetch: false
239
+ fetch: true
240
240
  searchTimeoutMs: 60000
241
241
 
242
242
  # ── self-modification ───────────────────────────────────────────────────────
@@ -261,3 +261,6 @@
261
261
 
262
262
  - id: tool-skill
263
263
  name: '@deepseek-ai/dsh-tool-skill'
264
+
265
+ - id: present
266
+ name: '@deepseek-ai/dsh-tool-present'
@@ -9,7 +9,7 @@ Every capability in this harness is a plugin row in a `cordis.yml`. There is no
9
9
 
10
10
  ## Off-limits
11
11
 
12
- **Never edit, delete, or overwrite a preset that ships with the deployment** — the `agent-presets` directory beside the deployment's own config, which supplies `standard`, `code`, `minimal`, and `cordis`. Never escalate the sandbox to reach it, even when a change there looks quicker. An upgrade overwrites that install, and corrupting `cordis` disables preset authoring itself. Reading a shipped composition is the intended way to start; writing to one is not, and neither is editing the host composition to work around a preset limitation.
12
+ **Never edit, delete, or overwrite a preset that ships with the deployment** — the `agent-presets` directory beside the deployment's own config, which supplies `standard`, `ptc`, `minimal`, and `cordis`. Never escalate the sandbox to reach it, even when a change there looks quicker. An upgrade overwrites that install, and corrupting `cordis` disables preset authoring itself. Reading a shipped composition is the intended way to start; writing to one is not, and neither is editing the host composition to work around a preset limitation.
13
13
 
14
14
  To change what a shipped preset does, copy it and edit the copy. Locally authored presets under the user root are yours to create, edit, and delete.
15
15
 
@@ -1,15 +1,15 @@
1
- # The `minimal` agent preset: a fixed-prompt, two-tool coding-agent composition.
1
+ # The `minimal` agent preset: a fixed-prompt, single-tool coding-agent composition.
2
2
  #
3
3
  # The persona is the complete system prompt, so global identity, Web orientation,
4
4
  # tool guidance, and later assembly listeners cannot add prompt text. Runtime
5
- # context snapshots are suppressed for this preset, and the model composes only
6
- # the persistent shell (`bash` on POSIX, `pwsh` on win32) and
7
- # `str_replace_editor`. Context compaction is absent.
5
+ # context snapshots are suppressed for this preset, and the model receives only
6
+ # the persistent shell (`bash` on POSIX, `pwsh` on win32). Context compaction is
7
+ # absent.
8
8
 
9
9
  - id: persona
10
10
  name: '@deepseek-ai/dsh-persona'
11
11
  config:
12
- text: You are a helpful software engineer assistant.
12
+ prefix: You are a helpful software engineer assistant.
13
13
  complete: true
14
14
  includeRuntimeContext: false
15
15
 
@@ -41,8 +41,7 @@
41
41
  description: |-
42
42
  Run commands in a bash shell
43
43
  * When invoking this tool, the contents of the "command" parameter does NOT need to be XML-escaped.
44
- * You don't have access to the internet via this tool.
45
- * You do have access to a mirror of common linux and python packages via apt and pip.
44
+ * Network access depends on the task environment. Prefer configured mirrors/proxies when they are available.
46
45
  * State is persistent across command calls and discussions with the user.
47
46
  * To inspect a particular line range of a file, e.g. lines 10-25, try 'sed -n 10,25p /path/to/the/file'.
48
47
  * Please avoid commands that may produce a very large amount of output.
@@ -68,21 +67,3 @@
68
67
  * Use native Windows paths (C:\...) and $env:NAME variables; this is PowerShell, not bash.
69
68
  * Please avoid commands that may produce a very large amount of output.
70
69
  * Please run long lived commands in the background, e.g. 'Start-Job' or start a server with Start-Process.
71
-
72
- # The bare local filesystem shadows the host's sandboxed provider only for this
73
- # preset. The editor shares that realm and requires absolute paths.
74
- - id: filesystem
75
- name: cordis:group
76
- group: true
77
- isolate:
78
- fs: true
79
- config:
80
- - id: fs-local
81
- name: '@deepseek-ai/dsh-fs-local'
82
- config:
83
- cwd: !!js process.env.DSH_CWD ?? process.cwd()
84
-
85
- - id: str-replace-editor
86
- name: '@deepseek-ai/dsh-tool-str-replace-editor'
87
- config:
88
- maxOutputChars: 16000
@@ -1,3 +1,3 @@
1
1
  name: 极简模式
2
- description: 仅提供持久 bash str_replace_editor 的双工具编码 Agent。
2
+ description: 仅提供持久 shell 的单工具编码 Agent。
3
3
  order: 3
@@ -1,9 +1,9 @@
1
- # The `code` agent preset: the standard coding agent, presented as Code Mode.
1
+ # The `ptc` agent preset: the standard coding agent, presented as PTC mode.
2
2
  #
3
- # Everything in `standard` is here unchanged. What is added is the `tool-presentation`
4
- # row: instead of one tool call per action, the model writes a TypeScript
5
- # program against a generated SDK and `run_code` executes it, so a sequence
6
- # that would be five round trips becomes one.
3
+ # Most of `standard` is here unchanged. The deliberate exception is the
4
+ # general-purpose `workflow` tool: PTC mode uses `run_code` as its model-authored
5
+ # composition surface. The `tool-presentation` row turns the remaining registry
6
+ # into a generated SDK, so a sequence that would be five round trips becomes one.
7
7
  #
8
8
  # The registry itself stays on the host plane — the agent loop's scheduler and
9
9
  # the API proxy's presenters are its consumers — so what this preset owns is
@@ -31,8 +31,9 @@
31
31
  - id: persona
32
32
  name: '@deepseek-ai/dsh-persona'
33
33
  config:
34
- text: >-
35
- You are a coding agent powered by the {{model}} model. Your working directory is {{cwd}}.
34
+ suffix: Your working directory is {{cwd}}.
35
+ prefix: >-
36
+ You are a coding agent powered by the {{model}} model.
36
37
 
37
38
  - id: agent-instructions
38
39
  name: '@deepseek-ai/dsh-agent-instructions'
@@ -95,12 +96,12 @@
95
96
 
96
97
  # ── goals ───────────────────────────────────────────────────────────────────
97
98
 
98
- # Only the model-facing tool. The goal SERVICE, its session driver, and the
99
- # `/goal` command stay on the host plane: the Gateway serves the goal domain as
100
- # Remote endpoints whose receiver comes from a generated descriptor, so it
101
- # resolves `goals` on the host and an entry-local realm here would hide it. The
102
- # registry is keyed by session anyway, so one host instance serves every
103
- # session. What a preset chooses is whether its agent can call the goal tool.
99
+ # The goal service and session driver stay on the host plane, where the Gateway
100
+ # can resolve them. The human command and model-facing tool register into this
101
+ # preset's scoped layers.
102
+ - id: command-goal
103
+ name: '@deepseek-ai/dsh-command-goal'
104
+
104
105
  - id: tool-goal
105
106
  name: '@deepseek-ai/dsh-tool-goal'
106
107
 
@@ -189,8 +190,13 @@
189
190
  config:
190
191
  provider: spawn
191
192
  toolName: subagent
193
+ modelSelectionSettings: true
192
194
  backgroundMode: continuable
193
195
 
196
+ # Fork omits model selection so provider/model stay equal to the parent and
197
+ # the inherited history remains eligible for KV Cache reuse. This preset
198
+ # keeps fork continuable; parent and child inherit the same messaging tool,
199
+ # while the parent id and return guidance follow the inherited history.
194
200
  - id: tool-subagent-fork
195
201
  name: '@deepseek-ai/dsh-tool-subagent'
196
202
  config:
@@ -227,6 +233,9 @@
227
233
 
228
234
  - id: tool-workflow
229
235
  name: '@deepseek-ai/dsh-tool-workflow'
236
+ # Keep the engine above for `ralph`, but do not publish a second
237
+ # model-authored orchestration surface beside `run_code` in PTC mode.
238
+ disabled: true
230
239
 
231
240
  - id: tool-ralph
232
241
  name: '@deepseek-ai/dsh-tool-ralph'
@@ -249,15 +258,18 @@
249
258
  - id: tool-web
250
259
  name: '@deepseek-ai/dsh-tool-web'
251
260
  config:
252
- fetch: false
261
+ fetch: true
253
262
  searchTimeoutMs: 60000
254
263
 
255
264
  # ── presentation ────────────────────────────────────────────────────────────
256
265
 
257
- # Code Mode for this agent alone. The row waits for the host's `codeRuntime`
266
+ # PTC mode for this agent alone. The row waits for the host's `codeRuntime`
258
267
  # rather than assuming it: a deployment that composes no TypeScript runtime
259
268
  # fails this preset at mount, naming this id, instead of at the first request.
260
269
  - id: tool-presentation
261
270
  name: '@deepseek-ai/dsh-agent-tool-presentation'
262
271
  config:
263
- mode: code
272
+ mode: ptc
273
+
274
+ - id: present
275
+ name: '@deepseek-ai/dsh-tool-present'
@@ -0,0 +1,3 @@
1
+ name: PTC 模式
2
+ description: 功能完整的编码 Agent,但默认不提供 workflow 工具;其他工具通过 PTC 模式 SDK 呈现,让模型用一个 TypeScript 程序组合多步操作。
3
+ order: 2
@@ -24,8 +24,9 @@
24
24
  - id: persona
25
25
  name: '@deepseek-ai/dsh-persona'
26
26
  config:
27
- text: >-
28
- You are a coding agent powered by the {{model}} model. Your working directory is {{cwd}}.
27
+ suffix: Your working directory is {{cwd}}.
28
+ prefix: >-
29
+ You are a coding agent powered by the {{model}} model.
29
30
 
30
31
  - id: agent-instructions
31
32
  name: '@deepseek-ai/dsh-agent-instructions'
@@ -88,12 +89,12 @@
88
89
 
89
90
  # ── goals ───────────────────────────────────────────────────────────────────
90
91
 
91
- # Only the model-facing tool. The goal SERVICE, its session driver, and the
92
- # `/goal` command stay on the host plane: the Gateway serves the goal domain as
93
- # Remote endpoints whose receiver comes from a generated descriptor, so it
94
- # resolves `goals` on the host and an entry-local realm here would hide it. The
95
- # registry is keyed by session anyway, so one host instance serves every
96
- # session. What a preset chooses is whether its agent can call the goal tool.
92
+ # The goal service and session driver stay on the host plane, where the Gateway
93
+ # can resolve them. The human command and model-facing tool register into this
94
+ # preset's scoped layers.
95
+ - id: command-goal
96
+ name: '@deepseek-ai/dsh-command-goal'
97
+
97
98
  - id: tool-goal
98
99
  name: '@deepseek-ai/dsh-tool-goal'
99
100
 
@@ -165,12 +166,6 @@
165
166
  # `workflows` is different — nothing outside an agent reads it — so every row
166
167
  # that reaches it shares one entry-local realm here, and a consumer left
167
168
  # outside would resolve a host registry this preset does not populate.
168
- #
169
- # `tool-subagent-report` is host-plane for the same reason as the registry,
170
- # not because a preset may not want it: it registers a CONTINUABLE SETUP on
171
- # that singleton rather than a tool this agent calls, and the setup list is
172
- # not scope-aware — one copy per mounted preset means every child gets
173
- # `report` registered once per live session, which throws on the second.
174
169
  - id: delegation
175
170
  name: cordis:group
176
171
  group: true
@@ -188,8 +183,13 @@
188
183
  config:
189
184
  provider: spawn
190
185
  toolName: subagent
186
+ modelSelectionSettings: true
191
187
  backgroundMode: continuable
192
188
 
189
+ # Fork omits model selection so provider/model stay equal to the parent and
190
+ # the inherited history remains eligible for KV Cache reuse. This preset
191
+ # keeps fork continuable; parent and child inherit the same messaging tool,
192
+ # while the parent id and return guidance follow the inherited history.
193
193
  - id: tool-subagent-fork
194
194
  name: '@deepseek-ai/dsh-tool-subagent'
195
195
  config:
@@ -248,5 +248,8 @@
248
248
  - id: tool-web
249
249
  name: '@deepseek-ai/dsh-tool-web'
250
250
  config:
251
- fetch: false
251
+ fetch: true
252
252
  searchTimeoutMs: 60000
253
+
254
+ - id: present
255
+ name: '@deepseek-ai/dsh-tool-present'
package/dist/grok-acp.js CHANGED
@@ -2,6 +2,7 @@ import { createInterface } from "node:readline";
2
2
  import { r as runtimeManifest } from "./chunks/runtime-manifest-FeOT7MvW.js";
3
3
  import { L as LODY_EXTENSION_METHODS } from "./chunks/methods-CvEDMahD.js";
4
4
  import { L as LODY_PLAN_MODE_CONFIG_ID, c as createPlanModeConfigOption } from "./chunks/plan-mode-BLOER-fv.js";
5
+ import { S as SessionUsageAccumulator } from "./chunks/usage-DY4Eg1Mf.js";
5
6
  import { spawn } from "node:child_process";
6
7
  function update(sessionId, value) {
7
8
  return { jsonrpc: "2.0", method: "session/update", params: { sessionId, update: value } };
@@ -260,10 +261,6 @@ function normalizePromptUsage(promptUsage) {
260
261
  ...Object.keys(modelUsage).length ? { modelUsage } : {}
261
262
  };
262
263
  }
263
- function usageNotification(sessionId, promptUsage) {
264
- const params = normalizePromptUsage(promptUsage);
265
- return params && lodyNotification(LODY_EXTENSION_METHODS.sessionUsageUpdate, { sessionId, ...params });
266
- }
267
264
  function normalizeBillingRateLimits(billing) {
268
265
  if (!billing || typeof billing !== "object") return void 0;
269
266
  const config = billing.config;
@@ -557,10 +554,14 @@ class GrokAcpCompatibilityProxy {
557
554
  return this.usageRefreshRequests(state.sessionId);
558
555
  }
559
556
  usageForPrompt(state, promptId, promptUsage) {
560
- const notification = usageNotification(state.sessionId, promptUsage);
561
- if (!notification) return void 0;
562
- if (!rememberPrompt(state.usagePromptIds, promptId)) return void 0;
563
- return notification;
557
+ const normalized = normalizePromptUsage(promptUsage);
558
+ if (!normalized || promptId == null) return void 0;
559
+ const modelUsage = normalized.modelUsage;
560
+ if (!modelUsage) return void 0;
561
+ const update2 = state.usageAccumulator.update(state.sessionId, String(promptId), modelUsage);
562
+ if (!update2) return void 0;
563
+ update2.usage = normalized.usage;
564
+ return lodyNotification(LODY_EXTENSION_METHODS.sessionUsageUpdate, update2);
564
565
  }
565
566
  handleClient(message) {
566
567
  if (!message || typeof message !== "object") return { toRuntime: [message], toClient: [] };
@@ -914,7 +915,7 @@ class GrokAcpCompatibilityProxy {
914
915
  })),
915
916
  reasoningEfforts,
916
917
  reasoningEffort: legacyEfforts.find((option) => option.selected)?.id ?? old?.reasoningEffort ?? reasoningEfforts[0],
917
- usagePromptIds: old?.usagePromptIds ?? /* @__PURE__ */ new Set(),
918
+ usageAccumulator: old?.usageAccumulator ?? new SessionUsageAccumulator(),
918
919
  usageRefreshPromptIds: old?.usageRefreshPromptIds ?? /* @__PURE__ */ new Set()
919
920
  };
920
921
  }