dsh-wsl-suite:給 DeepSeek Harness 接一個 WSL 工作區

前言

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

三個後果決定了這個插件的設計取捨:

  1. 不再輪詢。舊方案每 3 s 掃共享、每 30 s 走目錄樹;WSLg 開着時每半分鐘會把焦點拉回 Windows 桌面。常駐 agent 按需被問詢,空閒時沒有任何東西碰發行版。
  2. /mnt/<盤符> 可用。9P 共享只服務 ext4,drvfs 掛載對它不可見,舊方案要改路由、探測每個盤合成 /mnt 條目。在發行版內 /mnt/c 就是普通目錄,這些邏輯隨之刪掉。
  3. 搜索在發行版內執行。舊方案的 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 一致。

典型用法

裝好之後,典型路徑是:

  1. 重啓 harness,側欄「工作區」標題欄出現 W 按鈕(沒有就用底部列表入口)。
  2. 彈窗裏選發行版、瀏覽到目標目錄,點「添加此文件夾」。
  3. 包含這個工作區的會話,模型的預設自動出現 wsl-<mode> 孿生,shell/fs/grep/glob 都跑在發行版內。
  4. 側欄終端在發行版內打開,直接是發行版的 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

羽毛球分组比赛记分
小程序二维码

欢迎使用《羽毛球分组比赛记分》微信小程序

小夜