dsh-wsl-tool
已验证dsh-wsl-tool · v1.11.0 · MIT · Web 界面
WSL tools for DSH: run Linux commands (fresh shell, exit codes, timeouts, truncation spill, stdin), convert Windows↔WSL paths, report the WSL environment, and check a project's toolchain against the distribution — including commands that are really Window
安装
dsh plugin add dsh-wsl-tool 用 dsh --profile default --dump-config 确认 layer 已生效 —— 参见安装指南。
源码
标签
作者
说明文档
dsh-wsl
English | 简体中文
面向模型(model-facing)的 WSL 工具插件,用于 DeepSeek Harness(DSH)。它让智能体直接通过
wsl.exe 执行 Linux 命令——无需手写 .sh 脚本或 pwsh 包装——并且在命令执行这一层基本对齐
Linux 原生 DSH:真实的 Linux 内核与 bash、退出码与信号、超时、截断与落盘、可用内置 job 工具读回的
后台作业、stdin,以及 Windows/WSL 路径自动转换。
有两点限制值得在依赖它之前知道:wsl 调用位于 DSH 沙箱层之下,文件策略约束不到它
(见沙箱边界);项目放在 Windows 盘上时仍是 Windows 的文件系统语义——没有 POSIX
权限位、文件名大小写不敏感、收不到文件变更通知——速度也低一个数量级(见注意事项)。

