npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

dsh-wsl-tool

v1.12.5

Published

WSL tools for DSH: Linux commands with exit codes, timeouts and spill files; background jobs; path conversion; environment and project checks. Per task: a long-lived session for multi-step work, a fresh shell otherwise, subtasks above the composer. It als

Readme

dsh-wsl

English | 简体中文

面向模型(model-facing)的 WSL 工具插件,用于 DeepSeek Harness(DSH)。它让智能体直接通过 wsl.exe 执行 Linux 命令——无需手写 .sh 脚本或 pwsh 包装——并且在命令执行这一层基本对齐 Linux 原生 DSH:真实的 Linux 内核与 bash、退出码与信号、超时、截断与落盘、可用内置 job 工具读回的 后台作业、stdin,以及 Windows/WSL 路径自动转换。

它也能像你一样地干活:在一个你看得见的终端里,一件事一个 shell,每条命令都以一行可读的样子敲出来, 每件事都在输入框上方留下记录,点一下就能把键盘拿回来。

有两点限制值得在依赖它之前知道: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-term | 在本会话的终端里工作:一件事一个 shell,每件事都在输入框上方留下记录 |

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 自己会做什么,因此安装前就能看到具体改动。安装以 root 执行; 某一步失败即停下并附上它的输出末尾。

wsl-term

这里其他工具都是每次调用开一个全新 shell,这让它们可以安全并行,也是默认方式。wsl-term 反过来: 一件事一个 shell。它适合那些"shell 自身状态就是重点"的活——想看着它跑完的构建、交互式程序、 必须在多步之间保留的目录或环境。

wsl-term 就在这个终端里工作:

wsl-term({ action: "list" })                                  → 有哪些终端
wsl-term({ action: "read" })                                  → 当前屏幕
wsl-term({ action: "run", command: "make sta", subtask: "跑通 sta 流程" })
                                                              → 在那个终端里执行,返回输出与退出码
wsl-term({ action: "input", data: "ls", submit: true })       → 原始按键
wsl-term({ action: "interrupt" })                             → Ctrl-C
wsl-term({ action: "close", id: "…" })                        → 关闭你点名的那个终端
wsl-term({ action: "subtask-done", subtask: "跑通 sta 流程", result: "三条连跑全通过" })
                                                              → 写下结果,并决定这个 shell 的去留

每件事都留下一条记录

在 run 上给出 subtask,就会为这件事开一条记录,并记下它跑过的每条命令和退出码。输入框上方的显示把这些 记录列出来:正在运行的排在最上面,已经结束的带着结果排在后面;点开一行会看到详情——终端的完整描述、 结果、以及每条命令。subtask-done 负责写下结果并决定这个 shell 的去留:keepTerminal: true(默认) 把它闲置着等你下一步,false 则关掉工具自己开的那个 shell;你自己开的终端永远不会被这样关掉, 要关就用 close。这些记录只存在本次 DSH 运行的内存里:它们描述的是这次对话做过的事,重启会连同终端 一起清空。

你不必先自己开终端。run 与 read 会用本会话最新在运行的那个终端,你能直接看到它做了什么;如果一个都没有, 工具会自己开一个——那也是一个可用的 shell,但不会出现在侧边栏,所以想看得见就自己开。终端属于 本次 DSH 运行,重启即结束;应用在重新创建标签时会给终端一个新的 id,所以早前消息里的 id 可能 已经不存在——这时工具会退到最新在运行的那个终端,并说明它用了哪一个。

用之前有几点要知道:

  • 模型在终端里工作期间,该终端是只读的,而且之后仍然只读:点那个终端里的 「接管输入」 才能继续输入。 你正在打字时模型发起的调用会被拒绝,不会插进你那半行命令里。
  • read 返回的是屏幕内容:已经滚出去的内容取不回来;vim、htop 这类全屏程序返回的是它们的原始屏幕。
  • run 需要 POSIX 风格的 shell(sh、bash、zsh);cmd.exe 或 PowerShell 下请用 input。
  • 长输出不会丢。 构建与综合日志的结论在末尾,所以返回的是输出的末尾部分;完整内容会写入文件, 结果里给出该文件的路径。

