dsh-acp-enhanced 0.1.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.
- package/LICENSE +21 -0
- package/README-en.md +336 -0
- package/README.md +290 -0
- package/cordis.patch.yml +28 -0
- package/lib/codec.js +87 -0
- package/lib/index.js +1429 -0
- package/package.json +65 -0
- package/profile/cordis.yml +138 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Runmin Guo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README-en.md
ADDED
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
**[中文](README.md) | English**
|
|
2
|
+
|
|
3
|
+
# dsh-acp-enhanced
|
|
4
|
+
|
|
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** that speak ACP over JSON-RPC stdio.
|
|
8
|
+
|
|
9
|
+
The official `@deepseek-ai/dsh-acp` bridge is deliberately automation-only: it commits
|
|
10
|
+
text only after a whole message, carries no telemetry, and exposes no model/permission
|
|
11
|
+
controls. This project is a drop-in replacement that surfaces what the Web GUI has:
|
|
12
|
+
|
|
13
|
+
| Surface | ACP mechanism | What you see in Zed |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| **Block-level streaming** | `agent_message_chunk` per committed text block (`block-end`), grouped by `messageId` per model step | Text appears while the agent works; cancelled/retried blocks never leak torn output |
|
|
16
|
+
| **Token & context telemetry** | standard `usage_update` (`used` = context pressure, `size` = model context window) | Context meter in the agent status bar |
|
|
17
|
+
| **Cache hit rate / TPS / input-output-reasoning tokens / tool timing / turn count** | `usage_update._meta` + `tool_call` / `tool_call_update` `_meta` | Raw numbers every step (the `_meta` extension field carries the full breakdown) |
|
|
18
|
+
| **Tool-call visibility** | `tool_call` carries `rawInput` (parsed arguments) and `kind` (read/edit/execute/…); `tool_call_update` carries `rawOutput` (result preview, capped at 12k chars) | Tool cards expand to show the **exact arguments** (e.g. the bash command) and the **result**, with kind-based icons |
|
|
19
|
+
| **Model switching** | `session/set_config_option` with the `model` select (`provider/model` values from the live catalog; ACP grouped-select wire shape `{ group, name, options }`) | Config-option UI — switch to any model on the route |
|
|
20
|
+
| **Reasoning effort** | `session/set_config_option` with the `reasoning_effort` select (only when the routed model **exposes** selectable efforts) | Config-option UI (appears only when the route exposes efforts; see Design notes) |
|
|
21
|
+
| **Permission presets** | `session/set_config_option` (`permission_preset`) **and** ACP session modes via `session/set_mode` | Mode switcher / config-option UI |
|
|
22
|
+
| **Approval** | `session/request_permission` (allow-once / reject-once per tool call) | Native permission prompt |
|
|
23
|
+
| **Zed client file tools** | agent-side `zed_read_text_file` / `zed_write_text_file` / `zed_terminal` forwarded as `fs/read_text_file` / `fs/write_text_file` / `terminal/create` | Edits land in the agent panel's **"Edited files" section (diff + accept/reject)**; commands run in a **real Zed terminal** |
|
|
24
|
+
| **Zed form elicitation** | `ask_user_question` tool + `userQuestions` provider forwarded as `elicitation/create` (form mode) | Questions pop up as **native Zed forms**; options answerable in one click |
|
|
25
|
+
| **Plan panel** | `plan_mode` boolean config option + `plan/mode` events mapped to ACP `plan` updates | A **Plan status bar** at the bottom of the panel while plan mode is on; cleared when it leaves |
|
|
26
|
+
| **Session resume** | `loadSession` capability + `session/load` resumes the persisted agent via `agents.resume` and replays history as `user_message_chunk` / `agent_message_chunk` / `tool_call` | **Continue a previous thread** in Zed (long investigations keep their context) |
|
|
27
|
+
| **Session archive list** | `sessionCapabilities.list/delete`; `session/list` enumerates persisted sessions via `ctx.sessionPersistence.list()` (titles read from `session/title` events in the stored log), `session/delete` disposes the live agent and removes its persisted directory; `session/title` / `turn/end` push live `session_info_update`s | The **thread archive** shows all sessions (titled, sorted by last activity) — click to resume, delete to remove |
|
|
28
|
+
| **Empty-option suppression** | no `reasoning_effort` option is advertised when the routed model exposes no efforts | No empty, unclickable "Reasoning effort" chip |
|
|
29
|
+
|
|
30
|
+
## Preview
|
|
31
|
+
|
|
32
|
+
After picking **dsh-acp-enhanced** in Zed's AI Agent panel, you get:
|
|
33
|
+
|
|
34
|
+

