elk-9527/dsh-vscode

dsh-door

为正在运行的 DeepSeek Harness 内核提供仅监听本机回环地址的 ACP 接入点,供 VS Code 等本机客户端连接,共用模型、工具、记忆和会话,并支持历史查看、权限切换及调用 DSH 插件已注册的能力。

‍ 开发与运行时

dsh-acp-door

连接本机 DSH 与 VS Code 等外部程序,共用模型、工具、记忆和会话。支持历史查看、权限切换及已注册插件的能力调用。

旧版 v3 历史异步读取并在内存中缓存摘要,最多保留 16384 条或 8 MiB;不缓存完整对话正文。文件身份、大小或修改时间变化后重新读取,删除的记录从缓存移除。客户端通过 $/cancelRequest(id)或 ACP 的 $/cancel_request(requestId)取消查询,连接断开也会停止等待结果;v3 会同时停止后续读取,v4 内部读取仍由内核公开 sessionQuery 管理。每个连接最多同时等待 8 个历史请求。

需要已安装并运行的 DeepSeek Harness,以及支持 ACP 的外部客户端。接入点仅供本机使用;安装后需重启对应的 DSH 内核。

0.2.0 增加同一连接上的 IDE Bridge:插件主动注册能力、本机自动鉴权、可恢复运行与取消。 协议规范见仓库的 docs/bridge-v1.md。现有 ACP 会话、流式输出、历史和权限方法继续兼容。

English

dsh-acp-door opens an extra ACP (Agent Client Protocol) transport on a running DeepSeek Harness kernel — loopback only (127.0.0.1), for use on your own machine. ACP chat retains its local transport; IDE Bridge calls use a per-user endpoint credential and client lease. External clients such as the *DSH Panel* VS Code extension can then drive the very same kernel you already have open: same config, same memory, same tools, same session records.

ACP normally speaks over stdio, which can only be attached at process start; this plugin instead mounts one ACP bridge per TCP connection on top of the live kernel, and exposes a few bypass methods the core does not have over ACP (session listing, permission presets).

Install:

dsh plugin --profile <your-profile> add dsh-acp-door

Restart the kernel (or the desktop app) afterwards — plugins are loaded at startup.

For a compatible update of an installed copy, run:

dsh plugin --profile <your-profile> update dsh-acp-door

Restart the kernel afterwards.

Compatibility: DSH 0.1.5-rc.2, 0.2.0-rc.1, 0.2.0-rc.2, and previews 0.2.1-alpha.1 / 0.2.1-rc.2. DSH 0.2.1-rc.2 requires dsh-acp-door 0.5.2 or later.

安装

dsh plugin --profile <档名> add dsh-acp-door

安装完成后需要重启内核(或重启桌面端)才会生效:ACP 接入点插件(dsh-acp-door)在内核启动时加载。 该插件装入哪个档,外部程序即可连接该档的 DSH —— 桌面端所用的档由桌面端自身管理 (运行时命令行无法修改),因此通常装入用户自己的档。

若 VS Code 面板需要在桌面端未运行时自行启动 DSH,应使用可由命令行启动的网页配置集, 而不是 desktop 配置集。例如:

dsh --profile vscode-panel --from-default-profile web --dump-config
dsh plugin --profile vscode-panel add dsh-acp-door

安装后重启该配置集的内核。新会话默认使用该配置集中 DSH 当前选择的默认模型;无需把 服务商或模型名再复制到本插件配置中。若该配置集还没有选择模型,先在 DSH 的模型设置中完成选择。 不同电脑分别安装各自的 DSH 与本插件;接入点固定在 127.0.0.1,不支持远程设备连接、端口转发或隧道。

更新

常规兼容更新使用以下命令:

dsh plugin --profile <档名> update dsh-acp-door

该命令按当前配置集中声明的版本范围更新。更新完成后重启对应内核(桌面端使用的配置集则重启桌面端)。 若版本说明标为不兼容更新,按该版本的安装说明处理,避免在未阅读变更记录的情况下跨越大版本。

配置项(写入档的 cordis.patch.yml,见本包自带的同名文件):

