dsh-acp-enhanced 0.1.0 → 0.2.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/README-en.md +14 -6
- package/README.md +16 -8
- package/lib/index.js +155 -9
- package/package.json +31 -13
package/README-en.md
CHANGED
|
@@ -13,6 +13,7 @@ controls. This project is a drop-in replacement that surfaces what the Web GUI h
|
|
|
13
13
|
| Surface | ACP mechanism | What you see in Zed |
|
|
14
14
|
|---|---|---|
|
|
15
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
|
+
| **Reasoning streaming** | reasoning blocks (`blockType: 'reasoning'`) commit the same way, sent as `agent_thought_chunk` | the model's thinking scrolls live in Zed's thinking area |
|
|
16
17
|
| **Token & context telemetry** | standard `usage_update` (`used` = context pressure, `size` = model context window) | Context meter in the agent status bar |
|
|
17
18
|
| **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
19
|
| **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 |
|
|
@@ -26,19 +27,20 @@ controls. This project is a drop-in replacement that surfaces what the Web GUI h
|
|
|
26
27
|
| **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
28
|
| **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
29
|
| **Empty-option suppression** | no `reasoning_effort` option is advertised when the routed model exposes no efforts | No empty, unclickable "Reasoning effort" chip |
|
|
30
|
+
| **MCP servers** | `session/new` / `session/load` `mcpServers` mount `@deepseek-ai/dsh-mcp-client` per entry (stdio + streamable HTTP, resolved from the host dsh install for a single module instance); tools join as `mcp__<serverName>__<tool>`; a failing server never takes the session down | tools from any MCP server (database, browser, filesystem, …) land directly in the model's tool list |
|
|
29
31
|
|
|
30
32
|
## Preview
|
|
31
33
|
|
|
32
34
|
After picking **dsh-acp-enhanced** in Zed's AI Agent panel, you get:
|
|
33
35
|
|
|
34
|
-
|
|
36
|
+
<img src="assets/screenshots/approval-config-context.png" alt="Approval popup, model/reasoning-effort switches, context ring" width="560">
|
|
35
37
|
|
|
36
38
|
- Tool calls that need permission pop a **native approval prompt** (allow-once /
|
|
37
39
|
reject-once); below the input box sit the **model**, **reasoning effort**,
|
|
38
40
|
**permission preset**, and **plan mode** config options plus the **context usage
|
|
39
41
|
ring** (`usage_update` telemetry with cache hit rate, TPS, and more).
|
|
40
42
|
|
|
41
|
-
|
|
43
|
+
<img src="assets/screenshots/tool-cards-elicitation.png" alt="Tool call inputs and outputs, native Zed question form" width="320">
|
|
42
44
|
|
|
43
45
|
- **Tool cards** expand to show each call's full arguments (e.g. the exact bash
|
|
44
46
|
command) and the result preview (`rawInput` / `rawOutput`); when dsh needs your
|
|
@@ -206,6 +208,8 @@ dsh plugin --profile acp-enhanced add "link:/absolute/path/to/dsh-acp-enhanced/p
|
|
|
206
208
|
```sh
|
|
207
209
|
node scripts/acp-client.mjs # end-to-end smoke test (needs a routable provider)
|
|
208
210
|
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 (mounts a minimal stdio MCP server, no model calls)
|
|
212
|
+
node scripts/acp-smoke-keyless.mjs # keyless boot smoke (CI: auto-creates a profile → initialize → session/new)
|
|
209
213
|
node scripts/acp-resume-test.mjs # resume tests (two processes: create+persist → load+replay → continue)
|
|
210
214
|
ACP_DEBUG=1 dsh --profile acp-enhanced # lifecycle trace on stderr
|
|
211
215
|
```
|
|
@@ -218,8 +222,11 @@ switching, and `session/cancel`. `acp-client-tools.mjs` uses the SDK's
|
|
|
218
222
|
`zed_*` tool calls arrive as `fs/write_text_file`, `fs/read_text_file`,
|
|
219
223
|
`terminal/create` requests, that `ask_user_question` arrives as an `elicitation/create`
|
|
220
224
|
form (with enum options), that the `plan_mode` boolean toggle emits ACP `plan` updates
|
|
221
|
-
(on → entry, off → cleared), and that
|
|
222
|
-
|
|
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.
|
|
223
230
|
|
|
224
231
|
## Design notes
|
|
225
232
|
|
|
@@ -327,8 +334,9 @@ advertise an empty `reasoning_effort`.
|
|
|
327
334
|
- **Modes**: permission presets are presented as ACP session modes, so Zed's mode
|
|
328
335
|
switcher drives the sandbox/approval presets.
|
|
329
336
|
- **Known limitations** (inherited from the official bridge): baseline prompts only
|
|
330
|
-
(no image/audio
|
|
331
|
-
|
|
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.
|
|
332
340
|
Session resume and the archive list are supported (see above), but
|
|
333
341
|
`session/close` / `session/fork` / `session/resume` are not implemented (the
|
|
334
342
|
capabilities are not declared, so conforming clients do not call them);
|
package/README.md
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
| 能力 | ACP 机制 | 你在 Zed 中看到的 |
|
|
14
14
|
|---|---|---|
|
|
15
15
|
| **块级流式输出** | 每个已提交文本块(`block-end`)发送一条 `agent_message_chunk`,按每个模型 step 的 `messageId` 分组 | agent 工作时文本实时出现;被取消/重试的块不会残留撕裂的半截输出 |
|
|
16
|
+
| **推理流式** | reasoning 块(`blockType: 'reasoning'`)同样按块提交,发送 `agent_thought_chunk` | 模型的思考过程实时滚动(Zed 的 thinking 区域) |
|
|
16
17
|
| **Token 与上下文遥测** | 标准 `usage_update`(`used` = 上下文压力,`size` = 模型上下文窗口) | agent 状态栏中的上下文仪表 |
|
|
17
18
|
| **缓存命中率 / TPS / 输入-输出-推理 token / 工具耗时 / 轮次计数** | `usage_update._meta` + `tool_call` / `tool_call_update` 的 `_meta` | 每一步都有原始数字(`_meta` 扩展字段携带完整明细) |
|
|
18
19
|
| **工具调用可见性** | `tool_call` 携带 `rawInput`(解析后的参数对象)与 `kind`(read/edit/execute/…);`tool_call_update` 携带 `rawOutput`(结果预览,最多 12k 字符) | 工具卡片能展开看到**具体参数**(如 bash 执行的命令)与**执行结果**,并按工具类型渲染图标 |
|
|
@@ -26,18 +27,19 @@
|
|
|
26
27
|
| **会话恢复(resume)** | 声明 `loadSession` 能力 + `session/load` 走 `agents.resume` 加载持久化会话,并把历史回放为 `user_message_chunk` / `agent_message_chunk` / `tool_call` | 在 Zed 里可以**继续之前的对话线程**(长排查不丢上下文) |
|
|
27
28
|
| **会话归档列表** | 声明 `sessionCapabilities.list/delete`;`session/list` 从持久化存储(`ctx.sessionPersistence.list()`)枚举会话(标题从存储日志的 `session/title` 事件读取),`session/delete` 释放在线 agent 并删除其持久化目录;`session/title` / `turn/end` 实时推送 `session_info_update` | Zed 的**历史线程归档**能看到本项目的全部会话(带标题、按更新时间排序),可点击恢复,也可删除 |
|
|
28
29
|
| **空选项抑制** | 当前模型路由无 reasoning efforts 时不广播 `reasoning_effort` 配置项 | 不再出现一个空的、点不动的"Reasoning effort"chip |
|
|
30
|
+
| **MCP servers** | `session/new` / `session/load` 的 `mcpServers` 逐项挂载 `@deepseek-ai/dsh-mcp-client`(stdio + streamable HTTP,从宿主 dsh 安装解析、保证单实例);工具注册为 `mcp__<serverName>__<tool>`;失败的 server 不会拖垮会话 | 任意 MCP server 的工具(数据库、浏览器、文件系统…)直接进入模型工具列表 |
|
|
29
31
|
|
|
30
32
|
## 效果预览
|
|
31
33
|
|
|
32
34
|
在 Zed 的 AI Agent 面板中选择 **dsh-acp-enhanced** 后,你会看到:
|
|
33
35
|
|
|
34
|
-
|
|
36
|
+
<img src="assets/screenshots/approval-config-context.png" alt="审批弹窗与模型/推理强度切换、上下文环" width="560">
|
|
35
37
|
|
|
36
38
|
- 工具调用需要许可时弹出**原生审批弹窗**(allow-once / reject-once);输入框下方是
|
|
37
39
|
**模型**、**推理强度**、**权限预设**、**Plan mode** 配置项与**上下文用量环**
|
|
38
40
|
(`usage_update` 遥测,含缓存命中率、TPS 等明细)。
|
|
39
41
|
|
|
40
|
-
|
|
42
|
+
<img src="assets/screenshots/tool-cards-elicitation.png" alt="工具调用入参与输出、Zed 原生提问表单" width="320">
|
|
41
43
|
|
|
42
44
|
- **工具卡片**可展开查看每次调用的完整入参(如 bash 执行的命令)与结果预览
|
|
43
45
|
(`rawInput` / `rawOutput`);DSH 需要你确认或选择时,以 **Zed 原生表单**弹出
|
|
@@ -191,6 +193,8 @@ dsh plugin --profile acp-enhanced add "link:/absolute/path/to/dsh-acp-enhanced/p
|
|
|
191
193
|
node scripts/acp-client.mjs # 端到端冒烟测试(需要 DEEPSEEK_API_KEY)
|
|
192
194
|
DEEPSEEK_API_KEY=... node scripts/acp-client.mjs
|
|
193
195
|
node scripts/acp-client-tools.mjs # 客户端工具测试(模拟 Zed 的 fs/terminal/elicitation/plan 能力)
|
|
196
|
+
node scripts/acp-mcp-test.mjs # MCP 挂载测试(挂载一个最小 stdio MCP server,无模型调用)
|
|
197
|
+
node scripts/acp-smoke-keyless.mjs # keyless 冒烟(CI:自动建 profile → initialize → session/new)
|
|
194
198
|
node scripts/acp-resume-test.mjs # 会话恢复测试(两个进程:创建持久化 → 加载回放 → 续聊)
|
|
195
199
|
ACP_DEBUG=1 dsh --profile acp-enhanced # stderr 上的详细生命周期 trace
|
|
196
200
|
```
|
|
@@ -201,7 +205,10 @@ ACP_DEBUG=1 dsh --profile acp-enhanced # stderr 上的详细生命周期 trace
|
|
|
201
205
|
`fs.readTextFile/writeTextFile/terminal/elicitation` 能力,验证模型调用 `zed_*` 工具时请求以
|
|
202
206
|
`fs/write_text_file`、`fs/read_text_file`、`terminal/create` 正确到达客户端,`ask_user_question`
|
|
203
207
|
以 `elicitation/create` 表单(含 enum 选项)到达客户端,`plan_mode` 布尔开关触发 ACP `plan`
|
|
204
|
-
update
|
|
208
|
+
update(开→条目、关→清空),并验证 `reasoning_effort` 选项按路由条件出现(有 efforts 的路由
|
|
209
|
+
出现、无 efforts 的路由抑制)。`acp-mcp-test.mjs` 用 `scripts/fixtures/mcp-echo-server.mjs`
|
|
210
|
+
验证 `session/new` 的 `mcpServers` 被真实挂载(server 收到 initialize 与 tools/list)且相同
|
|
211
|
+
列表不重复挂载。
|
|
205
212
|
|
|
206
213
|
## 设计说明
|
|
207
214
|
|
|
@@ -283,8 +290,9 @@ update(开→条目、关→清空),并验证无 reasoning efforts 的路
|
|
|
283
290
|
配置项——Zed 就不会渲染一个空的、无法操作的"Reasoning effort"chip。切到带 efforts 的
|
|
284
291
|
模型后该选项自动重新出现(每次切换都会重播 `config_option_update`)。
|
|
285
292
|
- **模式**:权限预设被呈现为 ACP 会话模式,因此 Zed 的模式切换器驱动 sandbox/approval 预设。
|
|
286
|
-
- **已知限制**(继承自官方桥接器):仅 baseline prompt
|
|
287
|
-
`additionalDirectories
|
|
288
|
-
in-flight prompt
|
|
289
|
-
`session/
|
|
290
|
-
|
|
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,采用直接删除后端目录的方式。
|
package/lib/index.js
CHANGED
|
@@ -341,9 +341,14 @@ export function apply(ctx, config) {
|
|
|
341
341
|
switch (chunk.type) {
|
|
342
342
|
case 'block-start':
|
|
343
343
|
if (chunk.blockType === 'text') record.buffer[chunk.index] = ''
|
|
344
|
+
else if (chunk.blockType === 'reasoning') record.thoughtBuffer[chunk.index] = ''
|
|
344
345
|
break
|
|
345
346
|
case 'text-delta':
|
|
346
347
|
if (record.buffer[chunk.index] !== undefined) record.buffer[chunk.index] += chunk.text
|
|
348
|
+
else if (record.thoughtBuffer[chunk.index] !== undefined) record.thoughtBuffer[chunk.index] += chunk.text
|
|
349
|
+
break
|
|
350
|
+
case 'reasoning-delta':
|
|
351
|
+
if (record.thoughtBuffer[chunk.index] !== undefined) record.thoughtBuffer[chunk.index] += chunk.text
|
|
347
352
|
break
|
|
348
353
|
case 'block-end': {
|
|
349
354
|
const text = record.buffer[chunk.index]
|
|
@@ -358,6 +363,18 @@ export function apply(ctx, config) {
|
|
|
358
363
|
},
|
|
359
364
|
})
|
|
360
365
|
}
|
|
366
|
+
const thought = record.thoughtBuffer[chunk.index]
|
|
367
|
+
delete record.thoughtBuffer[chunk.index]
|
|
368
|
+
if (chunk.block.type === 'reasoning' && thought !== undefined && thought.length > 0) {
|
|
369
|
+
notify({
|
|
370
|
+
sessionId: record.agent.session.id,
|
|
371
|
+
update: {
|
|
372
|
+
sessionUpdate: 'agent_thought_chunk',
|
|
373
|
+
messageId: record.messageId,
|
|
374
|
+
content: { type: 'text', text: thought },
|
|
375
|
+
},
|
|
376
|
+
})
|
|
377
|
+
}
|
|
361
378
|
break
|
|
362
379
|
}
|
|
363
380
|
case 'usage':
|
|
@@ -886,6 +903,104 @@ export function apply(ctx, config) {
|
|
|
886
903
|
}
|
|
887
904
|
}
|
|
888
905
|
|
|
906
|
+
// ── per-session MCP servers (dsh-mcp-client mounts) ──────────────────────
|
|
907
|
+
|
|
908
|
+
const mcpMounts = new Map() // serverName → { configJson, fiber }
|
|
909
|
+
let mcpClientModule
|
|
910
|
+
let warnedNoMcpClient = false
|
|
911
|
+
|
|
912
|
+
function sanitizeMcpServerName(raw) {
|
|
913
|
+
const cleaned = String(raw ?? 'server').replace(/[^A-Za-z0-9_-]/g, '_').slice(0, 32)
|
|
914
|
+
return cleaned.length > 0 ? cleaned : 'server'
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
/** Project one ACP McpServer onto an mcp-client config, if supported. */
|
|
918
|
+
function mcpConfigFor(server, cwd, serverName) {
|
|
919
|
+
const envList = (list) => Object.fromEntries(
|
|
920
|
+
(Array.isArray(list) ? list : []).map((entry) => [entry.name, entry.value]),
|
|
921
|
+
)
|
|
922
|
+
if (typeof server.command === 'string') {
|
|
923
|
+
return {
|
|
924
|
+
transport: 'stdio',
|
|
925
|
+
serverName,
|
|
926
|
+
command: server.command,
|
|
927
|
+
args: Array.isArray(server.args) ? server.args : [],
|
|
928
|
+
env: envList(server.env),
|
|
929
|
+
cwd: typeof server.cwd === 'string' ? server.cwd : cwd,
|
|
930
|
+
// A dead or misconfigured server must not take the session down;
|
|
931
|
+
// its tools simply stay out of the model's tool list.
|
|
932
|
+
failOnStartupError: false,
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
if (server.type === 'http' && typeof server.url === 'string') {
|
|
936
|
+
return {
|
|
937
|
+
transport: 'streamable-http',
|
|
938
|
+
serverName,
|
|
939
|
+
url: server.url,
|
|
940
|
+
headers: envList(server.headers),
|
|
941
|
+
failOnStartupError: false,
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
return undefined
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
/**
|
|
948
|
+
* Mount/reuse/replace mcp-client instances for a session's server list.
|
|
949
|
+
* Mounts are connection-scoped: the latest session's list wins, so tools
|
|
950
|
+
* registered under `mcp__<serverName>__<tool>` are available to every
|
|
951
|
+
* session on this connection. `ctx.loader.import` resolves the module from
|
|
952
|
+
* the host dsh installation (single module instance, never a profile copy).
|
|
953
|
+
*/
|
|
954
|
+
async function syncMcpServers(servers, cwd) {
|
|
955
|
+
if (servers === undefined || servers.length === 0) return
|
|
956
|
+
let module = mcpClientModule
|
|
957
|
+
if (module === undefined) {
|
|
958
|
+
try {
|
|
959
|
+
module = await ctx.loader.import('@deepseek-ai/dsh-mcp-client')
|
|
960
|
+
mcpClientModule = module
|
|
961
|
+
} catch (error) {
|
|
962
|
+
if (!warnedNoMcpClient) {
|
|
963
|
+
warnedNoMcpClient = true
|
|
964
|
+
logger.warn(`acp-enhanced: ignoring ${servers.length} MCP server(s): @deepseek-ai/dsh-mcp-client is not available: ${String(error.message ?? error)}`)
|
|
965
|
+
}
|
|
966
|
+
return
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
const taken = new Set()
|
|
970
|
+
for (const entry of servers) {
|
|
971
|
+
const base = sanitizeMcpServerName(entry.name)
|
|
972
|
+
let serverName = base
|
|
973
|
+
for (let n = 2; taken.has(serverName); n += 1) serverName = `${base.slice(0, 28)}_${n}`
|
|
974
|
+
const cfg = mcpConfigFor(entry, cwd, serverName)
|
|
975
|
+
if (cfg === undefined) {
|
|
976
|
+
logger.warn(`acp-enhanced: skipping MCP server "${serverName}": unsupported transport`)
|
|
977
|
+
continue
|
|
978
|
+
}
|
|
979
|
+
const configJson = JSON.stringify(cfg)
|
|
980
|
+
const existing = mcpMounts.get(serverName)
|
|
981
|
+
if (existing !== undefined) {
|
|
982
|
+
if (existing.configJson === configJson) {
|
|
983
|
+
taken.add(serverName)
|
|
984
|
+
continue
|
|
985
|
+
}
|
|
986
|
+
try {
|
|
987
|
+
void existing.fiber.dispose()
|
|
988
|
+
} catch (error) {
|
|
989
|
+
logger.warn(`acp-enhanced: disposing MCP server "${serverName}": ${String(error)}`)
|
|
990
|
+
}
|
|
991
|
+
mcpMounts.delete(serverName)
|
|
992
|
+
}
|
|
993
|
+
try {
|
|
994
|
+
const fiber = ctx.plugin(module, cfg)
|
|
995
|
+
mcpMounts.set(serverName, { configJson, fiber })
|
|
996
|
+
taken.add(serverName)
|
|
997
|
+
logger.info(`acp-enhanced: mounted MCP server "${serverName}" (${cfg.transport})`)
|
|
998
|
+
} catch (error) {
|
|
999
|
+
logger.warn(`acp-enhanced: failed to mount MCP server "${serverName}": ${String(error)}`)
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
|
|
889
1004
|
// ── session records + history replay ─────────────────────────────────────
|
|
890
1005
|
|
|
891
1006
|
/** Build the bridge-owned protocol record for a fresh or resumed agent. */
|
|
@@ -922,6 +1037,7 @@ export function apply(ctx, config) {
|
|
|
922
1037
|
lastActivityAt: undefined,
|
|
923
1038
|
toolStats: { count: 0, totalMs: 0, lastCallAt: undefined, lastName: undefined },
|
|
924
1039
|
buffer: {},
|
|
1040
|
+
thoughtBuffer: {},
|
|
925
1041
|
contextWindow: undefined,
|
|
926
1042
|
lastUsage: undefined,
|
|
927
1043
|
selection,
|
|
@@ -973,6 +1089,15 @@ export function apply(ctx, config) {
|
|
|
973
1089
|
return parts.join('\n')
|
|
974
1090
|
}
|
|
975
1091
|
|
|
1092
|
+
/** Extract reasoning text from assistant content blocks (replay only). */
|
|
1093
|
+
function thoughtFromBlocks(content) {
|
|
1094
|
+
const parts = []
|
|
1095
|
+
for (const block of content ?? []) {
|
|
1096
|
+
if (block?.type === 'reasoning' && typeof block.text === 'string') parts.push(block.text)
|
|
1097
|
+
}
|
|
1098
|
+
return parts.join('\n')
|
|
1099
|
+
}
|
|
1100
|
+
|
|
976
1101
|
/**
|
|
977
1102
|
* Replay a resumed session's conversation to the client as ACP session
|
|
978
1103
|
* notifications. Zed inserts the thread before the load RPC completes, so
|
|
@@ -1001,12 +1126,22 @@ export function apply(ctx, config) {
|
|
|
1001
1126
|
}
|
|
1002
1127
|
case 'assistant/message': {
|
|
1003
1128
|
const text = textFromBlocks(event.data.message.content)
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1129
|
+
const thought = thoughtFromBlocks(event.data.message.content)
|
|
1130
|
+
if (text.trim().length === 0 && thought.trim().length === 0) break
|
|
1131
|
+
if (text.trim().length > 0) {
|
|
1132
|
+
await notifyNow(record, {
|
|
1133
|
+
sessionUpdate: 'agent_message_chunk',
|
|
1134
|
+
messageId: randomUUID(),
|
|
1135
|
+
content: { type: 'text', text },
|
|
1136
|
+
})
|
|
1137
|
+
}
|
|
1138
|
+
if (thought.trim().length > 0) {
|
|
1139
|
+
await notifyNow(record, {
|
|
1140
|
+
sessionUpdate: 'agent_thought_chunk',
|
|
1141
|
+
messageId: randomUUID(),
|
|
1142
|
+
content: { type: 'text', text: thought },
|
|
1143
|
+
})
|
|
1144
|
+
}
|
|
1010
1145
|
break
|
|
1011
1146
|
}
|
|
1012
1147
|
case 'tool/call':
|
|
@@ -1067,11 +1202,14 @@ export function apply(ctx, config) {
|
|
|
1067
1202
|
syncClientTools()
|
|
1068
1203
|
return Promise.resolve({
|
|
1069
1204
|
protocolVersion: PROTOCOL_VERSION,
|
|
1070
|
-
agentInfo: { name: 'deepseek-harness-acp-enhanced', version: '0.
|
|
1205
|
+
agentInfo: { name: 'deepseek-harness-acp-enhanced', version: '0.2.0' },
|
|
1071
1206
|
agentCapabilities: {
|
|
1072
1207
|
loadSession: true,
|
|
1073
1208
|
sessionCapabilities: { list: {}, delete: {} },
|
|
1074
1209
|
promptCapabilities: { image: false, audio: false, embeddedContext: false },
|
|
1210
|
+
// Stdio MCP servers always work; streamable HTTP maps onto
|
|
1211
|
+
// dsh-mcp-client's second transport. Legacy SSE does not.
|
|
1212
|
+
mcpCapabilities: { http: true, sse: false },
|
|
1075
1213
|
},
|
|
1076
1214
|
authMethods: [],
|
|
1077
1215
|
})
|
|
@@ -1099,6 +1237,7 @@ export function apply(ctx, config) {
|
|
|
1099
1237
|
}
|
|
1100
1238
|
const record = makeRecord(handle)
|
|
1101
1239
|
sessions.set(sessionId, record)
|
|
1240
|
+
await syncMcpServers(params.mcpServers, params.cwd)
|
|
1102
1241
|
const permission = permissionPresets()
|
|
1103
1242
|
return {
|
|
1104
1243
|
sessionId,
|
|
@@ -1125,7 +1264,7 @@ export function apply(ctx, config) {
|
|
|
1125
1264
|
if (params.additionalDirectories !== undefined && params.additionalDirectories.length > 0) {
|
|
1126
1265
|
throw invalidParams('additionalDirectories is not supported')
|
|
1127
1266
|
}
|
|
1128
|
-
|
|
1267
|
+
await syncMcpServers(params.mcpServers, params.cwd)
|
|
1129
1268
|
const sessionId = SessionId(params.sessionId)
|
|
1130
1269
|
const live = sessions.get(sessionId)
|
|
1131
1270
|
if (live !== undefined) {
|
|
@@ -1385,6 +1524,14 @@ export function apply(ctx, config) {
|
|
|
1385
1524
|
record.agent.cancel({ kind: 'user' })
|
|
1386
1525
|
settlePrompt(record, 'cancelled')
|
|
1387
1526
|
}
|
|
1527
|
+
for (const { fiber } of mcpMounts.values()) {
|
|
1528
|
+
try {
|
|
1529
|
+
void fiber.dispose()
|
|
1530
|
+
} catch (error) {
|
|
1531
|
+
logger.warn(`acp-enhanced: disposing MCP server mount failed: ${String(error)}`)
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
mcpMounts.clear()
|
|
1388
1535
|
quiescing = (async () => {
|
|
1389
1536
|
const subagents = ctx.get('subagents')
|
|
1390
1537
|
if (subagents?.drainContinuableDescendants !== undefined) {
|
|
@@ -1425,5 +1572,4 @@ function validateSessionParams(params) {
|
|
|
1425
1572
|
if (params.additionalDirectories !== undefined && params.additionalDirectories.length > 0) {
|
|
1426
1573
|
throw invalidParams('additionalDirectories is not supported')
|
|
1427
1574
|
}
|
|
1428
|
-
if (params.mcpServers.length > 0) throw invalidParams('mcpServers is not supported')
|
|
1429
1575
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-acp-enhanced",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
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",
|
|
@@ -32,7 +32,26 @@
|
|
|
32
32
|
"test:client": "node scripts/acp-client.mjs"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@agentclientprotocol/sdk": "
|
|
35
|
+
"@agentclientprotocol/sdk": "1.3.0",
|
|
36
|
+
"@deepseek-ai/schemastery": "^3.18.1",
|
|
37
|
+
"zod": "^4.4.3"
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"@deepseek-ai/cordis": "^4.0.1-rc.1",
|
|
41
|
+
"@deepseek-ai/cordis-plugin-include": "^1.0.6-rc.1",
|
|
42
|
+
"@deepseek-ai/cordis-plugin-loader": "^1.0.2-rc.1",
|
|
43
|
+
"@deepseek-ai/dsh-agent": "^0.1.0-rc.6",
|
|
44
|
+
"@deepseek-ai/dsh-agent-instructions": "^0.1.0-rc.6",
|
|
45
|
+
"@deepseek-ai/dsh-invariants": "^0.1.0-rc.6",
|
|
46
|
+
"@deepseek-ai/dsh-llm": "^0.1.0-rc.6",
|
|
47
|
+
"@deepseek-ai/dsh-mcp-client": "^0.1.0-rc.6",
|
|
48
|
+
"@deepseek-ai/dsh-permission-presets": "^0.1.0-rc.6",
|
|
49
|
+
"@deepseek-ai/dsh-session": "^0.1.0-rc.6",
|
|
50
|
+
"@deepseek-ai/dsh-session-query": "^0.1.0-rc.6",
|
|
51
|
+
"@deepseek-ai/dsh-tools": "^0.1.0-rc.6",
|
|
52
|
+
"@deepseek-ai/dsh-user-approval": "^0.1.0-rc.6"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
36
55
|
"@deepseek-ai/cordis": "4.0.1-rc.4",
|
|
37
56
|
"@deepseek-ai/cordis-plugin-include": "1.0.6-rc.4",
|
|
38
57
|
"@deepseek-ai/cordis-plugin-loader": "1.0.2-rc.4",
|
|
@@ -40,23 +59,22 @@
|
|
|
40
59
|
"@deepseek-ai/dsh-agent-instructions": "0.1.0-rc.6",
|
|
41
60
|
"@deepseek-ai/dsh-invariants": "0.1.0-rc.6",
|
|
42
61
|
"@deepseek-ai/dsh-llm": "0.1.0-rc.6",
|
|
62
|
+
"@deepseek-ai/dsh-mcp-client": "0.1.0-rc.6",
|
|
43
63
|
"@deepseek-ai/dsh-permission-presets": "0.1.0-rc.6",
|
|
44
64
|
"@deepseek-ai/dsh-session": "0.1.0-rc.6",
|
|
45
65
|
"@deepseek-ai/dsh-session-query": "0.1.0-rc.6",
|
|
46
66
|
"@deepseek-ai/dsh-tools": "0.1.0-rc.6",
|
|
47
|
-
"@deepseek-ai/dsh-user-approval": "0.1.0-rc.6"
|
|
48
|
-
"@deepseek-ai/schemastery": "^3.18.1",
|
|
49
|
-
"zod": "^4.4.3"
|
|
50
|
-
},
|
|
51
|
-
"peerDependencies": {
|
|
52
|
-
"@deepseek-ai/cordis": "^4.0.1-rc.1",
|
|
53
|
-
"@deepseek-ai/dsh-agent": "^0.0.1-rc.1",
|
|
54
|
-
"@deepseek-ai/dsh-llm": "^0.0.1-rc.1",
|
|
55
|
-
"@deepseek-ai/dsh-permission-presets": "^0.0.1-rc.1",
|
|
56
|
-
"@deepseek-ai/dsh-session": "^0.0.1-rc.1",
|
|
57
|
-
"@deepseek-ai/dsh-user-approval": "^0.0.1-rc.1"
|
|
67
|
+
"@deepseek-ai/dsh-user-approval": "0.1.0-rc.6"
|
|
58
68
|
},
|
|
59
69
|
"license": "MIT",
|
|
70
|
+
"repository": {
|
|
71
|
+
"type": "git",
|
|
72
|
+
"url": "git+https://github.com/grunmin/dsh-acp-enhanced.git"
|
|
73
|
+
},
|
|
74
|
+
"homepage": "https://github.com/grunmin/dsh-acp-enhanced",
|
|
75
|
+
"bugs": {
|
|
76
|
+
"url": "https://github.com/grunmin/dsh-acp-enhanced/issues"
|
|
77
|
+
},
|
|
60
78
|
"dsh": {
|
|
61
79
|
"bundle": {
|
|
62
80
|
"patch": "./cordis.patch.yml"
|