前言¶
Windows 上跑 DeepSeek Harness,工作区在扩展文件系统里,会话想跑在 Linux 侧时,常规做法是把目录通过 9P 共享给 WSL,或者每次执行命令都起一个 wsl.exe。前者有轮询和可见性问题,后者每次调用都有几百毫秒的固定开销。dsh-wsl-suite 换了一条基座:在发行版里放一个常驻 agent 进程,宿主通过 JSON-lines 管道按需问询。
这个插件是 dsh-wsl-workspace 的重写,由 stallenlu 维护,MIT 许可证,当前版本 0.1.4。它验证的是一个具体工作流:从侧栏添加 WSL 工作区、整个会话跑在发行版内、终端进发行版、模型拿到发行版原生的工具。
它解决的问题¶
先说三种接入方式的差异,数据来自 README 在本机 Ubuntu 上对真实发行版的实测:
| 操作 | 9P 共享 | 每次调用一个 wsl.exe spawn |
本 agent |
|---|---|---|---|
stat |
4.3 ms | 212–282 ms | 0.20 ms |
readdir |
2.8 ms | 212–282 ms | 0.25 ms |
| 跑一条命令 | — | 212–282 ms | 13–20 ms |
| 对 5.2 GB 仓库 glob(64,625 个文件) | 44 s,且漏掉约一半目录树 | — | 0.59 s |
三个后果决定了这个插件的设计取舍:
- 不再轮询。旧方案每 3 s 扫共享、每 30 s 走目录树;WSLg 开着时每半分钟会把焦点拉回 Windows 桌面。常驻 agent 按需被问询,空闲时没有任何东西碰发行版。
/mnt/<盘符>可用。9P 共享只服务ext4,drvfs 挂载对它不可见,旧方案要改路由、探测每个盘合成/mnt条目。在发行版内/mnt/c就是普通目录,这些逻辑随之删掉。- 搜索在发行版内执行。旧方案的
grep/glob靠打包的 Windows ripgrep 对共享跑:整仓走一遍 44 s(9P)对比发行版内 0.29 s,内容搜索 150 s 对比 0.79 s;而且 9P 那次返回 22,436 行、原生返回 41,838 行——静默漏了约一半目录树。WSL 变体换成跑发行版自己的rg。
这些数字都是 README 作者本机实测,换机器和发行版会有出入,但方向一致:跨系统边界的元数据操作和搜索,开销比发行版内自跑高一到两个量级。
架构:常驻 agent 而不是每命令一个进程¶
整体数据流:
浏览器 ──POST /dsh-wsl/api──▶ 宿主插件 ──管道──▶ node agent.mjs(发行版内)
│ │
└── ctx.fs / ctx.shell ◀─┘
agent/agent.mjs在发行版内运行的零依赖 Node 脚本,提供stat、lstat、readdir、read、readRange、write、mkdir、remove、realpath、exec、kill、ping,走 JSON lines。启动时重建一次登录环境,--exec也能看到~/.local/bin。src/channel.ts是管道的宿主侧:拉起 agent、按 id 配对回复、10 分钟空闲即关(空闲指无未完成请求,所以 30 分钟的测试跑不会被误杀)、每个发行版一条通道、agent 死后重开。src/shell.ts的WslShellExecutor处理退出码、流分离、实时流、杀进程组的超时、保尾输出上限;后台句柄的未观察 rejection 不会杀死宿主。src/fs.ts的WslFileSystem提供 12 个原语,含版本守卫写入、FS_STALE_VERSION/FS_AMBIGUOUS_EDIT/FS_NOT_TEXT处理、沙盒围栏、rename 原子写,以及逐字节保真的 CRLF。src/index.ts是宿主入口:发行版发现、wsl-<mode>物化、DSH_WSL_DISTRO,以及一条仅回环的 POST 路由。
核心功能¶
从侧栏添加 WSL 工作区。在「工作区」分区标题栏,按自带「添加工作区」控件旁的 W 按钮;标题栏不存在时回退到底部列表入口。弹窗列出已安装的发行版并浏览 Linux 文件系统,确认后通过宿主自己的工作区注册表登记。WSL 工作区只是路径为 UNC(\\wsl.localhost\Ubuntu\home\you\…)的宿主工作区,只有一条记录,没有私有存储。弹窗跟随 navigator.language,zh-* 显示简体中文。
侧栏终端跟着会话进发行版。src/terminal.ts 包装宿主唯一的 subprocess.spawnTerminal:请求里的 cwd 是 UNC 时,把这一次调用改写成
wsl.exe -d <distro> --cd <linux cwd> -- <shell>
于是宿主自己的 ConPTY 带起一个真正的 Linux pty,输入、输出、resize 全部回环。Windows cwd 的会话原样透传。请求的 POSIX shell 名会被带进发行版(/bin/zsh、/bin/fish 等,找不到退回 /bin/bash)。
模型拿到 WSL 原生工具。不给全局模式,而是给 profile 里每个健康预设生成一个 wsl-<mode> 孪生(standard → wsl-standard),shell/fs 工具换成 WSL 版,并新增跑发行版自带 ripgrep 的 grep/glob,覆盖 /mnt/<盘符>,对不可读目录宽容(exit 2 保留部分结果,而不是让整次调用失败)。预设里的人设、技能、任务、委派原样带过。
预设变换在 src/host/variants.ts 里按 id 在解析后的文档上匹配行,不碰缩进,因此对原版「嵌套 subagent 行不可见、报 tool "subagent" is already registered in this scope」这类缩进脆弱性免疫。它还会清掉被抬进 world 的行上残留的 process.platform === 'win32' 门,否则那个在宿主侧求值过的表达式会恰好禁用这个变体要提供的工具。
安装与启用¶
发布名是 dsh-wsl-suite(dsh-wsl 因 npm 防山寨规则无法注册)。在 profile 目录下安装:
# 在 C:\Users\<你>\.dsh\profiles\desktop\ 下
pnpm add dsh-wsl-suite # 或:npm install dsh-wsl-suite
然后在该 profile 配置文件的 dsh.profile.bundles 里加 "dsh-wsl-suite" 并重启 harness:
"dsh.profile.bundles": [ ..., "dsh-wsl-suite" ]
如果版本还没发到 registry,也可以从 GitHub 拉:
pnpm add "dsh-wsl-suite@github:stallenlu/dsh-wsl-suite"
读 README 时有一条命名规则必须记住,违反它会让启动静默失败:客户端 bundle 的注册 id 必须等于包名。宿主按包名给客户端 boot 图建 key,arrive() 只接受在该 id 下注册了 factory 的 bundle,否则入口永不激活。tsdown.config.ts 把这个 id 写进 bundle banner,tests/client.test.ts 断言它与 package.json 一致。
典型用法¶
装好之后,典型路径是:
- 重启 harness,侧栏「工作区」标题栏出现 W 按钮(没有就用底部列表入口)。
- 弹窗里选发行版、浏览到目标目录,点「添加此文件夹」。
- 包含这个工作区的会话,模型的预设自动出现
wsl-<mode>孪生,shell/fs/grep/glob 都跑在发行版内。 - 侧栏终端在发行版内打开,直接是发行版的 shell。
不进 UI 的宿主侧自检,直接验证管道:
curl -X POST http://127.0.0.1:<gui-port>/dsh-wsl/api \
-H "content-type: application/json" -d '{"method":"listDistros"}'
# → {"ok":true,"value":{"distros":[{"name":"Ubuntu","default":true}]}}
check 和 listDir 走发行版内的常驻 agent,所以 200 加真实条目就足以说明整条管道通。
本地改源码的开发循环,因为安装副本是复制不是符号链接,每处改动都要重装:
npm run verify # 类型检查 + 构建 + 测试
plugin_manager remove_bundle target=dsh-wsl-suite
plugin_manager install_bundle target=file:<源码目录>
然后重启 DSH。如果入口又消失了,先看 profile 里的 dsh.profile.bundles 是否还含 dsh-wsl-suite,再用 plugin_manager set_bundle target=dsh-wsl-suite enabled=true 热挂载。README 明确写了并发的 profile 改写曾把这个条目弄掉过。
适用场景与注意¶
适合的人:Windows 主力、WSL2 里做开发、希望 Harness 会话和工具链都落在 Linux 侧的人。如果你的会话本来就定在 Windows 上,这个插件的价值有限——它解决的是两边工具链不一致时的边界开销。
几件事需要留意:
- 插件以当前 dsh 进程的权限运行,装进 profile 就等于把工作区读写、命令执行的能力交给 Harness,安装前应先读一遍源码和许可证(MIT)。
- 客户端入口的挂载依赖宿主私有 DOM 结构(标题栏的 class 名),README 也承认这是从旧插件沿袭的手法:向上走到
[class*="_sectionHeader"]再注入。宿主发版改了类名,标题栏入口会失效,此时底部列表入口作为回退仍然可用。 - 阅读资料时注意区分。
dsh-wsl-suite是重写版,旧的dsh-wsl-workspace由另一组维护者维护;README 说明 dsh-wsl 的问题提交到本仓库,旧插件的问题去它自己的仓库。 - 工作量数据都来自 README 作者本机实测,只用于说明量级,不是在你的机器上可以复现的基准。
结语¶
这个插件的价值不在新概念,而在把 9P 共享和每命令一个 spawn 这两条慢路径换成发行版内一个常驻进程加按需问询。轮询的定时器被删掉、/mnt/<drive> 路径不再特殊、搜索回到发行版自己的 ripgrep。侧栏入口和终端跟随让 WSL 工作区在同一套 UI 里和 Windows 工作区没有差别。
目录页:https://www.skillhub.cn/plugins/stallenlu/dsh-wsl-suite
GitHub:https://github.com/stallenlu/dsh-wsl-suite