|
|
35
|
+
|
|
36
|
+
- Tool calls that need permission pop a **native approval prompt** (allow-once /
|
|
37
|
+
reject-once); below the input box sit the **model**, **reasoning effort**,
|
|
38
|
+
**permission preset**, and **plan mode** config options plus the **context usage
|
|
39
|
+
ring** (`usage_update` telemetry with cache hit rate, TPS, and more).
|
|
40
|
+
|
|
41
|
+

|
|
42
|
+
|
|
43
|
+
- **Tool cards** expand to show each call's full arguments (e.g. the exact bash
|
|
44
|
+
command) and the result preview (`rawInput` / `rawOutput`); when dsh needs your
|
|
45
|
+
confirmation or a choice, the question arrives as a **native Zed form**
|
|
46
|
+
(`ask_user_question` → `elicitation/create`) — click an option, no typing.
|
|
47
|
+
|
|
48
|
+
> **Repository layout** — this repo contains two independent packages:
|
|
49
|
+
> - `dsh-acp-enhanced` (repo root): the enhanced ACP bridge (`lib/index.js`).
|
|
50
|
+
> - `packages/dsh-web-search-openrouter/`: a standalone `ctx.web` search provider that
|
|
51
|
+
> routes `web_search` through any OpenAI-Responses gateway instead of DeepSeek's
|
|
52
|
+
> Anthropic `/messages` endpoint. It deliberately does **not** couple to the ACP
|
|
53
|
+
> bridge, so any profile (the Web GUI included) can mount it.
|
|
54
|
+
|
|
55
|
+
## Quick start
|
|
56
|
+
|
|
57
|
+
This package follows the official dsh plugin conventions (it declares `dsh.bundle`),
|
|
58
|
+
so installation is identical to any official bundle: **one command** —
|
|
59
|
+
`dsh plugin --profile <name> add <pkg>` auto-initializes the profile (the first layer
|
|
60
|
+
`dsh-base` already carries the whole agent stack), installs the package, and
|
|
61
|
+
**auto-appends it to the profile's bundle layers**. The shipped patch inserts the
|
|
62
|
+
`acp-enhanced` row and overrides the default model route — **no profile YAML to write**.
|
|
63
|
+
|
|
64
|
+
### Install (2 steps)
|
|
65
|
+
|
|
66
|
+
**Step 1 — install** (from the npm registry; no source checkout needed):
|
|
67
|
+
|
|
68
|
+
```sh
|
|
69
|
+
dsh plugin --profile acp-enhanced add dsh-acp-enhanced
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
> When hacking on the code itself, use `link:` to a local checkout instead (live
|
|
73
|
+
> edits, no registry round-trip):
|
|
74
|
+
> `dsh plugin --profile acp-enhanced add "link:/absolute/path/to/dsh-acp-enhanced"`
|
|
75
|
+
|
|
76
|
+
**Step 2 — register in Zed** (the model route and credentials all come from `env`; no
|
|
77
|
+
patch to write)
|
|
78
|
+
|
|
79
|
+
Register the agent under `agent_servers` in `~/.config/zed/settings.json`. Zed (a GUI
|
|
80
|
+
app) spawns agent processes with a minimal PATH, so use the shipped launcher
|
|
81
|
+
`scripts/dsh-acp-zed.sh` (it locates `node`/`dsh` itself).
|
|
82
|
+
|
|
83
|
+
#### Most common: DeepSeek official API (the default route)
|
|
84
|
+
|
|
85
|
+
```jsonc
|
|
86
|
+
{
|
|
87
|
+
// ...your existing settings...
|
|
88
|
+
"agent_servers": {
|
|
89
|
+
"dsh-acp-enhanced": {
|
|
90
|
+
"type": "custom",
|
|
91
|
+
"command": "/bin/bash",
|
|
92
|
+
"args": ["/absolute/path/to/dsh-acp-enhanced/scripts/dsh-acp-zed.sh"],
|
|
93
|
+
"env": {
|
|
94
|
+
"DSH_ACP_PROVIDER": "deepseek-official", // the official provider id
|
|
95
|
+
"DSH_ACP_MODEL": "deepseek-v4-flash" // the official model id
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
> This is the setup the author uses daily (macOS). `DSH_ACP_PROVIDER` / `DSH_ACP_MODEL`
|
|
103
|
+
> match the shipped patch's defaults (`deepseek-official` / `deepseek-v4-flash`), so
|
|
104
|
+
> **both can be omitted entirely** — writing them out just makes the route explicit in
|
|
105
|
+
> your Zed config. The API key does not have to live in Zed: store it in
|
|
106
|
+
> `~/.dsh/.credentials.yaml` (`DEEPSEEK_API_KEY`, mode 600) and the dsh credentials
|
|
107
|
+
> service resolves it; the launcher additionally falls back to inheriting the key from
|
|
108
|
+
> a running `dsh web` process.
|
|
109
|
+
|
|
110
|
+
Optional: pin the panel's default config options (model / plan mode / reasoning
|
|
111
|
+
effort; all still changeable in the panel at any time):
|
|
112
|
+
|
|
113
|
+
```jsonc
|
|
114
|
+
"dsh-acp-enhanced": {
|
|
115
|
+
// ...the type/command/args/env above...
|
|
116
|
+
"default_config_options": {
|
|
117
|
+
"model": "deepseek-official/deepseek-v4-flash",
|
|
118
|
+
"plan_mode": false,
|
|
119
|
+
"reasoning_effort": "high"
|
|
120
|
+
},
|
|
121
|
+
"favorite_config_option_values": {
|
|
122
|
+
"model": ["deepseek-official/deepseek-v4-flash", "deepseek-official/deepseek-v4-pro"]
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
#### Extended: route through an OpenAI-Responses gateway (e.g. a company model gateway)
|
|
128
|
+
|
|
129
|
+
Same install path; only the env values change to the provider/model the gateway
|
|
130
|
+
exposes plus the key env var it requires:
|
|
131
|
+
|
|
132
|
+
```jsonc
|
|
133
|
+
"dsh-acp-enhanced": {
|
|
134
|
+
"type": "custom",
|
|
135
|
+
"command": "/bin/bash",
|
|
136
|
+
"args": ["/absolute/path/to/dsh-acp-enhanced/scripts/dsh-acp-zed.sh"],
|
|
137
|
+
"env": {
|
|
138
|
+
"DSH_ACP_PROVIDER": "<gateway-provider-id>", // provider id exposed by the gateway
|
|
139
|
+
"DSH_ACP_MODEL": "<gateway-model-id>", // model id exposed by the gateway
|
|
140
|
+
"<KEY_ENV_NAME>": "<key>" // the key env var the gateway reads
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
> `<KEY_ENV_NAME>` is the env var the provider reads for its key (gateway adapters
|
|
146
|
+
> usually declare their own `apiKeyEnv`) — alternatively store it in
|
|
147
|
+
> `~/.dsh/.credentials.yaml` and let the dsh credentials service manage it. Every
|
|
148
|
+
> route uses the same install path; only the env values differ.
|
|
149
|
+
|
|
150
|
+
Zed hot-reloads settings. Open the **AI Agent panel** (`Cmd+Shift+A`) → pick
|
|
151
|
+
**dsh-acp-enhanced** in the top **agent selector** → send your first message. Replies
|
|
152
|
+
stream in real time, the status bar shows context usage, and the panel exposes Model /
|
|
153
|
+
Permission preset / Plan mode config options plus read-only / workspace-write /
|
|
154
|
+
full-access modes; the thread archive lists and resumes past sessions.
|
|
155
|
+
|
|
156
|
+
Verify locally (no Zed needed):
|
|
157
|
+
|
|
158
|
+
```sh
|
|
159
|
+
node scripts/acp-client.mjs # official default route, no env; expect ALL CHECKS PASSED
|
|
160
|
+
DSH_ACP_PROVIDER=... DSH_ACP_MODEL=... node scripts/acp-client.mjs # only for a custom route
|
|
161
|
+
env -i HOME=$HOME PATH=/usr/bin:/bin node scripts/acp-client.mjs \
|
|
162
|
+
/bin/bash /absolute/path/to/dsh-acp-enhanced/scripts/dsh-acp-zed.sh # Zed-like spawn
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Optional: route web_search through the same gateway
|
|
166
|
+
|
|
167
|
+
The bridge does not depend on it. If the gateway implements the OpenAI Responses
|
|
168
|
+
`web_search` server tool, you can route search through it too (reusing the same
|
|
169
|
+
credential). Install it as a plain dependency and append two blocks to the profile's
|
|
170
|
+
`cordis.patch.yml`:
|
|
171
|
+
|
|
172
|
+
```sh
|
|
173
|
+
dsh plugin --profile acp-enhanced add "link:/absolute/path/to/dsh-acp-enhanced/packages/dsh-web-search-openrouter"
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
`~/.dsh/profiles/acp-enhanced/cordis.patch.yml` (`<provider>` is your gateway provider id):
|
|
177
|
+
|
|
178
|
+
```yaml
|
|
179
|
+
- id: web
|
|
180
|
+
config:
|
|
181
|
+
searchProvider: <provider>
|
|
182
|
+
|
|
183
|
+
- insert:
|
|
184
|
+
- id: web-search-openrouter
|
|
185
|
+
name: 'dsh-web-search-openrouter'
|
|
186
|
+
config:
|
|
187
|
+
enabled: true
|
|
188
|
+
baseURL: http://<gateway-host>:<port>/v1
|
|
189
|
+
model: <your-model-id>
|
|
190
|
+
apiKeyEnv: <KEY_ENV_NAME>
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Troubleshooting
|
|
194
|
+
|
|
195
|
+
| Symptom | Cause & fix |
|
|
196
|
+
|---|---|
|
|
197
|
+
| `Server exited with status 127` / `exec: dsh: not found` | Zed's PATH lacks `node`/`dsh`. Use the shipped `dsh-acp-zed.sh` launcher (it resolves both); verify with `bash scripts/dsh-acp-zed.sh` in a clean shell. |
|
|
198
|
+
| `no API key for provider route "deepseek-official"` | The key cannot be resolved. Write `~/.dsh/.credentials.yaml` (see step 2), or set `env.DEEPSEEK_API_KEY` in the agent_servers entry. |
|
|
199
|
+
| Agent does not appear after editing settings | Run `zed: reload settings` (command palette) or restart Zed. |
|
|
200
|
+
| "Cannot switch models" or "context usage not shown" in Zed | Usually a ghost provider is selected (an adapter that is mounted but has no usable API key). The bridge filters ghost groups by default (only `config.provider` models are advertised); if it persists, check that the profile's `config.provider` points at a real routable route and reset the polluted `agent-default-model` default to it. See Design notes. |
|
|
201
|
+
| `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. |
|
|
202
|
+
| Need detailed diagnostics | Start with `ACP_DEBUG=1 dsh --profile acp-enhanced` (lifecycle trace on stderr). |
|
|
203
|
+
|
|
204
|
+
## Development
|
|
205
|
+
|
|
206
|
+
```sh
|
|
207
|
+
node scripts/acp-client.mjs # end-to-end smoke test (needs a routable provider)
|
|
208
|
+
node scripts/acp-client-tools.mjs # client-tool tests (mocks Zed fs/terminal/elicitation/plan)
|
|
209
|
+
node scripts/acp-resume-test.mjs # resume tests (two processes: create+persist → load+replay → continue)
|
|
210
|
+
ACP_DEBUG=1 dsh --profile acp-enhanced # lifecycle trace on stderr
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
The smoke client drives initialize → session/new → prompt (verifying block streaming,
|
|
214
|
+
`usage_update`, `tool_call`), config-option and mode switching, a second prompt after
|
|
215
|
+
switching, and `session/cancel`. `acp-client-tools.mjs` uses the SDK's
|
|
216
|
+
`ClientSideConnection` to mock Zed: it declares
|
|
217
|
+
`fs.readTextFile/writeTextFile/terminal/elicitation` capabilities and verifies that
|
|
218
|
+
`zed_*` tool calls arrive as `fs/write_text_file`, `fs/read_text_file`,
|
|
219
|
+
`terminal/create` requests, that `ask_user_question` arrives as an `elicitation/create`
|
|
220
|
+
form (with enum options), that the `plan_mode` boolean toggle emits ACP `plan` updates
|
|
221
|
+
(on → entry, off → cleared), and that routes without reasoning efforts no longer
|
|
222
|
+
advertise an empty `reasoning_effort`.
|
|
223
|
+
|
|
224
|
+
## Design notes
|
|
225
|
+
|
|
226
|
+
- **Block-level streaming**: text deltas accumulate per block index; a committed
|
|
227
|
+
`block-end` goes on the wire immediately. A retry restarts the same index, so the
|
|
228
|
+
torn tail of a cancelled attempt never reaches the client — ACP has no undo, and
|
|
229
|
+
this is the cleanest boundary.
|
|
230
|
+
- **Telemetry**: every provider `usage` sample is broadcast as `usage_update`
|
|
231
|
+
(used = input + cache read + cache write; size = the routed model's context
|
|
232
|
+
window), with the full breakdown in `_meta`: input/output/cache/reasoning tokens,
|
|
233
|
+
`cacheHitRate`, `tps` (generated tokens / step wall-clock), step elapsed, turn
|
|
234
|
+
count, and cumulative tool-call stats.
|
|
235
|
+
- **Tool-call visibility**: `tool_call` notifications carry `kind` and `rawInput`
|
|
236
|
+
(`JSON.parse` of the arguments, falling back to the raw string), so Zed's tool
|
|
237
|
+
cards expand to show the exact arguments (bash command, written file, ...);
|
|
238
|
+
`tool_call_update` carries `rawOutput` (a bounded text preview extracted from the
|
|
239
|
+
`ToolResultMessage`, truncated at 12k). **A key constraint on `kind` mapping**: Zed
|
|
240
|
+
treats `kind == 'execute'` as a terminal tool and `kind == 'edit'` as a diff tool,
|
|
241
|
+
and **hides rawInput for both**. So only `zed_terminal` (a real editor terminal)
|
|
242
|
+
maps to `execute`; bash/run_code/write tools stay `other` so rawInput renders —
|
|
243
|
+
otherwise the card shows only the tool name with no command. Also note the dsh
|
|
244
|
+
`tool/result` event carries `toolCallId` on `message.content[0].toolCallId`
|
|
245
|
+
(the `ToolResultBlock`), not on the event root — missing it makes the SDK reject
|
|
246
|
+
the whole `tool_call_update`. History replay (resume) carries the same fields.
|
|
247
|
+
- **Session config**: the `model` select enumerates the live model catalog
|
|
248
|
+
(`ctx.llm.listProviders` → `listModels` → `resolveModelInfo`), `reasoning_effort`
|
|
249
|
+
enumerates the routed model's efforts, `permission_preset` enumerates the mounted
|
|
250
|
+
presets. Writes go through `llm.resolveCallConfig` + `installModelSelection` (the
|
|
251
|
+
same mechanism the Web api-proxy uses) or `permissionPresets.apply`.
|
|
252
|
+
- **Model grouped-select wire shape**: the `model` option's groups must use the ACP
|
|
253
|
+
shape `{ group: <id>, name: <label>, options: [...] }`. An early version emitted
|
|
254
|
+
`{ groupName, options }`; Zed (`agent-client-protocol-schema` 1.4.0) silently
|
|
255
|
+
skipped the whole group on deserialization (`DefaultOnError` + `VecSkipError`),
|
|
256
|
+
leaving the dropdown empty — and the SDK mock client does not validate agent
|
|
257
|
+
responses, so tests missed it. Now `acp-client-tools.mjs` runs
|
|
258
|
+
`zSessionConfigOption.safeParse` on every config option, so this class of wire bug
|
|
259
|
+
cannot slip through again.
|
|
260
|
+
- **Reasoning-effort route limitation**: the `reasoning_effort` option is advertised
|
|
261
|
+
only when the routed model **exposes** efforts (`resolveModelInfo().reasoning.efforts`
|
|
262
|
+
non-empty). On routes without efforts, explicitly setting one is rejected by the
|
|
263
|
+
adapter (`does not support reasoning effort "high"`) — so the absence of an effort
|
|
264
|
+
dropdown there is **correct behavior**, not a bug; switching to a route that exposes
|
|
265
|
+
efforts makes the dropdown reappear automatically.
|
|
266
|
+
- **Model-catalog filtering (`includeAllProviders`, default off)**: by default only
|
|
267
|
+
`config.provider` models are advertised, keeping "ghost providers" (adapters that
|
|
268
|
+
are mounted but not routable — e.g. a `deepseek-official` with no usable API key)
|
|
269
|
+
out of the dropdown. Those models look switchable but every later prompt fails with
|
|
270
|
+
`MISSING_CREDENTIAL` (`no API key for provider route "xxx"`) — in Zed that shows up
|
|
271
|
+
as "cannot switch models, and no `usage_update` arrives because the turn failed"
|
|
272
|
+
(the Web GUI shows an unavailable banner for the current item; Zed does not, so the
|
|
273
|
+
same data looks broken there). Set `includeAllProviders: true` when multiple
|
|
274
|
+
providers are genuinely usable.
|
|
275
|
+
- **Default model cannot be poisoned**: `applySelection` persists the new selection as
|
|
276
|
+
the `agent-default-model` default only when `selected.provider === config.provider`
|
|
277
|
+
(or explicit `includeAllProviders`). An accidental switch to a non-routable provider
|
|
278
|
+
therefore affects only the current session and never corrupts the default route of
|
|
279
|
+
every later session.
|
|
280
|
+
- **Client-forwarding tools (Zed fs / terminal)**: on `initialize` the bridge reads
|
|
281
|
+
`clientCapabilities` and registers `zed_read_text_file` / `zed_write_text_file` /
|
|
282
|
+
`zed_terminal` (`ctx.tools.register` + `defineTool`) only when the client declares
|
|
283
|
+
the matching capabilities. Tool bodies forward to the editor via
|
|
284
|
+
`conn.readTextFile` / `conn.writeTextFile` / `conn.createTerminal`:
|
|
285
|
+
`zed_write_text_file` lands edits on Zed's own buffer (the "Edited files" section
|
|
286
|
+
with diff + accept/reject); `zed_terminal` runs the command in a real Zed terminal
|
|
287
|
+
and polls output (`terminal/output` is cumulative — take the last one), killing after
|
|
288
|
+
120s. Clients without those capabilities (e.g. pure automation) never see these tools.
|
|
289
|
+
- **Zed form elicitation**: when the client declares `elicitation.form`, the bridge
|
|
290
|
+
registers the `ask_user_question` tool (mirroring `dsh-tool-ask-user`'s definition
|
|
291
|
+
through the `ctx.userQuestions` seam) plus the matching UI provider: questions map
|
|
292
|
+
to an ACP `elicitation/create` (form mode) JSON Schema (single choice → `string` +
|
|
293
|
+
`enum`, multi → `array`, none → bare `string`); the user's native-form answer maps
|
|
294
|
+
back to `AskUserQuestionAnswer` for the model. decline/cancel end the tool call with
|
|
295
|
+
an error the model can route around. Note Zed's elicitation capability is an object
|
|
296
|
+
(`form: {}`), not a boolean — check for presence, not `=== true`.
|
|
297
|
+
- **Plan panel**: the `plan_mode` boolean config option toggles DSH plan mode via
|
|
298
|
+
`ctx.planMode.set(agent, active)`; `plan/mode` flips in `session/event` map to ACP
|
|
299
|
+
`plan` updates — one "planning" entry while active, cleared on exit. DSH plan mode
|
|
300
|
+
has no structured task list, so this is a state indicator, not a task list. The ACP
|
|
301
|
+
`plan` update is **flat** (`{ sessionUpdate: 'plan', entries: [...] }`), not
|
|
302
|
+
`{ plan: {...} }`.
|
|
303
|
+
- **Session resume (session/load)**: `initialize` declares `loadSession: true`;
|
|
304
|
+
`session/load` resumes the persisted agent via `ctx.agents.resume({ resumeSessionId })`
|
|
305
|
+
(`dsh-session-persistence-jsonl`, mounted by dsh-base), then replays history from the
|
|
306
|
+
event log: `user/message` (only `source.kind === 'user'` — synthetic injections like
|
|
307
|
+
system reminders and skill content are filtered) → `user_message_chunk`,
|
|
308
|
+
`assistant/message` text → `agent_message_chunk`, `tool/call`/`tool/result` →
|
|
309
|
+
`tool_call`/`tool_call_update`. Zed inserts the thread before the load RPC
|
|
310
|
+
completes, so the replay notifications reach it. After replay the session behaves
|
|
311
|
+
like a fresh one for further prompts.
|
|
312
|
+
- **Session archive list (session/list + session/delete)**: `initialize` declares
|
|
313
|
+
`sessionCapabilities: { list: {}, delete: {} }`; `session/list` enumerates
|
|
314
|
+
materialized sessions via `ctx.sessionPersistence.list()` (`SessionHeader`:
|
|
315
|
+
id/cwd/createdAt), titles come from live `session/title` events or are read
|
|
316
|
+
best-effort from the stored log (the last `session/title` event; oversized logs are
|
|
317
|
+
skipped), sorted by `updatedAt` descending. `session/delete` disposes the live agent
|
|
318
|
+
(`sessions.delete` + `dispose`), then removes the session's directory via
|
|
319
|
+
`persistence.locate(header)` — note dsh's persistence surface has **no official
|
|
320
|
+
delete API**, so this removes the backend directory directly. Live title/activity
|
|
321
|
+
changes are pushed as `session_info_update` notifications (`session/title` and
|
|
322
|
+
`turn/end` events).
|
|
323
|
+
- **Empty-effort suppression**: when the routed model exposes no reasoning efforts,
|
|
324
|
+
the `reasoning_effort` option is not advertised — Zed renders no empty, inoperable
|
|
325
|
+
"Reasoning effort" chip. Switching to a model with efforts makes the option reappear
|
|
326
|
+
(every switch replays `config_option_update`).
|
|
327
|
+
- **Modes**: permission presets are presented as ACP session modes, so Zed's mode
|
|
328
|
+
switcher drives the sandbox/approval presets.
|
|
329
|
+
- **Known limitations** (inherited from the official bridge): baseline prompts only
|
|
330
|
+
(no image/audio/MCP attachments), no `additionalDirectories`/MCP server attachment,
|
|
331
|
+
committed text streams at block granularity, and one in-flight prompt per session.
|
|
332
|
+
Session resume and the archive list are supported (see above), but
|
|
333
|
+
`session/close` / `session/fork` / `session/resume` are not implemented (the
|
|
334
|
+
capabilities are not declared, so conforming clients do not call them);
|
|
335
|
+
`session/delete` removes the backend directory directly because dsh's persistence
|
|
336
|
+
surface has no official delete API.
|