dsh-plugin-wsl-env 0.17.0 → 0.17.1
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.
- package/docs/CONFIGURATION.md +2 -0
- package/docs/CONFIGURATION.zh.md +288 -0
- package/docs/LIMITATIONS.md +129 -336
- package/docs/LIMITATIONS.zh.md +161 -0
- package/docs/QUICKSTART.md +2 -0
- package/docs/QUICKSTART.zh.md +168 -0
- package/package.json +5 -2
package/docs/CONFIGURATION.md
CHANGED
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
# Configuration
|
|
2
|
+
|
|
3
|
+
_本文是 [CONFIGURATION](CONFIGURATION.md) 的中文译本,以英文版为准。_
|
|
4
|
+
|
|
5
|
+
## Profile patch
|
|
6
|
+
|
|
7
|
+
profile 设置存放在 `$DSH_HOME/profiles/<name>/cordis.patch.yml`。可以用
|
|
8
|
+
[`examples/profile.cordis.patch.yml`](../examples/profile.cordis.patch.yml)
|
|
9
|
+
作为起点。随包的
|
|
10
|
+
[`cordis.patch.yml`](../cordis.patch.yml) 列出了本插件附带的行和值。
|
|
11
|
+
|
|
12
|
+
profile 层可以按 `id` 指向顶层行。这一层的插件行包括 `fs-routing`、
|
|
13
|
+
`directory-picker-wsl`、`subprocess-wsl`、`wsl-shell-env`、`wsl-preflight`、
|
|
14
|
+
`auto-preset` 和 `preset-wsl`。`wsl-shell` 和 `wsl-fs`
|
|
15
|
+
两行嵌套在 `preset-wsl.config.plugins` 内;直接按这两个 id 配置的
|
|
16
|
+
profile 条目会被忽略,并产生 `patch: entry <id> not found` 警告。
|
|
17
|
+
|
|
18
|
+
一行的 `config` 是整体替换。如果你想保留随包行中的非默认值,
|
|
19
|
+
必须把它们一并写入。这对 `preset-wsl` 尤其重要:
|
|
20
|
+
它的 config 包含完整的 preset 及其嵌套插件。
|
|
21
|
+
|
|
22
|
+
在下表中,**shipped** 表示该值在随包行中被显式设置。
|
|
23
|
+
其他值是 schema 默认值。省略的键使用 schema 默认值。
|
|
24
|
+
|
|
25
|
+
## 配置 `wsl-shell` 和 `wsl-fs`
|
|
26
|
+
|
|
27
|
+
这两行嵌套在 `preset-wsl` 内。使用辅助脚本修改它们的设置:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pnpm run preset --distro Ubuntu --session-sandbox false
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
支持的修改是 `--distro`、`--session-sandbox`、`--sandbox`、
|
|
34
|
+
`--mask-windows-drive` 和 `--agent-backend`。不带 `--out` 时,辅助脚本
|
|
35
|
+
把完整的 `preset-wsl` 块打印出来,供你手动合并进 profile。
|
|
36
|
+
使用 `pnpm run preset --check <file>` 可以检测直接指向嵌套行的
|
|
37
|
+
profile 层;检测到时会打印应使用的块。`--out` 会整体替换目标文件,
|
|
38
|
+
包括其中的其他覆盖,即使它已经包含 `preset-wsl` 行。
|
|
39
|
+
在不包含该行的文件已存在时,替换会被拒绝,除非传入 `--force`。
|
|
40
|
+
对已有 profile 建议手动合并。
|
|
41
|
+
|
|
42
|
+
要手动编辑 preset,需替换整个 `preset-wsl` 行。它的 config
|
|
43
|
+
不做深度合并:保留 `id`、`order` 以及 `plugins` 中的每一项。
|
|
44
|
+
从 `cordis.patch.yml` 或 `dsh --profile <name> --dump-config`
|
|
45
|
+
复制当前块,再修改嵌套的值。插件升级改变了 preset 后要重新复制。
|
|
46
|
+
|
|
47
|
+
### `wsl-shell`
|
|
48
|
+
|
|
49
|
+
| 键 | 默认 / shipped 值 | 说明 |
|
|
50
|
+
|---|---|---|
|
|
51
|
+
| `distro` | `''`(shipped) | 空或 `auto` 跟随请求工作目录所属的发行版。`default` 选择 WSL 默认发行版;给出名字则把命令固定到该发行版。 |
|
|
52
|
+
| `shell` | `''` | 空则解析发行版用户的登录 shell。设置绝对 Linux 路径可固定一个 shell。 |
|
|
53
|
+
| `loginShell` | `true`(shipped) | 对 POSIX shell 使用登录语义。非 POSIX shell 使用各自的命令形式。 |
|
|
54
|
+
| `agent` | `true` | 使用常驻 agent。不可用时,命令走一次性的 `wsl.exe` 路径。设为 `false` 则总是走该路径。Cwd 与沙箱拒绝会被直接返回。 |
|
|
55
|
+
| `agentBackend` | `auto`(shipped) | `auto` 选择包内 `agent/native/linux-<arch>/dsh-wsl-agent` 的静态 musl 二进制或 `DSH_WSL_AGENT_NATIVE` 覆盖;否则使用 shell agent。`native` 在准备失败时报错;`shell` 强制使用 shell agent。适用于命令、受限写入和共享读取。 |
|
|
56
|
+
| `sessionSandbox` | `false`;shipped 为 `true` | `true` 把会话跑在一个持久的 systemd user scope 中。要求 `agent: true`、systemd 为 PID 1(`/etc/wsl.conf` 中 `[boot] systemd=true`)、一个 user manager、`systemd-run` 和 Linger(`loginctl enable-linger <user>`)。`false` 使用按命令的沙箱。`auto` 在可用时使用 scope,否则按命令约束。结果会报告 `sessionScope: 'session'` 或 `'per-command'`。 |
|
|
57
|
+
| `sandbox` | `true` | 用 `bwrap` 约束命令。`bwrap` 缺失或不可用时关闭(fail closed)。`false` 则不加约束地运行命令。 |
|
|
58
|
+
| `interopVisibility` | 未设置;有效默认值为 `visible` | `visible`、`masked` 或 `disabled`。设置后优先于 `maskWindowsDrive`。见 [Windows interop](#windows-interop)。 |
|
|
59
|
+
| `maskWindowsDrive` | `false` | 对受限命令,在 `/mnt` 上挂载空的 tmpfs。见 [Windows interop](#windows-interop)。 |
|
|
60
|
+
| `cwd` | `''` | 默认工作目录。空则使用发行版用户家目录。 |
|
|
61
|
+
| `timeoutMs` / `maxTimeoutMs` | `120000` / `600000`(shipped) | 默认的单次调用超时,以及一次调用可请求的最大超时。 |
|
|
62
|
+
| `wslPath` | `wsl.exe` | `wsl.exe` 的路径。 |
|
|
63
|
+
| `hostCwd` | 未设置 | `wsl.exe` 进程的 Windows 工作目录。可用时默认为 `%SystemRoot%`。 |
|
|
64
|
+
| `forwardEnv` | `[]` | 额外通过 `WSLENV` 转发的宿主环境变量名。见 [环境变量转发](#environment-forwarding)。 |
|
|
65
|
+
|
|
66
|
+
### `wsl-fs`
|
|
67
|
+
|
|
68
|
+
| 键 | 默认 / shipped 值 | 说明 |
|
|
69
|
+
|---|---|---|
|
|
70
|
+
| `distro` | `''`(shipped) | 空或 `auto` 跟随目标路径所属的发行版。`default` 选择 WSL 默认发行版;给出名字则固定文件系统。 |
|
|
71
|
+
| `restrictToDistro` | `true`(shipped) | 固定发行版时,以 `FS_OUTSIDE_DISTRO` 拒绝属于其他发行版共享的路径。 |
|
|
72
|
+
| `sandbox` | `true` | 按沙箱策略检查写入。`bwrap` 不可用时,受限写入关闭(fail closed)。 |
|
|
73
|
+
| `sessionSandbox` | `false`;shipped 为 `true` | 使用与 `wsl-shell` 相同的会话 runtime。`auto` 在会话 runtime 不可用时回退到按策略约束的常驻 agent。 |
|
|
74
|
+
| `maskWindowsDrive` | `false` | 对受限变更隐藏 `/mnt`。读取不受影响。 |
|
|
75
|
+
| `substrate` | `agent`(shipped) | 文件操作使用发行版内 agent。旧值 `share` 在启动时被拒绝。 |
|
|
76
|
+
| `cwd` | `''` | 相对路径的基准目录。空则使用发行版用户家目录。 |
|
|
77
|
+
| `watchMaxDepth` | `0` | 监视整棵树。正值深度使用有界轮询,可能遗漏边界以下的变更,直到边界之上发生变更触发回调。 |
|
|
78
|
+
| `wslPath` | `wsl.exe` | `wsl.exe` 的路径。 |
|
|
79
|
+
| `diffBasisMaxBytes` | `10485760` | 覆盖写入 diff 每一侧的 UTF-8 字节上限。更大的文件不带 diff 直接写入。 |
|
|
80
|
+
|
|
81
|
+
### `fs-routing`
|
|
82
|
+
|
|
83
|
+
这个顶层行为根级 `ctx.fs` 调用提供服务。它把发行版 UNC 路径
|
|
84
|
+
路由到 agent 支撑的文件系统,把盘符路径路由到宿主文件系统。它的
|
|
85
|
+
发行版设置独立于 `wsl-fs`。
|
|
86
|
+
Loader 在挂载该行之前会校验宿主和发行版设置;
|
|
87
|
+
被 schema 拒绝的值会阻止路由器启动。字段值为 `null`
|
|
88
|
+
时使用其 schema 默认值。
|
|
89
|
+
|
|
90
|
+
| 键 | 默认 / shipped 值 | 说明 |
|
|
91
|
+
|---|---|---|
|
|
92
|
+
| `distro` | `''`(shipped) | 空或 `auto` 把每条 UNC 路径路由到其命名的发行版。`default` 选择 WSL 默认发行版;给出名字则固定路由。 |
|
|
93
|
+
| `wslPath` | `wsl.exe`(shipped) | `wsl.exe` 的路径,用于解析默认发行版。 |
|
|
94
|
+
| `sandbox` | `true`(shipped) | 按 `ctx.sandboxPolicy` 检查被路由的变更;`bwrap` 不可用时关闭(fail closed)。无论该围栏启用与否,该行都报告 `sandboxMode: undefined`。 |
|
|
95
|
+
| `maskWindowsDrive` | `false`(shipped) | 对受限的被路由变更隐藏 `/mnt`。 |
|
|
96
|
+
| `restrictToDistro` | `true`(shipped) | `distro` 被固定时,拒绝属于其他发行版共享的路径。 |
|
|
97
|
+
| `watchMaxDepth` | `0`(shipped) | 监视整棵树。正值深度使用有界轮询。 |
|
|
98
|
+
| `distroCwd` | `''` | 默认发行版工作目录;空则使用发行版用户家目录。 |
|
|
99
|
+
| `cwd` | `process.cwd()` | 宿主文件系统后端的工作目录。 |
|
|
100
|
+
| `diffBasisMaxBytes` | `10485760` | 宿主与被路由后端共享的覆盖写入 diff 上限。 |
|
|
101
|
+
|
|
102
|
+
根路由器是应用层的,不属于会话,因此不使用
|
|
103
|
+
`sessionSandbox`。
|
|
104
|
+
|
|
105
|
+
### 选择器与终端行
|
|
106
|
+
|
|
107
|
+
| 行 | 键 | 默认 / shipped 值 | 说明 |
|
|
108
|
+
|---|---|---|---|
|
|
109
|
+
| `directory-picker-wsl` | `preferredDistro` | `''`(shipped) | 首位展示的发行版,也是 Home 位置使用的发行版。 |
|
|
110
|
+
| | `includeHostHome` | `true`(shipped) | 包含 Windows 家目录。 |
|
|
111
|
+
| | `maxEntries` | `1000`(shipped) | 单个目录返回的最大条目数。 |
|
|
112
|
+
| | `wslPath` | `wsl.exe` | `wsl.exe` 的路径。 |
|
|
113
|
+
| | `distroCacheMs` | `5000` | 发行版列表的缓存时长。 |
|
|
114
|
+
| `subprocess-wsl` | `distro` | `''`(shipped) | 空或 `auto` 跟随启动目录所属的发行版。`default` 选择 WSL 默认;给出名字则固定发行版。 |
|
|
115
|
+
| | `shell` | `''` | 在发行版内固定的 shell。空则由 `wsl.exe` 选择登录 shell。 |
|
|
116
|
+
| | `loginShell` | `true` | 固定 shell 时的登录语义。 |
|
|
117
|
+
| | `hostSessions` | `true` | 在 Windows 文件夹上打开的会话,会在该文件夹中获得宿主 PowerShell。WSL 文件夹会话使用发行版 shell。设为 `false` 则所有会话都使用 composition 拥有的终端。 |
|
|
118
|
+
| | `terminalIdleReclaim` | `true` | 依据被标记的进程回收空闲的发行版终端。活动状态未知时暂停回收。 |
|
|
119
|
+
| | `cwd` | `''` | 未提供启动目录时的回退目录。 |
|
|
120
|
+
| | `wslPath` | `wsl.exe` | `wsl.exe` 的路径。 |
|
|
121
|
+
| | `hostCwd` | 未设置 | `wsl.exe` 进程的 Windows 工作目录。可用时默认为 `%SystemRoot%`。 |
|
|
122
|
+
| | `forwardEnv` | `[]` | 额外通过 `WSLENV` 转发的宿主环境变量名。 |
|
|
123
|
+
|
|
124
|
+
### 其他顶层行
|
|
125
|
+
|
|
126
|
+
| 行 | 键 | 默认值 | 说明 |
|
|
127
|
+
|---|---|---|---|
|
|
128
|
+
| `auto-preset` | `preset` | `wsl` | 会话工作区位于发行版内时绑定的 preset。操作者的显式选择会被保留。 |
|
|
129
|
+
| | `attempts` | `6` | 绑定 preset 的尝试次数。 |
|
|
130
|
+
| | `retryMs` | `150` | 两次尝试之间的延迟,单位毫秒。 |
|
|
131
|
+
| `wsl-preflight` | `distro` | `''` | 要检查的发行版。空或 `auto` 跟随会话工作区。 |
|
|
132
|
+
| | `wslPath` | `wsl.exe` | `wsl.exe` 的路径。 |
|
|
133
|
+
| | `sandbox` | `true` | 运行会话启动时的 `bwrap` 检查。 |
|
|
134
|
+
| | `sessionSandbox` | `true` | 同时检查 systemd、user manager、`systemd-run` 和 Linger。profile 不使用会话 runtime 时设为 false。 |
|
|
135
|
+
| `wsl-shell-env` | `distro` | `''` | 用于填充 `DSH_WSL_*` 环境事实的发行版。 |
|
|
136
|
+
| | `portsRefreshMs` | `10000` | 监听端口快照的刷新间隔,单位毫秒。 |
|
|
137
|
+
|
|
138
|
+
GUI 文件树和预览通过 `fs-routing` 使用随包的 `workspace-files` 行;
|
|
139
|
+
它们不使用单独的 WSL 提供者行。要修改其 peer 定义的限额,
|
|
140
|
+
在 profile 层按 id 覆盖 `workspace-files`。
|
|
141
|
+
|
|
142
|
+
## 共享的命令设置
|
|
143
|
+
|
|
144
|
+
shell 行从 `@deepseek-ai/dsh-bash-local` 继承输出与进程设置:
|
|
145
|
+
|
|
146
|
+
| 键 | 默认值 | 说明 |
|
|
147
|
+
|---|---|---|
|
|
148
|
+
| `maxOutputBytes` | `64000` | 每条流的捕获输出上限。agent 在发行版内执行该限制,并标记被截断的结果。 |
|
|
149
|
+
| `maxSpillBytes` | `67108864` | 一次性路径上每条流的 spill 文件上限。 |
|
|
150
|
+
| `graceMs` | `3000` | SIGTERM 与 SIGKILL 之间的宽限期。 |
|
|
151
|
+
|
|
152
|
+
`agent: true` 时,输出在发行版内被限制,且不写 spill 文件。
|
|
153
|
+
`maxSpillBytes` 和 `graceMs` 适用于 `agent: false`,或常驻 agent
|
|
154
|
+
不可用而改走一次性 `wsl.exe` 的情况。
|
|
155
|
+
|
|
156
|
+
### 模型运行命令的 zsh/fnm 配方
|
|
157
|
+
|
|
158
|
+
命令以 `<shell> -lc` 运行,因此 `~/.zshrc` 永远不会被读取——原因见
|
|
159
|
+
[LIMITATIONS](LIMITATIONS.md#shell-startup-semantics-wsl-shell)。把 fnm 的
|
|
160
|
+
hook 放进 `~/.zprofile`,并为沙箱准备一个回退——沙箱中 `/run` 是只读的,
|
|
161
|
+
fnm 无法创建它的 multishell 符号链接目录:
|
|
162
|
+
|
|
163
|
+
```zsh
|
|
164
|
+
# ~/.zprofile — fnm for non-interactive login shells (mirrors the ~/.zshrc hook)
|
|
165
|
+
path=("$HOME/.local/share/fnm" $path) # the fnm binary itself
|
|
166
|
+
_fnm_env="$(command -v fnm &>/dev/null && fnm env --shell zsh 2>/dev/null)" && eval "$_fnm_env"
|
|
167
|
+
if [[ -z "${FNM_MULTISHELL_PATH:-}" || ! -e "${FNM_MULTISHELL_PATH}/bin/node" ]]; then
|
|
168
|
+
path=("$HOME/.local/share/fnm/aliases/default/bin" $path) # sandbox fallback
|
|
169
|
+
fi
|
|
170
|
+
unset _fnm_env
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
交互式终端仍使用 `~/.zshrc` 中的 hook,不受影响;
|
|
174
|
+
`aliases/default` 回退只在 multishell 链接无法存在时接管。
|
|
175
|
+
|
|
176
|
+
## 环境变量转发
|
|
177
|
+
|
|
178
|
+
只有名字列在 `WSLENV` 中的宿主环境变量才会被 WSL 导入。各提供者
|
|
179
|
+
默认转发 `NO_COLOR`、`TERM`、`PAGER`、`GIT_PAGER`、`LANG`、`LC_ALL`
|
|
180
|
+
以及托管的 `DSH_*` 名字。`forwardEnv` 向该列表追加名字;
|
|
181
|
+
`PATH` 永远不会被转发。
|
|
182
|
+
|
|
183
|
+
对 `wsl-shell` 而言,`forwardEnv` 只作用于一次性 `wsl.exe` 路径。
|
|
184
|
+
常驻 agent 只接收托管的 `DSH_*` 环境,因此 `forwardEnv` 里的自定义名字
|
|
185
|
+
不会到达经由该 agent 运行的命令。`subprocess-wsl` 的设置
|
|
186
|
+
作用于它的 WSL 启动。
|
|
187
|
+
|
|
188
|
+
## 发行版选择
|
|
189
|
+
|
|
190
|
+
对命令与终端行而言,空的 `distro` 或 `distro: auto` 跟随
|
|
191
|
+
请求的 UNC 工作目录。当请求未指明发行版时,使用 WSL 的
|
|
192
|
+
默认发行版。对 `wsl-fs`,`auto` 跟随每个目标路径。对
|
|
193
|
+
`fs-routing`,每条 UNC 目标各自选择自己的发行版。
|
|
194
|
+
|
|
195
|
+
`distro: default` 总是选择 WSL 的默认发行版。给出发行版名字则把该行
|
|
196
|
+
固定到那个发行版。`restrictToDistro: true` 时,属于其他发行版共享的路径
|
|
197
|
+
会被以 `FS_OUTSIDE_DISTRO` 拒绝。挂载在 `/mnt/c` 的 Windows 盘
|
|
198
|
+
位于所选发行版之内,不会被这个围栏拒绝。
|
|
199
|
+
|
|
200
|
+
## Windows interop
|
|
201
|
+
|
|
202
|
+
`interopVisibility` 控制是否对受限命令遮蔽 `/mnt`;
|
|
203
|
+
它本身不会禁用 Windows interop。未设置时,`maskWindowsDrive: false`
|
|
204
|
+
给出 `visible` 行为。
|
|
205
|
+
|
|
206
|
+
| 值 | 效果 |
|
|
207
|
+
|---|---|
|
|
208
|
+
| `visible` | 保持 `/mnt` 可见。受限命令可以读取 Windows 文件,并通过 WSL interop 启动 Windows 可执行文件。 |
|
|
209
|
+
| `masked` | 在受限 profile 中,把空的 tmpfs 挂载到 `/mnt` 上。命令无法访问 `/mnt` 下的路径;其他挂载与不受限的文件读取不在覆盖范围内。 |
|
|
210
|
+
| `disabled` | 施加与 `masked` 相同的 `/mnt` 遮蔽;它不会禁用 interop,也不会修改 `wsl.conf`。请按下文单独配置发行版。 |
|
|
211
|
+
|
|
212
|
+
`maskWindowsDrive: true` 等价于 `interopVisibility: masked`。
|
|
213
|
+
设置了 `interopVisibility` 时,它优先于
|
|
214
|
+
`maskWindowsDrive`。
|
|
215
|
+
|
|
216
|
+
遮蔽只是收窄访问,并不关闭 interop。WSL 仍能运行被写入工作区的
|
|
217
|
+
Windows 可执行文件,因为 `binfmt_misc` 按文件内容分发。
|
|
218
|
+
因此强制级别仍是 `partial`,结果会报告 `windowsDrive: 'masked'`。
|
|
219
|
+
要在该发行版上禁用 interop,把下面这段加进 `/etc/wsl.conf`:
|
|
220
|
+
|
|
221
|
+
```ini
|
|
222
|
+
[interop]
|
|
223
|
+
enabled=false
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
然后运行 `wsl.exe --shutdown`。要关闭 interop,必须做这一发行版级修改。
|
|
227
|
+
|
|
228
|
+
要对两个嵌套行都应用 masked profile:
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
pnpm run preset --sandbox true --mask-windows-drive true
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
把打印出的块合并进 profile。`visible` 与 `masked` 的行为
|
|
235
|
+
由 `test/probe/interop-shell.sh` 对常驻与一次性两种命令都有覆盖。
|
|
236
|
+
发行版级的 disabled 状态需要手动检查,因为它会改动 WSL 配置。
|
|
237
|
+
|
|
238
|
+
## 沙箱与预检
|
|
239
|
+
|
|
240
|
+
`wsl-shell`、`wsl-fs` 和 `fs-routing` 的默认值都是 `sandbox: true`。
|
|
241
|
+
受限命令与变更要求发行版内有可用的 `bwrap`。缺失或不可用时,
|
|
242
|
+
命令以 `SANDBOX_UNAVAILABLE` 失败,写入以 `FS_IO_ERROR` 失败;
|
|
243
|
+
两种操作都不会回退到无约束运行。错误信息会区分
|
|
244
|
+
包缺失与 `bwrap` 存在但不可用两种情况。
|
|
245
|
+
|
|
246
|
+
用 bootstrap 辅助脚本安装该前置依赖:
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
scripts/bootstrap.sh <distro> --install
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
不带 `--install` 时,辅助脚本只做只读检查。在基于 apt 的
|
|
253
|
+
发行版上,直接安装命令是:
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
wsl.exe -d <distro> -u root -- apt-get install -y bubblewrap
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
会话打开时,`wsl-preflight` 行会运行这些检查。
|
|
260
|
+
`sessionSandbox: true` 时,它还会检查会话 runtime 的前置条件。
|
|
261
|
+
该警告仅供参考:它不会安装软件包,也不会改变提供者关闭(fail-closed)
|
|
262
|
+
的行为。在该行设置 `sandbox: false` 或移除该行可让沙箱警告静音。
|
|
263
|
+
profile 不使用会话 runtime 时,设置 `sessionSandbox: false`。
|
|
264
|
+
|
|
265
|
+
在 `wsl-shell` 或 `wsl-fs` 上设置 `sandbox: false` 后,命令或写入
|
|
266
|
+
将无约束地运行;其 `sandboxMode` 变为 `undefined`。工具层
|
|
267
|
+
会报告没有沙箱生效。在 `fs-routing` 上,该设置会禁用变更围栏;
|
|
268
|
+
无论开关如何,该行都报告 `sandboxMode: undefined`。
|
|
269
|
+
|
|
270
|
+
## 诊断配置问题与拒绝
|
|
271
|
+
|
|
272
|
+
使用 `dsh --profile <name> --dump-config` 检查合成后的 profile,
|
|
273
|
+
无需真正启动。输出会显示某行的覆盖是否生效。
|
|
274
|
+
使用 `scripts/bootstrap.sh <distro>` 检查发行版前置依赖。
|
|
275
|
+
|
|
276
|
+
| 现象 | 结果 | 处理 |
|
|
277
|
+
|---|---|---|
|
|
278
|
+
| 命令沙箱不可用 | `SANDBOX_UNAVAILABLE`;消息区分 `bwrap` 缺失或不可用。 | 安装 `bubblewrap`、修复用户命名空间,或在 `wsl-shell` 上设置 `sandbox: false`。 |
|
|
279
|
+
| 受限写入不可用 | `FS_IO_ERROR`,附带 `bwrap` 补救方法。 | 安装或修复 `bubblewrap`;要禁用围栏,在 `wsl-fs` 或 `fs-routing` 上设置 `sandbox: false`。 |
|
|
280
|
+
| 发行版已停止时文件操作失败 | `FS_IO_ERROR`:发行版 substrate 不可用。 | 启动该发行版。若 agent 仍不可用,重启会话。 |
|
|
281
|
+
| 命令无法在所选发行版中启动 | `WSL_E_*` 错误码,指明发行版。 | 启动该发行版或修改 `distro`。更宽的权限无法解决发行版已停止的问题。 |
|
|
282
|
+
| `wsl.exe` 无法启动 | 错误指明可执行文件与该行的 `wslPath`。 | 修正 `wslPath`,并确认已安装 WSL。 |
|
|
283
|
+
| 命令执行中 WSL 挂起 | 命令到达截止时间仍未完成。 | 重试一次;仍不行则运行 `wsl.exe --shutdown` 并重新打开会话。 |
|
|
284
|
+
| 被监视的变更没有通知 | 轮询可能遗漏保留所观察元数据的变更;不保证报错。 | 查看 [LIMITATIONS.md](LIMITATIONS.md) 中的监视器限制。 |
|
|
285
|
+
| 其他发行版的路径被拒绝 | `FS_OUTSIDE_DISTRO`。 | 使用 `distro: ''` 或 `auto` 跟随工作区,或在目标发行版中打开会话。 |
|
|
286
|
+
|
|
287
|
+
完整错误参考见 [ARCHITECTURE.md — Error codes](ARCHITECTURE.md#error-codes),
|
|
288
|
+
设计边界见 [LIMITATIONS.md](LIMITATIONS.md)。
|
package/docs/LIMITATIONS.md
CHANGED
|
@@ -1,377 +1,170 @@
|
|
|
1
1
|
# Limitations
|
|
2
2
|
|
|
3
|
+
*A Chinese translation lives at [LIMITATIONS.zh](LIMITATIONS.zh.md); the English version is authoritative.*
|
|
4
|
+
|
|
3
5
|
What the plugin does not do, and why. A limitation that is documented is a design
|
|
4
|
-
boundary; one that is not is a bug report waiting to happen.
|
|
6
|
+
boundary; one that is not is a bug report waiting to happen. Each entry states the
|
|
7
|
+
limit and the remedy; the reasoning lives in [ARCHITECTURE.md](ARCHITECTURE.md) and
|
|
8
|
+
the documents it links.
|
|
5
9
|
|
|
6
10
|
## The filesystem substrate (`wsl-fs`)
|
|
7
11
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
- **
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
- **
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
agent syncs the staged file before publication; the shell agent attempts
|
|
44
|
-
`sync -f` but ignores failure. Neither syncs the containing directory after
|
|
45
|
-
the rename or link. A kernel panic or power loss can therefore lose data or
|
|
46
|
-
the published directory entry; the operation is not a crash-consistent store.
|
|
47
|
-
- **A Linux filename containing a backslash cannot be addressed.** The UNC
|
|
48
|
-
display form (`\\wsl.localhost\<distro>\...`) is the file's identity
|
|
49
|
-
everywhere - the session header, the GUI, the caches - and the backslash is
|
|
50
|
-
the UNC's separator, so `a\b.ts` in the distro round-trips to `a/b.ts`: the
|
|
51
|
-
tools would silently address a different path. This is the coordinate
|
|
52
|
-
system's own limit — a backslash-delimited identity cannot spell a name
|
|
53
|
-
containing a backslash — not a defect of the substrate; a rename away from
|
|
54
|
-
backslash names is the workaround.
|
|
55
|
-
- **Stage two routes mutations through a confined resident.** A write or edit
|
|
56
|
-
under a confined policy runs on its own long-lived agent, spawned inside the
|
|
57
|
-
bwrap profile that binds exactly what the mode grants — so the kernel, not
|
|
58
|
-
just the host-side check, refuses a write outside the workspace, and the
|
|
59
|
-
refusal carries `FS_SANDBOX_DENIED` (the command path's dialect). Costs to
|
|
60
|
-
know: `bubblewrap` becomes required for confined writes (a distro without it
|
|
61
|
-
refuses them with the bootstrap command, rather than downgrading); a policy
|
|
62
|
-
change addresses a different resident, so the first write after switching the
|
|
63
|
-
Permissions selector pays one agent startup; and a write the policy grants to
|
|
64
|
-
the distro's `/tmp` lands in the distro's real `/tmp`, because the
|
|
65
|
-
file-writes resident binds it read-write — where the read tools, the plain
|
|
66
|
-
resident and the user's own shell all see it. The ephemeral tmpfs belongs to
|
|
67
|
-
the command side alone, so a file handed over from a command still goes
|
|
68
|
-
through the session workspace.
|
|
69
|
-
- **`glob` and `grep` run the distro's own rg** (`lib/search-route.js`): the
|
|
70
|
-
tool's arguments are forwarded verbatim, so results, output format and exit
|
|
71
|
-
codes are rg's own — at ext4 speed, with the distro's `.gitignore` rules.
|
|
72
|
-
A distro without rg fails the search outright: exit 127, with the message
|
|
73
|
-
from the exec launcher and never from rg itself (which is the missing
|
|
74
|
-
program) — `setsid: failed to execute rg: No such file or directory` where
|
|
75
|
-
the distro has `setsid`, the script shell's `rg: not found` where it does
|
|
76
|
-
not. The install command `scripts/bootstrap.sh` prints — `wsl.exe -d <distro>
|
|
77
|
-
-u root -- apt-get install -y bubblewrap git ripgrep inotify-tools`, or the
|
|
78
|
-
detected family's spelling — installs it. `--install` runs that command only
|
|
79
|
-
when `bubblewrap` itself is missing, so a distro that has bwrap but not rg
|
|
80
|
-
needs the printed line run by hand. A search whose directory is a Windows
|
|
81
|
-
folder keeps the host binary, which is native there.
|
|
82
|
-
- **`watch()` prefers inotify, with a polling fallback.** When `inotifywait`
|
|
83
|
-
is installed and the target is an unbounded directory, the watcher uses
|
|
84
|
-
recursive inotify watches and coalesces events to one callback per 0.5s.
|
|
85
|
-
Otherwise — a file target, a positive `maxDepth`, or no `inotify-tools` — it
|
|
86
|
-
is the `find -newer` poll loop (a `stat` fingerprint for a file), so the
|
|
87
|
-
coarse-invalidation caveats below apply.
|
|
88
|
-
- **With `sessionSandbox: false`, a confined command's `/tmp` is not the write
|
|
89
|
-
tool's `/tmp`.** `workspace-write` mounts a fresh tmpfs at `/tmp` for the
|
|
90
|
-
command — that is what makes it ephemeral — while the file tools' fence grants
|
|
91
|
-
the distro's *real* `/tmp`. Both are writable, so neither side is refused, but a
|
|
92
|
-
file the write tool puts in `/tmp` is invisible to `bash` and vice versa. Hand
|
|
93
|
-
a file between them through the session workspace. The shipped preset turns
|
|
94
|
-
the session runtime on, and `wsl-fs` shares it for confined writes, so
|
|
95
|
-
commands and writes bind the same real `/tmp` and workspace.
|
|
96
|
-
- A distro that is stopped or unregistered while the app runs takes the file
|
|
97
|
-
tools' substrate with it: reads and writes report the substrate-unavailable
|
|
98
|
-
error naming the cause. A death with nothing in flight costs nothing — the
|
|
99
|
-
next call starts a fresh transport, so a distro that comes back is served
|
|
100
|
-
again. A death with a request in flight spends the resident's one permitted
|
|
101
|
-
rebuild, and when that rebuild cannot complete — the distro is still down, or
|
|
102
|
-
gone — the resident is permanently out: every later file operation fails the
|
|
103
|
-
same way until the app restarts.
|
|
104
|
-
- `watch()` observes from inside the distro, so callbacks are coarse
|
|
105
|
-
invalidation, not per-file notifications. With inotify they arrive as events
|
|
106
|
-
(coalesced to 0.5s); with the polling fallback they arrive on the poll
|
|
107
|
-
cadence, not instantly. In the polling path a creation is seen even when the
|
|
108
|
-
new file's own mtime is old: creating it bumps the containing directory's
|
|
109
|
-
mtime, and the scan includes the watched directory itself (deletions are seen
|
|
110
|
-
the same way). What escapes the polling stamp is a change that leaves every
|
|
111
|
-
scanned mtime older than it — an in-place `cp -p` onto an existing file (new
|
|
112
|
-
content, a copied-back mtime), or a `tar -x` whose archive restores the
|
|
113
|
-
containing directory's own mtime. That blind spot does not apply to the
|
|
114
|
-
inotify path. A FILE target is watched by its `mtime:size` fingerprint: a
|
|
115
|
-
rename-over or an in-place write fires, a removal reports the target as gone,
|
|
116
|
-
and a change that preserves both mtime and size escapes it. Watching a 9p
|
|
117
|
-
share from the Windows side remains unsupported — the loop runs where the
|
|
118
|
-
kernel can actually report change.
|
|
12
|
+
One substrate: the resident in-distro agent (ext4 for distro files). The retired
|
|
13
|
+
Windows-side share backend (`substrate: "share"`) is refused at boot; WSL-routed
|
|
14
|
+
operations never use the 9p share.
|
|
15
|
+
|
|
16
|
+
- **A new file publishes with mode 0600.** Overwrites and edits keep the original
|
|
17
|
+
bits; `chmod` when a new file must be executable.
|
|
18
|
+
- **When the agent is out, file operations fail closed** — `FS_IO_ERROR` naming the
|
|
19
|
+
substrate, with the recovery path. One crash rides a single rebuild; a spent
|
|
20
|
+
rebuild enters bounded revival (2/4/8 s); three deaths are permanent until the app
|
|
21
|
+
restarts. There is no fallback I/O — deliberately.
|
|
22
|
+
- **The mutation guard is not atomic compare-and-swap.** Creates use a no-replace
|
|
23
|
+
link; overwrites re-verify the version before the rename. A mismatch returns
|
|
24
|
+
`FS_STALE_VERSION`, but a change between check and rename can still be overwritten.
|
|
25
|
+
- **A cancelled write can have an unknown outcome** — inspect the file before
|
|
26
|
+
retrying.
|
|
27
|
+
- **A successful write is not crash-durable.** The staged file is synced, the
|
|
28
|
+
containing directory is not: a power loss can lose the entry.
|
|
29
|
+
- **A Linux filename containing a backslash cannot be addressed** — the backslash is
|
|
30
|
+
the UNC separator, so `a\b.ts` round-trips to `a/b.ts`. Rename away from backslash
|
|
31
|
+
names; this is the coordinate system's own limit.
|
|
32
|
+
- **Confined writes run on their own bwrap-confined resident** — kernel-enforced,
|
|
33
|
+
refusing outside the workspace with `FS_SANDBOX_DENIED`. Costs: `bubblewrap`
|
|
34
|
+
required; a policy change pays one agent startup; a write granted to `/tmp` lands
|
|
35
|
+
in the distro's real `/tmp`.
|
|
36
|
+
- **`glob`/`grep` run the distro's rg**; a distro without rg fails the search
|
|
37
|
+
(exit 127). Install: `wsl.exe -d <distro> -u root -- apt-get install -y bubblewrap
|
|
38
|
+
git ripgrep inotify-tools` (or the detected family's spelling). `--install` runs it
|
|
39
|
+
only for a missing `bubblewrap`.
|
|
40
|
+
- **`watch()` is coarse invalidation, not per-file notifications**: inotify events
|
|
41
|
+
coalesced to 0.5 s, or a `find -newer` poll loop otherwise. The polling path misses
|
|
42
|
+
an in-place `cp -p` or a `tar -x` that restores older mtimes; a FILE target watches
|
|
43
|
+
an `mtime:size` fingerprint. 9p watching from Windows stays unsupported.
|
|
44
|
+
- **With `sessionSandbox: false`, a confined command's `/tmp` differs from the write
|
|
45
|
+
tools' `/tmp`** — hand files over through the session workspace. The shipped
|
|
46
|
+
preset (`sessionSandbox: true`) shares one real `/tmp` and has no such split.
|
|
119
47
|
- `editText` reads the whole file into memory before writing it back.
|
|
48
|
+
- A distro that dies with a request in flight spends the resident's one rebuild;
|
|
49
|
+
still down, the resident stays out until the app restarts.
|
|
120
50
|
|
|
121
51
|
## The GUI workspace-files service
|
|
122
52
|
|
|
123
|
-
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
(
|
|
129
|
-
|
|
130
|
-
-
|
|
131
|
-
|
|
132
|
-
round trips instead of one, and a deep page read streams the preceding lines
|
|
133
|
-
through the pipe before cutting. Expect the GUI file tree and previews to be
|
|
134
|
-
slower than a direct agent call, especially on large files; the model's own
|
|
135
|
-
file operations are unaffected — they use the preset filesystem and the
|
|
136
|
-
resident agent directly. Measured 2026-10-19 with
|
|
137
|
-
[`test/probe/gui-preview.sh`](https://github.com/Big-Dao/dsh-plugin-wsl-env/blob/main/test/probe/gui-preview.sh) (20 iterations on
|
|
138
|
-
this machine): the routed backend's `stat` p95 2.7 ms, `listDir` p95 5.1 ms,
|
|
139
|
-
and a 1 MiB `readByteRange` p95 **63.5 ms/MiB** — the service's own round
|
|
140
|
-
trips sit on top of those, and the 1 s/MiB budget has a wide margin. No
|
|
141
|
-
optimization is justified by these numbers; revisit only with a measured
|
|
142
|
-
regression.
|
|
143
|
-
|
|
144
|
-
- **`fs-routing` is the seam.** The shipped service depends on the routed root
|
|
145
|
-
filesystem staying enabled and faithful to the `ctx.fs` contract (`resolve`,
|
|
146
|
-
`lstat`, `stat`, `listDir`, `streamText`, `readBytes`, `watch`, `contains`,
|
|
147
|
-
`processPath`). Disabling `fs-routing` restores the host 9p-backed root
|
|
148
|
-
filesystem, and the GUI file service then crosses 9p for WSL workspaces. The
|
|
149
|
-
shipped row's own caps (`maxBytes`, `maxFileBytes`, `maxLines`, `maxEntries`)
|
|
150
|
-
can be overridden on the `workspace-files` row.
|
|
53
|
+
- The GUI file tree and previews use the shipped `workspace-files` row, reading WSL
|
|
54
|
+
paths through the routed root filesystem — deliberately not replaced (a server-only
|
|
55
|
+
takeover would drop the browser `file` provider the Sidebar previews need).
|
|
56
|
+
- **Expect extra round trips** (roughly 4-6 per listing/stat/page; measured p95:
|
|
57
|
+
`stat` 2.7 ms, `listDir` 5.1 ms, 1 MiB read 63.5 ms/MiB — 2026-10-19,
|
|
58
|
+
[probe](https://github.com/Big-Dao/dsh-plugin-wsl-env/blob/main/test/probe/gui-preview.sh)).
|
|
59
|
+
The model's own file operations are unaffected.
|
|
60
|
+
- `fs-routing` is the seam: disabling it restores the host 9p-backed root filesystem.
|
|
61
|
+
The shipped row's caps can be overridden on the `workspace-files` row.
|
|
151
62
|
|
|
152
63
|
## `@` completion
|
|
153
64
|
|
|
154
|
-
- **`@` references
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
median over 9p versus **1.5 ms** on ext4 (~56×;
|
|
159
|
-
[`probe:file-reference`](https://github.com/Big-Dao/dsh-plugin-wsl-env/blob/main/test/probe/file-reference-9p.sh), 2026-10-19).
|
|
160
|
-
[`src/file-reference-wsl.ts`](https://github.com/Big-Dao/dsh-plugin-wsl-env/blob/main/src/file-reference-wsl.ts) is the distro-side
|
|
161
|
-
traversal (built and unit-tested) but is **not** in `cordis.patch.yml`: the
|
|
162
|
-
funnels it would replace are module-internal. The fallback is the 9p walk
|
|
163
|
-
until upstream exposes `FileReferenceTraversal`. Cost, seam and wire
|
|
164
|
-
trigger: [UPSTREAM-FILE-REFERENCE-SEAM.md](UPSTREAM-FILE-REFERENCE-SEAM.md).
|
|
65
|
+
- **`@` references walk the 9p share** (~56× slower than ext4 on a probed tree,
|
|
66
|
+
2026-10-19). The distro-side traversal is built but not mounted — it needs the
|
|
67
|
+
upstream `FileReferenceTraversal` seam:
|
|
68
|
+
[UPSTREAM-FILE-REFERENCE-SEAM.md](UPSTREAM-FILE-REFERENCE-SEAM.md).
|
|
165
69
|
|
|
166
70
|
## The sandbox
|
|
167
71
|
|
|
168
|
-
- **The one-shot fallback has only the host deadline
|
|
169
|
-
|
|
170
|
-
`
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
agent
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
a reachable `systemd --user`, `systemd-run`, or `Linger` is refused on the
|
|
192
|
-
first command with the remedy. Set `sessionSandbox: false` on the preset's
|
|
193
|
-
`wsl-shell` to keep the per-command shape. A **hard crash** of the app can
|
|
194
|
-
leave a stale `dsh-wsl-*.scope` and its background processes behind; a
|
|
195
|
-
graceful session end stops the scope (validated). The session preflight
|
|
196
|
-
sweeps stale scopes when the next WSL session opens: it stops a
|
|
197
|
-
`dsh-wsl-*.scope` only when its cgroup has no agent process (`wsl-agent.sh`
|
|
198
|
-
or the native `dsh-wsl-agent`), so another live session's runtime is never
|
|
199
|
-
touched.
|
|
200
|
-
- **The sandbox does not govern WSL interop.** A confined command can still run a
|
|
201
|
-
Windows program, which escapes the Linux-side boundary. The plugin reports this as
|
|
202
|
-
`enforcement: partial`. See [ARCHITECTURE.md](ARCHITECTURE.md#reported-enforcement).
|
|
203
|
-
The `maskWindowsDrive` key on `wsl-shell`, `wsl-fs` and `fs-routing` narrows the
|
|
204
|
-
hole: the confined profile shadows `/mnt` with an empty tmpfs, so files and
|
|
205
|
-
executables at paths under `/mnt` are hidden. Other mounts, alternate access
|
|
206
|
-
paths and unrestricted file reads are not covered by this mask. It is not a
|
|
207
|
-
closure — a command that can write the workspace can write an executable there and
|
|
208
|
-
run it (binfmt interop dispatches on file content, not location) — so enforcement
|
|
209
|
-
stays `partial`, the result's sandbox fact reports `windowsDrive: "masked"`, and
|
|
210
|
-
distro-level disabling of WSL interop (`[interop] enabled=false` in
|
|
211
|
-
`wsl.conf`) must be configured separately. On `wsl-shell`, an explicitly set
|
|
212
|
-
`interopVisibility` takes precedence over `maskWindowsDrive`.
|
|
213
|
-
- **`bubblewrap` must be installed**, or both providers fail immediately. The
|
|
214
|
-
usability probe re-runs after a failure, so installing bubblewrap while the app
|
|
215
|
-
runs is believed on the next command; only a success is cached. The refusal
|
|
216
|
-
classifies the probe's failure — missing versus present-but-failing — and the
|
|
217
|
-
direct install command follows the distro's package family; a probe failure
|
|
218
|
-
with no recognisable shape falls back to the install remedy with the raw
|
|
219
|
-
output quoted. A session-start preflight (`wsl-preflight`) surfaces the same
|
|
220
|
-
text as a warning; it is advisory and never installs.
|
|
221
|
-
- `workspace-write` binds the workspace root as writable, and bubblewrap refuses a
|
|
222
|
-
bind whose source directory does not exist, so a session whose workspace directory was
|
|
223
|
-
deleted fails with a runner error; the plugin does not recreate it. A workdir that
|
|
224
|
-
does not exist — a configured `cwd` pointing at a deleted directory, for instance — is
|
|
225
|
-
a separate case and is now reported as an error naming that directory, because
|
|
226
|
-
`wsl.exe` would otherwise run the command in `/` and exit 0.
|
|
72
|
+
- **The one-shot fallback has only the host deadline** — a killed `wsl.exe` does not
|
|
73
|
+
relay the kill into the distro, so the Linux process tree can outlive the call.
|
|
74
|
+
Prefer `agent: true` and the session runtime for long commands.
|
|
75
|
+
- **Batch reads share one native agent per distro** (16 concurrent calls in
|
|
76
|
+
2.4-4.1 ms; first call pays ~70 ms preparation).
|
|
77
|
+
- **aarch64 runs under qemu in CI only** (no Windows-on-ARM machine in reach —
|
|
78
|
+
[COMPATIBILITY.md](COMPATIBILITY.md#scope-decisions)). `auto` still selects the
|
|
79
|
+
packaged binary; `native` refuses on preparation failure; `shell` forces the shell
|
|
80
|
+
agent.
|
|
81
|
+
- **The session runtime needs systemd and a lingering user manager** — refused on the
|
|
82
|
+
first command with the remedy otherwise; `sessionSandbox: false` keeps the old
|
|
83
|
+
per-command shape. A hard app crash can leave a stale `dsh-wsl-*.scope`; the
|
|
84
|
+
preflight sweeps stale scopes on the next WSL session (never another live
|
|
85
|
+
session's).
|
|
86
|
+
- **The sandbox does not govern WSL interop** — reported as `enforcement: partial`.
|
|
87
|
+
`maskWindowsDrive` narrows `/mnt` but is not a closure; distro-level
|
|
88
|
+
`[interop] enabled=false` in `wsl.conf` must be configured separately.
|
|
89
|
+
[ARCHITECTURE.md](ARCHITECTURE.md#reported-enforcement).
|
|
90
|
+
- **`bubblewrap` must be installed**, or both providers fail with the install
|
|
91
|
+
command; a session-start preflight surfaces the same text as an advisory warning.
|
|
92
|
+
- A workspace directory deleted mid-session fails with a runner error; the plugin
|
|
93
|
+
does not recreate it. A configured `cwd` that does not exist is reported as an
|
|
94
|
+
error naming the directory.
|
|
227
95
|
|
|
228
96
|
## Shell startup semantics (`wsl-shell`)
|
|
229
97
|
|
|
230
|
-
Commands run as `<shell> -lc <command>` —
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
commands. Non-interactivity is equally deliberate: interactive startup files
|
|
234
|
-
print prompts and plugin output into the streams the model reads, define
|
|
235
|
-
aliases that shadow real executables, and can keep the shell alive after the
|
|
236
|
-
command finishes. What follows from that split:
|
|
98
|
+
Commands run as `<shell> -lc <command>` — login (profile scripts sourced, so version
|
|
99
|
+
managers work) and **non-interactive** (no prompt/plugin output in the model's
|
|
100
|
+
streams).
|
|
237
101
|
|
|
238
|
-
- **`~/.zshrc`
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
(`eval "$(fnm env)"`, nvm, cargo) into `~/.zprofile` or `~/.zshenv`, which
|
|
245
|
-
login shells do read; keep prompt and completion plugins in `.zshrc`.
|
|
246
|
-
- **The confined sandbox mounts `/run` read-only, so fnm's multishell PATH
|
|
247
|
-
cannot form there.** `fnm env` works by creating a per-shell symlink
|
|
248
|
-
directory under `/run/user/<uid>/fnm_multishells/`; inside `bwrap` that
|
|
249
|
-
creation fails, and with it the PATH entry `fnm env` exists to provide.
|
|
250
|
-
A profile that must work in both worlds should fall back to fnm's stable
|
|
251
|
-
alias directory when the multishell link did not materialize — see the
|
|
252
|
-
recipe in [Configuration](CONFIGURATION.md#zshfnm-recipe-for-model-run-commands).
|
|
253
|
-
The plugin does not relax the sandbox for this: a writable `/run` inside the
|
|
254
|
-
fence would be a real weakening of it, traded for one tool's convenience.
|
|
102
|
+
- **`~/.zshrc` / `~/.bashrc` are never read.** Move machine-level PATH setup
|
|
103
|
+
(`fnm`, nvm, cargo) into `~/.zprofile` or `~/.zshenv`; keep prompts and completion
|
|
104
|
+
in the rc file.
|
|
105
|
+
- **fnm's multishell PATH cannot form inside the sandbox** (`/run` is read-only) —
|
|
106
|
+
use the fallback recipe in
|
|
107
|
+
[Configuration](CONFIGURATION.md#zshfnm-recipe-for-model-run-commands).
|
|
255
108
|
|
|
256
109
|
## Agent diagnostics
|
|
257
110
|
|
|
258
|
-
- **The doctor's `agent.handshake`
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
- **`dsh-wsl-*.scope` counts are point-in-time.** The doctor lists and inspects
|
|
266
|
-
the user scopes once; a scope created or stopped a moment later is not
|
|
267
|
-
reflected. `--fix` runs the same sweep the session preflight uses.
|
|
268
|
-
|
|
269
|
-
## The doctor's sessions table
|
|
270
|
-
|
|
271
|
-
- **The sessions table shows distro-side facts only.** The cross-distro view
|
|
272
|
-
(`distro → scope → state → resident pids`) attests what the distro can see:
|
|
273
|
-
which scopes exist and which pids inside them are agent processes. A
|
|
274
|
-
resident's idle countdown and its `lastError` live in the host process that
|
|
275
|
-
owns it — there is no IPC channel from a plugin script to the app, so the
|
|
276
|
-
table cannot show them (`WSL-313`'s upstream ask). The agent.handshake
|
|
277
|
-
check notes a live resident's presence when the scope survey sees one, and
|
|
278
|
-
says that its own probe starts a fresh one.
|
|
111
|
+
- **The doctor's `agent.handshake` probes a fresh resident**, not the live one —
|
|
112
|
+
a running resident's `lastError` stays in the app log (`WslAgent.status()` exposes
|
|
113
|
+
it in-process).
|
|
114
|
+
- **`dsh-wsl-*.scope` counts are point-in-time**; `--fix` runs the preflight's sweep.
|
|
115
|
+
- **The doctor's `git.win-safe` probe reports the Windows git trust through interop
|
|
116
|
+
`git.exe`** — the `safe.directory` whitelist that WSL change review needs when
|
|
117
|
+
Windows git is installed (see Terminals below).
|
|
279
118
|
|
|
280
119
|
## Session-start advisories
|
|
281
120
|
|
|
282
|
-
- **There is no session-UI notice seam in DSH 0.2.1.**
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
anything, never cancels the session and never changes the fail-closed
|
|
286
|
-
behavior of the first command. The finding goes to the host log
|
|
287
|
-
(`logger.warn`) and into the session's runtime context as a `wsl-preflight`
|
|
288
|
-
entry — model-visible on the next assembly — but the GUI today has no
|
|
289
|
-
toast/banner service a plugin can publish to. `pnpm run doctor` is the
|
|
290
|
-
deterministic human-facing check; the exact upstream ask is
|
|
121
|
+
- **There is no session-UI notice seam in DSH 0.2.1.** `wsl-preflight` failures go
|
|
122
|
+
to the host log and the session's runtime context (model-visible next assembly) —
|
|
123
|
+
advisory only, never installing or cancelling. The upstream ask:
|
|
291
124
|
[UPSTREAM-SESSION-NOTICE.md](UPSTREAM-SESSION-NOTICE.md).
|
|
292
125
|
- **The runtime-context entry is a repeated snapshot, not a dismissible
|
|
293
|
-
notification.**
|
|
294
|
-
it has no severity decoration and no dismiss control. Removing the
|
|
295
|
-
`wsl-preflight` row (or setting `sandbox: false` on it) silences the probe
|
|
296
|
-
entirely.
|
|
126
|
+
notification.** Removing the `wsl-preflight` row silences the probe entirely.
|
|
297
127
|
|
|
298
128
|
## Ports
|
|
299
129
|
|
|
300
|
-
- **The port poller keeps the resident agent permanently warm
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
while a distro's session is active the agent never idles out: one `wsl.exe`
|
|
304
|
-
and one in-distro agent process stay resident per active distro even with no
|
|
305
|
-
command in flight. The poller starts only for a distro a session actually
|
|
306
|
-
uses; raising `portsRefreshMs` past the agent's idle timeout restores idle
|
|
307
|
-
shutdown, at the cost of staler port facts.
|
|
130
|
+
- **The port poller keeps the resident agent permanently warm** per active distro
|
|
131
|
+
(refresh 10 s). Raising `portsRefreshMs` past the agent's idle timeout restores
|
|
132
|
+
idle shutdown, at the cost of staler port facts.
|
|
308
133
|
|
|
309
134
|
## The directory picker
|
|
310
135
|
|
|
311
|
-
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
walk the host used to pay per entry. Entries arrive NUL-separated, so a
|
|
315
|
-
directory name containing a newline lists as one exact row (WSL-306 retired
|
|
316
|
-
the `ls -1ALp` line parse that used to split such a name into two rows or
|
|
317
|
-
drop it).
|
|
136
|
+
- Distro levels list from inside the distro in one round trip (NUL-separated, so
|
|
137
|
+
newline-bearing directory names list exactly — WSL-306); a one-shot `wsl.exe --exec
|
|
138
|
+
find` when the resident is out.
|
|
318
139
|
|
|
319
140
|
## Terminals
|
|
320
141
|
|
|
321
|
-
- Terminal routing is per-session by workspace coordinates
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
- **
|
|
326
|
-
|
|
327
|
-
`
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
the
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
change are in [UPSTREAM-TERMINAL-TITLE.md](UPSTREAM-TERMINAL-TITLE.md).
|
|
344
|
-
The plugin now ships host `shellCandidates` (`wsl.exe` dedupes into the default's
|
|
345
|
-
kind; `powershell`/`cmd` appear as themselves), and a manually selected shell is
|
|
346
|
-
titled after itself — pick PowerShell from the shell menu and the tab reads
|
|
347
|
-
`powershell`. Double-clicking a title to rename also works.
|
|
348
|
-
- Distro terminals ARE reclaimed when idle — the observation is distro-side:
|
|
349
|
-
each terminal is launched with a `DSH_TERMINAL_ID` marker, and the handle's
|
|
350
|
-
`inspectActivity` counts the marked processes through the resident agent
|
|
351
|
-
(`lib/terminal-activity.js`). Exactly one (the shell at its prompt) is
|
|
352
|
-
`idle`; more than one (a command, a pipeline, a nested shell) is `busy`;
|
|
353
|
-
`unknown` — the agent out, the distro stopped — pauses reclamation, because
|
|
354
|
-
a probe that cannot answer must not authorize a close. Consequences to
|
|
355
|
-
know: a nested shell keeps the terminal busy by design (closing it would
|
|
356
|
-
lose that shell), so does any process the shell's startup files spawn with
|
|
357
|
-
the inherited marker (an `ssh-agent` launched in `.bashrc` reads as busy);
|
|
358
|
-
the idle threshold is the controller's `unattendedTimeoutMs` (2 h by
|
|
359
|
-
default); and the host-side shell-activity integration is still not armed
|
|
360
|
-
for these launches — `wsl.exe` on win32 fails its gate — which is why the
|
|
361
|
-
observation is a scan rather than shell cooperation. `terminalIdleReclaim:
|
|
362
|
-
false` on the `subprocess-wsl` row restores close-by-hand: the launch then
|
|
363
|
-
carries no `DSH_TERMINAL_ID` marker, and the handle keeps the shipped
|
|
364
|
-
`unknown` activity, which never accumulates idle.
|
|
365
|
-
- Windows-folder host terminals (the `powershell.exe` branch) keep the shipped
|
|
366
|
-
behaviour: `unknown` activity, no reclamation.
|
|
142
|
+
- Terminal routing is per-session by workspace coordinates: a WSL-folder session gets
|
|
143
|
+
the distro shell, a Windows-folder session gets `powershell.exe` in its own
|
|
144
|
+
directory. The shell MENU's default entry stays the configured profile; a
|
|
145
|
+
directory-less launch lands in the distro.
|
|
146
|
+
- **Windows git must trust the share** (`safe.directory`), or WSL workspaces'
|
|
147
|
+
changed-files cards silently disappear (the discovery call answers
|
|
148
|
+
`detected dubious ownership`). One-time remedy on Windows:
|
|
149
|
+
`git config --global --add safe.directory "//wsl.localhost/*"`, then restart the
|
|
150
|
+
app. Two alternatives: the doctor's `git.win-safe` probe reports the state, and a
|
|
151
|
+
machine **without** Windows git needs nothing — the provider routes discovery into
|
|
152
|
+
the distro and translates the output into UNC form.
|
|
153
|
+
- **A NEW tab's title reads `WSL`** whatever it runs — the controller titles from the
|
|
154
|
+
composition's profile before the provider rewrites the launch, and a plugin cannot
|
|
155
|
+
correct the default ([UPSTREAM-TERMINAL-TITLE.md](UPSTREAM-TERMINAL-TITLE.md)).
|
|
156
|
+
The menu ships host candidates (`powershell`, `cmd`): a manually selected shell is
|
|
157
|
+
titled after itself; double-click rename also works.
|
|
158
|
+
- **Distro terminals ARE reclaimed when idle** — the handle counts the marked
|
|
159
|
+
processes through the resident agent (exactly one at its prompt = `idle`; a nested
|
|
160
|
+
shell or an `ssh-agent` from startup files reads `busy` by design). Threshold: the
|
|
161
|
+
controller's `unattendedTimeoutMs` (2 h). `terminalIdleReclaim: false` restores
|
|
162
|
+
close-by-hand. Windows-folder host terminals keep the shipped no-reclamation
|
|
163
|
+
behaviour.
|
|
367
164
|
|
|
368
165
|
## Host "Open in app"
|
|
369
166
|
|
|
370
|
-
- **Opening VS Code
|
|
371
|
-
|
|
372
|
-
`
|
|
373
|
-
`Code.exe` starts as Node, exits 1, and the UI reports launch-failed. File
|
|
374
|
-
Explorer and other non-Electron launchers are unaffected; the failure is the
|
|
375
|
-
same on a Windows folder and on a WSL UNC workspace. This repository does
|
|
376
|
-
not own that spawn path. The record and the upstream fix are in
|
|
167
|
+
- **Opening VS Code from "Open in app" can fail independently of this plugin** — the
|
|
168
|
+
Host runs with `ELECTRON_RUN_AS_NODE=1`, which the launcher inherits, so
|
|
169
|
+
`Code.exe` starts as Node and exits 1. Not owned by this repository:
|
|
377
170
|
[UPSTREAM-OPEN-IN-APP.md](UPSTREAM-OPEN-IN-APP.md).
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# Limitations
|
|
2
|
+
|
|
3
|
+
_本文是 [LIMITATIONS](LIMITATIONS.md) 的中文译本,以英文版为准。_
|
|
4
|
+
|
|
5
|
+
插件不做什么,以及为什么。写进文档的限制是设计边界;
|
|
6
|
+
没写进文档的迟早会变成 bug 报告。每一条都说明限制本身和补救方法;
|
|
7
|
+
推理过程在 [ARCHITECTURE.md](ARCHITECTURE.md) 及其链接的文档中。
|
|
8
|
+
|
|
9
|
+
## 文件系统 substrate(`wsl-fs`)
|
|
10
|
+
|
|
11
|
+
只有一个 substrate:发行版内的常驻 agent(发行版文件用 ext4)。已退役的
|
|
12
|
+
Windows 侧 share 后端(`substrate: "share"`)在启动时被拒绝;WSL 路由的
|
|
13
|
+
操作永远不会走 9p 共享。
|
|
14
|
+
|
|
15
|
+
- **新建文件以 0600 权限发布。** 覆盖与编辑保留原有权限位;
|
|
16
|
+
新文件需要可执行时请 `chmod`。
|
|
17
|
+
- **agent 不在时,文件操作关闭(fail closed)** —— 报 `FS_IO_ERROR`,指明
|
|
18
|
+
substrate,并附带恢复路径。一次崩溃可用一次重建;重建耗尽后进入有界复活
|
|
19
|
+
(2/4/8 s);三次死亡则永久退出,直到应用重启。没有回退 I/O——这是有意为之。
|
|
20
|
+
- **变更守卫不是原子的比较并交换。** 创建使用不可替换的 link;覆盖在 rename 前
|
|
21
|
+
会重新校验版本。不匹配时返回 `FS_STALE_VERSION`,但检查与 rename 之间发生的
|
|
22
|
+
变更仍可能被覆盖。
|
|
23
|
+
- **被取消的写入结果可能是未知的** —— 重试前先检查文件。
|
|
24
|
+
- **写入成功不等于崩溃持久。** 被暂存的文件会 sync,但所在目录不会:
|
|
25
|
+
断电可能丢失该目录项。
|
|
26
|
+
- **含反斜杠的 Linux 文件名无法被寻址** —— 反斜杠是 UNC 分隔符,
|
|
27
|
+
`a\b.ts` 会往返成 `a/b.ts`。请把文件名改掉,不要用反斜杠;
|
|
28
|
+
这是坐标系自身的限制。
|
|
29
|
+
- **受限写入运行在各自的 bwrap 约束常驻 agent 上** —— 由内核强制,
|
|
30
|
+
工作区之外以 `FS_SANDBOX_DENIED` 拒绝。代价:需要 `bubblewrap`;
|
|
31
|
+
策略变更要付出一次 agent 启动;授予 `/tmp` 的写入落在发行版真实的
|
|
32
|
+
`/tmp` 中。
|
|
33
|
+
- **`glob`/`grep` 使用发行版的 rg**;没有 rg 的发行版搜索失败
|
|
34
|
+
(退出码 127)。安装:`wsl.exe -d <distro> -u root -- apt-get install -y bubblewrap
|
|
35
|
+
git ripgrep inotify-tools`(或检测到的包管理族对应的写法)。`--install` 只在
|
|
36
|
+
缺少 `bubblewrap` 时才执行安装。
|
|
37
|
+
- **`watch()` 是粗粒度失效,不是逐文件通知**:inotify 事件合并到 0.5 s,
|
|
38
|
+
否则使用 `find -newer` 轮询循环。轮询路径会遗漏原地的 `cp -p`,以及恢复
|
|
39
|
+
较旧 mtime 的 `tar -x`;FILE 目标监视的是 `mtime:size` 指纹。从 Windows
|
|
40
|
+
侧做 9p 监视仍然不支持。
|
|
41
|
+
- **`sessionSandbox: false` 时,受限命令的 `/tmp` 与写入工具的 `/tmp`
|
|
42
|
+
不同** —— 请通过会话工作区交接文件。随包 preset(`sessionSandbox: true`)
|
|
43
|
+
共享同一个真实 `/tmp`,不存在这种分裂。
|
|
44
|
+
- `editText` 在写回之前把整个文件读进内存。
|
|
45
|
+
- 发行版在请求进行中死亡,会耗尽常驻 agent 的唯一一次重建;
|
|
46
|
+
仍未恢复时,常驻 agent 将保持不可用,直到应用重启。
|
|
47
|
+
|
|
48
|
+
## GUI 的 workspace-files 服务
|
|
49
|
+
|
|
50
|
+
- GUI 文件树和预览使用随包的 `workspace-files` 行,通过被路由的根文件系统
|
|
51
|
+
读取 WSL 路径——这是有意不替换的(服务端单方面接管会丢掉侧边栏预览
|
|
52
|
+
所依赖的浏览器 `file` 资源提供者)。
|
|
53
|
+
- **预期多出几轮往返**(每次 listDir/stat/分页约 4-6 次;实测 p95:
|
|
54
|
+
`stat` 2.7 ms、`listDir` 5.1 ms、1 MiB 读取 63.5 ms/MiB —— 2026-10-19,
|
|
55
|
+
[探针](https://github.com/Big-Dao/dsh-plugin-wsl-env/blob/main/test/probe/gui-preview.sh))。
|
|
56
|
+
模型自己的文件操作不受影响。
|
|
57
|
+
- `fs-routing` 是接缝:禁用它即可恢复宿主 9p 支撑的根文件系统。
|
|
58
|
+
随包行的上限可以在 `workspace-files` 行上覆盖。
|
|
59
|
+
|
|
60
|
+
## `@` 补全
|
|
61
|
+
|
|
62
|
+
- **`@` 引用走 9p 共享**(在已探针的目录树上比 ext4 慢约 56 倍,
|
|
63
|
+
2026-10-19)。发行版侧的遍历已经写好,但尚未挂载——它需要上游的
|
|
64
|
+
`FileReferenceTraversal` 接缝:
|
|
65
|
+
[UPSTREAM-FILE-REFERENCE-SEAM.md](UPSTREAM-FILE-REFERENCE-SEAM.md)。
|
|
66
|
+
|
|
67
|
+
## 沙箱
|
|
68
|
+
|
|
69
|
+
- **一次性回退路径只有宿主截止时间** —— `wsl.exe` 被杀时不会把 kill 传进
|
|
70
|
+
发行版,因此 Linux 进程树可能比调用活得更久。长命令建议
|
|
71
|
+
`agent: true` 并使用会话 runtime。
|
|
72
|
+
- **批量读取按发行版共享一个原生 agent**(16 个并发调用 2.4-4.1 ms;
|
|
73
|
+
第一次调用要付出约 70 ms 的准备)。
|
|
74
|
+
- **aarch64 只在 CI 中于 qemu 下运行**(身边没有 Windows-on-ARM 机器——
|
|
75
|
+
[COMPATIBILITY.md](COMPATIBILITY.md#scope-decisions))。`auto` 仍会选择
|
|
76
|
+
包内二进制;`native` 在准备失败时拒绝;`shell` 强制使用 shell agent。
|
|
77
|
+
- **会话 runtime 需要 systemd 和带 linger 的 user manager** —— 否则在
|
|
78
|
+
第一条命令时被拒绝并给出补救方法;`sessionSandbox: false` 保留旧的
|
|
79
|
+
按命令形态。应用硬崩溃可能留下过期的 `dsh-wsl-*.scope`;预检会在下一个
|
|
80
|
+
WSL 会话时清扫过期 scope(从不清扫其他活跃会话的)。
|
|
81
|
+
- **沙箱不管辖 WSL interop** —— 上报为 `enforcement: partial`。
|
|
82
|
+
`maskWindowsDrive` 收窄 `/mnt` 但不是关闭;发行版级 `wsl.conf` 中的
|
|
83
|
+
`[interop] enabled=false` 需要另行配置。
|
|
84
|
+
[ARCHITECTURE.md](ARCHITECTURE.md#reported-enforcement)。
|
|
85
|
+
- **必须安装 `bubblewrap`**,否则两个提供者都会带着安装命令失败;
|
|
86
|
+
会话启动预检会以建议性警告的形式给出同样的文本。
|
|
87
|
+
- 会话中途被删除的工作区目录会以 runner 错误失败;插件不会重建它。
|
|
88
|
+
配置的 `cwd` 不存在时,报错会指明该目录。
|
|
89
|
+
|
|
90
|
+
## shell 启动语义(`wsl-shell`)
|
|
91
|
+
|
|
92
|
+
命令以 `<shell> -lc <command>` 运行——登录 shell(会 source profile 脚本,
|
|
93
|
+
因此版本管理器可用)且**非交互**(模型的输出流中没有提示符/插件输出)。
|
|
94
|
+
|
|
95
|
+
- **`~/.zshrc` / `~/.bashrc` 永远不会被读取。** 把机器级的 PATH 设置
|
|
96
|
+
(`fnm`、nvm、cargo)移进 `~/.zprofile` 或 `~/.zshenv`;提示符和补全
|
|
97
|
+
留在 rc 文件里。
|
|
98
|
+
- **fnm 的 multishell PATH 在沙箱内无法形成**(`/run` 只读)——
|
|
99
|
+
使用 [Configuration](CONFIGURATION.md#zshfnm-recipe-for-model-run-commands)
|
|
100
|
+
中的回退配方。
|
|
101
|
+
|
|
102
|
+
## Agent 诊断
|
|
103
|
+
|
|
104
|
+
- **doctor 的 `agent.handshake` 探测的是一个新起的常驻 agent**,而不是
|
|
105
|
+
在跑的那个——运行中常驻 agent 的 `lastError` 留在应用日志里
|
|
106
|
+
(进程内可通过 `WslAgent.status()` 查看)。
|
|
107
|
+
- **`dsh-wsl-*.scope` 计数是某一时刻的快照**;`--fix` 会执行预检的清扫。
|
|
108
|
+
- **doctor 的 `git.win-safe` 探针通过 interop 的 `git.exe` 报告 Windows git
|
|
109
|
+
的信任状态** —— 装了 Windows git 时,WSL 变更审查需要那份
|
|
110
|
+
`safe.directory` 白名单(见下文"终端")。
|
|
111
|
+
|
|
112
|
+
## 会话启动建议
|
|
113
|
+
|
|
114
|
+
- **DSH 0.2.1 没有会话 UI 通知接缝。** `wsl-preflight` 的失败进入宿主日志
|
|
115
|
+
和会话的 runtime context(下次组装时对模型可见)——仅供参考,从不安装、
|
|
116
|
+
从不取消。对上游的请求见:
|
|
117
|
+
[UPSTREAM-SESSION-NOTICE.md](UPSTREAM-SESSION-NOTICE.md)。
|
|
118
|
+
- **runtime context 条目是重复的快照,不是可关闭的通知。**
|
|
119
|
+
移除 `wsl-preflight` 行即可让该探针完全静音。
|
|
120
|
+
|
|
121
|
+
## 端口
|
|
122
|
+
|
|
123
|
+
- **端口轮询让常驻 agent 在每个活跃发行版上永久保温**
|
|
124
|
+
(10 s 刷新)。把 `portsRefreshMs` 调到超过 agent 的空闲超时即可恢复
|
|
125
|
+
空闲关停,代价是端口事实更陈旧。
|
|
126
|
+
|
|
127
|
+
## 目录选择器
|
|
128
|
+
|
|
129
|
+
- 发行版层级从发行版内部一次往返完成列举(NUL 分隔,因此含换行的
|
|
130
|
+
目录名也能精确列出——WSL-306);常驻 agent 不在时用一次性的
|
|
131
|
+
`wsl.exe --exec find`。
|
|
132
|
+
|
|
133
|
+
## 终端
|
|
134
|
+
|
|
135
|
+
- 终端路由按会话的工作区坐标进行:WSL 文件夹会话获得发行版 shell,
|
|
136
|
+
Windows 文件夹会话在其自身目录获得 `powershell.exe`。shell 菜单的
|
|
137
|
+
默认条目保持为配置的 profile;不带目录的启动落在发行版内。
|
|
138
|
+
- **Windows git 必须信任该共享**(`safe.directory`),否则 WSL 工作区的
|
|
139
|
+
changed-files 卡片会无声消失(发现调用返回 `detected dubious ownership`)。
|
|
140
|
+
Windows 上的一次性补救:
|
|
141
|
+
`git config --global --add safe.directory "//wsl.localhost/*"`,然后重启
|
|
142
|
+
应用。两个替代方案:doctor 的 `git.win-safe` 探针可报告该状态;而
|
|
143
|
+
**没有** Windows git 的机器什么都不用做——提供者把发现请求路由进
|
|
144
|
+
发行版,并把输出转写成 UNC 形式。
|
|
145
|
+
- **NEW 标签页的标题显示 `WSL`**,无论它实际运行什么——控制器在提供者
|
|
146
|
+
改写启动命令之前就按 composition 的 profile 命名了,而插件无法纠正
|
|
147
|
+
这个默认值([UPSTREAM-TERMINAL-TITLE.md](UPSTREAM-TERMINAL-TITLE.md))。
|
|
148
|
+
菜单附带宿主候选(`powershell`、`cmd`):手动选择的 shell 会以自身命名;
|
|
149
|
+
双击重命名也可行。
|
|
150
|
+
- **发行版终端空闲时确实会被回收** —— 通过常驻 agent 对被标记的进程计数
|
|
151
|
+
(提示符处恰好一个进程 = `idle`;嵌套 shell 或启动文件拉起的
|
|
152
|
+
`ssh-agent` 按设计读作 `busy`)。阈值是控制器的
|
|
153
|
+
`unattendedTimeoutMs`(2 h)。`terminalIdleReclaim: false` 恢复手动关闭。
|
|
154
|
+
Windows 文件夹的宿主终端保持随包的不回收行为。
|
|
155
|
+
|
|
156
|
+
## 宿主的"在应用中打开"
|
|
157
|
+
|
|
158
|
+
- **从"在应用中打开"启动 VS Code 可能独立于本插件而失败** ——
|
|
159
|
+
宿主带着 `ELECTRON_RUN_AS_NODE=1` 运行,启动器会继承它,于是
|
|
160
|
+
`Code.exe` 以 Node 身份启动并以退出码 1 结束。不属于本仓库管:
|
|
161
|
+
[UPSTREAM-OPEN-IN-APP.md](UPSTREAM-OPEN-IN-APP.md)。
|
package/docs/QUICKSTART.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Quickstart
|
|
2
2
|
|
|
3
|
+
*A Chinese translation lives at [QUICKSTART.zh](QUICKSTART.zh.md); the English version is authoritative.*
|
|
4
|
+
|
|
3
5
|
Steps from a Windows + WSL 2 machine to a working WSL session.
|
|
4
6
|
This page is the entry point; every claim links to the document that owns it.
|
|
5
7
|
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# Quickstart
|
|
2
|
+
|
|
3
|
+
_本文是 [QUICKSTART](QUICKSTART.md) 的中文译本,以英文版为准。_
|
|
4
|
+
|
|
5
|
+
从一台 Windows + WSL 2 机器到可用的 WSL 会话,只需以下步骤。
|
|
6
|
+
本页是入口;每条说明都链接到负责该主题的文档。
|
|
7
|
+
|
|
8
|
+
## 1. 前置条件
|
|
9
|
+
|
|
10
|
+
| 需求 | 如何检查 | 缺失时 |
|
|
11
|
+
|---|---|---|
|
|
12
|
+
| Windows 10/11,启用 WSL 2 | `wsl.exe -l -v` 显示 `VERSION 2` | `wsl.exe --install` |
|
|
13
|
+
| 一个发行版 | 上面列表中有一行 | `wsl.exe --install -d Ubuntu` |
|
|
14
|
+
| `bubblewrap` | `wsl.exe -d <distro> -- which bwrap` | `wsl.exe -d <distro> -u root -- apt-get install -y bubblewrap` |
|
|
15
|
+
| systemd + Linger(会话 runtime 需要) | `node <plugin>/scripts/doctor.mjs`(已安装)或 `pnpm run doctor`(检出目录) | 在 `/etc/wsl.conf` 设置 `[boot] systemd=true`,`wsl --shutdown`,然后 `loginctl enable-linger "$USER"`;或设置 `sessionSandbox: auto`/`false` |
|
|
16
|
+
| Node `^22.19.0` 或 `>=24.0.0` | 仅源码/开发工作需要 | 使用已发布的插件时,发行版内不需要 Node |
|
|
17
|
+
|
|
18
|
+
doctor(`scripts/doctor.mjs`,或在检出目录中运行 `pnpm run doctor`)会检查
|
|
19
|
+
环境并针对检测到的失败打印补救方法。发行版的 WSL 版本请单独用
|
|
20
|
+
`wsl.exe -l -v` 确认。doctor 默认只读,除非传入 `--fix`(清理过期 scope)或
|
|
21
|
+
`--fix --install`(安装软件包,需确认)。
|
|
22
|
+
|
|
23
|
+
## 2. 安装
|
|
24
|
+
|
|
25
|
+
在一个可以使用 DSH CLI 的 Windows PowerShell 中:
|
|
26
|
+
|
|
27
|
+
```powershell
|
|
28
|
+
dsh plugin --profile <profile> add dsh-plugin-wsl-env
|
|
29
|
+
# 若解析到旧版本(发布当天的元数据缓存),显式指定:
|
|
30
|
+
# dsh plugin --profile <profile> add dsh-plugin-wsl-env@<latest>
|
|
31
|
+
dsh --profile <profile> --dump-config # 输出中必须包含插件的行
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
如果 profile 已覆盖 preset,请保持覆盖为完整的
|
|
35
|
+
`preset-wsl` 块——当某个层直接按 id 指向 `wsl-shell`/`wsl-fs` 时,
|
|
36
|
+
`pnpm run preset --check <patch>` 会明确报错。
|
|
37
|
+
|
|
38
|
+
## 3. 打开 WSL 工作区
|
|
39
|
+
|
|
40
|
+
在 GUI 中,通过选择器打开文件夹(`\\wsl.localhost\<distro>\home\<you>\proj`),
|
|
41
|
+
或直接打开 UNC 路径。会话的命令、文件工具、终端和预览
|
|
42
|
+
都在该发行版中运行;打开 Windows 文件夹则保留正常的 Windows 环境。
|
|
43
|
+
两个发行版可以同时打开,各自拥有自己的环境。
|
|
44
|
+
|
|
45
|
+
### 在 VS Code 中打开工作区(Remote-WSL)
|
|
46
|
+
|
|
47
|
+
把 WSL 文件夹当作 Windows UNC 共享打开也可行,但属于降级模式:
|
|
48
|
+
文件走 9p,终端是 PowerShell,扩展运行在 Windows 上。完整的
|
|
49
|
+
集成方式是 VS Code 的 Remote-WSL——server 运行在发行版内,集成终端
|
|
50
|
+
是发行版的 shell,文件操作直接落在本地 ext4 上:
|
|
51
|
+
|
|
52
|
+
```powershell
|
|
53
|
+
# from the plugin checkout:
|
|
54
|
+
scripts/open-wsl-vscode.ps1 -Path "\\wsl.localhost\ubuntu\home\me\proj"
|
|
55
|
+
# or by hand:
|
|
56
|
+
code --folder-uri "vscode-remote://wsl+ubuntu/home/me/proj"
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
要求 PATH 上有 `code` shim(VS Code 安装器会添加),以及
|
|
60
|
+
Remote-WSL 扩展。
|
|
61
|
+
|
|
62
|
+

|
|
63
|
+
|
|
64
|
+
终端面板运行在同一个发行版中——`uname -a` 返回的是
|
|
65
|
+
发行版的内核,而不是 Windows 的:
|
|
66
|
+
|
|
67
|
+

|
|
68
|
+
|
|
69
|
+
侧边栏的预览也能就地渲染 WSL 文件:
|
|
70
|
+
|
|
71
|
+

|
|
72
|
+
|
|
73
|
+
## 4. 验证
|
|
74
|
+
|
|
75
|
+
让模型执行一条命令,例如 `pwd && uname -a`。预期结果:Linux 路径和
|
|
76
|
+
发行版内核。如果第一条命令失败,错误信息会给出补救方法;
|
|
77
|
+
[doctor](CONFIGURATION.md) 和[故障排查表](../README.md#troubleshooting)
|
|
78
|
+
覆盖了常见情况。
|
|
79
|
+
|
|
80
|
+
下面的探针会实时运行随包的、由内核强制的 bwrap profile——
|
|
81
|
+
工作区内的写入生效,测试性的区外写入被以 `EROFS` 拒绝,
|
|
82
|
+
严格 profile 则把 `/mnt` 完全隐藏
|
|
83
|
+
([`test/probe/sandbox.sh`](https://github.com/Big-Dao/dsh-plugin-wsl-env/blob/main/test/probe/sandbox.sh)):
|
|
84
|
+
|
|
85
|
+
这是部分隔离,不是 Windows 安全边界:Windows interop
|
|
86
|
+
仍可启动 `/mnt` 之外的可执行文件。见 [LIMITATIONS.md](LIMITATIONS.md)。
|
|
87
|
+
|
|
88
|
+

|
|
89
|
+
|
|
90
|
+
## 配方
|
|
91
|
+
|
|
92
|
+
命令在模型的会话中运行,位于发行版内,从工作区根目录出发。
|
|
93
|
+
|
|
94
|
+
### Node / TypeScript
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
node -v && pnpm -v
|
|
98
|
+
pnpm install
|
|
99
|
+
pnpm test
|
|
100
|
+
pnpm dev # a dev server survives the command that started it with sessionSandbox: true
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
快照成功后(默认每 10 s 刷新一次),server 的端口可能出现在 `DSH_WSL_PORTS`
|
|
104
|
+
中;从 Windows 通过 `http://localhost:<port>` 访问转发的端口。
|
|
105
|
+
|
|
106
|
+
### Python
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
python3 -m venv .venv && . .venv/bin/activate
|
|
110
|
+
pip install -r requirements.txt
|
|
111
|
+
python -m pytest
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
使用发行版的解释器,而不是 Windows 的:会话的 `python3` 是
|
|
115
|
+
发行版 `PATH` 上的那个(包括 pyenv/conda shim)。
|
|
116
|
+
|
|
117
|
+
### Rust
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
cargo build --release
|
|
121
|
+
cargo test
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
target 会针对发行版的架构(x86_64 或 aarch64)原生编译;
|
|
125
|
+
不要复用 Windows 的 `target/` 目录。
|
|
126
|
+
|
|
127
|
+
### Go
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
go build ./...
|
|
131
|
+
go test ./...
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
模块缓存通常位于发行版用户的 `$GOPATH` 下;其实际位置和底层文件系统
|
|
135
|
+
取决于 Go 环境和挂载情况。
|
|
136
|
+
|
|
137
|
+
### Monorepo
|
|
138
|
+
|
|
139
|
+
把仓库根目录作为工作区打开。每个包的命令都在同一个发行版中运行;
|
|
140
|
+
`pnpm -r`、`turbo`、`nx` 和 `make` 看到的是同一个 Linux 文件系统。
|
|
141
|
+
当 `inotifywait` 可用时,无上限目录的文件监视使用 inotify;
|
|
142
|
+
否则回退到轮询。如果遗漏了某次变更,见
|
|
143
|
+
[CONFIGURATION.md](CONFIGURATION.md) 中的监视器边界。
|
|
144
|
+
|
|
145
|
+
## 兼容性
|
|
146
|
+
|
|
147
|
+
诚实的现状,不是承诺。"已探针"指本仓库记录了一次真机运行;
|
|
148
|
+
"目标"指设计上支持,但这条腿尚未在这里执行过。
|
|
149
|
+
|
|
150
|
+
| 层级 | 配置 | 状态 |
|
|
151
|
+
|---|---|---|
|
|
152
|
+
| 1 | Ubuntu / Debian,systemd,x86_64 | 已探针。Debian 的 harness 级探针针对 `wslfs-debian` profile 通过(2026-10-26)。 |
|
|
153
|
+
| 2 | Fedora、openSUSE、Arch、Alpine;aarch64 原生 agent | 发行版矩阵见 [COMPATIBILITY.md](COMPATIBILITY.md)。aarch64 的 18 项检查在 CI 中于 qemu 下运行;物理 aarch64 WSL 不在范围内。 |
|
|
154
|
+
| 不支持 | WSL 1、Linux/macOS 宿主、无 WSL 2 的 Windows | 不支持;插件依附于 `wsl.exe` |
|
|
155
|
+
|
|
156
|
+
设置 `sessionSandbox: auto` 时,没有 systemd/Linger 的发行版仍能获得
|
|
157
|
+
按命令的 `bubblewrap` 沙箱,且结果会报告更窄的 scope;
|
|
158
|
+
`true` 则直接拒绝。沙箱取舍见
|
|
159
|
+
[CONFIGURATION.md](CONFIGURATION.md#sandbox-and-preflight),实测延迟见
|
|
160
|
+
[PERFORMANCE.md](PERFORMANCE.md)。
|
|
161
|
+
|
|
162
|
+
## 演示
|
|
163
|
+
|
|
164
|
+
上面的截图展示了选择器、终端、预览和沙箱探针。
|
|
165
|
+
目前还没有 GIF。[故障排查表](../README.md#troubleshooting)
|
|
166
|
+
和探针输出(`pnpm run probe:sandbox`、`pnpm run probe:gui-preview`)
|
|
167
|
+
提供了可复现的证据;其余演示工作在
|
|
168
|
+
[ROADMAP-1.0.md](ROADMAP-1.0.md#wsl-302--discoverability-and-docs-entry) 中跟踪。
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-wsl-env",
|
|
3
|
-
"version": "0.17.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.17.1",
|
|
4
|
+
"description": "在 WSL 的 Linux 实例中打开项目工作区,在对应 Linux 空间中进行命令执行、运行 shell 脚本和文件访问,以获得全套 Linux Shell 工具链收益,并通过 bubblewrap 对命令进行沙箱隔离",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|
|
7
7
|
"dsh",
|
|
@@ -100,6 +100,9 @@
|
|
|
100
100
|
"docs/PERFORMANCE.md",
|
|
101
101
|
"docs/COMPATIBILITY.md",
|
|
102
102
|
"docs/CONFIGURATION.md",
|
|
103
|
+
"docs/LIMITATIONS.zh.md",
|
|
104
|
+
"docs/CONFIGURATION.zh.md",
|
|
105
|
+
"docs/QUICKSTART.zh.md",
|
|
103
106
|
"docs/LIMITATIONS.md",
|
|
104
107
|
"docs/RELEASING.md",
|
|
105
108
|
"docs/REQUIREMENTS.zh.md",
|