| name | ide-runbook |
| description | Operate the dsh-engineering-services DSH host plugin — install into a dsh profile, restart to activate, run the three-pillar smoke test (LSP/task/debug), and troubleshoot the real Windows/environment pitfalls. Trigger when someone needs to install/upgrade/reinstall the plugin (`dsh plugin --profile ... add link:...`), restart dsh web and confirm the engineering-services tools appear, run the LSP/DAP/task smoke verification, or hit the known failure modes (pnpm optional-dependency downloads, tsserver ENOENT / no tsserver.js, debugpy NoneType/nope module, tools not showing after install). 运维/装机/重启生效/冒烟/排查 dsh-engineering-services 时触发。 |
dsh-engineering-services 运维手册(Runbook)
操作层面手册:把插件装进 dsh profile → 重启让工具生效 → 冒烟验证三支柱 → 排查真实坑。ide-three-pillars(同目录)讲工具怎么用,本手册讲服务怎么装上、怎么确认活着、坏了怎么修。两者互补。
全部命令与路径基于真实端到端验证(2026-08,Windows / PowerShell 7)。$PWD = 插件仓库根目录(含 package.json 的 @local/dsh-engineering-services)。
一次装好(含踩坑后的一键收尾)
插件的安装入口是 dsh CLI 的插件转发(dsh plugin 只是把参数透传给 profile 目录里的 pnpm):
cd C:\Documents\GitHub\dsh\dsh-engineering-services
dsh plugin --profile web add link:$PWD
最容易被「假失败」骗的关键
pnpm 失败 ≠ 插件安装失败。 dsh plugin add 会顺带解析 next/sharp(web 前端)的所有跨平台可选原生包(@img/sharp-*、@next/swc-* 的 linux/darwin/wasm 变体),而 dsh 只在 pnpm exit 0 时才把插件写进 dsh.profile.bundles。两条独立的事:
- 依赖 + junction 只要 link 写进
package.json、node_modules/@local/dsh-engineering-services junction 建好就已成(即使 pnpm 后续 exit 1)。
- bundle 注册(决定工具能不能进会话)必须 pnpm exit 0,否则
dsh-engineering-services 只在 dependencies、不在 bundles → 装「成功」了工具也不出现。
所以看到 dsh: pnpm failed in profile directory ... 时,先确认这两步现状再决定是否重试。
Windows 上 pnpm 卡在可选包下载的标准修复
现象:[WARN] GET https://registry...)/(@img/sharp-*|@next/swc-*).tgz error (UND_ERR_DESTROYED) 反复重试后 dsh: pnpm failed。这通常是 Windows SChannel 证书吊销离线(curl 报 CRYPT_E_REVOCATION_OFFLINE)打挂了大 tarball 的 TLS 流,而 Node/undici 各异——可先验证点:node -e "fetch('<同一 tarball URL>').then(r=>r.arrayBuffer()).then(b=>console.log(b.byteLength))" 能下全,就说明是 pnpm 层问题不是断网。
收尾命令(在插件目录):
cd C:\Documents\GitHub\dsh\dsh-engineering-services
dsh plugin --profile web add link:$PWD --no-optional # 跳过可选包;但也可能触发 lockfile 缺条目
若 ERR_PNPM_LOCKFILE_MISSING_DEPENDENCY(lockfile 被污染,含 97 条外来平台条目)或仍反复下外来包,干净重建 lockfile + 平台锁定:
先编辑 %USERPROFILE%\.dsh\profiles\web\pnpm-workspace.yaml,让它最终长这样(pnpm>=10 的设置读这个文件,不读 package.json 的 "pnpm" 字段):
packages:
- .
nodeLinker: hoisted
autoInstallPeers: false
allowBuilds:
sharp: true
supportedArchitectures:
os:
- win32
cpu:
- x64
libc:
- current
再在 PowerShell 里重建 lockfile 并触发 bundle 注册:
$prof = "$env:USERPROFILE\.dsh\profiles\web"
Remove-Item "$prof\pnpm-lock.yaml" -Force # 删掉被污染(97 条外来平台条目)的 lockfile 重建
& pnpm --dir $prof install --no-frozen-lockfile --network-concurrency=1
# 确认 exit 0(应看到 "... Done in ... using pnpm")
cd C:\Documents\GitHub\dsh\dsh-engineering-services
dsh plugin --profile web add link:$PWD # 再跑官方 add,触发 bundles 注册
怎么确认「工具真的会进会话」(不必等重启就能验)
bundle 行只有在 profile 组合里出现才算真注册。可用 dsh 的组合导出直接查(--dump-config 组树不启服务):
node "C:\Users\LEGION\AppData\Roaming\npm\node_modules\@deepseek-ai\dsh\lib\bin.js" web --dump-config |
Select-String -Pattern "engineering-services|@local/dsh-engineering"
期望输出里出现:
# == @local/dsh-engineering-services
- id: engineering-services
name: '@local/dsh-engineering-services'
没有 → 插件还没进 dsh.profile.bundles,工具进不了会话,先解决上面的 pnpm exit 0 问题。
重启让工具生效
bundle 组合是在 dsh web 进程启动时加载的。改 profile 组合(install/bundle 注册/插件 patch)后必须重启 dsh web,否则工具依旧看不到。重启后可用 cordis_inspect(宿主工具的 Tool.listTools)核对 14 个工具是否出现:lsp_diagnostics ... lsp_workspace_symbol(12 个)+ debug + task。
冒烟验证四层(按顺序)
1) LSP —— pyright(Python)与 typescript-language-server(TS/JS)
pyright 自带分析器、无需额外依赖,任何 .py 都能验。下面这条用小脚本驱动插件内置的 runner,直接对真实文件做符号查询,验证「spawn → JSON-RPC → 结果」整条 server 链路(不受 tool-schema 传参/路径校验影响):
$smoke = "$env:TEMP\py-smoke.mjs"
@'
import { loadRuntime } from "file:///C:/Documents/GitHub/dsh/dsh-engineering-services/lib/lsp/adapters.js";
import { runNavigationQuery } from "file:///C:/Documents/GitHub/dsh/dsh-engineering-services/lib/lsp/runner.js";
const root = "C:/Documents/GitHub/dsh/deepseek-harness"; // 工作区内的真实目录
const file = root + "/python/sdk/src/deepseek_harness/client.py";
const rt = loadRuntime(root);
const py = rt.adapters.find(a => a.name === "pyright");
const out = await runNavigationQuery(py, { mode: "symbols", file, root },
rt.timeoutMs, undefined, { ui: { setStatus: () => {} } }, "lsp");
console.log(typeof out === "string" ? out : JSON.stringify(out));
'@ | Set-Content $smoke -Encoding utf8
node $smoke # 期望:打印 client.py 的符号表(ModelT/HarnessClient/__init__/...),即链路通
或直接对真实文件调用 lsp_diagnostics / lsp_symbols。
tsserver 的三个真坑(全踩过):
spawn ...typescript-language-server ENOENT:Windows 上 npm 会装一个 845 字节无扩展名 Unix shim,而 resolveCommandPath 旧实现把 "" 排第一 → shim 抢在真实 .cmd 前被 spawn → ENOENT。已修:src/lsp/command.ts 扩展名顺序改为 [".exe",".cmd",".bat",""]、"" 放最后。症状再现时先查这个顺序(改源码后要 npm run build 再重启才生效)。
Could not find a valid TypeScript installation · ensure "typescript" is installed · tsserver.path:typescript-language-server 需要工作区能解析到带 lib/tsserver.js 的 TS5.x。npm 全局装的 typescript@7(原生/Go 版)没有 tsserver.js → 用它必挂。这是环境依赖缺口、非插件 bug。标准修法 = 铺 junction,让工作区向上能找到带 tsserver.js 的 TS5(见下节「tsserver 的标准修法」),比 npm i -D typescript@5 更稳——pnpm 单体仓库的 typescript 走 store、不会 build 进普通 node_modules,tsserver 直解不到。
- pyright 路径越界报错:host 的 LSP 工具按会话工作区(
C:\Documents\GitHub\dsh)收口,--dump/带外驱动时把 root 指到工作区内的真实文件,别指到工作区外的临时目录。
2) tsserver 的标准修法(铺 junction 让工作区可解析 TS5)
pnpm 单体仓库(如 deepseek-harness)的 typescript 是 devDep、走 pnpm store,不会出现在普通 node_modules/typescript,所以 tsserver 的「从文件向上找 node_modules/typescript」机制找不到它。最稳的修法不是 npm i(会污染锁),而是在工具会用到的根目录铺 junction 指向一份带 tsserver.js 的 TS5:
# 目标 TS5(实测可用,带 lib/tsserver.js):以某处已装的 typescript@5.9.x 为准
$ts5 = "C:\Documents\GitHub\NodeMemoryGraph\node_modules\typescript"
# 在会话工作区根 + pnpm 单体仓库根各铺一个,让 workspace 检测向上能找到它
New-Item -ItemType Junction -Path "C:\Documents\GitHub\dsh\node_modules\typescript" -Target $ts5 -ErrorAction SilentlyContinue
New-Item -ItemType Directory -Path "C:\Documents\GitHub\dsh\deepseek-harness\node_modules" -Force | Out-Null
New-Item -ItemType Junction -Path "C:\Documents\GitHub\dsh\deepseek-harness\node_modules\typescript" -Target $ts5 -ErrorAction SilentlyContinue
要点:
- junction 是目录重解析,不碰 pnpm 锁、可随时
Remove-Item 撤掉,比动依赖树安全。
- root 用的是「工具实际传入的 root」——所以会话工作区根和每个被分析的工程根都要能向上解析到 TS5,铺 junction 要铺在那些根的
node_modules。
- 可选双保险:
~/.dsh/pi-lsp.json 给 typescript-language-server 配 initialization.tsserver.path 指向 tsserver.js(实测 junction 起效时非必需,但无副作用)。
- 诊断出的
Cannot find module 'node:xxx' / Cannot find name 'process' 是深层 junction 只带 typescript、不带工程类型依赖图的真实类型诊断,恰好证明 tsserver 在真跑——不是假绿。
3) Task —— 发现 + 执行
- 发现:
task list(package.json scripts / Makefile / justfile)。
- 执行:
task run name=build;exec 用 command="..."。
- 验证:插件仓库
package.json 有 3 个 npm 任务(build/check/install);task run name=check(tsc)应 exit 0。
4) Debug —— debugpy(Python)与 js-debug(JS/TS)
- 启动即验:
debug action=launch(program=<.py>, cwd) → 应 status=running。
- debugpy 解释器是最常见的「能装上、一启动就挂」:默认用
${env:DSH_DEBUGPY_PYTHON|python3.13},但很多机器上 python3.13 根本没装 debugpy(ModuleNotFoundError: No module named 'debugpy'),而系统 python(3.14)装了 1.8.21。排查:
node -e "const{spawnSync}=require('child_process');['python','python3.13'].forEach(p=>{const r=spawnSync(p,['-c','import debugpy,sys;print(sys.executable,debugpy.__version__)'],{encoding:'utf8'});console.log(p,'exit',r.status,r.stdout.trim()||r.stderr.trim().slice(0,80))})"
谁 exit 0 就把谁设进环境变量:$env:DSH_DEBUGPY_PYTHON="python"(或永久 setx)。别照抄「Python 用 3.13」的旧笔记——以你机器上谁有 debugpy 为准。
- js-debug 用内置
lib/dap/js-debug,测试常驻/长循环脚本(快脚本断点会 unbound)。
5) 自举检查 —— 用工具验工具(最终确认层)
第三层冒烟证明「工具能用」,这层更进一步:用插件自带的 LSP 工具去查插件自己的源码,一石三鸟——既证明工具真的在干活、又顺带对插件源码本身做了真实类型诊断、还能抓出「改了 src 但 host 没加载新模块」这类假绿。
实操样例:
lsp_diagnostics(root=插件仓库根, server=pyright|tsserver, paths=src/lsp/command.ts) → 对自编辑文件应返回 0 新增诊断;
lsp_symbols(root=插件仓库根, server=typescript-language-server, path=src/lsp/dsh-lsp.ts) → 应能解析出工具全部符号(lspTools / workspaceSymbolTool / skippedUnavailable…)。
要点:
- 对「我改过的文件」跑
lsp_diagnostics:任一被编辑的源码应 0 新增类型错误。若报错,说明编辑引入了真实类型问题(或 host 仍在用旧模块 → 先 npm run build + 重启)。
lsp_symbols / lsp_workspace_symbol 解析插件自己的源文件:能列出修复里加的局部(如 workspace-symbol 的 skippedUnavailable/targets/skipUnavailable),证明构建产物里真的带上了这些改动,不是读了旧的 lib。
- 多文件交叉复验:别只验一个文件(避免单样品)。对两个以上不同文件/不同 server 各查一次,任一通过即非运气。
lsp_workspace_symbol 不该再报 biome command not found:若仍报,说明 workspace-symbol 的 skip 修复没进 host——回到「改了 src 不见变化 → 重启」。
故障排查速查表
| 症状 | 根因 | 处理 |
|---|
dsh: pnpm failed 但 link 写了 | pnpm 下外来可选包失败 | 平台锁定 + 重建 lockfile(见上) |
| 工具压根不出现(重启后) | 插件没进 dsh.profile.bundles | 跑官方 add 到 pnpm exit 0;--dump-config 核对行 |
lsp_diagnostics/tsserver ENOENT | 无扩展名 Windows shim 抢跑 | 查 command.ts 扩展名顺序;改后 npm run build + 重启 |
tsserver valid TypeScript installation | 工作区向上找不到带 tsserver.js 的 TS5(全局 TS7 无) | 铺 junction:目标根的 node_modules/typescript 指到 TS5(见「tsserver 的标准修法」) |
debug launch 即挂 / ModuleNotFoundError: debugpy | 默认解释器 python3.13 无 debugpy | DSH_DEBUGPY_PYTHON 指向有 debugpy 的解释器 |
改了 src/** 不见变化 | host 进程缓存了旧模块 | npm run build 后重启 dsh web |
lsp_workspace_symbol 报 biome command not found | workspace-symbol 没过滤未装 server | 确认 dsh-lsp.ts 的 skip 修复进了 host(重启);已修 |
关键命令清单(速抄)
# 安装/重装(在插件仓库根目录)
dsh plugin --profile web add link:$PWD
# 组合校验(不启服务)
node "C:\Users\LEGION\AppData\Roaming\npm\node_modules\@deepseek-ai\dsh\lib\bin.js" web --dump-config
# 重建(改了 src)
npm run build # scripts/build.mjs → tsc + 拷 vendored dap assets 到 lib/
# 类型检查
npm run check # tsc --noEmit -p tsconfig.json
# tsserver 让工作区可解析 TS5(铺 junction,选做)
New-Item -ItemType Junction -Path "C:\GitHub\dsh\node_modules\typescript" -Target "<某份有 tsserver.js 的 typescript@" -ErrorAction SilentlyContinue
# 会话内核对工具
# → 用宿主 inspect:Tool.listTools(12 LSP + debug + task)
# 环境变量(会话级)
$env:DSH_DEBUGPY_PYTHON = "python"
# 自举检查(工具验工具):对插件源码跑诊断/符号,见「冒烟验证四层 · 5」
# → lsp_diagnostics(root=插件仓库, server=pyright|tsserver, paths=src/lsp/command.ts) = 0 诊断
# → lsp_symbols(root=插件仓库, server=typescript-language-server, path=src/lsp/dsh-lsp.ts) 出符号表
边界
- 本手册的坑是 Windows / PowerShell 特定的;Linux/macOS 无无扩展名 shim 问题、native 二进制直行。
- 除 pyright/tsserver/debugpy/js-debug 外的服务器/适配器多为实验性配置,未纳入本手册的冒烟范围。
- 详细架构见仓库
README.md / DETAILED.md(迁移对照表),src/lsp|dap|task/ 为核心逻辑。