键 · 默认 · 说明 host · 127.0.0.1 · 固定监听地址。其它值会被忽略,接入点不会暴露到局域网。 port · 47821 · 监听端口。也可用环境变量 DSH_ACP_DOOR_PORT 覆盖(0.0.11 起)。 provider / model · 留空,跟随 DSH · 可选的成对覆盖;必须同时填写才会固定新会话的初始模型。 preset · standard · 新会话挂载的 agent preset 套件。

解决的问题

ACP 默认使用标准输入输出:该通道只能在进程启动时接入。 而 DSH Desktop 已处于运行状态时,外部程序无法接入该通道。

DSH 自带的 ACP 插件支持注入传输层(其源码中有对应实现):

const stream = config.stream ?? ndJsonStream(Writable.toWeb(process.stdout), Readable.toWeb(process.stdin));

因此该插件仅执行一项操作:在每个 TCP 连接上挂载一份 ACP 桥。

DSH Desktop(运行中)
├── 内核:agents / llm / sessions / 工具 / 记忆 / 技能      ← 只有一份
├── 网页界面                                     (原有)
└── 该插件:127.0.0.1:47821  ── 每个连接 = 一份 ACP 桥      (新增)

会话预设补挂的必要性

这是该插件除监听端口之外唯一执行的实际操作,也是最易出错的环节,因此单独说明。

桌面端与网页端架构停用了宿主层的全部工具 —— dsh-web-app 的补丁中:

- id: tool-bash
  disabled: true
- id: tool-pwsh
  disabled: true
- id: tool-jobs
  disabled: true
- id: tool-fs
  disabled: true
- id: tool-fs-search
  disabled: true

它的注释说明了理由:*"the Web surface disables them here and lets each session mount a preset instead"* —— 即工具改为 每个会话按 agent preset 挂载一套。

而 @deepseek-ai/dsh-acp 创建 agent 时只传入工作目录、从不指定预设 (dsh-acp/lib/index.js 的 AcpSession.create 中为 meta: { cwd })。 两者结合后,经 ACP 建立的会话缺少工具来源:宿主层未提供工具,预设层也未指定预设 —— 即一个无法调用工具的 agent(该 agent 会声明「我来读一下这个文件」,随后无法执行任何操作)。

内核自身的报错文案指明正确做法:

(join through AgentPresets.mount() or composeFrom() in the agent factory setup)

因此该插件监听 agent/created,使用公开的 agentPresets 补上这一步。新建会话与 恢复的会话使用两个不同的入口 —— 该结论来自实测,并非推测:

情况 · 所用入口 · 原因 新建的会话 · select(agent, id) · 该入口重新组装 agent 的作用域,并将本次选择追加进会话事件日志(agent-preset/selected)。 恢复的会话(断线重连 / 打开历史会话) · mount(agent.ctx, id) · 内核按「是否执行过回合」为 select 设置了锁(agent-preset/locked,属设计行为)。恢复时 agent 的作用域为重新组装的结果,缺少该调用时作用域为空。mount 是工厂期使用的入口,只负责组装,不检查该锁。

恢复会话与工具挂载

恢复的会话需要重新挂载工具预设。select() 适用于新建会话;恢复时遇到预设锁,改用 mount(agent.ctx, id),确保恢复后的 Agent 具有该预设提供的工具。

两项必须注意的约束(均已实测)

  1. `agentPresets` 必须写成声明式依赖。 cordis 的服务代理对未声明的属性

直接抛错(cannot get property "…" without inject)。该异常发生在内核 创建 agent 的同步流程中:或打断 session/new,或被 async 捕获, 使该插件静默失败。因此该服务写入 inject,缺少该服务的 profile 不会启动这个插件。 同一结论也适用于 mount():该入口需要 agent 的作用域上下文(agent.ctx), 传入错误对象会抛 refusing to compose an unscoped context。

  1. 存在一处竞态,必须由入站闸阻塞。 内核派发 agent/created 时不等待

监听器返回的 promise,因此「补挂预设」与「客户端发送第一个 prompt」并行执行。 客户端请求先到达时,模型尚无完整工具清单。因此传输层对该会话的 session/prompt 设置入站闸,等待预设挂载完成再发送。

