Chuyển đến nội dung chính

dsh-wsl-tool

Đã xác minh

dsh-wsl-tool · v1.11.0 · MIT · Giao diện 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

Cài đặt

dsh plugin add dsh-wsl-tool

Xác nhận layer đã áp bằng dsh --profile default --dump-config — xem hướng dẫn cài plugin.

Mã nguồn

Thẻ

Tác giả

Readme

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 下的文件夹名可以 不同——必须一致的是你安装时使用的那个名字。

  1. 将本包加入 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> 从本地检出安装 (依赖名会取本包自身的名字)。

  2. 不必再做别的。本包的 cordis.patch.yml 会在组合包加载时自己插入 tool-wsl 行 (进程级),因此这些工具对所有 agent preset 都可用,无需额外接线。不需要选择任何模式, 也不要在 preset 里加任何东西——左侧栏的 WSL 面板同样是这样出现的。

    不要再在 preset 里列一遍 tool-wsl:DSH 按名字注册工具,第二次注册会直接失败 —— tool "wsl" is already registered in this scope。这一行只由组合包提供。

  3. 重启 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。