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 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
- ![Approval popup, model/reasoning-effort switches, context ring](assets/screenshots/approval-config-context.png)
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
- ![Tool call inputs and outputs, native Zed question form](assets/screenshots/tool-cards-elicitation.png)
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 routes without reasoning efforts no longer
222
- advertise an empty `reasoning_effort`.
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/MCP attachments), no `additionalDirectories`/MCP server attachment,
331
- committed text streams at block granularity, and one in-flight prompt per session.
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
- ![审批弹窗与模型/推理强度切换、上下文环](assets/screenshots/approval-config-context.png)
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
- ![工具调用入参与输出、Zed 原生提问表单](assets/screenshots/tool-cards-elicitation.png)
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(开→条目、关→清空),并验证无 reasoning efforts 的路由不再广播空 `reasoning_effort`。
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(无图片/音频/MCP 附件)、不支持
287
- `additionalDirectories`/MCP server 附加、已确认文本按块粒度流式,且每个会话同时只能有一个
288
- in-flight prompt。会话恢复与归档列表已支持(见上),但 `session/close` / `session/fork` /
289
- `session/resume` 未实现(不声明能力,合规客户端不会调用);`session/delete` 因 dsh 持久化
290
- 面没有官方删除 API,采用直接删除后端目录的方式。
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
- if (text.trim().length === 0) break
1005
- await notifyNow(record, {
1006
- sessionUpdate: 'agent_message_chunk',
1007
- messageId: randomUUID(),
1008
- content: { type: 'text', text },
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.1.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
- if (params.mcpServers.length > 0) throw invalidParams('mcpServers is not supported')
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.1.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": "0.25.1",
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"