命令以你的身份运行,和你自己敲完全一样,而且每条命令在终端里都是一行短而可读的样子 (__dsh_term_run 'make syn')——因为工具第一次在这个终端里跑命令时,会先定义好这个一行的包装函数。 拿回来的是两个标记之间的输出,外加退出码。这个工具没有开关:它一直在,工作的终端要么是你开的、 要么是它为本会话开的,而它敲的每一条命令你都在终端里看得见;危险命令仍然要先过守卫(除非调用方明确说明 那是有意为之)。

它只在需要同一个 shell 跨多步时才值得用 —— 想看着它跑完的长构建、交互式程序、必须保留的目录或环境。 自洽的命令交给 wsl 更划算:每次全新 shell、可以并行、根本不需要终端,也不会把键盘从你手里拿走。

以 root 执行(asRoot)

WSL 的 root 不需要密码,因此可以让单次调用以 root 执行(即 wsl -u root)。它由面板里的一项开关把关 (「管理员模式(root)」,默认关):

wsl({ command: 'apt-get install -y jq', description: 'install jq', asRoot: true })

授权来自开关而不是参数:开关关着时,asRoot: true 会被明确拒绝并告知该打开哪个开关,而不是悄悄降级成 你的普通用户。每次以 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-doctor 项目体检 | 是否注册 wsl-doctor | | wsl-bootstrap 安装工具链 | 是否注册 wsl-bootstrap,它可能往发行版里装东西。默认关,且每次调用还得显式关掉 dryRun | | 后台任务 | wsl 是否接受 runInBackground | | 自动转换路径 | 每次调用的 translatePaths 默认值 | | 默认跟随会话工作区 | 未传 workdir 时从会话目录开始,而不是 ~。默认开:关掉的话,agent 写的相对路径都会落进 Linux 家目录 —— 那儿在资源管理器里看不见,还会撑大 WSL 磁盘镜像 | | Linux 默认工作目录 | 未传 workdir 时使用的固定 Linux 目录(如 /mnt/d/project)。填了就优先于上面的开关;清空则回到跟随会话 | | WSL 终端启动路径 | 可选的侧边栏终端从哪个目录启动 —— 写进你 profile patch 里 terminal-controller 那一行的 --cd <目录>(见可选:在侧边栏开一个 WSL 终端)。留空则删掉该参数;当前值也是从这个文件读的 | | 终端历史行数 | 那个终端保留多少行输出(同一行上的 scrollback)。平台默认 1000;填 0 不保留,留空回到默认。这两项都在重启 DSH 后生效 | | 危险命令守卫 | 危险命令是否必须显式 allowDangerous | | 管理员模式(root) | wsl 是否接受 asRoot: true(wsl -u root)。WSL 的 root 免密,所以这项开关就是授权本身;默认关 |

面板改的是插件自己的配置,所以同样的值也可以手写进 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 生效。

它旁边的「终端历史行数」在同一行上设置 scrollback:终端保留多少行输出,长构建才不会被滚掉 (平台默认 1000 行)。填 0 表示不保留,留空则回到默认。历史越长,应用与宿主两侧占用的内存越多, 所以这是一个由你决定的数字,而不是一个固定的大值。写入规则与生效时机(重启 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/<盘>/...),或任意显式路径 |

无法解析或越界的值会回退到默认值——一个写错的环境变量不该让所有工具一起挂掉。 改动即时生效(包括工具开关):注册工具时会拿到它自己的 disposer,所以每次设置更新都会把期望的工具集 与已注册的工具集做差分,开关一拨工具就出现或消失,不需要重启——这一点重要,因为开关就是在面板里拨的。 唯一例外是「WSL 终端启动路径」:它改的是别的插件在 profile patch 里的启动参数,那个插件在启动时读, 所以那一项要重启 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 的配方、计划生成与结果渲染 | | lib/terminal.js | wsl-term 背后的文本与选择逻辑:去转义、命令标记、终端选择 |

  • 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          # 826 项宿主检查(跑在真实 WSL 上,仅用 shim 顶替 ctx.subprocess)+ 170 项客户端检查
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。