dsh-plugin-wsl-env 0.11.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/CHANGELOG.md +181 -0
  2. package/CONTRIBUTING.md +29 -7
  3. package/README.md +124 -138
  4. package/SECURITY.md +11 -1
  5. package/SUPPORT.md +29 -2
  6. package/cordis.patch.yml +17 -19
  7. package/docs/ARCHITECTURE.md +30 -29
  8. package/docs/CONFIGURATION.md +81 -21
  9. package/docs/LIMITATIONS.md +71 -6
  10. package/docs/MIGRATION.md +98 -0
  11. package/docs/PARITY.md +1 -1
  12. package/docs/PERFORMANCE.md +60 -0
  13. package/docs/QUICKSTART.md +125 -0
  14. package/docs/README.zh.md +117 -137
  15. package/docs/ROADMAP-1.0.md +780 -0
  16. package/docs/ROADMAP.md +4 -0
  17. package/docs/UPSTREAM-FILE-REFERENCE-SEAM.md +80 -0
  18. package/docs/UPSTREAM-LSP-SEAM.md +90 -0
  19. package/docs/UPSTREAM-SESSION-NOTICE.md +95 -0
  20. package/lib/agent-errors.d.ts +19 -0
  21. package/lib/agent-errors.js +22 -0
  22. package/lib/agent-exec.js +2 -2
  23. package/lib/agent.d.ts +68 -5
  24. package/lib/agent.js +129 -22
  25. package/lib/distro-route.d.ts +75 -0
  26. package/lib/distro-route.js +86 -0
  27. package/lib/fs-routing.d.ts +4 -1
  28. package/lib/index.d.ts +166 -37
  29. package/lib/index.js +353 -75
  30. package/lib/paths.d.ts +15 -0
  31. package/lib/paths.js +28 -0
  32. package/lib/preflight.d.ts +46 -3
  33. package/lib/preflight.js +83 -14
  34. package/lib/shell-env.d.ts +9 -3
  35. package/lib/shell-env.js +121 -43
  36. package/lib/subprocess.d.ts +12 -3
  37. package/lib/subprocess.js +21 -3
  38. package/package.json +23 -15
  39. package/scripts/diagnose.sh +9 -88
  40. package/scripts/doctor.mjs +548 -0
  41. package/scripts/profile-preset.mjs +254 -0
  42. package/lib/workspace-files-route.d.ts +0 -136
  43. package/lib/workspace-files-route.js +0 -225
  44. package/lib/workspace-files-wsl.d.ts +0 -257
  45. package/lib/workspace-files-wsl.js +0 -418
