从 Web GUI 添加 WSL 工作区,无需在 WSL 之中再次安装 dsh 以及相关工具,bash 命令与文件读写运行在本机 WSL 发行版内,Windows 文件仍可访问。
安装
# GitHub 源码(首次需按提示配置 allowBuilds 构建授权后重试)
dsh plugin --profile web add github:6Mikao9/dsh-wsl-workspace
装任何插件都等于在你的机器上跑第三方代码,权限和你本人一样大——能读你的文件、用你的凭据、访问网络,工具审批管不到它。GitHub 来源的插件还会在安装时执行构建脚本——pnpm 默认拦截,所以安装可能停在 ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED 或 ERR_PNPM_IGNORED_BUILDS;dsh 会打印出需要添加的确切键名,把它加进该 profile 的 pnpm-workspace.yaml 的 allowBuilds 下,重跑一次即可装上。放行构建本身就是一次信任判断:请只安装可信来源,并尽量锁定 commit(github:owner/repo#sha)。
README
仓库内路径说明。 本包发布的是
lib/、cordis.patch.yml、两份 README、TESTING.md、两份 CHANGELOG、LICENSE、NOTICE与docs/driving.*(另有docs/README.md,npm 会打包任何名为README的文件,一并带上);src/、tests/、scripts/、ci/以及docs/的其余部分只存在于仓库里、不随包发布——本文里点它们的名字是在给坐标,不是在给一个能从 tarball 里打开的文件。
English · 中文 · 日本語 · 한국어 · Français · Deutsch · Español · Português · Русский
在 DeepSeek Harness Web GUI 中「添加 WSL 工作区」:让 agent 会话的 bash 命令与文件读写都运行在本机 WSL 发行版里,路径均为 Linux 形式,WSL 内无需安装任何工具链。会话可同时访问 WSL 与 Windows 两个系统——bash 命令在 WSL 发行版内执行,Windows 文件随时可通过 /mnt/<drive>(如 /mnt/c/Users/...)访问。
安装
三种方式任选其一,然后重启 dsh web:
# 1) npm 包
dsh plugin --profile web add dsh-wsl-workspace
# 2) GitHub 仓库(仓库内已含预构建 lib/,无需本地构建)
dsh plugin --profile web add https://github.com/dsh-wsl-workspace-maintainers/dsh-wsl-workspace
# 3) 本地目录(开发/自用)
dsh plugin --profile web add D:\path\to\dsh-wsl-workspace
重启 dsh web 后,工作区区头「添加工作区」右侧出现 W 按钮;侧栏折叠成轨道时,它仍位于底部 Settings 旁。
兼容性
这份构建声明兼容下列 DSH 版本,每一条都在隔离实例上实测过(独立 DSH_HOME、依赖固定到该版本、跑满整套门禁):
0.1.0-rc.7 · 0.1.0-rc.8 · 0.1.1-rc.1 · 0.1.1-rc.2 · 0.1.2-rc.1 · 0.1.3-alpha.2 · 0.1.5-rc.1 · 0.1.5-rc.2 · 0.1.7-rc.1 · 0.1.7-rc.2 · 0.2.0-rc.2 · 0.2.1-rc.2
这份列表与 package.json 里的 dsh.compatibility.dshReleases 一一对应,单测会在两者不一致时失败。其中 0.2.1-rc.2 就是 DSH Desktop 0.2.1-rc.2 自带的 DSH 版本——判决取自该应用自己打包的那份宿主(resources/app.asar 内),不只是 npm 上那份——所以桌面版由同一份声明覆盖;应用内帮助面板(对话框右上角「?」)会显示同一组 chip 以及插件版本。插件在运行时自动识别 DSH 版本并选用对应的 API;两边都不支持时会明确报错,而不是留下一个空工作区。版本不在列表里通常仍然可用,但未经验证。
使用
点这个 W 按钮,或在「添加工作区」菜单里直接选「添加WSL工作区」,打开「添加 WSL 工作区」对话框。先从下拉框选择一个发行版,再浏览目录树或直接输入 Linux 绝对路径(如 /home/me/proj),可以点「检查」确认路径存在。对话框文案跟随 DSH 界面语言。用户名是可选项:留空则以该发行版的默认用户运行,填写该发行版里的某个 Linux 用户名则以该用户运行(等价于 wsl.exe -u <用户名>)。用户名只影响 bash 命令的运行身份,文件工具通过 Windows 侧的 WSL 共享访问、不受其影响;每个工作区填写的用户名保存在 <dshHome>/wsl-workspaces.json,删除对应条目(或重开对话框重建工作区)即可恢复默认用户。
点「创建并打开」后,新会话随即运行在 WSL:bash 工具在所选发行版内执行命令,read/write/edit 读写 WSL 文件,模型看到的所有路径都是 Linux 形式。模式选择器照常可用——标准、PTC、极简、创造都会自动落到对应的 WSL 变体(选择器里的 WSL 变体条目为中英双语,如 WSL · Standard mode(标准模式));会话内仍可通过 /mnt/<drive>(如 /mnt/c/Users/...)访问 Windows 文件。对话框右上角的「?」按钮会展开一页说明:这份构建声明兼容的 DSH 版本、插件的用法与特性,以及它无法绕过的已知限制。

行为与权限说明
- bash 工具:以配置的用户名在 WSL 发行版内运行(留空 = 发行版默认用户,通常为 root),可对发行版内任意路径读写。Windows 的 ACL 沙箱无法包裹
wsl.exe(子进程运行在 Linux 内核侧),WSL 自身即隔离边界,DSH 文件策略不作用于 bash。 - 文件工具(read/write/edit):经 Windows 侧的 WSL 9P 共享访问;用户名设置不影响它们。宿主原本会提供的两处能力,现在由 WSL 世界自己在内部补上——变体在预设的 isolate realm 里挂自己的
fs提供者,宿主的fs-sandbox包装层不在调用路径上。符号链接:共享只列得出链接、解不开目标,链接路径以前会被当成不存在的文件;现在resolve/lstat会向发行版问一次真实路径(wsl.exe … readlink -f)并从那里继续,链接本身绝不会被普通文件替换。访问模式:write/edit完全按宿主后端的做法由ctx.sandboxPolicy围栏——同一份writableRoots白名单(另加发行版的/tmp,即会话所在世界的临时区)、同样的FS_SANDBOX_DENIED(工具层会渲染成拒绝)、同样的sandboxMode(工具据此声明升级)。由于围栏在解析之后执行,判定的是真实路径:链接指向工作区外就按工作区外处理,workspace-write下会被拒绝。完全没有策略服务的部署则不围栏,与宿主一致。文件引用(issue #49):会话里的文件链接、工具行的行号引用、回合末尾的「已改动文件」都走右侧栏,地址里带的是模型原样写下的 Linux 路径。宿主用node:path.resolve(cwd, path)解析它,而 POSIX 绝对路径在 Windows 上是「当前盘根目录下的相对路径」,所以文档面板以前报文件不存在:/mnt/d/…落到工作区所在的盘(D:\mnt\d\…),发行版内的路径落到 cwd 共享的根下,而 drvfs 挂载在 9P 下服务不了、报EPERM。客户端半边现在先把地址翻译好再交给侧栏——/mnt/<盘符>/…换回X:\…,其余 Linux 路径走该发行版的 UNC 共享(发行版取自会话的 UNC 工作区,或/mnt/<盘符>工作区注册时存下的记录)——tab 的内容与同一地址下的元数据读取一并修好。插件不认为是 WSL 会话的地址原样不动;逐字节重建不出来、或发行版未知的地址也原样透传,不做猜测:宿主平面没有可用的钩子——fs与workspaceFiles端点属于别的插件,cordis 也不允许第二个provide抢别人 fiber 持有的名字。另外,只有 0.1.5-rc.1 及以后才有右侧栏与文档预览,六个更早的已声明版本上这个钩子根本不会安装。 - 文件搜索(grep / glob):宿主的搜索套件驱动的是打包进来的 Windows ripgrep,模型交给它的路径却全是 Linux 路径,所以 WSL 变体一直把这一行丢掉、让模型自己在 shell 里搜。现在世界挂上自己的同类实现(
src/host/wsl-search.ts→lib/wsl-search.js),搜索在发行版内执行:工具名、参数 schema、条数上限、输出 schema(Line N:分组、命中数表头、超限尾部提示)、搜索卡片与超限结果落盘全部沿用@deepseek-ai/dsh-tool-fs-search自己导出的部件,所以模型看到的东西与宿主一致。grep用发行版自带的 GNU grep(-rnIEH -Z,POSIX ERE:\d、\w、\b、(?i)可用,环视与反向引用不支持),像 ripgrep 默认那样跳过隐藏文件/目录与node_modules,不读.gitignore,{a,b}会展开成多个--include,含/的 include 在插件进程里按相对路径匹配(ripgrep 语义);发行版没有 GNU grep 时明确报错,而不是给出错位的记录。glob用 GNUfind列出文件(-printf直接带出修改时间,不必逐个 stat),在插件里按 gitignore 风格匹配(*不跨分隔符、**跨、?、[...]、{a,b},前导!取反),并按修改时间从旧到新排序——与rg --sort=modified一致。两者都直接搜 Linux 真实目录(符号链接、权限、不理会忽略规则),不走 9P 共享;都跳过版本库目录,也都不跟进递归中遇到的符号链接(同样是 ripgrep 的默认)。源预设没有搜索套件的模式(极简)不会凭空多出这两个工具。 - 技能目录(skill catalog):从会话 cwd 最近的
.git祖先开始(没有.git祖先则用 cwd 本身)向下扫描.dsh/skills与.agents/skills(含嵌套项目),上限为 4 层目录、64 个技能目录、4096 个已访问目录。建议把工作区注册在你实际工作的项目根;若注册目录本身在更大的 git 仓库里,扫描会从该仓库根开始(与宿主规则一致),同级项目可能一并出现。Windows 侧共享解不开的 Linux 符号链接会改由发行版解析(wsl.exe … readlink -f,每次查找最多 32 条、并发 4 条),解析后从真实路径继续扫描,因此ln -s链进来的项目、以及它下面的嵌套项目都能被发现,并按真实路径去重。技能正文始终实时读取。生成预设会把skill-filesystem行的watch固定为false(对\\wsl.localhost\...开监视会失败),插件改为自己轮询,分两档:轻量档每 3 秒重查一次已发布的技能目录、并给每个技能文件记一次修改时间与大小,所以会话中途新增、删除或改写技能都会在下一个回合生效——改写这条正是时间戳的意义:模型看到的目录只在注册表修订号变化时重建,而光看目录列表分不出一个被改写的SKILL.md和没动过的文件。完整重新发现每 30 秒一次,因为只有走一遍才可能找到此前不存在的技能目录(比如新项目里的第一个.dsh/skills)。两档都不重读技能正文。 在某些 Windows 和 WSLg 版本上,轮询 WSL 文件共享会唤醒空闲的发行版并启动 WSLg 的远程桌面客户端,可能短暂抢占键盘焦点。如果你不使用 Linux 图形应用,可以在%UserProfile%\.wslconfig的[wsl2]节中设置guiApplications=false,为所有 WSL 2 发行版关闭 WSLg。重启 WSL 后设置生效。执行wsl --shutdown前,请先保存工作并关闭正在运行的 WSL 应用。Issue #130 · 微软 WSL 配置说明 - shell 的生命周期:
bash现在有状态——cd、export 的变量、激活的 venv、后台任务都跨调用保留。它是发行版里一只长命的bash --norc -i,由本插件启动(src/host/wsl-bash-tool.ts+src/host/wsl-bash-session.ts→lib/wsl-bash-tool.js)并经管道驱动:每条命令以 base64 送出,shell 用一条 NUL 分隔的记录回答,记录里带着这次调用独有的 nonce 和退出码——完成是一个字节通道上的事件,而不是在终端画面上匹配一行字。这是有意替换宿主的 PTY 持久 shell:它的判据是"在终端文本里找到哨号行,并要求退出码后面紧跟换行",而交互式 Linux shell 会重绘那一行,把擦掉的格子留成哨号之后的空格,于是判定永远不成立——在真实的 DSH Desktop 0.2.x 会话里,连续三次调用各挂 303.8 秒,之后宿主把 shell 重置(issue #51)。登录环境没有丢:会话启动时依次 source/etc/profile、~/.profile、/etc/bash.bashrc、~/.bashrc,把它们自己的输出丢掉,并关闭 history expansion,于是命令里的!就是模型写的那个意思。PTY 那一档用DSH_WSL_PTY_SHELL=1仍可挂载以便对照——那一档挂的是宿主 PTY 后端指向本插件的中继脚本(src/host/wsl-relay.ts→lib/wsl-relay.js,中继把 PTY 交给wsl.exe … bash -i);世界同时隔离并提供自己的 no-opsandbox能力(src/host/wsl-sandbox.ts)——宿主的 Windows ACL 运行器读不了\\wsl.localhost\…工作区根的安全描述符。命令都在发行版内运行,不受 DSH 文件策略约束——WSL 自身就是隔离边界。包装带来的两个后果写在工具描述里:整轮会话只有这一只 shell,所以某次调用里的cd决定下一次从哪里开始——请用绝对路径或在命令开头显式cd;命令以&结尾会在调用内部被后台化——调用立刻返回 exit code 0 且没有输出,真正的输出晚到,甚至落进下一次调用的输出里。后台任务请写成单独一行的( 长任务 > log 2>&1 ) &,或使用后台任务工具。终端是按进程实际在做什么给的,不再按命令的名字:一旦命令卡在键盘上,工具会在它卡住的过程中把它看出来、停下来,并在同一次调用里用属于它自己的伪终端重跑一遍——伪终端由会话内部的script -qec起,而记录仍由外层帧在script之外写出,所以升级碰不到协议(机械部分在src/host/wsl-bash-tty.ts,那份读数本身在src/host/wsl-bash-starve.ts)。判别靠从会话外面向发行版看第二眼:进程状态、/proc/<pid>/wchan、它的描述符里有没有终端、以及它在不在烧 CPU——把"在等一次按键"和"在干活"分开的就是这几列,而不是命令名。需要从管道拿输入的命令也能拿到:调用的stdin参数随同一帧送进发行版、落成命令读取的文件(上限 32 KiB;更大的输入会被点名拒绝且什么都不执行——被喂了半份输入的程序会以看起来像它自己的错的方式失败);不传stdin时命令读/dev/null,这正是命令自己的读操作不会吃掉结束调用的那些字节的原因。tty: true仍可一上来就要终端,tty: false则否决那次重跑;一次在终端上失败、又不落在已知形状里的调用会自己说明是哪一层、以及一发就能定案的对照(见 docs/tty-triage.md,该件为英文)。2026-10-05 实测的几发(maintainer-local,不随包发布):sh -c 'read x < /dev/tty'——程序名是sh,过去任何一份名单里都没有它——在 628 毫秒被停下、1 760 毫秒带回它自己的GOT=;同样安静、同样不耗 CPU 的sleep 4一步没被碰;sudo要密码时会把自己的/proc条目藏起来(它清掉了 dumpable 标志),于是改由发行版的 root 权限读同一枚探针——root 看得到它自己的用户看不到的那次等待——2 412 毫秒变成 2 412 毫秒,带回 sudo 自己的话;一条为等待键盘的请求写了 60 000 毫秒期限的调用 1 733 毫秒就返回了。旧规则为同样的形状付出的是默认两分钟外加一次会话重建,而之所以改用名单:本票里量着了两件事——ssh-copy-id因为扫描停在连字符上而永远匹配不上,printf x; vim note.txt因为只读第一个词而烧掉 121 703 毫秒。重跑就是第二次执行,所以正文会明说:一条在走到它的提示之前已经做过些什么的命令,现在把那部分做了两遍。万一某条命令把 shell 卡住,会话会被重建,工作目录和 export 的变量回放进新会话——而只是跑得久的命令不会被第二次执行。边界是一只 agent 一只 shell:第二只 agent 的cd、export 的变量、别名与脱离进程都属于它自己,同时发出的两次调用各自带回各自的答案,重建时的进程清扫只停携带本会话令牌的那些进程,而一只 agent 自己的作用域结束时它的 shell 随之停止——于是一个窗口里几轮会话不会攒出没人能关掉的 shell。有一件事最好在 agent 需要 root 之前就知道:这只 shell 是非交互的,所以会要密码的sudo在这里没人能答——它约 100 毫秒失败,带回 sudo 自己的话和两条出路(人在产品右栏的终端标签里跑一次,那里能输密码;或给会话用户配 sudoers 的NOPASSWD,或用DSH_WSL_USER把会话直接跑成 root)。本插件没有任何设置能让密码提示变得可回答,也不跑探测去假装知道结论:sudoers 的授权是按命令给的,一句"这个用户有没有 NOPASSWD"对刚失败的那条命令而言就是猜测。这只 shell 与宿主自己的bash工具所有不同之处都逐行列在 docs/bash-parity.md(该表与兼容性证据同为英文),每行写明是谁的行为、用户会看到什么、以及判决;这张表被两枚闸读:tests/wsl-bash-parity.test.ts拿装着的宿主包比对双方声明的参数与输出字段,scripts/compatibility/bash-parity-real.mjs用同一份探针脚本在真发行版上把两个工具各问一遍、再逐字段对答案——于是"没写进台账的差异"与"写了但已消失的差异"都是构建红,而不是用户意外。 - 可敲键的终端(
wsl_terminal):管道无法被敲键,所以会提问的程序——sudo要密码、ssh首次连接的指纹、REPL、编辑器、TUI——过去没有 agent 能给的答案:它被诊断、停下、改用一次性伪终端重跑,只为让模型读到程序自己的抱怨,剩下的只能由人接手。现在世界多挂了一扇 agent 能敲进去的门:open开一个交互终端,send打文字(默认回车,submit: false只打字不回车),read翻看保留的屏幕,signal发SIGINT/SIGTERM/SIGKILL/SIGTSTP/SIGHUP(SIGINT就是 Ctrl-C),close关闭,list列出开着的。它就是宿主的 PTY 注册表与dsh-terminal-bash后端指向本插件的中继脚本——右栏终端标签背后那套机械,只是键盘换到了 agent 手上——而且它起的 shell 跑在工作区配置的用户下:宿主自己构造 PTY 子进程的环境、会把DSH_*全部丢掉,所以用户是从工作区存储里读回来的。其余一切仍走上面那枚管道bash:管道给的是每个字节,终端屏幕只是 160 列渲染加有限滚动缓冲。三件事写在明处:后端的静默窗口从默认 3000 毫秒下调到 1200 毫秒(来源注在src/host/variants.ts),于是宿主没认出提示符的那一发约 1.2–1.8 秒返回、而不是约 3.0–3.6 秒;一发send会说明自己落在哪一档,而不是把安静的屏幕说成提示符;敲进去的一切都会进对话记录,所以密码只能来自用户。真发行版实测(两档平面 ×root/ruler,bash-session-real66/66 格):open477–490 毫秒、打进去的命令有回答、敲键能到达阻塞在/dev/tty上的程序(GOT=…)、submit: false只打字不执行而下一次回车执行它、SIGINT结束sleep 30且 shell 存活、close之后不留bash -i。 - 可跟踪的后台任务:
bash_background把一条命令放到后台并立刻返回注册表里的 job id,之后宿主的job_list/job_output(增量读取、状态流转、完成通告)/job_kill都照常工作。之所以需要这一行:持久 shell 的 schema 只声明了command,没有生产者时job_list永远回答"没有后台任务",而传给bash的run_in_background: true会被静默忽略——参数 schema 不禁止额外属性,所以没有任何地方报错。这是真实会话里被发现的问题。该工具只在持久 shell 存在时挂载;保留一次性 bash 行的世界本来就有run_in_background。 - 老宿主回退到一次性 shell:持久 shell 是宿主的代码,而在 Windows 上它需要一个平台进程检查器,那个东西从
0.1.0-rc.8才有——在0.1.0-rc.7里spawnTerminal会在启动任何进程之前抛subprocess-local: terminal inspection is unsupported on platform win32,于是该版本里每一次bash调用都直接失败(不碰 PTY 的 grep/glob 仍正常)。因此插件在启动时探测底座而不是假设:它把一个不存在的程序交给spawnTerminal,这只会走到检查器那一步、不会真的创建进程,报错信息就能说明是哪一半失败。答案是否时,生成的世界保留一次性的dsh-tool-bash行(走本插件自己的ctx.shell,不经 PTY),模型得到的是一个能用的无状态 shell,而不是每次调用都报错。八个已声明版本里只有0.1.0-rc.7是这种状态,之后的版本都拿到持久 shell。 wsl.exe在发行版尚未启动时向 stderr 打印的 localhost 端口转发提示(乱码但无害)可忽略。
从别的 agent 驱动它
docs/driving.zh.md(随包发布)回答新电脑 + 新 agent 猜不出来的五件:怎么无头起一枚宿主(以及必须删掉的那一枚环境变量——不删会以退出码 0 静默退出)、登录 URL 与它的 token 从哪来(实测:进程存活期间 token 不会用掉,要作废就重启实例)、本插件的路由与怎么在运行时枚举它的方法(describe)、怎么把文字送进 composer 并发出去(composer 是 Lexical:只发一发 beforeinput,绝不用 execCommand)、以及默认 provider 报 401 时改哪一处。同样的内容也在插件的 describe 方法里(route/loopbackOnly/methods/installProof/driving),随 HTTP 面一起走。
更新日志
完整版本历史(最新在前)已抽到单独的 CHANGELOG.zh.md(英文:CHANGELOG.md);本文只保留上面「行为与权限说明」的当前口径。
相关文档
docs/ 绝大多数是仓库内文档(不在 package.json#files 里;随包发布的是 docs/driving.zh.md、docs/driving.md 与 docs/README.md——最后一枚是因为 npm 会打包任何名为 README 的文件)。在仓库里,各文档的位置、语言与口径见 docs/README.md(英文索引):设计方案记录、支撑 dsh.compatibility.dshReleases 声明的逐版本兼容性证据,以及已被取代、原文保留在 docs/archive/ 的历史结论。改动或发布前的验证步骤见 TESTING.md。
许可与出处
MIT,详见 LICENSE 与 NOTICE,NOTICE 精确列明:
- 改编/继承源码:DeepSeek Harness(MIT)的
dsh-bash-local(执行器机制)、dsh-fs-local(WslFileSystem子类化)、shipped agent presets(变体生成读取/变换); - 设计参考(未复制源码):dsh-bash-terminal(MIT,wsl argv/WSLENV 思路)、dsh-side-panel(BSD-3-Clause,Host 路由模式)、vpshub(MIT,路线图参考)。
发布/再分发时请保留 LICENSE 与 NOTICE。
致谢
特别感谢 dsh-deep-whale(DSH Web 鲸鱼娘皮肤系列 · 深海女仆工坊 maid-atelier,CC BY-NC-SA 4.0):鲸鱼娘皮肤插件为 DeepSeek Harness Web 界面带来了一整套可爱的皮肤,让 DSH 的日常使用更有温度。
链接
同类插件
XINY11451/dsh-wsl★ 10
将 DSH 的工作范围扩展到 WSL。插件支持了发行版中的文件直接被 DSH 读取、写入、编辑与搜索。面对不同任务需求,AI可根据任务需求自行选择执行一次性命令/自建终端执行多条命令以完成任务。支持在 WSL 终端中键入 dsh 命令调用 DSH(详情在 WSL 终端键入dsh help),深度融合了 WSL 与 DSH。用户新建的所有终端支持与AI可视化协作。
Edge-Echo/dsh-win-toolkit★ 4
面向 DSH agent 的 Windows 原生工具:剪贴板读写、系统通知、hosts 文件查看与网络诊断,均返回结构化数据(带 UI 卡片),底层为防注入的 PowerShell。
173787247/dsh-wsl-env★ 3
向 system prompt 注入 WSL 发行版、Linux 路径映射、/mnt/c 的 CRLF 与 git 注意点,以及 NODE_USE_ENV_PROXY。
173787247/dsh-wsl-open★ 3
把 DeepSeek Harness 聊天里的 WSL Linux 路径在 Windows 默认程序或资源管理器中打开。
Han-1413141/dsh-wsl-native★ 3
在同一个 Windows 窗口中使用 Windows 与原生 Linux DSH 会话,支持 WSL 环境切换和双向系统操作。
liyu34/dsh-wsl-tray★ 3
为运行在 WSL 的 DSH 提供 Windows 桌面快捷方式和系统托盘启动器:完全隐藏启动,托盘菜单支持打开/重启/退出,插件配置页可管理快捷方式。
社区评论
评论公开保存在 GitHub Discussions。加载评论会连接 GitHub 和 Giscus;发表内容需要 GitHub 账号。