工具
插件注册五个工具(后两个可单独关掉,其中 wsl-bootstrap 默认关):
| 工具 | 用途 |
|---|---|
wsl |
执行 Linux 命令,返回带退出码、超时与截断标记的 stdout/stderr |
wsl-path |
通过 wslpath 在 Windows 与 WSL 路径间互转 |
wsl-env |
汇总 WSL 环境(发行版、内核、CPU、内存、磁盘) |
wsl-doctor |
对照项目清单与发行版实际能力,指出缺什么——包括那些其实是 Windows 二进制的命令。只读 |
wsl-bootstrap |
按固定配方在发行版里补装缺失的工具链,先出计划再动手 |
wsl
执行形式:
wsl.exe [-d <distro>] -e bash -lc "cd <workdir> && <command>"
返回 stdout/stderr,并附加 [exit code: N] / [killed by signal: ...] /
[timed out after Nms; the command was killed] / [output truncated: ...] 标记。
<distro> 取调用参数 distro,其次 DSH_WSL_DISTRO,都没有时完全不传 -d,
由 wsl.exe 使用系统默认发行版——这正是本插件能装到没有 Ubuntu-22.04 的机器上的原因。
超过 Windows 命令行上限(32767 字符)的脚本会改为通过 stdin 交给
wsl.exe [-d <distro>] -e bash -ls 执行,因此长命令没有体积上限。
传 stdin 可以把文本喂给命令(默认是 /dev/null,交互式命令会立刻 EOF);传
runInBackground: true 则把命令交给宿主的任务注册表,模型随后用已有的
job_output 工具读它、用 job_kill 停它——不需要任何新工具。后台任务需要
preset 组合里有 @deepseek-ai/dsh-tool-jobs;没有时会直接报错说明,而不是悄悄退化成
前台执行。
wsl-path
双向转换路径:C:\Users\me\a.txt → /mnt/c/Users/me/a.txt,或
/home/me/a.txt → \\wsl.localhost\Ubuntu-22.04\home\me\a.txt。方向自动识别,
也可用 direction: 'win' | 'linux' 强制指定。
wsl-env
返回发行版列表、被探测的发行版、内核与架构、CPU 数、内存与磁盘占用,以及这台机器 实际能做什么:
distro: Ubuntu-22.04 (system default)
Linux 6.6.87.2-microsoft-standard-WSL2 x86_64
nproc: 24
Ubuntu 22.04.5 LTS · WSL2 · cgroup v2
systemd: yes · docker: not installed
GPU: /dev/dxg present (GPU passthrough enabled) · nvidia-smi: GPU 0: NVIDIA GeForce RTX 5070 Laptop GPU
drives: /mnt/c /mnt/d
/etc/wsl.conf: [boot];systemd=true;[user];default=xiny; · .wslconfig: not set
launcher: WSL 版本: 2.6.3.0 · 内核版本: 6.6.87.2-1 · WSLg 版本: 1.0.71 · Windows: 10.0.26200.9457
让智能体在动手前就知道有什么可用:WSL1 还是 WSL2、服务是否由 systemd 托管、cgroup 版本
(容器相关)、GPU 直通、docker(未装/只有 CLI/守护进程版本)、挂载了哪些盘,以及
/etc/wsl.conf 与 Windows 侧 .wslconfig 的配置。可传可选参数 distro。
每一项都是可选的,且如实降级:探针没跑成的行会被省略,读到了但为空的值会写明
(docker: not installed、.wslconfig: not set),探测失败会写进摘要而不是被丢掉,
发行版不存在则直接报错,而不是给出一份残缺答案。注意 wsl --version 输出是本地化的,
因此其标签按启动器原样透传、不按名称解析;Direct3D/MSRDC/DXCore 版本作为噪声被省略。
wsl-doctor
回答一个项目自己的 README 答不了的问题:这份代码指望的工具链,在这套发行版里真的能用吗?
它读取目录里的清单(package.json 与各种 lockfile、.nvmrc、tsconfig.json、Cargo.toml、
go.mod、pyproject.toml、requirements.txt、Dockerfile、docker-compose.yml、Makefile、
CMakeLists.txt),然后逐项给出它在 Linux 侧的位置:
distro: Ubuntu-22.04 (system default)
workspace: /mnt/d/proj (Windows drive mount /mnt/d — builds, installs and git are much slower here; …)
project: package.json, pnpm-lock.yaml
needs (from the project): node, npm, pnpm
MISSING node — not on PATH in this distribution (from package.json)
WINDOWS npm — resolves to /mnt/c/Program Files/nodejs/npm, a Windows binary reached through interop; …
MISSING pnpm — not on PATH in this distribution (from pnpm-lock.yaml)
usable on the Linux side: python3 3.10.12, gcc 11.4.0, make 4.3, git 2.34.1, curl, rsync, tar, sudo
Windows binaries on the Linux PATH (interop): npm -> /mnt/c/Program Files/nodejs/npm, npx -> …
privileges: uid=1000 (xiny) · sudo: requires a password (unusable from a tool call) · root: switched off
next: node, npm are not usable — wsl-bootstrap({ recipes: ["node", "pnpm"] }) installs them …
那行 WINDOWS 就是它存在的理由。WSL 会把 Windows 的 PATH 接到 Linux 的后面,于是
npm 在一套根本没有 node 的发行版里"找得到"(本机实测就是
/mnt/c/Program Files/nodejs/npm),模型拿它去跑 Linux 目录时报的错两头都不沾。整体只读、
开销很小,值得在把失败归咎于命令本身之前先跑一次。可选参数 workspace(Linux 目录;默认取
配置的目录,否则取会话工作区)与 distro。
wsl-bootstrap
在发行版内部补装缺失的工具链,只认固定配方:node(Node.js LTS 装进 /usr/local,
校验 sha256,并启用 corepack)、pnpm、python(python3 + pip + venv)、build
(build-essential)、tools(jq、rsync、curl、git、ca-certificates)。依赖按需带上:
pnpm 会带上 node,curl 缺失时 node 会带上 tools。
它默认关,且 dryRun 默认为 true,所以第一次调用只打印计划、什么都不改:
dry run — nothing has been installed (distro Ubuntu-22.04)
already present:
python — python3, pip and venv (apt)
to install (python, tools):
[tools] apt packages: ca-certificates curl git jq rsync — runs as root inside the distribution
export DEBIAN_FRONTEND=noninteractive; apt-get update && apt-get install -y ca-certificates curl git jq rsync
apt would report:
Inst libjq1 (1.6-2.1ubuntu3.2 Ubuntu:22.04/jammy-updates [amd64])
…
run it for real with `dryRun: false`.
所有 apt 配方会合并成一步(apt-get update 是慢的那一半,每个配方各跑一次看起来就像卡死),
并附带一条 apt-get -s 只读模拟,让计划直接显示 apt 自己会做什么。已经满足的配方会被跳过,
所以第二次跑很便宜。步骤通过 wsl -u root 执行——为什么见下一节——某一步失败即停下并附上
它的输出末尾。
以 root 执行(asRoot)
sudo 在工具调用里根本用不了:它要密码,而除非你显式喂 stdin,stdin 就是 /dev/null。
WSL 自带答案——wsl.exe -u root 免密(本机实测:sudo -n 失败时它照样给出 uid=0)——
于是 wsl 工具把它暴露成 asRoot: true,由面板里一项开关把关(「管理员模式(root)」,默认关):
wsl({ command: 'apt-get install -y jq', description: 'install jq', asRoot: true })
授权来自开关而不是参数:开关关着时,asRoot: true 会被明确拒绝并给出开关位置,而不是
悄悄降级成你的普通用户——一个要了 root 却拿到权限错误的人,会去错误的地方找原因。每次以 root
执行的结果都会标注([ran as root: wsl -u root]),转录里说得清发生过什么;危险命令守卫则
独立生效:rm -rf 无论是不是 root 都仍需 allowDangerous: true。
安装
发布到 npm 的包名是 dsh-wsl-tool,不是 dsh-wsl:registry 判定 dsh-wsl 与既有包
is-wsl 过于相似而拒绝,换 token 或改设置都无法绕过。仓库名、插件名与市场条目仍沿用
dsh-wsl。组合包的 patch 用相对路径指向自身入口,因此 node_modules 下的文件夹名可以
不同——必须一致的是你安装时使用的那个名字。
将本包加入 DSH 的 profile(
profiles/<profile>/package.json):{ "dependencies": { "dsh-wsl-tool": "file:<path-to-this-repo>" } }或运行
dsh plugin add --profile <profile> dsh-wsl-tool从 npm 安装,或用dsh plugin add --profile <profile> file:<path-to-this-repo>从本地检出安装 (依赖名会取本包自身的名字)。不必再做别的。本包的
cordis.patch.yml会在组合包加载时自己插入tool-wsl行 (进程级),因此这些工具对所有 agent preset 都可用,无需额外接线。不需要选择任何模式, 也不要在 preset 里加任何东西——左侧栏的 WSL 面板同样是这样出现的。不要再在 preset 里列一遍
tool-wsl:DSH 按名字注册工具,第二次注册会直接失败 ——tool "wsl" is already registered in this scope。这一行只由组合包提供。重启 DSH。
设置面板是唯一可选的一块:它的表单需要 @deepseek-ai/schemastery,多数 profile 已经有了
(只要装过任何依赖它的插件;没有的话把它加进 profile 的依赖即可)。没有它时,工具与
面板里的「WSL 终端启动路径」照常可用,只是开关不显示 —— 面板会直接说明这一点,不会一直转圈等待。
兼容性
清单里声明了它需要的 DSH —— "engines": { "dsh": ">=0.1.7-rc.2" },也就是本节记录的
下限。插件市场会从已发布的清单读取这条声明,在条目上标成要求(DSH >=0.1.7-rc.2),并在
装到更老的宿主上之前给出提醒;DSH 自身不读 engines,所以它是提示而不是门禁。那个
-rc.2 不能省:只写 >=0.1.7 会把本条验证过的 0.1.7-rc.2 排除掉,市场于是会把插件
判成与它自己验证过的宿主不兼容。
已在 DSH 0.1.7-rc.2 上验证(本插件的更早版本曾在 0.1.5-rc.2 上验证):工具 schema 通过 DSH 自己的
assertSupportedJsonSchema;subprocess 接缝是跑在真实 provider 上而非替身;后台任务
路径跑在真实 job 注册表上,包含 0.1.7 收紧的「会话 id 属主围栏」。这套检查就是
test/real-seam.mjs,约一分钟即可重验一个新宿主——升级后把它指向新的 DSH 安装即可:
DSH_SUBPROCESS_LOCAL=/path/to/dsh/node_modules npm run test:real
后台任务由调用方会话持有(owner: exec.agent.id),这既是模型能用
job_output/job_kill 读回它的依据,也是其他会话读不到它的围栏;exec 里没有 agent 时
任务则是无主的。
左侧栏的 WSL 面板
插件带了一个小的客户端半:桌面版左侧栏里多一个 WSL 入口,面板里每个功能一个开关,每个开关 配一行说明。
| 开关 | 管什么 |
|---|---|
wsl 命令执行 |
是否注册 wsl 工具 |
wsl-path 路径转换 |
是否注册 wsl-path 工具 |
wsl-env 能力体检 |
是否注册 wsl-env 工具 |
| 后台任务 | wsl 是否接受 runInBackground |
| 自动转换路径 | 每次调用的 translatePaths 默认值 |
| 默认跟随会话工作区 | 未传 workdir 时从会话目录开始,而不是 ~。默认开:关掉的话,agent 写的相对路径都会落进 Linux 家目录 —— 那儿在资源管理器里看不见,还会撑大 WSL 磁盘镜像 |
| Linux 默认工作目录 | 未传 workdir 时使用的固定 Linux 目录(如 /mnt/d/project)。填了就优先于上面的开关;清空则回到跟随会话 |
| WSL 终端启动路径 | 可选的侧边栏终端从哪个目录启动 —— 写进你 profile patch 里 terminal-controller 那一行的 --cd <目录>(见可选:在侧边栏开一个 WSL 终端)。留空则删掉该参数;当前值也是从这个文件读的 |
| 危险命令守卫 | 危险命令是否必须显式 allowDangerous |
| 管理员模式(root) | wsl 是否接受 asRoot: true(wsl -u root)。WSL 的 root 免密,所以这项开关就是授权本身;默认关 |
| wsl-doctor 项目体检 | 注册 wsl-doctor |
| wsl-bootstrap 安装工具链 | 注册 wsl-bootstrap,它可能往发行版里装东西。默认关,且每次调用还得显式关掉 dryRun |
面板改的是插件自己的配置,所以同样的值也可以手写进 profile patch(- id: tool-wsl 加 config:)
或用环境变量设。优先级由 lib/config.js 定:插件配置 > 环境变量 > 内置默认值;开关停在默认值时
下层说了算 —— 这正是"从没打开过面板的人,DSH_WSL_WORKDIR=session 依然生效"的原因。
「WSL 终端启动路径」是唯一的例外:侧边栏终端属于另一个插件,所以那个输入框改的是你 profile 的
patch 层 —— 每次写入前先备份该文件,只重写终端那一行的 args,写后还会读回来核对,只要不是"只改了
这一行"就当场还原。
改动在下次启动 DSH 后生效:宿主每次挂载只读一次该配置,面板里也写着这句。发行版与超时属于"值"
而不是"功能":在 patch 里或用 DSH_WSL_DISTRO / DSH_WSL_TIMEOUT_MS 设置,面板只显示当前生效值。
这个设置界面需要 @deepseek-ai/schemastery(插件把它声明为可选 peer 依赖):没有它工具照常按
默认值工作,只是没有面板。
面板最底部是一个反馈入口,旁边跟着一段简短的提交指南,以及一个「复制插件信息」按钮(详见
反馈)。按钮会向宿主半的 /dsh-wsl-tool/info 索取本插件自己的信息 —— 包名、版本、仓库
(都从 package.json 读出)、Node 与平台 —— 外加它正运行在哪个 DSH 构建里、以及 wsl-env
那一套 WSL 事实(默认发行版、内核、能力标记),然后连同当前生效的配置一起复制出来。
这些值没有一个是"发版时要记得手改"的字符串;探测失败时它写明失败,绝不编造。
可选:在侧边栏开一个 WSL 终端
桌面版侧边栏终端可以开 WSL 而不是 Windows shell。这是可选的 —— 装插件不会改你终端默认
开什么 —— 启用用的 patch 随包提供:extras/terminal-wsl.patch.yml。
启用方式:把里面的条目复制进你自己 profile 的 patch 层
($DSH_HOME/profiles/<profile>/cordis.patch.yml);命令行启动也可以改成
--patch <已安装文件路径>。下次启动应用时生效。
这个启动目录也能在面板里设:顶部的「WSL 终端启动路径」(挨着「默认 Linux 工作目录」)会从那个
文件读出当前值,再把 --cd <目录> 写到该行 args 上 —— 写前先备份 patch 文件,只改这一行,
写后读回来核对。清空则删掉该参数,终端回到跟随会话工作区(启动时的 Windows 目录会被翻译成
/mnt/…);填 ~ 固定到 Linux 家目录。改完同样重启 DSH 生效。
- id: terminal-controller
config:
shell:
path: 'C:\Windows\System32\wsl.exe'
name: WSL
args: ['-e', 'bash', '-l']
- 「新建终端」的列表来自组合里的
terminal-controller行:它把配置的shell排在最前,同时保留 它发现的powershell/cmd/bash等可选。那个选择列表属于核心 UI,所以按 id 覆盖该行是 受支持的入口 —— 这也是它没法做成普通插件条目的原因。 - 不锁定发行版:
wsl.exe跟随系统默认,与工具在未设DSH_WSL_DISTRO时的规则一致。要锁定就 在args里加-d <名字>。 - 会话工作区是 Windows 路径,
wsl.exe会自动翻译,因此终端像 Windows shell 一样落在/mnt/<盘>/…;而且是真 PTY(xterm-256color),全屏程序可用。 - 按 id 的 patch 会整段替换该行 config,你之前在该行上设过的东西(终端上限、scrollback、
自定义
shellCandidates)要一并重述。
wsl 参数
| 参数 | 必填 | 类型 | 说明 |
|---|---|---|---|
command |
是 | string | 要执行的 Linux 命令 |
description |
是 | string | 简短的界面说明文字 |
workdir |
否 | string | Linux 路径(/home/me、~/src)或 Windows 路径,默认 ~ |
timeoutMs |
否 | number | 超时毫秒数,默认 600000(10 分钟);到点后杀进程并把结果标记为超时 |
distro |
否 | string | WSL 发行版;默认使用系统默认发行版 |
env |
否 | object | 要导出的额外环境变量(键必须是合法 shell 变量名) |
stdin |
否 | string | 在命令运行前写入其 stdin 的文本(UTF-8) |
runInBackground |
否 | boolean | 作为后台任务运行并立即返回任务 id;用 job_output 读、job_kill 停 |
allowDangerous |
否 | boolean | 置 true 才允许执行危险命令 |
asRoot |
否 | boolean | 本次调用以 root 执行(wsl -u root);面板「管理员模式」关着时会被拒绝 |
translatePaths |
否 | boolean | 默认 true;置 false 时 command 原样传入,不做路径改写 |
配置
面板里的开关与取值字段(tools.wsl、tools.path、tools.env、tools.doctor、
tools.bootstrap、backgroundJobs、translatePaths、startInSessionWorkspace、workdir、
dangerGuard、allowRoot、distro、timeoutMs)都写在 profile patch 里,各自行上有说明。
其中两项故意没有环境变量:dangerGuard(它的关闭位置本身就是风险)与 allowRoot
(WSL 的 root 免密,所以那项开关就是授权本身)。环境变量这一层:
| 环境变量 | 默认值 | 作用 |
|---|---|---|
DSH_WSL_DISTRO |
(系统默认) | 为所有调用固定发行版 |
DSH_WSL_TIMEOUT_MS |
600000 |
模型命令的默认超时;单次调用可用 timeoutMs 覆盖 |
DSH_WSL_MAX_TIMEOUT_MS |
86400000 |
单次 timeoutMs 的上限,对齐平台 shell 工具的 maxTimeoutMs;默认超时也受它约束 |
DSH_WSL_MAX_OUTPUT_BYTES |
65536 |
每条流的内存窗口(1 KiB – 8 MiB);设到 64 MiB 以上时也会抬高落盘上限 |
DSH_WSL_WORKDIR |
home |
不传 workdir 时的起点:home(Linux 的 ~)、session(会话工作目录,Windows 检出对应 /mnt/<盘>/...),或任意显式路径 |
无法解析或越界的值会回退到默认值——一个写错的环境变量不该让所有工具一起挂掉。 配置在挂载时读取一次,改动需重启 DSH 生效。
注意事项
每次调用都在全新的 shell 中执行——cwd / 变量 / 函数不会在调用间保留。
stdin 默认是
/dev/null,除非传stdin:交互式命令(read、cat、不带-S的sudo密码提示)否则会立刻收到 EOF,无法等待输入;本插件不会、也无法弹出任何提示。 通过stdin传的密码会被记进会话记录。workdir默认~;设DSH_WSL_WORKDIR=session可改为从会话工作目录开始(见"配置")。发行版取调用参数 →
DSH_WSL_DISTRO→ 系统默认,代码里不再硬编码兜底名称。发行版不存在时会给出明确报错(
distribution "X" is not registered),而不是 一个原始-1退出码;其他启动器错误(Wsl/Service/WSL_E_*)也会带错误码上报, 不会被当成命令自身的退出状态。后台任务可以活得比这次调用久:
runInBackground: true立即返回任务 id(wsl-N) 并把工作登记进宿主任务注册表,job_output读它(标记与前台一致)、job_kill停它。 后台模式下 10 分钟默认超时不适用,但显式timeoutMs仍然生效;非零退出与前台一样 报成completed并把退出码写进 detail。command与workdir中的 Windows 路径会自动转换为/mnt/...:C:\Users\me\a.txt→/mnt/c/Users/me/a.txt;含空格、括号的路径与一行多个路径都支持 (C:\Program Files\Git、C:/Program Files/Git、C:\Program Files (x86)\Steam、cp C:\a.txt D:\b.txt两个路径都会转换);\\wsl.localhost\<发行版>\home\x与\\wsl$\<发行版>\home\x→/home/x;- 只是"看起来像"盘符的文本不会被动:单个小写字母后跟
/(如a:/b)、其他表达式里的 盘符(如sed "s/C:\x/y/")、URL 里的疑似盘符段; - 当路径要交给Windows 程序(经 interop 调用)时请传
translatePaths: false: WSL 不会把/mnt/c/...反向翻译,notepad.exe C:\file.txt需要原始写法。 该开关只影响command;workdir始终会被转换。
workdir与wsl-path参数中的~会被 shell 展开(~/my dir可用)。其余路径 一律单引号包裹,因此路径里的$VAR不会展开。每条流输出上限 64 KiB:保留尾部,标记中会给出保存完整输出的落盘文件路径, 信息不会不可恢复:
[stdout truncated: at most the last 65536 of 1288895 bytes were kept; full stream: C:\...\stdout.log]timeoutMs默认 10 分钟,避免卡死的wsl.exe永久挂住调用;需要长时间运行的命令可以传更大的值, 但上限是DSH_WSL_MAX_TIMEOUT_MS(默认 24 小时)——手滑不会变成"永不超时"。结果里回报的始终是 实际生效的那个期限。到点会连 Linux 侧进程一起杀掉(Windows 上由 provider 使用taskkill /T /F)。宿主 shell 的环境事实会转发进发行版(WSL 默认不跨边界传 Windows 环境变量):
DSH_SESSION_ID、DSH_SHELL、以及翻译成/mnt/...形式的DSH_HOME会在命令前export,脚本因此能看到与平台自带 shell 工具一致的会话事实。这些值按次从宿主的shellEnv注册表解析(与其他 shell 工具同一个来源), 因为它们属于会话而非宿主常量——直接读宿主process.env会什么都读不到、静默地什么都不转发。DSH_WEB_URL故意不转发——它是 Windows 侧服务的127.0.0.1地址, 而默认 NAT 模式下 WSL 访问不到 Windows 回环(实测127.0.0.1与主机 IP 均返回 HTTP 000,服务本身 也只绑回环),转进去只会给一个打不开的 URL。显式传入的env条目总是覆盖转发值。wsl-env还会报出会话文件所在的位置以及它是否落在 Windows 盘挂载上:workspace: /mnt/d/DSHworkarea (Windows drive mount /mnt/d — builds, installs and git are much slower here; prefer a path under /home when it matters)这个提示值得当真。作者机器实测:128 MB 顺序写在 ext4 上约 2.1 GB/s,在
/mnt/d上约 247 MB/s;创建 400 个小文件 ext4 不到 10 ms,/mnt/d要 0.72 s (后来复测:884 vs 116 MB/s、13 ms vs 745 ms,比值稳定在约 8× 与 50×)。不只是慢。
/mnt/<盘>是 9p(drvfs)挂载,保留的是 Windows 的文件系统语义:chmod/chown不生效(chmod 600读回来是777)、文件名大小写不敏感(大小写写错只在 Linux CI 上才炸)、符号链接与可执行位是合成的,而且 inotify 完全不工作——发行版里的 watcher 对两侧写入都收不到任何事件(用 inotify 探针实测:/mnt/d上 Windows 侧写入与 Linux 侧写入均为 0 事件,而同一探针在 ext4 上正常报出创建/修改/关闭写入)。因此 dev server、--watch模式 与文件监听的测试在 Windows 盘上都是瞎的。需要时把项目放到/home下:语义、监听事件与上面那档 速度一次性都回来。危险命令默认被拒绝,除非调用时传
allowDangerous: true:- 任何递归删除——
rm -r、rm -rf、rm -r -f、rm -R --force、rm --recursive—— 因为 stdin 指向/dev/null时不会产生任何提示,rm -r tree会静默删除整棵树。 每次rm调用按其所在命令段单独判定,所以rm a -f; rm b -r不能靠拼接标志蒙过去; - 往块设备
dd、mkfs、分区/擦除类工具(fdisk、parted、wipefs、mkswap…)、 电源控制(shutdown、reboot、systemctl reboot…)、重定向到块设备、fork 炸弹; - 防护容忍命令词的各种写法(
sudo rm -r -f、bash -c "rm -rf /"、find . -exec rm -rf {} +、rm$IFS-rf、\rm -rf、$(which rm) -rf),但设备/电源类 工具只在命令位置匹配,因此查看它们是允许的:man fdisk、grep -rn reboot /var/log/syslog、echo "the mkfs tool formats disks"都能正常执行。
- 任何递归删除——
stderr 中重复出现的启动器噪声会被过滤:localhost 代理警告与 procps 的
screen size is bogus行。使用
wsl.exe -e(--exec),引号与$VAR展开行为与普通 shell 一致; 默认的--透传会破坏单引号和变量。
沙箱边界
DSH 的文件沙箱在两个点实施:shell 执行器(@deepseek-ai/dsh-bash-sandbox、-pwsh-sandbox,把
argv 包一层过 ctx.sandbox)和文件系统服务(@deepseek-ai/dsh-fs-sandbox,对两个写操作加策略栅栏)。
wsl 两者都不经过——它通过宿主 subprocess 服务直接拉起 wsl.exe,位于那一层之下。因此
workspace-write 策略约束不到它:它能写 Linux 侧能写的任何位置,也能写 /mnt/<盘> 下 Windows
允许的任何位置。
这不是靠"接入沙箱"能补上的缺口。Windows 上的沙箱最终落到 ACL / 受限令牌,而文件操作发生在
Linux 内核里:写 /home/... 动的是发行版自己的文件系统镜像,任何 Windows 令牌都够不着;写
/mnt/c/... 要经文件系统桥,ACL 是否生效不可依赖。把 wsl.exe 包起来只能约束"启动器",约束不了写入
——而给出虚假的隔离感,比明说不隔离更危险。(平台自己的 dsh-fs-sandbox 也坦白它的边界:
"containment, not a security boundary"。)
wsl 实际拥有的保护是上面那套危险命令守卫:一份确定性的拒绝清单,不是内核边界。请把它当作
"能碰到你的 WSL 安装能碰的一切"来授予权限。
从插件列表安装
本包声明了 dsh.bundle manifest(见 package.json),因此仓库被列表收录后可按
名称安装,例如 dsh plugin add dsh-wsl-tool,市场(storefront)也会提供一键安装。
上文 file: 的本地安装方式仍然有效。
工作原理
插件是一个 cordis 模块,注入宿主平面的 tools 与 subprocess 注册表。index.js
只是入口,实现按职责拆分:
| 模块 | 职责 |
|---|---|
lib/config.js |
默认值与环境变量覆盖,挂载时解析一次 |
lib/paths.js |
shell 引号处理与 Windows → WSL 路径转换 |
lib/guard.js |
危险命令规则 |
lib/result.js |
启动器噪声过滤、截断事实、标记渲染 |
lib/diagnostics.js |
wsl-env 的能力探针、解析器与输出行 |
lib/runner.js |
唯一的 spawn 路径与启动器错误分类 |
lib/tools/*.js |
工具定义(schema / execute / presentCall) |
lib/doctor.js |
wsl-doctor 的项目探针:一段 shell、它的解析器与报告组装 |
lib/bootstrap.js |
wsl-bootstrap 的配方、计划生成与结果渲染 |
apply()解析配置并注册被启用的工具,每个都有 JSON-schema 参数定义、输出 schema、render钩子与异步execute。- 所有调用都走同一条 spawn 路径(
runner.runWsl):通过宿主subprocess执行wsl.exe -d <distro> -e bash -lc "<exports; cd workdir && command>",stdout/stderr 上限由配置决定(超出最多落盘 64 MiB),abort 后有 3 秒宽限期,超时默认 10 分钟。 插件自身的探测调用另有 30 秒硬上限,避免 WSL 服务卡死时永久挂住工具调用。 env条目在命令前部以export形式注入,确保可靠到达 Linux 侧;Windows 盘符 路径在构造命令前先改写为/mnt/...。- 输出为
{ exitCode, signal, timedOut, timeoutMs, truncated, stdout, stderr, stdoutTotalBytes, stdoutDroppedBytes, stderrTotalBytes, stderrDroppedBytes, stdoutSpillPath, stderrSpillPath, jobId };render钩子将其格式化为文本并附加上述 标记。截断标记引用窗口大小本身而不是由解码文本推算的数字——窗口起点落在多字节字符 中间时后者会差一两个字节。 jobId只有后台启动才会赋值,其余路径一律null,因此两种情况下声明形状都成立。- 启动与结算被拆成两步(
runner.launch/settle):后台任务要同步交给注册表一对cancel/done,而前台调用只是 await 同一个 settle。被取消的任务会把 provider 的 "target 启动前即被终止"拒绝映射成killed——JobHooks.done不允许 reject。 - 危险命令防护把命令按
;/&/|/换行切成段,每次rm调用按自身标志单独判定, 设备/电源类工具在命令位置匹配后才拦截。
插件不发布任何自身服务,且由它自己的组合包 patch 进程级插入,因此既不需要 realm, 也不需要任何 preset 条目。
开发
插件是纯 ESM,无构建步骤,除 DSH 宿主平面外无运行时依赖。迭代方式:把 profile 依赖指向本仓库:
{ "dependencies": { "dsh-wsl-tool": "file:/path/to/dsh-wsl" } }
然后重启 DSH,在任意会话里调用工具即可:组合包 patch 是进程级注册的,没有哪个 preset 需要声明这一行。
测试
npm test # 591 项宿主检查(跑在真实 WSL 上,仅用 shim 顶替 ctx.subprocess)+ 133 项客户端检查
npm run test:real # 同一套检查改跑真实 provider,外加 seam 事实套件
npm test 只替换 ctx.subprocess,用一个复刻了 seam 行为(有界尾窗、落盘文件、
终止阶梯)的 shim 驱动这些工具跑在真实 WSL 上,覆盖路径改写、workdir 引号处理、
危险命令防护、发行版选择、退出码/超时/截断标记、wsl-path、wsl-env、参数校验、
配置解析、启动器错误分类、返回结构与各自 output.schema 的一致性,以及模型可见
目录的 token 预算。
npm run test:real 把同一套检查改跑在 LocalSubprocessRuntime 上——shim 不允许与
真实 seam 漂移——然后跑 test/real-seam.mjs,校验 shim 无法担保的事实
(readFrom(0).nextOffset 是否为整条流字节总数、落盘文件是否完整、超时是否真的杀掉
wsl.exe 的 Linux 侧进程),并用 DSH 自己的 assertSupportedJsonSchema 校验全部
schema。它需要一份 DSH 安装:
DSH_SUBPROCESS_LOCAL=/path/to/dsh/node_modules npm run test:real
同步到 profile
file: 依赖是副本,改本仓库不会改变 DSH 实际加载的内容。任何改动之后:
npm run sync # 复制到 ~/.dsh/profiles/desktop/node_modules/dsh-wsl
npm run sync -- /path/to/profiles/<profile>/node_modules/<你的依赖名>
目标目录必须是 profile 依赖名对应的文件夹(上面的默认值就是本检出的依赖名); 插件按相对路径加载自身入口,因此文件夹叫什么并不影响加载。
然后重启 DSH——插件在加载时只导入一次。
发版流程(tag 触发的流水线、npm 包名与目录条目)见 PUBLISHING.md。
收录
本仓库带有 dsh-plugin topic,并已提交至 awesome-dsh-plugin 社区列表的 wsl 分类。
反馈
侧边栏面板最后一段就是反馈入口,它不会自己发送任何东西。
- 缺陷或明确的需求 → 开一个 Issue。 模板会问一次环境,这决定了报告能不能被复现、要不要多来回一轮。
- 用法问题与想法 → Discussions。
- GitHub 打不开? 面板里的「复制插件信息」按钮会向宿主半索取这个包自己的信息 ——
包名、版本、仓库(直接读
package.json),加上 Node、平台与当前生效的配置 —— 连同面板正显示着的开关状态一起复制出来。粘进 Issue 的「补充」栏即可,只剩正文要自己写。 提交指南就在按钮旁边,也在面板里。
那段文本在发出去之前你都能看清、都能改:不采集路径、主机名或任何凭据,插件也不对外发起请求 —— 按钮读的那个路由由本机宿主提供,不走网络。 属于别处的问题请发到各自的仓库 —— DSH 本体 → deepseek-harness,市场界面 → dsh-market,收录与目录 → awesome-dsh-plugin。 完整分流表见 SUPPORT.md。