预设恢复来源

对于 v3 会话格式:

  • 桌面端建立的会话,第一行 header 中含 "agentPreset":"standard";
  • 经该插件建立的会话,不含 agentPreset —— 建立后读取即不存在,执行完一个回合后再读仍不存在。

即内核仅在桌面端所使用的建会话方式下将预设写入记录。因此:

  1. 该插件无法从记录中获知原先挂载的预设 → 该插件在内核进程内保存一份

sessionId → preset 映射(跨连接共享),客户端也会在 session/resume 的 _meta 中指定所需预设,两个来源合并使用。

  1. 这也是上文缺陷的根源:不读取记录则无法自动还原,必须依靠 mount 补挂。

配置

字段 · 默认 · 说明 host · 127.0.0.1 · 固定监听地址。其它值会被忽略,接入点不会暴露到局域网。 port · 47821 · 监听端口;传 0 由系统分配空闲端口。 provider · — · 可选。与 model 同时填写时,固定新会话的初始模型服务商。 model · — · 可选。与 provider 同时填写时,固定新会话的初始模型名。 preset · standard · 新会话挂载的 agent preset:standard(标准)/ ptc / cordis(创造)/ minimal(极简)。 diagLog · 关 · 诊断日志文件路径;也可用环境变量 DSH_ACP_DOOR_DIAG。用于排查时序问题,未配置时不写任何文件。

初始模型的选择顺序

  1. 插件配置中同时填写了 provider 与 model:使用这组显式覆盖。
  2. 未完整填写:读取 DSH 的 agentDefaultModel.currentSelection(),即该配置集当前选择的默认模型。
  3. 旧版 DSH 没有该服务且也没有完整显式配置:状态接口报告“模型未就绪”,新版 VS Code 面板会在

建会话前提示先选择模型。其它客户端也可调用 dsh-door/status 自检。

只填写 provider 或 model 其中一项不会与 DSH 的值拼接;这组不完整配置会被忽略并记录诊断。 DSH 冷启动时,插件会先等待用户设置加载完成再开放端口,避免短暂读取到基础默认模型。

DSH 内核兼容性

DSH · 历史会话 · 权限预设 0.2.0-rc.1 / 0.2.0-rc.2 · 公开 sessionQuery 服务,v4 会话格式 · catalog/current/resolve/set 0.2.1-alpha.1 / 0.2.1-rc.2 · 公开 sessionQuery 服务,v4 会话格式 · catalog/current/resolve/set 0.1.5-rc.2 · 只读解析本机 v3 会话文件 · selectFor/permissionState/current/set

两条路径对外提供相同的 dsh-door/… 方法。接入点的状态回复会用 historyKind、 sessionFormat 和 permissionKind 报告实际选中的适配器。不会在插件中私自解析 v4: 该格式由 DSH 0.2 的 sessionQuery 负责读取和迁移。

状态中的可选 protocolVersion 与 runtime.acpVersion 分别报告接入协议及实际加载的 ACP 包版本。

旧 npm 发行物的依赖使用版本范围:DSH 0.1.5-rc.2 可解析到 ACP 0.1.5-rc.3。兼容矩阵分别记录两个版本;依赖范围包含该 ACP 版本不表示已经验收同号的 DSH 发行物。 无法识别的已有历史服务会报告 history: false,不尝试以旧格式读取新数据。

版本变更

