dsh-acp-enhanced 0.2.0 → 0.2.1
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-en.md +95 -224
- package/README.md +82 -180
- package/package.json +1 -1
package/README-en.md
CHANGED
|
@@ -3,65 +3,74 @@
|
|
|
3
3
|
# dsh-acp-enhanced
|
|
4
4
|
|
|
5
5
|
An enhanced [Agent Client Protocol](https://agentclientprotocol.com) (ACP) server for
|
|
6
|
-
[DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (dsh), built for
|
|
7
|
-
editors like **Zed
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
6
|
+
[DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (dsh), built for ACP
|
|
7
|
+
editors like **Zed**. It is a drop-in replacement for the official `@deepseek-ai/dsh-acp`
|
|
8
|
+
bridge: the official bridge only streams plain text, this one exposes the Web GUI's
|
|
9
|
+
capabilities — streaming, telemetry, model/permission control, session management, MCP —
|
|
10
|
+
over the ACP wire.
|
|
11
|
+
|
|
12
|
+
## Features
|
|
13
|
+
|
|
14
|
+
### Output & telemetry
|
|
15
|
+
|
|
16
|
+
- **Block + reasoning streaming**: text blocks and the model's thinking arrive live
|
|
17
|
+
(`agent_message_chunk` / `agent_thought_chunk`); cancelled/retried attempts never leak
|
|
18
|
+
torn output
|
|
19
|
+
- **Full telemetry**: context usage ring plus cache hit rate / TPS / input-output-reasoning
|
|
20
|
+
tokens / tool timing / turn counts (`usage_update._meta` carries the full breakdown)
|
|
21
|
+
|
|
22
|
+
### Model & permissions
|
|
23
|
+
|
|
24
|
+
- **Model switching**: live `provider/model` catalog dropdown (ACP grouped-select wire shape)
|
|
25
|
+
- **Reasoning effort**: `reasoning_effort` dropdown — only when the routed model exposes
|
|
26
|
+
selectable efforts
|
|
27
|
+
- **Permission presets**: read-only / workspace-write / full-access session modes
|
|
28
|
+
- **Approval**: native allow-once / reject-once prompts per tool call
|
|
29
|
+
|
|
30
|
+
### Zed deep integration
|
|
31
|
+
|
|
32
|
+
- **Tool cards**: expand to see each call's full arguments and result preview
|
|
33
|
+
(`rawInput` / `rawOutput`), with per-kind icons
|
|
34
|
+
- **Zed files & terminal**: `zed_read_text_file` / `zed_write_text_file` / `zed_terminal`
|
|
35
|
+
put file edits into Zed's "edited files" area (diff + accept/reject) and commands into a
|
|
36
|
+
real Zed terminal
|
|
37
|
+
- **Native form questions**: `ask_user_question` → `elicitation/create` form, click an
|
|
38
|
+
option, no typing
|
|
39
|
+
- **Plan panel**: plan mode toggle → "planning" status bar in Zed
|
|
40
|
+
|
|
41
|
+
### Sessions
|
|
42
|
+
|
|
43
|
+
- **Resume & archive**: `session/load` restores past threads (full replay);
|
|
44
|
+
`session/list` / `session/delete` manage the thread archive (titled, sorted by last
|
|
45
|
+
activity); live title updates
|
|
46
|
+
|
|
47
|
+
### MCP
|
|
48
|
+
|
|
49
|
+
- **MCP servers**: `session/new` `mcpServers` mount any MCP server (stdio + streamable
|
|
50
|
+
HTTP); tools join as `mcp__<server>__<tool>`; a failing server never takes the session
|
|
51
|
+
down
|
|
31
52
|
|
|
32
53
|
## Preview
|
|
33
54
|
|
|
34
|
-
After picking **dsh-acp-enhanced** in Zed's AI Agent panel
|
|
55
|
+
After picking **dsh-acp-enhanced** in Zed's AI Agent panel:
|
|
35
56
|
|
|
36
57
|
<img src="assets/screenshots/approval-config-context.png" alt="Approval popup, model/reasoning-effort switches, context ring" width="560">
|
|
37
58
|
|
|
38
|
-
- Tool calls that need permission pop a **native approval prompt
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
ring** (`usage_update` telemetry with cache hit rate, TPS, and more).
|
|
59
|
+
- Tool calls that need permission pop a **native approval prompt**; below the input box sit
|
|
60
|
+
the model, reasoning effort, permission preset, plan mode options and the context usage
|
|
61
|
+
ring.
|
|
42
62
|
|
|
43
63
|
<img src="assets/screenshots/tool-cards-elicitation.png" alt="Tool call inputs and outputs, native Zed question form" width="320">
|
|
44
64
|
|
|
45
|
-
- **Tool cards** expand to show
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
(`ask_user_question` → `elicitation/create`) — click an option, no typing.
|
|
49
|
-
|
|
50
|
-
> **Repository layout** — this repo contains two independent packages:
|
|
51
|
-
> - `dsh-acp-enhanced` (repo root): the enhanced ACP bridge (`lib/index.js`).
|
|
52
|
-
> - `packages/dsh-web-search-openrouter/`: a standalone `ctx.web` search provider that
|
|
53
|
-
> routes `web_search` through any OpenAI-Responses gateway instead of DeepSeek's
|
|
54
|
-
> Anthropic `/messages` endpoint. It deliberately does **not** couple to the ACP
|
|
55
|
-
> bridge, so any profile (the Web GUI included) can mount it.
|
|
65
|
+
- **Tool cards** expand to show full arguments and result previews; when dsh needs your
|
|
66
|
+
confirmation or a choice, the question arrives as a **native Zed form** — click an
|
|
67
|
+
option, no typing.
|
|
56
68
|
|
|
57
69
|
## Quick start
|
|
58
70
|
|
|
59
|
-
This package follows the official dsh plugin conventions (it declares `dsh.bundle`),
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
`dsh-base` already carries the whole agent stack), installs the package, and
|
|
63
|
-
**auto-appends it to the profile's bundle layers**. The shipped patch inserts the
|
|
64
|
-
`acp-enhanced` row and overrides the default model route — **no profile YAML to write**.
|
|
71
|
+
This package follows the official dsh plugin conventions (it declares `dsh.bundle`), so
|
|
72
|
+
installation matches any official bundle: **one command** — auto-initializes the profile,
|
|
73
|
+
installs the package, appends the bundle layer; no profile YAML to write.
|
|
65
74
|
|
|
66
75
|
### Install (2 steps)
|
|
67
76
|
|
|
@@ -71,16 +80,12 @@ so installation is identical to any official bundle: **one command** —
|
|
|
71
80
|
dsh plugin --profile acp-enhanced add dsh-acp-enhanced
|
|
72
81
|
```
|
|
73
82
|
|
|
74
|
-
> When hacking on the code
|
|
75
|
-
> edits, no registry round-trip):
|
|
83
|
+
> When hacking on the code, use `link:` to a local checkout instead (live edits):
|
|
76
84
|
> `dsh plugin --profile acp-enhanced add "link:/absolute/path/to/dsh-acp-enhanced"`
|
|
77
85
|
|
|
78
|
-
**Step 2 — register in Zed** (
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
Register the agent under `agent_servers` in `~/.config/zed/settings.json`. Zed (a GUI
|
|
82
|
-
app) spawns agent processes with a minimal PATH, so use the shipped launcher
|
|
83
|
-
`scripts/dsh-acp-zed.sh` (it locates `node`/`dsh` itself).
|
|
86
|
+
**Step 2 — register in Zed** (under `agent_servers` in `~/.config/zed/settings.json`;
|
|
87
|
+
Zed spawns agents with a minimal PATH, so use the shipped launcher
|
|
88
|
+
`scripts/dsh-acp-zed.sh`, which locates `node`/`dsh` itself)
|
|
84
89
|
|
|
85
90
|
#### Most common: DeepSeek official API (the default route)
|
|
86
91
|
|
|
@@ -101,16 +106,12 @@ app) spawns agent processes with a minimal PATH, so use the shipped launcher
|
|
|
101
106
|
}
|
|
102
107
|
```
|
|
103
108
|
|
|
104
|
-
>
|
|
105
|
-
>
|
|
106
|
-
>
|
|
107
|
-
>
|
|
108
|
-
> `~/.dsh/.credentials.yaml` (`DEEPSEEK_API_KEY`, mode 600) and the dsh credentials
|
|
109
|
-
> service resolves it; the launcher additionally falls back to inheriting the key from
|
|
110
|
-
> a running `dsh web` process.
|
|
109
|
+
> Both env vars match the shipped patch's defaults, so **they can be omitted entirely** —
|
|
110
|
+
> writing them out just makes the route explicit. The API key does not have to live in Zed:
|
|
111
|
+
> store it in `~/.dsh/.credentials.yaml` (`DEEPSEEK_API_KEY`) and the dsh credentials
|
|
112
|
+
> service resolves it; the launcher also falls back to a running `dsh web` process's key.
|
|
111
113
|
|
|
112
|
-
Optional: pin the panel's default config options (
|
|
113
|
-
effort; all still changeable in the panel at any time):
|
|
114
|
+
Optional: pin the panel's default config options (all still changeable in the panel):
|
|
114
115
|
|
|
115
116
|
```jsonc
|
|
116
117
|
"dsh-acp-enhanced": {
|
|
@@ -128,8 +129,8 @@ effort; all still changeable in the panel at any time):
|
|
|
128
129
|
|
|
129
130
|
#### Extended: route through an OpenAI-Responses gateway (e.g. a company model gateway)
|
|
130
131
|
|
|
131
|
-
Same install path; only the env values change to the provider/model the gateway
|
|
132
|
-
|
|
132
|
+
Same install path; only the env values change to the provider/model the gateway exposes
|
|
133
|
+
plus the key env var it requires:
|
|
133
134
|
|
|
134
135
|
```jsonc
|
|
135
136
|
"dsh-acp-enhanced": {
|
|
@@ -144,39 +145,32 @@ exposes plus the key env var it requires:
|
|
|
144
145
|
}
|
|
145
146
|
```
|
|
146
147
|
|
|
147
|
-
> `<KEY_ENV_NAME>`
|
|
148
|
-
>
|
|
149
|
-
> `~/.dsh/.credentials.yaml` and let the dsh credentials service manage it. Every
|
|
150
|
-
> route uses the same install path; only the env values differ.
|
|
148
|
+
> `<KEY_ENV_NAME>` can also be omitted and the key stored in
|
|
149
|
+
> `~/.dsh/.credentials.yaml` instead.
|
|
151
150
|
|
|
152
151
|
Zed hot-reloads settings. Open the **AI Agent panel** (`Cmd+Shift+A`) → pick
|
|
153
|
-
**dsh-acp-enhanced** in the
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
152
|
+
**dsh-acp-enhanced** in the agent selector → send your first message: replies stream in
|
|
153
|
+
real time, the status bar shows context usage, the panel exposes Model / Permission preset
|
|
154
|
+
/ Plan mode options plus three modes, and the thread archive lists and resumes past
|
|
155
|
+
sessions.
|
|
157
156
|
|
|
158
157
|
Verify locally (no Zed needed):
|
|
159
158
|
|
|
160
159
|
```sh
|
|
161
160
|
node scripts/acp-client.mjs # official default route, no env; expect ALL CHECKS PASSED
|
|
162
161
|
DSH_ACP_PROVIDER=... DSH_ACP_MODEL=... node scripts/acp-client.mjs # only for a custom route
|
|
163
|
-
env -i HOME=$HOME PATH=/usr/bin:/bin node scripts/acp-client.mjs \
|
|
164
|
-
/bin/bash /absolute/path/to/dsh-acp-enhanced/scripts/dsh-acp-zed.sh # Zed-like spawn
|
|
165
162
|
```
|
|
166
163
|
|
|
167
164
|
### Optional: route web_search through the same gateway
|
|
168
165
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
`cordis.patch.yml`:
|
|
166
|
+
If the gateway implements the OpenAI Responses `web_search` server tool, you can route
|
|
167
|
+
search through it too (reusing the same credential). Install the sub-package and append
|
|
168
|
+
two blocks to the profile's `cordis.patch.yml`:
|
|
173
169
|
|
|
174
170
|
```sh
|
|
175
|
-
dsh plugin --profile acp-enhanced add
|
|
171
|
+
dsh plugin --profile acp-enhanced add dsh-web-search-openrouter
|
|
176
172
|
```
|
|
177
173
|
|
|
178
|
-
`~/.dsh/profiles/acp-enhanced/cordis.patch.yml` (`<provider>` is your gateway provider id):
|
|
179
|
-
|
|
180
174
|
```yaml
|
|
181
175
|
- id: web
|
|
182
176
|
config:
|
|
@@ -192,153 +186,30 @@ dsh plugin --profile acp-enhanced add "link:/absolute/path/to/dsh-acp-enhanced/p
|
|
|
192
186
|
apiKeyEnv: <KEY_ENV_NAME>
|
|
193
187
|
```
|
|
194
188
|
|
|
195
|
-
|
|
189
|
+
## Troubleshooting
|
|
196
190
|
|
|
197
|
-
| Symptom |
|
|
191
|
+
| Symptom | Fix |
|
|
198
192
|
|---|---|
|
|
199
|
-
| `
|
|
200
|
-
| `no API key for provider route "
|
|
201
|
-
|
|
|
202
|
-
|
|
|
203
|
-
| `session/new` reports `additionalDirectories is not supported` | The bridge only supports baseline sessions; Zed does not send extra directories by default — remove them if a custom config sends them. |
|
|
204
|
-
| Need detailed diagnostics | Start with `ACP_DEBUG=1 dsh --profile acp-enhanced` (lifecycle trace on stderr). |
|
|
193
|
+
| `exec: dsh: not found` (status 127) | Use the shipped `dsh-acp-zed.sh` launcher (locates node/dsh itself) |
|
|
194
|
+
| `no API key for provider route "xxx"` | Write `~/.dsh/.credentials.yaml`, or set `env.DEEPSEEK_API_KEY` on the agent_servers entry |
|
|
195
|
+
| Cannot switch models / context usage missing | A "phantom provider" route was picked; this bridge filters them by default (only `config.provider`'s models are advertised) — point the profile's provider at a real route |
|
|
196
|
+
| Need detailed diagnostics | `ACP_DEBUG=1 dsh --profile acp-enhanced` (stderr lifecycle trace) |
|
|
205
197
|
|
|
206
198
|
## Development
|
|
207
199
|
|
|
208
200
|
```sh
|
|
209
|
-
node scripts/acp-client.mjs # end-to-end smoke
|
|
201
|
+
node scripts/acp-client.mjs # end-to-end smoke (needs an API key)
|
|
210
202
|
node scripts/acp-client-tools.mjs # client-tool tests (mocks Zed fs/terminal/elicitation/plan)
|
|
211
|
-
node scripts/acp-mcp-test.mjs # MCP mount test (
|
|
212
|
-
node scripts/acp-smoke-keyless.mjs # keyless boot smoke (CI
|
|
213
|
-
node scripts/acp-resume-test.mjs # resume
|
|
214
|
-
ACP_DEBUG=1 dsh --profile acp-enhanced # lifecycle trace on stderr
|
|
203
|
+
node scripts/acp-mcp-test.mjs # MCP mount test (no model calls)
|
|
204
|
+
node scripts/acp-smoke-keyless.mjs # keyless boot smoke (CI)
|
|
205
|
+
node scripts/acp-resume-test.mjs # session resume test
|
|
215
206
|
```
|
|
216
207
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
`
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
(on → entry, off → cleared), and that the `reasoning_effort` option is route-conditional
|
|
226
|
-
(present with non-empty options on routes with efforts, suppressed on routes without).
|
|
227
|
-
`acp-mcp-test.mjs` uses `scripts/fixtures/mcp-echo-server.mjs` to verify that
|
|
228
|
-
`session/new` `mcpServers` are really mounted (the server receives initialize and
|
|
229
|
-
tools/list) and that an identical list is reused, not re-mounted.
|
|
230
|
-
|
|
231
|
-
## Design notes
|
|
232
|
-
|
|
233
|
-
- **Block-level streaming**: text deltas accumulate per block index; a committed
|
|
234
|
-
`block-end` goes on the wire immediately. A retry restarts the same index, so the
|
|
235
|
-
torn tail of a cancelled attempt never reaches the client — ACP has no undo, and
|
|
236
|
-
this is the cleanest boundary.
|
|
237
|
-
- **Telemetry**: every provider `usage` sample is broadcast as `usage_update`
|
|
238
|
-
(used = input + cache read + cache write; size = the routed model's context
|
|
239
|
-
window), with the full breakdown in `_meta`: input/output/cache/reasoning tokens,
|
|
240
|
-
`cacheHitRate`, `tps` (generated tokens / step wall-clock), step elapsed, turn
|
|
241
|
-
count, and cumulative tool-call stats.
|
|
242
|
-
- **Tool-call visibility**: `tool_call` notifications carry `kind` and `rawInput`
|
|
243
|
-
(`JSON.parse` of the arguments, falling back to the raw string), so Zed's tool
|
|
244
|
-
cards expand to show the exact arguments (bash command, written file, ...);
|
|
245
|
-
`tool_call_update` carries `rawOutput` (a bounded text preview extracted from the
|
|
246
|
-
`ToolResultMessage`, truncated at 12k). **A key constraint on `kind` mapping**: Zed
|
|
247
|
-
treats `kind == 'execute'` as a terminal tool and `kind == 'edit'` as a diff tool,
|
|
248
|
-
and **hides rawInput for both**. So only `zed_terminal` (a real editor terminal)
|
|
249
|
-
maps to `execute`; bash/run_code/write tools stay `other` so rawInput renders —
|
|
250
|
-
otherwise the card shows only the tool name with no command. Also note the dsh
|
|
251
|
-
`tool/result` event carries `toolCallId` on `message.content[0].toolCallId`
|
|
252
|
-
(the `ToolResultBlock`), not on the event root — missing it makes the SDK reject
|
|
253
|
-
the whole `tool_call_update`. History replay (resume) carries the same fields.
|
|
254
|
-
- **Session config**: the `model` select enumerates the live model catalog
|
|
255
|
-
(`ctx.llm.listProviders` → `listModels` → `resolveModelInfo`), `reasoning_effort`
|
|
256
|
-
enumerates the routed model's efforts, `permission_preset` enumerates the mounted
|
|
257
|
-
presets. Writes go through `llm.resolveCallConfig` + `installModelSelection` (the
|
|
258
|
-
same mechanism the Web api-proxy uses) or `permissionPresets.apply`.
|
|
259
|
-
- **Model grouped-select wire shape**: the `model` option's groups must use the ACP
|
|
260
|
-
shape `{ group: <id>, name: <label>, options: [...] }`. An early version emitted
|
|
261
|
-
`{ groupName, options }`; Zed (`agent-client-protocol-schema` 1.4.0) silently
|
|
262
|
-
skipped the whole group on deserialization (`DefaultOnError` + `VecSkipError`),
|
|
263
|
-
leaving the dropdown empty — and the SDK mock client does not validate agent
|
|
264
|
-
responses, so tests missed it. Now `acp-client-tools.mjs` runs
|
|
265
|
-
`zSessionConfigOption.safeParse` on every config option, so this class of wire bug
|
|
266
|
-
cannot slip through again.
|
|
267
|
-
- **Reasoning-effort route limitation**: the `reasoning_effort` option is advertised
|
|
268
|
-
only when the routed model **exposes** efforts (`resolveModelInfo().reasoning.efforts`
|
|
269
|
-
non-empty). On routes without efforts, explicitly setting one is rejected by the
|
|
270
|
-
adapter (`does not support reasoning effort "high"`) — so the absence of an effort
|
|
271
|
-
dropdown there is **correct behavior**, not a bug; switching to a route that exposes
|
|
272
|
-
efforts makes the dropdown reappear automatically.
|
|
273
|
-
- **Model-catalog filtering (`includeAllProviders`, default off)**: by default only
|
|
274
|
-
`config.provider` models are advertised, keeping "ghost providers" (adapters that
|
|
275
|
-
are mounted but not routable — e.g. a `deepseek-official` with no usable API key)
|
|
276
|
-
out of the dropdown. Those models look switchable but every later prompt fails with
|
|
277
|
-
`MISSING_CREDENTIAL` (`no API key for provider route "xxx"`) — in Zed that shows up
|
|
278
|
-
as "cannot switch models, and no `usage_update` arrives because the turn failed"
|
|
279
|
-
(the Web GUI shows an unavailable banner for the current item; Zed does not, so the
|
|
280
|
-
same data looks broken there). Set `includeAllProviders: true` when multiple
|
|
281
|
-
providers are genuinely usable.
|
|
282
|
-
- **Default model cannot be poisoned**: `applySelection` persists the new selection as
|
|
283
|
-
the `agent-default-model` default only when `selected.provider === config.provider`
|
|
284
|
-
(or explicit `includeAllProviders`). An accidental switch to a non-routable provider
|
|
285
|
-
therefore affects only the current session and never corrupts the default route of
|
|
286
|
-
every later session.
|
|
287
|
-
- **Client-forwarding tools (Zed fs / terminal)**: on `initialize` the bridge reads
|
|
288
|
-
`clientCapabilities` and registers `zed_read_text_file` / `zed_write_text_file` /
|
|
289
|
-
`zed_terminal` (`ctx.tools.register` + `defineTool`) only when the client declares
|
|
290
|
-
the matching capabilities. Tool bodies forward to the editor via
|
|
291
|
-
`conn.readTextFile` / `conn.writeTextFile` / `conn.createTerminal`:
|
|
292
|
-
`zed_write_text_file` lands edits on Zed's own buffer (the "Edited files" section
|
|
293
|
-
with diff + accept/reject); `zed_terminal` runs the command in a real Zed terminal
|
|
294
|
-
and polls output (`terminal/output` is cumulative — take the last one), killing after
|
|
295
|
-
120s. Clients without those capabilities (e.g. pure automation) never see these tools.
|
|
296
|
-
- **Zed form elicitation**: when the client declares `elicitation.form`, the bridge
|
|
297
|
-
registers the `ask_user_question` tool (mirroring `dsh-tool-ask-user`'s definition
|
|
298
|
-
through the `ctx.userQuestions` seam) plus the matching UI provider: questions map
|
|
299
|
-
to an ACP `elicitation/create` (form mode) JSON Schema (single choice → `string` +
|
|
300
|
-
`enum`, multi → `array`, none → bare `string`); the user's native-form answer maps
|
|
301
|
-
back to `AskUserQuestionAnswer` for the model. decline/cancel end the tool call with
|
|
302
|
-
an error the model can route around. Note Zed's elicitation capability is an object
|
|
303
|
-
(`form: {}`), not a boolean — check for presence, not `=== true`.
|
|
304
|
-
- **Plan panel**: the `plan_mode` boolean config option toggles DSH plan mode via
|
|
305
|
-
`ctx.planMode.set(agent, active)`; `plan/mode` flips in `session/event` map to ACP
|
|
306
|
-
`plan` updates — one "planning" entry while active, cleared on exit. DSH plan mode
|
|
307
|
-
has no structured task list, so this is a state indicator, not a task list. The ACP
|
|
308
|
-
`plan` update is **flat** (`{ sessionUpdate: 'plan', entries: [...] }`), not
|
|
309
|
-
`{ plan: {...} }`.
|
|
310
|
-
- **Session resume (session/load)**: `initialize` declares `loadSession: true`;
|
|
311
|
-
`session/load` resumes the persisted agent via `ctx.agents.resume({ resumeSessionId })`
|
|
312
|
-
(`dsh-session-persistence-jsonl`, mounted by dsh-base), then replays history from the
|
|
313
|
-
event log: `user/message` (only `source.kind === 'user'` — synthetic injections like
|
|
314
|
-
system reminders and skill content are filtered) → `user_message_chunk`,
|
|
315
|
-
`assistant/message` text → `agent_message_chunk`, `tool/call`/`tool/result` →
|
|
316
|
-
`tool_call`/`tool_call_update`. Zed inserts the thread before the load RPC
|
|
317
|
-
completes, so the replay notifications reach it. After replay the session behaves
|
|
318
|
-
like a fresh one for further prompts.
|
|
319
|
-
- **Session archive list (session/list + session/delete)**: `initialize` declares
|
|
320
|
-
`sessionCapabilities: { list: {}, delete: {} }`; `session/list` enumerates
|
|
321
|
-
materialized sessions via `ctx.sessionPersistence.list()` (`SessionHeader`:
|
|
322
|
-
id/cwd/createdAt), titles come from live `session/title` events or are read
|
|
323
|
-
best-effort from the stored log (the last `session/title` event; oversized logs are
|
|
324
|
-
skipped), sorted by `updatedAt` descending. `session/delete` disposes the live agent
|
|
325
|
-
(`sessions.delete` + `dispose`), then removes the session's directory via
|
|
326
|
-
`persistence.locate(header)` — note dsh's persistence surface has **no official
|
|
327
|
-
delete API**, so this removes the backend directory directly. Live title/activity
|
|
328
|
-
changes are pushed as `session_info_update` notifications (`session/title` and
|
|
329
|
-
`turn/end` events).
|
|
330
|
-
- **Empty-effort suppression**: when the routed model exposes no reasoning efforts,
|
|
331
|
-
the `reasoning_effort` option is not advertised — Zed renders no empty, inoperable
|
|
332
|
-
"Reasoning effort" chip. Switching to a model with efforts makes the option reappear
|
|
333
|
-
(every switch replays `config_option_update`).
|
|
334
|
-
- **Modes**: permission presets are presented as ACP session modes, so Zed's mode
|
|
335
|
-
switcher drives the sandbox/approval presets.
|
|
336
|
-
- **Known limitations** (inherited from the official bridge): baseline prompts only
|
|
337
|
-
(no image/audio attachments), no `additionalDirectories`, committed text streams at
|
|
338
|
-
block granularity, and one in-flight prompt per session. MCP servers (stdio +
|
|
339
|
-
streamable HTTP) are supported; legacy SSE and `acp` transports are not advertised.
|
|
340
|
-
Session resume and the archive list are supported (see above), but
|
|
341
|
-
`session/close` / `session/fork` / `session/resume` are not implemented (the
|
|
342
|
-
capabilities are not declared, so conforming clients do not call them);
|
|
343
|
-
`session/delete` removes the backend directory directly because dsh's persistence
|
|
344
|
-
surface has no official delete API.
|
|
208
|
+
## Known limitations
|
|
209
|
+
|
|
210
|
+
Baseline prompts only (no image/audio attachments), no `additionalDirectories`, text
|
|
211
|
+
streams at block granularity, one in-flight prompt per session. MCP supports stdio and
|
|
212
|
+
streamable HTTP (legacy SSE / `acp` transports are not advertised).
|
|
213
|
+
`session/close` / `session/fork` / `session/resume` are not implemented (capabilities
|
|
214
|
+
undeclared, compliant clients will not call them); `session/delete` removes the persisted
|
|
215
|
+
directory directly because dsh persistence has no official delete API.
|
package/README.md
CHANGED
|
@@ -3,76 +3,79 @@
|
|
|
3
3
|
# dsh-acp-enhanced
|
|
4
4
|
|
|
5
5
|
面向 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(dsh)的增强版
|
|
6
|
-
[Agent Client Protocol](https://agentclientprotocol.com)(ACP
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
6
|
+
[Agent Client Protocol](https://agentclientprotocol.com)(ACP)服务器,为 **Zed** 等 ACP
|
|
7
|
+
编辑器设计。它是官方 `@deepseek-ai/dsh-acp` 桥接器的即插即用替代品:官方桥只做纯文本
|
|
8
|
+
输出,本桥把 Web GUI 的能力(流式、遥测、模型/权限控制、会话管理、MCP)全部暴露到
|
|
9
|
+
ACP 线上。
|
|
10
|
+
|
|
11
|
+
## 特性
|
|
12
|
+
|
|
13
|
+
### 输出与遥测
|
|
14
|
+
|
|
15
|
+
- **块级流式 + 推理流式**:文本块与思考过程实时到达(`agent_message_chunk` /
|
|
16
|
+
`agent_thought_chunk`),取消/重试不留半截输出
|
|
17
|
+
- **完整遥测**:上下文用量环 + 缓存命中率 / TPS / 输入-输出-推理 token / 工具耗时 /
|
|
18
|
+
轮次计数(`usage_update._meta` 携带全量明细)
|
|
19
|
+
|
|
20
|
+
### 模型与权限
|
|
21
|
+
|
|
22
|
+
- **模型切换**:实时 `provider/model` 目录下拉(按 ACP 规范分组线格式)
|
|
23
|
+
- **推理强度**:`reasoning_effort` 下拉——仅当当前路由暴露可选 efforts 时出现
|
|
24
|
+
- **权限预设**:read-only / workspace-write / full-access 三种会话模式
|
|
25
|
+
- **审批**:工具调用弹出原生 allow-once / reject-once 审批
|
|
26
|
+
|
|
27
|
+
### Zed 深度集成
|
|
28
|
+
|
|
29
|
+
- **工具卡片**:展开可见每次调用的完整参数与结果预览(`rawInput` / `rawOutput`),
|
|
30
|
+
按工具类型渲染图标
|
|
31
|
+
- **Zed 文件与终端**:`zed_read_text_file` / `zed_write_text_file` / `zed_terminal` 把
|
|
32
|
+
文件编辑放进 Zed 的"编辑文件"区(diff + 接受/拒绝)、命令跑在 Zed 真实终端
|
|
33
|
+
- **原生表单提问**:`ask_user_question` → `elicitation/create` 表单,选项即点即答
|
|
34
|
+
- **Plan 面板**:plan mode 开关 → Zed 底部"规划中"状态条
|
|
35
|
+
|
|
36
|
+
### 会话
|
|
37
|
+
|
|
38
|
+
- **恢复与归档**:`session/load` 恢复历史线程(完整回放);`session/list` /
|
|
39
|
+
`session/delete` 管理线程归档(带标题、按更新时间排序);标题实时推送
|
|
40
|
+
|
|
41
|
+
### MCP
|
|
42
|
+
|
|
43
|
+
- **MCP servers**:`session/new` 的 `mcpServers` 挂载任意 MCP server(stdio +
|
|
44
|
+
streamable HTTP),工具以 `mcp__<server>__<tool>` 注入;失败的 server 不会拖垮会话
|
|
31
45
|
|
|
32
46
|
## 效果预览
|
|
33
47
|
|
|
34
|
-
在 Zed 的 AI Agent 面板中选择 **dsh-acp-enhanced**
|
|
48
|
+
在 Zed 的 AI Agent 面板中选择 **dsh-acp-enhanced** 后:
|
|
35
49
|
|
|
36
50
|
<img src="assets/screenshots/approval-config-context.png" alt="审批弹窗与模型/推理强度切换、上下文环" width="560">
|
|
37
51
|
|
|
38
|
-
-
|
|
39
|
-
|
|
40
|
-
(`usage_update` 遥测,含缓存命中率、TPS 等明细)。
|
|
52
|
+
- 工具调用需要许可时弹出**原生审批弹窗**;输入框下方是模型、推理强度、权限预设、
|
|
53
|
+
Plan mode 配置项与上下文用量环。
|
|
41
54
|
|
|
42
55
|
<img src="assets/screenshots/tool-cards-elicitation.png" alt="工具调用入参与输出、Zed 原生提问表单" width="320">
|
|
43
56
|
|
|
44
|
-
-
|
|
45
|
-
|
|
46
|
-
(`ask_user_question` → `elicitation/create`),选项即点即答,无需手动输入。
|
|
47
|
-
|
|
48
|
-
> **仓库结构** —— 本仓库包含两个相互独立的包:
|
|
49
|
-
> - `dsh-acp-enhanced`(仓库根目录):增强版 ACP 桥接器(`lib/index.js`)。
|
|
50
|
-
> - `packages/dsh-web-search-openrouter/`:独立的 `ctx.web` 搜索 provider,让 `web_search`
|
|
51
|
-
> 走任意 OpenAI-Responses 网关,而非 DeepSeek 的 Anthropic `/messages` 端点。它刻意
|
|
52
|
-
> **不**与 ACP 桥接器耦合,因此任何 profile(包括 Web GUI)都可挂载。
|
|
57
|
+
- **工具卡片**可展开查看完整入参与结果预览;DSH 需要确认/选择时以 **Zed 原生表单**
|
|
58
|
+
弹出,选项即点即答。
|
|
53
59
|
|
|
54
60
|
## 快速开始
|
|
55
61
|
|
|
56
|
-
本包遵循 dsh 官方插件规范(声明了 `dsh.bundle
|
|
57
|
-
|
|
58
|
-
`dsh-base` 已含整套 agent 栈)、安装包,并把本包**自动追加进 bundle 层**。包自带的
|
|
59
|
-
patch 会插入 `acp-enhanced` 行并覆写默认模型路由,**全程无需手写 profile YAML**。
|
|
62
|
+
本包遵循 dsh 官方插件规范(声明了 `dsh.bundle`),安装与官方组合包一致:**一条命令**
|
|
63
|
+
完成,自动初始化 profile、安装包、追加 bundle 层,全程无需手写 profile YAML。
|
|
60
64
|
|
|
61
65
|
### 安装(2 步)
|
|
62
66
|
|
|
63
|
-
**第 1 步:安装**(从 npm registry
|
|
67
|
+
**第 1 步:安装**(从 npm registry,无需下载源码)
|
|
64
68
|
|
|
65
69
|
```sh
|
|
66
70
|
dsh plugin --profile acp-enhanced add dsh-acp-enhanced
|
|
67
71
|
```
|
|
68
72
|
|
|
69
|
-
>
|
|
73
|
+
> 开发/改源码时用 `link:` 指向本地 checkout(改动实时生效):
|
|
70
74
|
> `dsh plugin --profile acp-enhanced add "link:/absolute/path/to/dsh-acp-enhanced"`
|
|
71
75
|
|
|
72
|
-
**第 2 步:注册进 Zed
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
拉起 agent 进程,因此用随附启动器 `scripts/dsh-acp-zed.sh`(它自己会定位 `node`/`dsh`)。
|
|
76
|
+
**第 2 步:注册进 Zed**(在 `~/.config/zed/settings.json` 的 `agent_servers` 里注册;
|
|
77
|
+
Zed 会用极简 PATH 拉起 agent,因此用随附启动器 `scripts/dsh-acp-zed.sh` 定位
|
|
78
|
+
`node`/`dsh`)
|
|
76
79
|
|
|
77
80
|
#### 最常见:DeepSeek 官方 API(默认路由)
|
|
78
81
|
|
|
@@ -93,13 +96,12 @@ dsh plugin --profile acp-enhanced add dsh-acp-enhanced
|
|
|
93
96
|
}
|
|
94
97
|
```
|
|
95
98
|
|
|
96
|
-
>
|
|
97
|
-
>
|
|
98
|
-
>
|
|
99
|
-
>
|
|
100
|
-
> 启动脚本还会兜底继承正在运行的 `dsh web` 进程的 key。
|
|
99
|
+
> 这两项 env 与包自带 patch 的缺省值一致,**省略也能工作**——显式写上只是让路由意图
|
|
100
|
+
> 一目了然。API key 不必写进 Zed:存入 `~/.dsh/.credentials.yaml`
|
|
101
|
+
> (`DEEPSEEK_API_KEY`)由 dsh 凭据服务解析即可;启动脚本还会兜底继承正在运行的
|
|
102
|
+
> `dsh web` 进程的 key。
|
|
101
103
|
|
|
102
|
-
|
|
104
|
+
可选:固定面板默认项(都可随时在面板里改):
|
|
103
105
|
|
|
104
106
|
```jsonc
|
|
105
107
|
"dsh-acp-enhanced": {
|
|
@@ -132,35 +134,29 @@ dsh plugin --profile acp-enhanced add dsh-acp-enhanced
|
|
|
132
134
|
}
|
|
133
135
|
```
|
|
134
136
|
|
|
135
|
-
> `<KEY_ENV_NAME>`
|
|
136
|
-
> `apiKeyEnv`);同样可以不写在 Zed 里,而是存进 `~/.dsh/.credentials.yaml` 由凭据服务
|
|
137
|
-
> 统一管理。路由换哪种模型都走同一条安装路径,只是 env 值不同。
|
|
137
|
+
> `<KEY_ENV_NAME>` 也可以省掉,把 key 存进 `~/.dsh/.credentials.yaml` 统一管理。
|
|
138
138
|
|
|
139
|
-
Zed 会热重载设置。打开 **AI Agent 面板**(`Cmd+Shift+A`)→
|
|
140
|
-
**dsh-acp-enhanced** →
|
|
141
|
-
|
|
142
|
-
|
|
139
|
+
Zed 会热重载设置。打开 **AI Agent 面板**(`Cmd+Shift+A`)→ agent 选择器选
|
|
140
|
+
**dsh-acp-enhanced** → 输入第一条消息即可:回复实时流式返回,状态栏显示上下文用量,
|
|
141
|
+
面板顶部有 Model / Permission preset / Plan mode 配置项与三种模式,线程归档可恢复
|
|
142
|
+
历史会话。
|
|
143
143
|
|
|
144
144
|
本地验证(无需 Zed):
|
|
145
145
|
|
|
146
146
|
```sh
|
|
147
147
|
node scripts/acp-client.mjs # 官方默认路由,无需 env;期望 ALL CHECKS PASSED
|
|
148
148
|
DSH_ACP_PROVIDER=... DSH_ACP_MODEL=... node scripts/acp-client.mjs # 自定义路由时再传
|
|
149
|
-
env -i HOME=$HOME PATH=/usr/bin:/bin node scripts/acp-client.mjs \
|
|
150
|
-
/bin/bash /absolute/path/to/dsh-acp-enhanced/scripts/dsh-acp-zed.sh # 模拟 Zed 的 spawn 方式
|
|
151
149
|
```
|
|
152
150
|
|
|
153
151
|
### 可选:web_search 走同一个网关
|
|
154
152
|
|
|
155
|
-
|
|
156
|
-
|
|
153
|
+
若网关实现 OpenAI Responses 的 `web_search` 服务端工具,可把搜索也路由到网关(复用
|
|
154
|
+
同一凭据)。装子包并给 profile 的 `cordis.patch.yml` 追加两段:
|
|
157
155
|
|
|
158
156
|
```sh
|
|
159
|
-
dsh plugin --profile acp-enhanced add
|
|
157
|
+
dsh plugin --profile acp-enhanced add dsh-web-search-openrouter
|
|
160
158
|
```
|
|
161
159
|
|
|
162
|
-
`~/.dsh/profiles/acp-enhanced/cordis.patch.yml`(`<provider>` 填你的网关 provider id):
|
|
163
|
-
|
|
164
160
|
```yaml
|
|
165
161
|
- id: web
|
|
166
162
|
config:
|
|
@@ -176,123 +172,29 @@ dsh plugin --profile acp-enhanced add "link:/absolute/path/to/dsh-acp-enhanced/p
|
|
|
176
172
|
apiKeyEnv: <KEY_ENV_NAME>
|
|
177
173
|
```
|
|
178
174
|
|
|
179
|
-
|
|
175
|
+
## 故障排查
|
|
180
176
|
|
|
181
|
-
| 症状 |
|
|
177
|
+
| 症状 | 处理 |
|
|
182
178
|
|---|---|
|
|
183
|
-
| `
|
|
184
|
-
| `no API key for provider route "
|
|
185
|
-
|
|
|
186
|
-
|
|
|
187
|
-
| `session/new` 报 `additionalDirectories is not supported` | ACP 桥接器仅支持 baseline;Zed 默认不会发送额外目录——若自定义配置发送了就移除它。 |
|
|
188
|
-
| 需要详细诊断 | 用 `ACP_DEBUG=1 dsh --profile acp-enhanced` 启动(stderr 上的生命周期 trace)。 |
|
|
179
|
+
| `exec: dsh: not found`(status 127) | 用随附 `dsh-acp-zed.sh` 启动器(自定位 node/dsh) |
|
|
180
|
+
| `no API key for provider route "xxx"` | 写入 `~/.dsh/.credentials.yaml`,或在 agent_servers 里设 `env.DEEPSEEK_API_KEY` |
|
|
181
|
+
| 无法切换模型 / 上下文用量不显示 | 选到了不可路由的"幽灵 provider";本桥默认过滤(只广播 `config.provider` 的模型),确认 profile 的 provider 指向真实路由 |
|
|
182
|
+
| 需要详细诊断 | `ACP_DEBUG=1 dsh --profile acp-enhanced`(stderr 生命周期 trace) |
|
|
189
183
|
|
|
190
184
|
## 开发
|
|
191
185
|
|
|
192
186
|
```sh
|
|
193
|
-
node scripts/acp-client.mjs #
|
|
194
|
-
|
|
195
|
-
node scripts/acp-
|
|
196
|
-
node scripts/acp-
|
|
197
|
-
node scripts/acp-
|
|
198
|
-
node scripts/acp-resume-test.mjs # 会话恢复测试(两个进程:创建持久化 → 加载回放 → 续聊)
|
|
199
|
-
ACP_DEBUG=1 dsh --profile acp-enhanced # stderr 上的详细生命周期 trace
|
|
187
|
+
node scripts/acp-client.mjs # 端到端冒烟(需要 API key)
|
|
188
|
+
node scripts/acp-client-tools.mjs # 客户端工具测试(模拟 Zed 的 fs/terminal/elicitation/plan)
|
|
189
|
+
node scripts/acp-mcp-test.mjs # MCP 挂载测试(无模型调用)
|
|
190
|
+
node scripts/acp-smoke-keyless.mjs # keyless 冒烟(CI 用)
|
|
191
|
+
node scripts/acp-resume-test.mjs # 会话恢复测试
|
|
200
192
|
```
|
|
201
193
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
`
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
出现、无 efforts 的路由抑制)。`acp-mcp-test.mjs` 用 `scripts/fixtures/mcp-echo-server.mjs`
|
|
210
|
-
验证 `session/new` 的 `mcpServers` 被真实挂载(server 收到 initialize 与 tools/list)且相同
|
|
211
|
-
列表不重复挂载。
|
|
212
|
-
|
|
213
|
-
## 设计说明
|
|
214
|
-
|
|
215
|
-
- **块级流式输出**:文本增量按块索引累积;`block-end` 一旦确认就立即上送线上。重试会重启
|
|
216
|
-
同一个索引,因此被取消尝试的残留尾部永远到不了客户端——ACP 没有撤销机制,这是最干净的边界。
|
|
217
|
-
- **遥测**:每个 provider 的 `usage` 样本都会以 `usage_update` 广播(used = 输入 + 缓存命中
|
|
218
|
-
+ 缓存写入;size = 所路由模型的上下文窗口),完整明细在 `_meta` 中:输入/输出/缓存/推理
|
|
219
|
-
token、`cacheHitRate`、`tps`(生成 token / step 墙钟耗时)、step 耗时、轮次计数,以及累计的
|
|
220
|
-
工具调用统计。
|
|
221
|
-
- **工具调用可见性**:`tool_call` 通知带 `kind` 与 `rawInput`(`JSON.parse` 参数,失败则回退为
|
|
222
|
-
字符串),Zed 的工具卡片因此能展开看到具体参数(bash 的命令、写入的文件等);
|
|
223
|
-
`tool_call_update` 带 `rawOutput`(从 `ToolResultMessage` 的文本块提取结果预览,截断 12k)。
|
|
224
|
-
**`kind` 映射有个关键约束**:Zed 把 `kind == 'execute'` 当**终端工具**、`kind == 'edit'`
|
|
225
|
-
当 **diff 工具**,两者都会**隐藏 rawInput**。所以只有真正在 Zed 里开终端的 `zed_terminal`
|
|
226
|
-
用 `execute`;bash/run_code/写文件等一律 `other`(rawInput 正常显示),否则就会出现"卡片只
|
|
227
|
-
显示 bash 字样、看不到命令"的现象。另注意 dsh 的 `tool/result` 事件里 `toolCallId` 在
|
|
228
|
-
`message.content[0].toolCallId`(`ToolResultBlock`)上,不在事件根——漏取会导致 SDK 校验
|
|
229
|
-
拒绝整条 `tool_call_update`。历史回放(resume)同样携带这些字段。
|
|
230
|
-
- **会话配置**:`model` 下拉框枚举实时模型目录(`ctx.llm.listProviders` → `listModels` →
|
|
231
|
-
`resolveModelInfo`),`reasoning_effort` 下拉框枚举当前路由的可用强度,`permission_preset`
|
|
232
|
-
枚举已挂载的预设。修改走 `llm.resolveCallConfig` 与 `installModelSelection`(与 Web
|
|
233
|
-
api-proxy 使用的同一机制)或 `permissionPresets.apply` 写路径。
|
|
234
|
-
- **模型分组线格式**:`model` 选项的分组必须是 ACP 规范的
|
|
235
|
-
`{ group: <id>, name: <label>, options: [...] }`。早期版本发成了 `{ groupName, options }`,
|
|
236
|
-
Zed(`agent-client-protocol-schema` 1.4.0)反序列化时把整个组跳过(`DefaultOnError` +
|
|
237
|
-
`VecSkipError`),于是 `model` 下拉框变空、模型无法选择——而 SDK 的 mock 客户端不校验
|
|
238
|
-
响应所以测试没拦住;现在 `acp-client-tools.mjs` 会对每个 config option 跑
|
|
239
|
-
`zSessionConfigOption.safeParse`,这类线格式错误不会再漏网。
|
|
240
|
-
- **推理强度的路由限制**:`reasoning_effort` 选项只在模型路由**暴露** efforts 时广播
|
|
241
|
-
(`resolveModelInfo().reasoning.efforts` 非空)。对不暴露 efforts 的路由,显式设置强度会被
|
|
242
|
-
适配器拒绝(`does not support reasoning effort "high"`)——所以这类路由下 Zed 里没有推理
|
|
243
|
-
强度下拉是**正确行为**,不是桥接器 bug;换到暴露 efforts 的路由后下拉框会自动出现。
|
|
244
|
-
- **模型目录过滤(`includeAllProviders`,默认关)**:默认只广播 `config.provider` 的模型,
|
|
245
|
-
避免把"幽灵 provider"(已挂载但不可路由的适配器,例如没有可用 API key 而仍挂载的
|
|
246
|
-
`deepseek-official`)列进下拉框。这些模型在列表里看起来可切换,但一旦选中,后续每次
|
|
247
|
-
prompt 都会以 `MISSING_CREDENTIAL`(`no API key for provider route "xxx"`)失败——在
|
|
248
|
-
Zed 里表现为"无法切换模型、且因 turn 失败而不再收到 `usage_update`,上下文用量不显示"
|
|
249
|
-
(Web GUI 会对不可路由的当前项显示 unavailable 横幅,Zed 没有,所以同样的数据在 Zed
|
|
250
|
-
里看起来就是坏的)。需要多 provider 都可用时设 `includeAllProviders: true`。
|
|
251
|
-
- **默认模型不被污染**:`applySelection` 只有在 `selected.provider === config.provider`(或
|
|
252
|
-
显式 `includeAllProviders`)时才把新选择持久化为 `agent-default-model` 默认值。否则一次
|
|
253
|
-
误切到不可路由的 provider 只会作用于当前会话,不会写坏后续所有新会话的默认路由。
|
|
254
|
-
- **客户端转发工具(Zed fs / terminal)**:`initialize` 时读取 `clientCapabilities`,仅在客户端
|
|
255
|
-
声明对应能力时,向 agent 注册 `zed_read_text_file` / `zed_write_text_file` / `zed_terminal`
|
|
256
|
-
三个工具(`ctx.tools.register` + `defineTool`)。工具体通过 `conn.readTextFile` /
|
|
257
|
-
`conn.writeTextFile` / `conn.createTerminal` 把请求转发给编辑器:`zed_write_text_file` 让
|
|
258
|
-
文件编辑落在 Zed 自己的 buffer 上,出现在 agent 面板的"编辑文件"区(diff + 接受/拒绝);
|
|
259
|
-
`zed_terminal` 让命令跑在 Zed 真实终端里并轮询输出(`terminal/output` 是累计内容,取最后
|
|
260
|
-
一次即可),120s 超时后 kill。无这些能力的客户端(如纯自动化测试)不会看到这些工具。
|
|
261
|
-
- **Zed 表单提问(elicitation)**:客户端声明 `elicitation.form` 时,桥接器注册
|
|
262
|
-
`ask_user_question` 工具(复刻 `dsh-tool-ask-user` 的定义,走 `ctx.userQuestions` seam)
|
|
263
|
-
以及对应的 UI provider:把问题映射成 ACP `elicitation/create`(form 模式)的 JSON Schema
|
|
264
|
-
(单选 → `string`+`enum`,多选 → `array`,无选项 → 裸 `string`),用户在 Zed 里以原生
|
|
265
|
-
表单作答后,答案映射回 `AskUserQuestionAnswer` 喂回模型。decline/cancel 会以错误结束该次
|
|
266
|
-
工具调用,模型可据此改道。注意 Zed 的 elicitation 能力是对象(`form: {}`)而非布尔,
|
|
267
|
-
判断用"存在"而非 `=== true`。
|
|
268
|
-
- **Plan 面板**:`plan_mode` 布尔配置项走 `ctx.planMode.set(agent, active)` 切换 DSH plan
|
|
269
|
-
mode(Zed 的布尔开关即点即用);`session/event` 里的 `plan/mode` 翻转被映射为 ACP `plan`
|
|
270
|
-
update——开时一条"规划中"条目,关时清空。DSH 的 plan mode 没有结构化任务列表,所以这是
|
|
271
|
-
状态指示而非任务清单。注意 ACP 的 `plan` update 是**扁平**形状
|
|
272
|
-
(`{ sessionUpdate: 'plan', entries: [...] }`),不是 `{ plan: {...} }`。
|
|
273
|
-
- **会话恢复(session/load)**:`initialize` 声明 `loadSession: true`;`session/load` 通过
|
|
274
|
-
`ctx.agents.resume({ resumeSessionId })` 从持久化存储(`dsh-session-persistence-jsonl`,
|
|
275
|
-
dsh-base 已挂载)恢复 agent,然后按事件日志回放历史:`user/message`(仅
|
|
276
|
-
`source.kind === 'user'` 的真实人类消息,过滤 system-reminder 等合成注入)→
|
|
277
|
-
`user_message_chunk`,`assistant/message` 的文本 → `agent_message_chunk`,
|
|
278
|
-
`tool/call`/`tool/result` → `tool_call`/`tool_call_update`。Zed 在线程插入后才完成 load
|
|
279
|
-
RPC,所以回放通知能被线程接收。回放完成后该会话与新建会话一样支持继续 prompt。
|
|
280
|
-
- **会话归档列表(session/list + session/delete)**:`initialize` 声明
|
|
281
|
-
`sessionCapabilities: { list: {}, delete: {} }`;`session/list` 用
|
|
282
|
-
`ctx.sessionPersistence.list()` 枚举已物化的会话(`SessionHeader`:id/cwd/createdAt),
|
|
283
|
-
标题优先取实时 `session/title` 事件记录,缺失时用 `persistence.readRaw(id)` 扫存储日志里
|
|
284
|
-
最后一个 `session/title` 事件(>8MB 的日志跳过);按 `updatedAt` 倒序返回。`session/delete`
|
|
285
|
-
先释放在线 agent(`sessions.delete` + `dispose`),再通过 `persistence.locate(header)` 拿到
|
|
286
|
-
该会话目录物理路径并整体删除——注意 dsh 持久化面**没有官方的删除 API**,这一步是直接删
|
|
287
|
-
后端目录。实时标题/活动变化通过 `session_info_update` 通知推送(`session/title` 与
|
|
288
|
-
`turn/end` 事件)。
|
|
289
|
-
- **空 effort 抑制**:当前路由模型不暴露 reasoning efforts 时,不广播 `reasoning_effort`
|
|
290
|
-
配置项——Zed 就不会渲染一个空的、无法操作的"Reasoning effort"chip。切到带 efforts 的
|
|
291
|
-
模型后该选项自动重新出现(每次切换都会重播 `config_option_update`)。
|
|
292
|
-
- **模式**:权限预设被呈现为 ACP 会话模式,因此 Zed 的模式切换器驱动 sandbox/approval 预设。
|
|
293
|
-
- **已知限制**(继承自官方桥接器):仅 baseline prompt(无图片/音频附件)、不支持
|
|
294
|
-
`additionalDirectories`、已确认文本按块粒度流式,且每个会话同时只能有一个
|
|
295
|
-
in-flight prompt。MCP servers(stdio + streamable HTTP)已支持,但 legacy SSE
|
|
296
|
-
传输与 `acp` 传输不声明。`session/close` / `session/fork` / `session/resume` 未实现
|
|
297
|
-
(不声明能力,合规客户端不会调用);`session/delete` 因 dsh 持久化面没有官方删除
|
|
298
|
-
API,采用直接删除后端目录的方式。
|
|
194
|
+
## 已知限制
|
|
195
|
+
|
|
196
|
+
仅 baseline prompt(无图片/音频附件)、不支持 `additionalDirectories`、文本按块粒度
|
|
197
|
+
流式、每会话同时一个 in-flight prompt。MCP 支持 stdio 与 streamable HTTP(不声明
|
|
198
|
+
legacy SSE / `acp` 传输)。`session/close` / `session/fork` / `session/resume` 未实现
|
|
199
|
+
(不声明能力,合规客户端不会调用);`session/delete` 因 dsh 持久化无官方删除 API,
|
|
200
|
+
采用直接删除后端目录的方式。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-acp-enhanced",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Enhanced ACP server for DeepSeek Harness: block-level streaming, usage/stat telemetry (cache hit rate, token speed, input/output tokens, context length, turns, tool timing), model & reasoning-effort switching, and permission-preset control over the ACP wire (Zed-friendly)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dsh",
|