@@ -0,0 +1,125 @@
1
+ # Quickstart
2
+
3
+ Five minutes from a clean Windows + WSL 2 machine to a working WSL session.
4
+ This page is the entry point; every claim links to the document that owns it.
5
+
6
+ ## 1. Prerequisites
7
+
8
+ | Need | How to check | If missing |
9
+ |---|---|---|
10
+ | Windows 10/11 with WSL 2 | `wsl.exe -l -v` shows `VERSION 2` | `wsl.exe --install` |
11
+ | A distro | one line in the list above | `wsl.exe --install -d Ubuntu` |
12
+ | `bubblewrap` | `wsl.exe -d <distro> -- which bwrap` | `wsl.exe -d <distro> -u root -- apt-get install -y bubblewrap` |
13
+ | systemd + Linger (for the session runtime) | `node <plugin>/scripts/doctor.mjs` (installed) or `pnpm run doctor` (checkout) | `[boot] systemd=true` in `/etc/wsl.conf`, `wsl --shutdown`, then `loginctl enable-linger "$USER"`; or set `sessionSandbox: auto`/`false` |
14
+ | Node 22.19+ or 24 | only needed for source/development work | not needed to use the published plugin |
15
+
16
+ The doctor (`scripts/doctor.mjs`, or `pnpm run doctor` from a checkout) covers all
17
+ of the above in one pass and prints a copy-paste remedy per failure. It is read-only unless you pass `--fix` (stale scopes) or
18
+ `--fix --install` (packages, after a confirmation).
19
+
20
+ ## 2. Install
21
+
22
+ From a Windows PowerShell where the DSH CLI is available:
23
+
24
+ ```powershell
25
+ dsh plugin --profile <profile> add dsh-plugin-wsl-env
26
+ dsh --profile <profile> --dump-config # the plugin rows must be there
27
+ ```
28
+
29
+ For a profile that already overrides the preset, keep the override as the full
30
+ `preset-wsl` block — `pnpm run preset --check <patch>` fails loudly when a layer
31
+ targets `wsl-shell`/`wsl-fs` by id.
32
+
33
+ ## 3. Open a WSL workspace
34
+
35
+ In the GUI, open a folder through the picker (`\\wsl.localhost\<distro>\home\<you>\proj`)
36
+ or an UNC path directly. The session's commands, file tools, terminal and previews
37
+ all run in that distro; a Windows folder keeps the normal Windows environment.
38
+ Two distros can be open at once, each with its own environment.
39
+
40
+ ## 4. Verify
41
+
42
+ Ask for a command, e.g. `pwd && uname -a`. Expected: the Linux path and the
43
+ distro kernel. If the first command fails, the error names the remedy; the
44
+ [doctor](CONFIGURATION.md) and the [Troubleshooting table](../README.md#troubleshooting)
45
+ cover the common cases.
46
+
47
+ ## Recipes
48
+
49
+ Commands run in the model's session, inside the distro, from the workspace root.
50
+
51
+ ### Node / TypeScript
52
+
53
+ ```bash
54
+ node -v && pnpm -v
55
+ pnpm install
56
+ pnpm test
57
+ pnpm dev # a dev server survives the command that started it with sessionSandbox: true
58
+ ```
59
+
60
+ The server's port appears in `DSH_WSL_PORTS` on the next snapshot (10 s by
61
+ default); open it from Windows at `http://localhost:<port>`.
62
+
63
+ ### Python
64
+
65
+ ```bash
66
+ python3 -m venv .venv && . .venv/bin/activate
67
+ pip install -r requirements.txt
68
+ python -m pytest
69
+ ```
70
+
71
+ Use the distro's interpreter, not a Windows one: the session's `python3` is the
72
+ one on the distro `PATH` (pyenv/conda shims included).
73
+
74
+ ### Rust
75
+
76
+ ```bash
77
+ cargo build --release
78
+ cargo test
79
+ ```
80
+
81
+ Targets are compiled natively for the distro's architecture (x86_64 or aarch64);
82
+ do not reuse Windows `target/` directories.
83
+
84
+ ### Go
85
+
86
+ ```bash
87
+ go build ./...
88
+ go test ./...
89
+ ```
90
+
91
+ The module cache lives in the distro's `$GOPATH`, so builds stay on ext4.
92
+
93
+ ### Monorepo
94
+
95
+ Open the repository root as the workspace. Each package's commands run from the
96
+ same distro; `pnpm -r`, `turbo`, `nx` and `make` see one Linux filesystem. The
97
+ file watcher uses inotify inside the distro (`watchMaxDepth: 0`). If a change is
98
+ missed, see the watcher bounds in [CONFIGURATION.md](CONFIGURATION.md).
99
+
100
+ ## Compatibility
101
+
102
+ Honest status, not a promise. "Probed" means a real-machine run is recorded in
103
+ this repository; "target" means the design supports it but the leg has not been
104
+ executed here.
105
+
106
+ | Tier | Configuration | Status |
107
+ |---|---|---|
108
+ | 1 | Ubuntu 24.04 or Debian 12, systemd, x86_64 | Probed on Ubuntu (the project's development machine; the real-machine suite is manual). Debian is a Tier 1 target, probe pending |
109
+ | 2 | Other systemd distros, aarch64 | The aarch64 native binary is built by CI but not executed here; aarch64 WSL or qemu is the pending leg |
110
+ | Unsupported | WSL 1, Linux/macOS hosts, Windows without WSL 2 | Not supported; the plugin attaches to `wsl.exe` |
111
+
112
+ With `sessionSandbox: auto` a distro without systemd/Linger still gets the
113
+ per-command `bubblewrap` sandbox and the result reports the narrower scope;
114
+ `true` refuses. See [CONFIGURATION.md](CONFIGURATION.md#the-strict-profile) for
115
+ the sandbox trade-offs and [PERFORMANCE.md](PERFORMANCE.md) for the measured
116
+ latency.
117
+
118
+ ## Demo
119
+
120
+ There is no GIF or screenshot in this repository yet: producing one needs a
121
+ capture of the GUI, which this project has not recorded. The
122
+ [Troubleshooting table](../README.md#troubleshooting) and the probe output
123
+ (`pnpm run probe:sandbox`, `pnpm run probe:gui-preview`) are the reproducible
124
+ evidence in the meantime; the absence is recorded in
125
+ [ROADMAP-1.0.md](ROADMAP-1.0.md#wsl-302--discoverability-and-docs-entry).
package/docs/README.zh.md CHANGED
@@ -6,201 +6,181 @@
6
6
  [![npm](https://img.shields.io/npm/v/dsh-plugin-wsl-env)](https://www.npmjs.com/package/dsh-plugin-wsl-env)
7
7
  [![license](https://img.shields.io/npm/l/dsh-plugin-wsl-env)](../LICENSE)
8
8
 
9
- 这个插件让 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 在 WSL 子系统里干活:模型的命令在子系统里执行,文件工具读写的是子系统的真实文件,文件夹选择器能打开子系统目录,GUI 终端开的也是子系统里的 shell。
9
+ 一个 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 插件,让会话在 Windows 的 WSL 2 发行版中运行。
10
10
 
11
- **只支持 Windows + WSL2。** 安装只需一条命令,插件自身没有依赖。环境按会话生效:打开 Windows 文件夹的会话,用的还是原来的 Windows 工具。
11
+ Harness 进程仍在 Windows 上。命令、文件操作、文件夹选择器和 GUI 终端都使用发行版环境。发行版内不安装辅助服务或守护进程,也不开放端口,受限命令在 `bubblewrap` 中运行。
12
12
 
13
- [安装](#安装) · [使用](#使用) · [方案对比](#方案对比) · [配置](#配置) · [配方](#配方) · [架构](#架构) · [沙箱](#沙箱) · [常见问题](#常见问题) ·
14
- [开发](#开发) · [文档](#文档)
13
+ 仅支持 Windows + WSL 2。
15
14
 
16
- ## 安装
15
+ ## 为什么用这个插件
17
16
 
18
- 在 Windows 终端里执行这四条命令:
17
+ Windows 上的编程工具要在 WSL 项目中工作,有三条路:把整个工具装进发行版、连接发行版内的辅助服务,或由插件直接提供发行版环境。本插件是第三条。两类会话共用一套流程,同时保留发行版的真实工具和命令沙箱。
19
18
 
20
- ```powershell
21
- dsh wsl --from-default-profile web --dump-config # 1. 创建 profile(dsh 的一套命名配置),模板用 Web
22
- dsh plugin --profile wsl add dsh-plugin-wsl-env # 2. 安装插件,配置自动带上
23
- dsh --profile wsl --dump-config # 3. 快速检查:不启动,只看拼好的配置
24
- dsh --profile wsl # 4. 启动
25
- ```
19
+ - **真实的 Linux 环境。** 命令在发行版自己的登录 shell 中运行,`PATH`、`nvm`、`cargo`、`pyenv` 和 rc 文件都生效。文件工具直接读写发行版 ext4 上的真实文件。
20
+ - **发行版内没有服务。** 没有辅助服务、没有守护进程、没有监听端口。唯一持久化的内容是 `~/.cache/dsh-wsl/agent/` 下按内容寻址的原生 agent 副本。
21
+ - **默认有沙箱。** 受限命令在发行版内的 `bubblewrap` 中运行。缺少 `bubblewrap` 时命令直接失败,不会脱箱运行。
22
+ - **按会话生效。** 打开发行版文件夹的会话获得发行版环境;打开 Windows 文件夹的会话保留 Windows 工具。两者可同时运行在一个 harness 进程中。
23
+ - **与 GUI 集成。** 文件夹选择器列出已安装的发行版,终端在发行版内打开 shell,模型能看到监听端口。
26
24
 
27
- 第 3 步应该能看到一行 `# == dsh-plugin-wsl-env`——那就是插件的配置已经进了你的 profile。插件自带配置,第 2 步会自动接好,基本安装不需要手动改任何文件。
25
+ ### 为什么是 Linux,而不是 PowerShell
28
26
 
29
- **第一条命令之前,先给子系统安装 bubblewrap。** 每条命令都运行在 bubblewrap 沙箱内(默认还包在会话级 systemd scope 里),而多数子系统不预装它。缺失时,命令直接失败,不会脱箱运行:
27
+ 多数编程 agent 的训练材料以 Linux 为主,开源工具链也大多先在 Linux 上构建和测试。让 agent 在 Windows shell 里工作,等于要求它在每次调用时做一层翻译:
30
28
 
31
- ```powershell
32
- wsl.exe -d <子系统> -u root -- apt-get install -y bubblewrap # Debian/Ubuntu
33
- ```
29
+ - **训练先验。** 示例、CI 脚本、Dockerfile 和大多数公开答案用的是 POSIX shell 和 GNU 工具;模型的第一反应是 `bash`,不是 PowerShell。
30
+ - **工具链支持。** Node、Python、Rust、Go、C/C++ 和多数构建系统先在 Linux 上验证。Windows 支持往往是移植,引号、路径、换行和文件锁都不一样。
31
+ - **Shell 语义。** PowerShell 和 `cmd` 在引号、转义、管道、环境变量、退出码和路径分隔符上都与 POSIX 不同。混用两套语法,很容易产生看起来像模型犯的低级错误。
32
+ - **环境漂移。** Windows 侧的 PowerShell 版本、执行策略、PATH 和已装工具因人而异;Linux 环境更容易复现和容器化。
33
+
34
+ 结果是更多失败的命令、更多重试,以及更多时间花在语法而不是任务上。生成的代码也一样:shell 片段、构建命令和 CI 风格的一行命令通常按 POSIX 编写,发行版才是它们原样运行的地方。真实的 Linux 登录 shell 去掉了这层翻译。现代 PowerShell 和 Windows 原生工具在缩小差距,但默认先验和多数项目脚本仍然假设 POSIX。
34
35
 
35
- 在插件检出目录里,`pnpm run bootstrap -- <子系统>` 检查插件用到的四个工具(bubblewrap、ripgrep、git、inotify-tools),并按子系统的包管理器打印安装命令;加 `--install` 直接执行。若跳过此步,会话打开时会报告缺失的包及确切的安装命令。
36
+ 本插件把 GUI 留在 Windows,只把执行环境移进发行版,让 agent 得到它的训练和工具链都假设的那个 shell。
36
37
 
37
- **默认 preset 还要求 systemd。** 命令在会话级的 systemd user scope 内执行(后台 dev server 存活、`/tmp` 共享、`ps` 能看到整个会话)。在 `/etc/wsl.conf` 里设 `[boot] systemd=true`,重启 WSL,并执行一次 `loginctl enable-linger "$USER"`。缺任何一项,会话会带修复命令拒绝;在 preset 的 `wsl-shell` 上设 `sessionSandbox: false` 可回到每命令沙箱。
38
+ ## 方案对比
38
39
 
39
- 然后在 GUI 里打开一个 `\\wsl.localhost\<子系统>\...` 下的文件夹。选择器会列出每个已安装的子系统,**New terminal** 开的也是子系统里的 shell。
40
+ | | 桌面远程(VS Code、ZCode) | 本插件 |
41
+ |---|---|---|
42
+ | 发行版内 | 辅助服务 | 一个缓存的 agent 可执行文件 |
43
+ | 网络 | 监听端口 | 无 |
44
+ | 命令沙箱 | 无 | `bubblewrap` |
45
+ | 卸载 | 删除服务 | 删除缓存 |
40
46
 
41
- 卸载:`dsh plugin --profile wsl remove dsh-plugin-wsl-env`。升级:再执行一次同样的 `add` 命令。
47
+ 桌面远程重连更快、更成熟。如果 CLI agent 能覆盖你的工作,直接装进 WSL 更简单。本插件面向的场景是:harness 留在 Windows,同时要求命令有沙箱。
42
48
 
43
- > **改了 `lib/` 下的文件?先同步,再重启应用。** 应用加载的是 Windows 侧那份代码副本,所以先跑 `pnpm run sync:windows`。不同步就重启,加载的还是旧代码;运行中的进程还会缓存模块。见[开发](#开发)。
49
+ ## 要求
44
50
 
45
- ## 使用
51
+ - Windows 10 或 11,启用 WSL 2,并至少有一个发行版。
52
+ - 发行版内安装 `bubblewrap`,必需。
53
+ - 默认的会话沙箱需要 systemd:在 `/etc/wsl.conf` 设置 `[boot] systemd=true`,重启 WSL,然后执行一次 `loginctl enable-linger "$USER"`。没有 systemd 时,在 `wsl-shell` 和 `wsl-fs` 上都设置 `sessionSandbox: false`。
54
+ - 可选:`ripgrep` 用于搜索,`git` 用于变更快照,`inotify-tools` 用于文件监视。
46
55
 
47
- 把一个子系统文件夹作为工作区打开——比如 `\\wsl.localhost\ubuntu\home\you\project`——然后问模型:"我现在在什么内核上,`/etc/os-release` 里写了什么?" 它会在子系统里执行 `uname -r` 并读取这个文件。不复制文件,也不经过 `/mnt/c`。
56
+ 发行版内不需要 Node.js。
48
57
 
49
- - **子系统文件夹自动获得子系统环境。** 会话一打开子系统文件夹,就会被自动配上 WSL 环境。第一条命令就已经是对的。
50
- - **命令在你期望的地方运行。** 命令在子系统里、用你自己的登录 shell 执行——所以你的 `PATH`、`nvm`、`cargo`、`pyenv` 和 rc 配置全部生效。shell 用的是子系统配置的那个,不是写死的 bash。
51
- - **文件就是真实的文件。** `/home/you/x` 和 `\\wsl.localhost\ubuntu\home\you\x` 是同一个文件。`/mnt/c/...` 照常通向 Windows 磁盘。
52
- - **终端也是。** 右侧栏 → *New terminal*:子系统里的 shell,落在会话所在目录。
53
- - **端口可见性。** 模型能看到子系统内正在监听的端口(约每 10 秒刷新),并给出它启动的 dev server 的确切地址。WSL2 将 localhost 转发给 Windows,浏览器直接可访问。
54
- - **权限和 Linux 主机一致。** 权限选择器有三档:`read-only`、`workspace-write`(默认)和 `danger-full-access`。有操作被拒绝时,模型会收到一次"用刚好够用的权限重试"的提议。只有 `danger-full-access` 是不带沙箱运行的。
58
+ ## 安装
55
59
 
56
- ## 方案对比
60
+ 在 Windows 终端执行:
57
61
 
58
- Windows 上的编程工具要在 WSL 项目上干活,一共三条路:
62
+ ```powershell
63
+ dsh wsl --from-default-profile web --dump-config # 创建 profile
64
+ dsh plugin --profile wsl add dsh-plugin-wsl-env # 安装插件及其配置
65
+ dsh --profile wsl --dump-config # 检查:输出 "# == dsh-plugin-wsl-env"
66
+ dsh --profile wsl # 启动
67
+ ```
59
68
 
60
- 1. **把工具装进 WSL。** Codex CLI、Claude Code、ZCode CLI 的官方建议都是这条,而且没错——如果一款 CLI 能覆盖你的工作,它就是最简单的路。前提是整个工具都能搬进 WSL;DeepSeek Harness 搬不进去,所以只剩下面两条。
61
- 2. **桌面应用远程连进 WSL。** VS Code(Remote-WSL,开源镜像 [open-remote-wsl](https://github.com/jeanp413/open-remote-wsl))和 [ZCode](https://github.com/zai-org/ZCode) 这类 agent 桌面端把应用留在 Windows,往子系统里装一个服务程序。很成熟。代价:服务目录(`~/.vscode-server`、`~/.zcode/server`)、一个端口,命令无沙箱。
62
- 3. **本插件。** Harness 留在 Windows。子系统内不安装任何内容、不监听端口,命令运行在 bubblewrap 沙箱内。缺失时,会话打开即报告缺失项与安装命令。
69
+ 第一条命令之前安装 `bubblewrap`:
63
70
 
64
- | | 第二条(桌面远程) | 第三条(本插件) |
65
- |---|---|---|
66
- | 往子系统里装了什么 | 一个服务目录 | 什么都不装 |
67
- | 是否监听端口 | 是 | 否 |
68
- | 命令沙箱 | 无 | bubblewrap + systemd 会话 scope |
69
- | 卸载之后 | 手动删除服务目录 | 无残留 |
70
- | WSL 升级出了问题 | 服务可能需要修复 | 下一条命令自动恢复 |
71
+ ```powershell
72
+ wsl.exe -d <发行版> -u root -- apt-get install -y bubblewrap
73
+ ```
71
74
 
72
- **各方案的取舍。** 桌面远程重开工作区是即时的——其服务常驻运行;VS Code 的远程有多年生产打磨。装进 WSL 的 CLI 不需要任何额外机制。本插件只针对一种场景:DeepSeek Harness 运行在 Windows 上,同时要求 WSL 内的工作在沙箱中执行。
75
+ 在插件检出目录中,`pnpm run bootstrap -- <发行版>` 检查四个发行版工具,并按包管理器打印安装命令;加 `--install` 直接执行。
73
76
 
74
- ## 配置
77
+ 升级:再次执行 `add` 命令。卸载:`dsh plugin --profile wsl remove dsh-plugin-wsl-env`;之后可删除 `~/.cache/dsh-wsl`。如果你为了恢复预览在 profile 里加过 `workspace-files-wsl` 的临时覆盖,升级后请删掉:插件不再挂载该行。
75
78
 
76
- 每个设置都挂在一个有名字的"行"上。想改哪个,就在 `$DSH_HOME/profiles/<名字>/cordis.patch.yml` 里加一行同 id 的覆盖。值得知道的键:
79
+ ## 使用
77
80
 
78
- | 行 | 键 | 默认值 | 说明 |
79
- |---|---|---|---|
80
- | `wsl-shell` | `distro` | `''` | 用哪个子系统;空表示 WSL 的默认子系统 |
81
- | | `sandbox` | `true` | 命令跑在 `bubblewrap` 沙箱里;`false` 关掉沙箱 |
82
- | | `sessionSandbox` | `true` | 会话内的命令跑在同一个常驻 systemd user scope 里:后台 dev server 跨命令存活、`/tmp` 共享、`ps` 能看到整个会话。需要 systemd 为 PID 1、user manager 可达、`Linger` 打开;预检失败会带修复命令拒绝。设 `false` 回到每命令沙箱 |
83
- | | `agentBackend` | `auto` | 会话 runtime 的 in-distro agent:`auto` 在存在已校验来源(打包二进制或 `DSH_WSL_AGENT_NATIVE` 开发覆盖)时用 native,否则用 shell;`native` 准备失败时让首条命令失败而不是静默回退;`shell` 强制 shell。命令和受限写入共用该选择。native 是默认后端,shell agent 是回退 |
84
- | | `maskWindowsDrive` | `false` | 让沙箱里的命令看不到 `/mnt`,收窄互操作这个洞(只是收窄,关不死,见[沙箱](#沙箱)) |
85
- | `wsl-fs` | `distro` | `''` | 同上 |
86
- | | `restrictToDistro` | `true` | 拒绝属于**别的子系统**的路径(`/mnt/c` 属于本子系统,不受影响)。拒绝码是 `FS_OUTSIDE_DISTRO`,放宽权限也解不开 |
87
- | | `sandbox` | `true` | 文件写入按同一份策略检查 |
88
- | | `sessionSandbox` | `true` | 受限写入跑在与命令相同的会话 runtime 里(同一个真实 `/tmp`、同一份 workspace mount);需要同样的 systemd/linger 配置;`false` 回到每策略受限 resident |
89
- | | `substrate` | `agent` | 文件操作如何到达子系统。只剩一种:子系统内的 agent,读写直接落在子系统自己的文件系统上(原生符号链接与权限位)。旧 `"share"` 选项走 9p 共享,启动时即被拒绝 |
90
- | `directory-picker-wsl` | `includeHostHome` | `true` | 选择器里同时列出 Windows 家目录 |
91
- | `subprocess-wsl` | `distro` | `''` | GUI 终端开在哪个子系统 |
81
+ 打开一个发行版文件夹作为工作区,例如 `\\wsl.localhost\ubuntu\home\you\project`。
92
82
 
93
- `wsl-shell` 和 `wsl-fs` 这两行**按 id 覆盖不到**:它们嵌在 `wsl` preset 自己的配置里(preset 是 dsh 能发给会话的一套命名环境),在自己的覆盖文件里写 `- id: wsl-shell` 不会生效——loader 只会警告 `patch: entry "wsl-shell" not found`,实际值不变。要改,就把整个 `preset-wsl` 行复制过去改写;配方见 [docs/CONFIGURATION.md](CONFIGURATION.md#overriding-the-rows-inside-preset-wsl)。
83
+ - 打开哪个发行版的文件夹,就在哪个发行版执行——即使它不是 WSL 的默认发行版。
84
+ - 命令在发行版登录 shell 中运行。`/home/you/x` 与 `\\wsl.localhost\ubuntu\home\you\x` 是同一个文件。
85
+ - **New terminal** 在同一发行版、同一目录打开 shell。
86
+ - 模型能看到监听端口,并返回 dev server 的 URL。WSL 2 会把 localhost 转发到 Windows。
87
+ - 权限选择器:`read-only`、`workspace-write`(默认)、`danger-full-access`。只有 `danger-full-access` 不经过沙箱。
94
88
 
95
- [`cordis.patch.yml`](../cordis.patch.yml) 是每个随包值的注释参考。[docs/CONFIGURATION.md](CONFIGURATION.md) 列出其余键(`shell`、`loginShell`、`cwd`、`timeoutMs`、`preferredDistro`、`maxEntries` 等);[examples/profile.cordis.patch.yml](../examples/profile.cordis.patch.yml) 是一份可以照抄修改的本机配置。
89
+ ## 配置
96
90
 
97
- ## 配方
91
+ 配置写在 `$DSH_HOME/profiles/<名字>/cordis.patch.yml` 的命名行上。主要设置:
98
92
 
99
- - **让子系统里的 git 用 Windows 的已存凭据**:`git config --global credential.helper "/mnt/c/Program\ Files/Git/mingw64/bin/git-credential-manager.exe"`(路径按你 Windows 侧 Git 的安装位置调整)。设置后,子系统里 `git push` 用的就是 Windows 保存的那份凭据,不用再输密码。
100
- - **项目放在子系统的磁盘上。** 模型操作的是子系统自己磁盘上的 Linux 路径(`/home/...`),快。它也能通过 `/mnt/c` 访问 Windows 文件,但那条桥在大量小文件时明显慢。`pnpm run bootstrap <子系统>` 会告诉你 ripgrep、git、inotifywait(分别用于搜索和文件监视)装没装。
101
- - **环境变量**:WSL 只转发 `WSLENV` 里点名的变量。本插件转发自己的 `DSH_*` 值,其中两个装着 Windows 路径的(`DSH_HOME`、`DSH_PROFILE_DIR`)会被翻译成 Linux 路径。你的 `PATH` 永远不转发——转发了会盖住子系统自己的 PATH。
93
+ | 行 | 键 | 默认值 | 作用 |
94
+ |---|---|---|---|
95
+ | `wsl-shell` | `distro` | `''` | 发行版模式;空/`auto` 跟随工作区,`default` 强制 WSL 默认 |
96
+ | | `sandbox` | `true` | 用 `bubblewrap` 约束命令 |
97
+ | | `sessionSandbox` | `true` | 会话命令跑在同一个 systemd user scope 中 |
98
+ | | `agentBackend` | `auto` | 发行版内 agent:`auto`、`native` 或 `shell` |
99
+ | | `maskWindowsDrive` | `false` | 让受限命令看不到 `/mnt` |
100
+ | `wsl-fs` | `distro` | `''` | 发行版模式;空/`auto` 跟随每个目标路径 |
101
+ | | `restrictToDistro` | `true` | 固定发行版(`default` 或名字)时拒绝属于其他发行版的路径 |
102
+ | | `sandbox` | `true` | 按策略检查受限写入 |
103
+ | | `sessionSandbox` | `true` | 受限写入跑在会话 runtime 中 |
104
+ | | `substrate` | `agent` | 发行版内 agent;`share` 在启动时被拒绝 |
105
+ | `directory-picker-wsl` | `includeHostHome` | `true` | 同时列出 Windows 家目录 |
106
+ | `subprocess-wsl` | `distro` | `''` | 发行版模式;空/`auto` 跟随启动目录 |
107
+
108
+ `wsl-shell` 和 `wsl-fs` 位于随包的 `wsl` preset 中,按 id 覆盖无效。要改就覆盖整个 `preset-wsl` 行;配方和其余键见 [docs/CONFIGURATION.md](CONFIGURATION.md)。[`cordis.patch.yml`](../cordis.patch.yml) 是随包值的注释参考。
102
109
 
103
110
  ## 架构
104
111
 
105
- 一个 DSH 进程可以同时服务两类会话:工作区在 Windows 目录的,和工作区在子系统里的。插件的部件分两层放置,原因很简单——有的东西属于某一个会话,有的属于整个应用:
112
+ 一个 harness 进程可同时服务 Windows 文件夹会话和发行版文件夹会话。服务分两层挂载:
106
113
 
107
114
  ```text
108
- 组合层(composition,每个进程一份)
109
- ├─ subprocess-wsl GUI 终端的执行环境
110
- │ 子系统目录会话 → 子系统 shell,落在会话的 Linux 目录
111
- │ Windows 目录会话 → powershell.exe,落在会话自己的目录
112
- ├─ directory-picker-wsl 在 Windows 家目录旁列出已安装的子系统
113
- ├─ workspace-files-wsl 子系统工作区的 GUI 文件树与预览,由子系统内部提供
114
- ├─ fs-routing 根级文件系统,按路径坐标分流
115
- │ 子系统 UNC → 常驻助手;盘符路径 → 宿主原生后端
116
- ├─ wsl-shell-env 向模型暴露 DSH_WSL_DISTRO / _SHELL / _HOME / _PORTS
117
- └─ auto-preset 会话打开子系统目录时,自动绑定 wsl preset
118
-
119
- preset-wsl(wsl agent preset;这个环境里的服务相互独立)
120
- ├─ wsl-shell ctx.shell — wsl.exe --exec <登录 shell>,在子系统内经 bubblewrap 约束
121
- └─ wsl-fs ctx.fs — 子系统的真实文件,经常驻助手落在 ext4
115
+ 应用层(每个进程一份)
116
+ ├─ directory-picker-wsl、workspace-files、subprocess-wsl
117
+ ├─ fs-routing:根级文件系统,按路径分流
118
+ └─ auto-preset:会话打开发行版文件夹时绑定 wsl preset
119
+
120
+ wsl preset(每个会话)
121
+ ├─ wsl-shell:命令
122
+ └─ wsl-fs:文件操作
122
123
  ```
123
124
 
124
- **为什么分两层。** `wsl-shell` 和 `wsl-fs` 是随会话变化的两件事,所以放在 `wsl` preset 里:会话打开子系统目录时,`auto-preset` 给它绑上这个 preset;打开 Windows 目录的会话继续用原生 Windows 工具。一个进程,每个会话各有各的环境。GUI 终端是例外:它读的是应用层的配置,永远看不到 preset,所以 `subprocess-wsl` 挂在应用层。GUI 文件树和根级文件系统正好反过来——每个会话都用它们,单个 preset 拥有不了——所以 `workspace-files-wsl` 和 `fs-routing` 也挂在应用层。它们替换了随包的两行;根级路由的设计见 [docs/root-fs-routing.md](root-fs-routing.md)。
125
-
126
- **命令的执行路径。** 一个会话有一个常驻 agent,运行在 systemd user scope 内。默认它是 native `dsh-wsl-agent`——按 distro 架构选择的包内静态 musl 二进制,由 `agentBackend: auto` 选中;二进制缺失或 copy/digest/exec 校验失败时回退到 runtime 的 shell agent,`agentBackend: shell` 强制 shell,`native` 则拒绝回退。agent 在你的登录 shell 中、bubblewrap mount profile 内执行每条命令。scope 比命令活得久,所以后台 dev server 存活、`/tmp` 是子系统真实 `/tmp`、`ps` 能看到整个会话。设 `sessionSandbox: false` 时,agent 仍是每子系统一个、每条命令一套 bwrap profile(`--unshare-pid`、临时 `/tmp`),后台进程不存活,且不使用 native 后端。agent 不可用时(或 `agent: false`),改走一次性 `wsl.exe`——结果一致、略慢、仍是每命令隔离。
127
-
128
- **文件操作的执行路径。** 读、写、搜索同样发生在子系统内部:使用子系统自己的磁盘与工具。子系统工作区的搜索运行子系统内的 `rg`,不使用 Windows 侧副本走 9p 共享。文件写入与命令沙箱使用同一份策略检查;`wsl-fs` 的 `sessionSandbox` 打开时(默认),受限写入跑在与命令相同的会话 runtime 里(同一个 native/shell agent),看到相同的 `/tmp` 与 workspace mount。读操作走共享的 native 读 agent(A7;包内二进制,shell 回退):16 个并发读实测约 2.4 ms,此前 shell 为 342–614 ms。
125
+ - `wsl-shell` 通过 systemd user scope 内的一个常驻 agent 执行命令。agent 默认是发行版架构(`x86_64` 或 `aarch64`)对应的包内静态 musl `dsh-wsl-agent`,shell agent 作为回退。每条命令在登录 shell 中、`bubblewrap` mount profile 下运行。scope 比命令活得久,所以后台服务存活,`ps` 能看到整个会话。设置 `sessionSandbox: false` 后,agent 仍按发行版常驻,但每条命令使用自己的 profile。
126
+ - `wsl-fs` 通过同一个发行版内 agent 提供读写。受限写入运行在 mount profile 与策略一致的常驻 agent 中,由内核执行写入边界。没有宿主机侧的文件回退:agent 不可用时文件操作直接失败。
127
+ - GUI 文件树和预览使用随包的 `workspace-files` 行,而不是插件的替换行:它的客户端半边是浏览器的 `file` 资源提供者。WSL 路径经根级路由读取,仍在发行版内,但会多几次 agent 往返。
128
+ - 应用层服务按路径分流:发行版 UNC 路径走发行版,Windows 盘符路径留在宿主。见 [docs/root-fs-routing.md](root-fs-routing.md)。
129
129
 
130
130
  ## 沙箱
131
131
 
132
- 命令跑在子系统内的 `bubblewrap` 沙箱里,文件写入按同一份策略检查。Windows 自带的沙箱在这里用不上:它的受限账户根本连不到 WSL。
133
-
134
- | 模式 | 子系统里的命令可以做什么 |
132
+ | 模式 | 命令可以做什么 |
135
133
  |---|---|
136
- | `read-only` | 整个子系统只读;会挂一个新建的 `/dev`,`/dev/null` 和 `/dev/shm` 可用,其余不可写 |
137
- | `workspace-write` | 上面全部,加上会话工作区可写;`/tmp` 在会话 runtime 里是子系统真实 `/tmp`(`sessionSandbox: false` 时每命令 profile 挂临时 tmpfs) |
138
- | `danger-full-access` | 不加沙箱;用于你批准的放宽权限请求 |
134
+ | `read-only` | 读整个发行版;挂载新的 `/dev`,其余不可写 |
135
+ | `workspace-write` | 上面的权限,加上会话工作区;会话 runtime 下 `/tmp` 是发行版真实 `/tmp` |
136
+ | `danger-full-access` | 不经过沙箱 |
139
137
 
140
- **bubblewrap 是必需项,缺了就宁可不做。** 没有它,每条受限命令都报 `SANDBOX_UNAVAILABLE`——命令不会脱着沙箱运行。想关掉沙箱,在管这件事的行上设 `sandbox: false`:命令归 `wsl-shell`,文件写入归 `wsl-fs`,根级写入归顶层的 `fs-routing`(应用自己用的那套文件系统)。(前两行要经 `preset-wsl` 改,见[配置](#配置)。)关掉后,模型会如实收到"这些操作没有沙箱"的说明。
138
+ 设置 `sessionSandbox: false` 时,每条命令使用私有的 `/tmp`,而不是发行版的真实 `/tmp`。
141
139
 
142
- **已知边界。** 子系统内的命令仍可启动 Windows 程序(`/mnt/c/.../*.exe`),bubblewrap 不覆盖 Windows 进程。`pnpm run probe:sandbox` 会在你的机器上演示这条边界。在三个行上设 `maskWindowsDrive: true` 能收窄它:`/mnt` 会从命令的视野里消失,Windows 磁盘的文件读不到、程序启动不了。但并未完全关闭——命令仍可将 Windows 程序复制进工作区后运行——因此上报仍为 `partial`。彻底关闭的方法在子系统层面:`wsl.conf` 里设 `[interop] enabled=false`(见 [docs/CONFIGURATION.md](CONFIGURATION.md))。
140
+ `bubblewrap` 是必需项。缺失或损坏时,受限命令报 `SANDBOX_UNAVAILABLE`,不会运行。在对应的行设置 `sandbox: false` 可关闭沙箱;模型会收到"该操作无沙箱"的说明。
143
141
 
144
- 设计见 [docs/ARCHITECTURE.md](ARCHITECTURE.md#sandbox),插件不做的事情见 [docs/LIMITATIONS.md](LIMITATIONS.md)。
142
+ 隔离上报为 `partial`。发行版内的命令仍可通过 `/mnt/c` 启动 Windows 程序,`bubblewrap` 无法控制 Windows 进程。`maskWindowsDrive: true` 让受限视图看不到 `/mnt`,能收窄这个洞但不能关死。彻底关闭要在发行版层面设置 `[interop] enabled=false`(`/etc/wsl.conf`)。见 [docs/ARCHITECTURE.md](ARCHITECTURE.md#sandbox) 和 [SECURITY.md](../SECURITY.md)。
145
143
 
146
144
  ## 常见问题
147
145
 
148
- | 现象 | 原因 | 处理 |
149
- |---|---|---|
150
- | 每条命令都报 `SANDBOX_UNAVAILABLE` | 子系统里没有 `bubblewrap`,或者装了但坏的——错误消息会告诉你是哪种、该怎么办 | 按错误消息里的指引处理,或 `pnpm run bootstrap -- <子系统> --install`,或在 `wsl-shell`(命令)与 `wsl-fs`(写入)上都设 `sandbox: false`(根级写入的 `fs-routing` 行另有一份;前两行怎么改见[配置](#配置)) |
151
- | 命令或写入在会话目录外被拒绝 | 正常现象:`workspace-write` 只允许写会话目录 | 接受工具给出的放宽权限提示,或把会话开在你需要的目录上 |
152
- | 会话目录内的写入也被拒绝 | 会话处于 `read-only` 模式 | 切换权限选择器 |
153
- | 重复执行 `dsh plugin add` 没有任何输出,`--dump-config` 也没变化 | 插件已经装过了,没有新东西可报告——这是成功,不是失败 | 不用做任何事:层已经就位,`dsh --profile wsl --dump-config` 仍能看到 `# == dsh-plugin-wsl-env` |
154
- | 终端打开后还是 `cmd.exe` | 这一层里的 `terminal-controller` 行没有生效 | 用 `dsh --profile wsl --dump-config` 确认能看到 `shell: { path: wsl.exe, name: WSL }` |
155
- | 改了 `lib/` 但不生效 | 应用跑的是 Windows 侧那份代码副本,运行中的进程还会缓存模块 | 先 `pnpm run sync:windows` 更新副本,再重启应用 |
156
- | 用 `link:\\wsl.localhost\...` 安装后符号链接是坏的 | pnpm 无法链接 UNC 路径 | 改链接 Windows 路径;在子系统里开发请用运行时镜像,见[开发](#开发) |
157
- | `glob`/`grep` 很慢 | 子系统里没装 `rg`;Windows 目录的搜索不受影响 | 运行 `pnpm run bootstrap -- <子系统> --install`(装 ripgrep);子系统工作区的搜索永远用子系统里的 rg,不走慢桥 |
158
- | 终端活动显示 `unknown` | 仅在子系统内 agent 暂时不可用时出现——distro 终端是从子系统内部观察的(`/proc` 里扫 `DSH_TERMINAL_ID` 标记:只有 shell 是 `idle`,在跑任何命令是 `busy`) | 确认子系统在运行;空闲终端会在控制器的空闲超时(默认 2 小时)后自动关闭,`terminalIdleReclaim: false` 可以改成手动关 |
159
- | 结果里出现 `FS_*` 码 | 码本身说明了是谁拒绝的、怎么解除 | 见 [docs/ARCHITECTURE.md](ARCHITECTURE.md#error-codes) 的错误码表 |
146
+ | 现象 | 处理 |
147
+ |---|---|
148
+ | 每条命令都报 `SANDBOX_UNAVAILABLE` | 在发行版内安装 `bubblewrap`,或设置 `sandbox: false` |
149
+ | 会话在第一条命令前就被拒绝 | 启用 systemd 和 linger,或在 `wsl-shell`、`wsl-fs` 上设置 `sessionSandbox: false` |
150
+ | 发行版内 `glob`、`grep` 很慢 | 运行 `pnpm run bootstrap -- <发行版> --install` 安装 `ripgrep` |
151
+ | `dsh plugin add` 没有输出 | 插件已安装;这是成功 |
152
+ | 改了 `lib/` 但不生效 | 运行 `pnpm run sync:windows`,然后重启应用 |
153
+
154
+ 更多错误码和拒绝原因见 [docs/ARCHITECTURE.md](ARCHITECTURE.md#error-codes),插件不做的事见 [docs/LIMITATIONS.md](LIMITATIONS.md)。
160
155
 
161
156
  ## 开发
162
157
 
163
158
  ```bash
164
- pnpm run build # 从 src/ 生成 lib/(产物提交进仓,改了 src/ 必须重新构建)
165
- pnpm test # 风格、语法、构建一致性、类型检查、单元测试
166
- pnpm run sync:windows # 把检出复制到应用加载的 Windows 侧副本(改 lib/ 后必跑,再重启应用)
167
- pnpm run test:coverage # 带覆盖率阈值的单元测试(需 Node 22.8+)
168
- pnpm run diagnose # 只读诊断报告(可直接贴进 issue):版本、工具、bwrap 探测
169
- pnpm run bootstrap -- <子系统> # 检查四个子系统侧工具;加 --install 安装缺失项
170
- pnpm run probe:sandbox # 在子系统里实测 bubblewrap 能管什么、管不了什么
171
- pnpm run probe # 文件系统探针,需要真实子系统(仅 Windows + WSL)
172
- pnpm run probe:sandbox-shell # 启动真实 harness,驱动受限执行器
173
- pnpm run probe:terminal # 通过终端 provider 打开一个 PTY
174
- pnpm run probe:substrate # 用真实 wsl.exe 驱动文件助手(在子系统内运行)
175
- pnpm run probe:watch # 在真实目录上装子系统内监视器(在子系统内运行)
176
- pnpm run probe:agent # 常驻路径与回退路径的对比(在子系统内运行)
177
- pnpm run probe:exec # agent 执行:超时、杀停、cwd 失败(在子系统内运行)
178
- pnpm run probe:missing-wsl # 启动一个 wslPath 无法启动的 profile(仅 Windows + WSL)
179
- pnpm run probe:picker # 列出选择器的根级、拒绝路径与上限(仅 Windows + WSL)
180
- pnpm run probe:mode # 哪些 POSIX 权限信息能穿过共享层(需 Windows node 与已装的 harness,不启动 profile)
181
- pnpm run probe:sandbox-off # 验证 sandbox: false 确实关掉两侧沙箱(仅 Windows + WSL)
159
+ pnpm install # 开发依赖
160
+ pnpm run build # 从 src/ 生成 lib/(lib/ 提交进仓)
161
+ pnpm test # 风格、语法、构建一致性、类型、单元测试
162
+ pnpm run test:coverage # 带覆盖率阈值的单元测试
163
+ pnpm run sync:windows # 更新应用加载的 Windows 侧副本
182
164
  ```
183
165
 
184
- 应用跑的是 Windows 侧的代码副本,不是你的检出。改了 `lib/` 下任何文件,先 `pnpm run sync:windows` 再重启应用——否则重启加载的还是旧代码。`pnpm run build` 负责从 `src/` 生成 `lib/`;产物提交进仓,`pnpm test` 会检查两者一致。
185
-
186
- 首次运行前 `pnpm install` 一次装好开发依赖。测试用到钉版的 `@deepseek-ai/*` 包;插件本身发布时不带任何依赖。CI 在 Linux 和 Windows 上用 Node 22、24 跑测试,Node 24 另跑覆盖率。
166
+ 工具链跟随 harness 宿主:Node `^22.19.0 || >=24.0.0`。`src/` 是 TypeScript,`lib/` 是生成并提交的产物,必须保持同步。应用加载的是 Windows 侧的 `lib/` 副本,所以构建后要运行 `sync:windows` 并重启应用。
187
167
 
188
- [docs/ARCHITECTURE.md](ARCHITECTURE.md) 有文件布局、挂载方式和沙箱设计;[CONTRIBUTING.md](../CONTRIBUTING.md) 有开发循环、镜像、完整质量门清单和已验证内容。
168
+ 真机探针仅在 Windows + WSL 下运行。见 [CONTRIBUTING.md](../CONTRIBUTING.md) 和 `test/probe/`。
189
169
 
190
170
  ## 文档
191
171
 
192
172
  | 文档 | 内容 |
193
173
  |---|---|
194
- | [docs/CONFIGURATION.md](CONFIGURATION.md) | 每个配置键,以及如何覆盖 |
195
- | [docs/ARCHITECTURE.md](ARCHITECTURE.md) | provider、挂载方式、路径坐标、沙箱、测试分层、文件布局 |
196
- | [docs/ROADMAP.md](ROADMAP.md) | 路线图:已完成、下一步(A7 native 共享读、A8 aarch64)、已推迟(v6) |
197
- | [docs/LIMITATIONS.md](LIMITATIONS.md) | 插件不做的事情,以及原因 |
198
- | [docs/VALUE.zh.md](VALUE.zh.md) | 本插件的价值、代价,以及什么情况下不该用它 |
199
- | [CONTRIBUTING.md](../CONTRIBUTING.md) | 开发循环、质量门、约定、已验证内容 |
200
- | [SECURITY.md](../SECURITY.md) | 如何私下上报漏洞 |
201
- | [SUPPORT.md](../SUPPORT.md) | 支持范围,以及去哪里提问 |
174
+ | [docs/CONFIGURATION.md](CONFIGURATION.md) | 所有配置键及覆盖方法 |
175
+ | [docs/ARCHITECTURE.md](ARCHITECTURE.md) | 服务、路径路由、沙箱、错误码 |
176
+ | [docs/LIMITATIONS.md](LIMITATIONS.md) | 插件不做的事 |
177
+ | [docs/ROADMAP.md](ROADMAP.md) | 当前计划和推迟项 |
178
+ | [docs/ROADMAP-1.0.md](ROADMAP-1.0.md) | 1.0 与生态优化计划 |
179
+ | [docs/VALUE.zh.md](VALUE.zh.md) | 价值与取舍 |
180
+ | [CONTRIBUTING.md](../CONTRIBUTING.md) | 开发循环、质量门、探针 |
181
+ | [SECURITY.md](../SECURITY.md) | 私下上报漏洞 |
182
+ | [SUPPORT.md](../SUPPORT.md) | 支持范围与提问渠道 |
202
183
  | [docs/RELEASING.md](RELEASING.md) | 发布检查单 |
203
- | [docs/archive/README.en.md](archive/README.en.md) | 归档工程记录的英文索引 |
204
184
 
205
185
  ## 许可
206
186