版本 · 改了什么 0.1.0 · 兼容 DSH 0.2:历史使用公开 sessionQuery 和 v4 格式,权限使用新版 catalog 接口;保留 DSH 0.1 的 v3/selectFor 回退路径,并报告当前适配器。 0.0.16 · 建立、恢复或关闭会话后及时释放连接内的请求期索引与挂载状态,避免长连接持续积累过期记录;关闭失败时保留仍有效的会话状态。 0.0.15 · 卸载、重载或退出时完整关闭所有客户端连接;单条连接清理失败不再阻断其余连接。 0.0.14 · 默认跟随 DSH 当前模型;新增安全状态接口;修复冷启动时用户设置尚未加载导致首次连接选错模型。 0.0.13 · 固定接入点监听 127.0.0.1,配置中的其它监听地址会被忽略。 0.0.12 · 新增 dsh-door/permission/get 与 dsh-door/permission/set 两个旁路方法:将内核 @deepseek-ai/dsh-permission-presets 的权限预设清单与切换透传给客户端(ACP 仅暴露模型与推理强度两个 config option,权限选择器属于其「刻意不提供」的 DSH 专用 UI 类别)。清单不写死 —— 内核配置了什么就返回什么(read-only/workspace-write/danger-full-access 来自 dsh-base,auto-approval 由 dsh-auto-approval-plugin 添加,用户也可自行添加),因此客户端一侧与桌面端始终为同一真源。permissionPresets 是可选依赖(ctx.inject),档中未挂载该服务时该插件照常工作、仅返回「这个内核没有权限预设」。回归测试:test/permission.js(纯函数 29 项)+ 扩展一侧的 test/permission-live.js(真内核,四档全切一遍 24 项)。 0.0.9 · 修复两个缺陷:① 建会话失败时的预设指定会留在队列中,被下一次建会话取走(用户未指定却挂载了其它模式)—— 出错的回复原先未被处理(预筛要求该行含 "result");② 入站闸等待预设挂载没有超时,内核某个服务返回永不落定的 promise 时,session/prompt 会被永久阻塞 —— 症状为「发送消息后毫无响应、也没有任何报错」。现在入站与出站使用同一上限(MOUNT_WAIT_MS,经 waitForMount()),到时放行。回归测试:test/frames.js 第 8 节第 (6) 段、第 9 节。 0.0.8 · 新增 dsh-door/sessions/list 与 dsh-door/sessions/get 两个旁路方法(该插件只读解析 $DSH_HOME/sessions,多帧 zstd)。供「内核没有 session/load、面板又需查看历史」的场景使用。另修复出站中继必须返回 WritableStream(写成 TransformStream 且无人消费 readable 时,该插件的回复始终无法发出,客户端表现为「已连接但不应答」)。 0.0.7 · 模式(agent preset)切换;修复「断线重连之后会话没有工具」(遇到预设锁时改用工厂期的 mount() 补挂)。 0.0.5 · 首个可用版本。

注意:DSH 0.2 的 desktop 档由 Electron 应用独占管理,命令行会拒绝 dsh plugin --profile desktop …。请在 Desktop 的插件界面安装或更新本插件;命令面板中的 DSH:准备或修复自启配置只处理可由命令行管理的 vscode-panel 档。

旁路方法:ACP 未提供的能力

该插件除转发 ACP 之外,还自行应答少量带 dsh-door/… 前缀的请求帧(不转发给内核, 就地回复),因为 ACP 协议中不包含对应能力:

方法 · 该插件所需的 params · 该插件的回复 · 起始版本 dsh-door/status · {} · { version, model: {ready, source, provider?, model?}, capabilities } · 0.0.14 dsh-door/sessions/list · {}(可选 cwd;从 0.3.0 起支持 limit、offset、query) · { skipped, sessions: [...] };0.3.0 起含 total、offset、nextOffset? · 0.0.8 dsh-door/sessions/get · { id } · { card, entries, truncated } · 0.0.8 dsh-door/permission/get · { id } · { currentValue, options: [{value, name?, description?}], defaultPreset? } · 0.0.12 dsh-door/permission/set · { id, value } · 同 permission/get(修改之后回读的真实状态) · 0.0.12

权限相关的两个方法无法经由 ACP 提供:@deepseek-ai/dsh-acp 仅将模型与推理强度 暴露为 session/set_config_option,其 README 写明该实现「刻意不提供 DSH 专用呈现数据 与交互式 UI 功能」。权限选择器正属于该类别。

实现上需注意两点:

  1. permissionPresets 是可选依赖 —— 使用 ctx.inject(['permissionPresets'], …)

而不写入 export const inject。若写死,未挂载该服务的档(例如极简的自建档) 会导致整个插件无法加载,而非仅权限这一项不可用。

  1. 切换后回读再回复(settledPermission),不采用乐观更新:若某项设置被其它

机制阻塞,客户端显示的是真实状态。