dsh-plugin-wsl-env 0.16.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/CHANGELOG.md +57 -1
- package/agent/native/linux-aarch64/dsh-wsl-agent +0 -0
- package/agent/native/linux-x86_64/dsh-wsl-agent +0 -0
- package/agent/wsl-agent.sh +47 -7
- package/cordis.patch.yml +15 -9
- package/docs/ARCHITECTURE.md +14 -6
- package/docs/CONFIGURATION.md +23 -0
- package/docs/CONFIGURATION.zh.md +288 -0
- package/docs/LIMITATIONS.md +133 -299
- package/docs/LIMITATIONS.zh.md +161 -0
- package/docs/QUICKSTART.md +2 -0
- package/docs/QUICKSTART.zh.md +168 -0
- package/docs/UPSTREAM-TERMINAL-TITLE.md +10 -12
- package/lib/agent-exec.d.ts +5 -0
- package/lib/agent-protocol.d.ts +18 -5
- package/lib/agent-protocol.js +11 -6
- package/lib/agent.d.ts +9 -1
- package/lib/agent.js +2 -2
- package/lib/git-exec.d.ts +117 -0
- package/lib/git-exec.js +105 -0
- package/lib/git-route.d.ts +43 -13
- package/lib/git-route.js +54 -15
- package/lib/subprocess.d.ts +21 -7
- package/lib/subprocess.js +63 -9
- package/package.json +4 -1
- package/scripts/doctor.mjs +20 -0
package/CHANGELOG.md
CHANGED
|
@@ -14,7 +14,63 @@ document: the 0.1.0 entry's `design-history §` references are to
|
|
|
14
14
|
keeps the superseded designs under the section numbers they had while they
|
|
15
15
|
were part of the record.
|
|
16
16
|
|
|
17
|
-
## [
|
|
17
|
+
## [0.17.0] - 2026-11-01
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- **Git snapshot spawns are now agent-first with a one-shot fallback.**
|
|
22
|
+
Protocol v6 adds a stdin payload to the EXEC frame (a seventh header field
|
|
23
|
+
flagging one trailing base64 line), which is what lets `workspace-changes`'
|
|
24
|
+
snapshot calls — including `check-ignore -z --stdin` — run on the warm
|
|
25
|
+
resident agent instead of a one-shot `wsl.exe`. Measured on `ubuntu`
|
|
26
|
+
(2026-11-01): ~232 ms per one-shot git call (nearly all `wsl.exe` process
|
|
27
|
+
startup) against ~1.6 ms on the resident — a 6-8 call snapshot saves well
|
|
28
|
+
over a second. The per-stream caps map onto the agent's single wire cap with
|
|
29
|
+
a client-side per-stream slice (`lib/git-exec.js`), and the environment
|
|
30
|
+
rides the agent's per-request `env` (`env --` argv prefix, no `SETENV`
|
|
31
|
+
pollution) carrying only the translated `GIT_*` names. Both agents speak v6
|
|
32
|
+
(the shell agent decodes the payload through command substitution, which
|
|
33
|
+
strips trailing newlines — NUL separators survive; the native binary
|
|
34
|
+
delivers bytes verbatim), so host library and shipped binaries upgrade
|
|
35
|
+
together; the doctor's handshake reports any mismatch.
|
|
36
|
+
|
|
37
|
+
- **WSL change review no longer requires Windows git.** When
|
|
38
|
+
`resolveExecutable('git')` fails on the host, the provider answers with a
|
|
39
|
+
distro sentinel (`/usr/bin/git`); every workspace-changes spawn — the
|
|
40
|
+
`rev-parse` discovery included — then routes into the distro, and the
|
|
41
|
+
snapshot facade translates the discovery call's absolute-path output lines
|
|
42
|
+
into UNC form (`translateDiscoveryOutput`). A machine with Windows git is
|
|
43
|
+
unchanged (host discovery + `safe.directory` whitelist). The
|
|
44
|
+
`resolveExecutable` override is scoped to the one name and only fires on
|
|
45
|
+
the super's failure.
|
|
46
|
+
|
|
47
|
+
- **The doctor probes the Windows git trust.** A new `git.win-safe` check
|
|
48
|
+
reports the `safe.directory` whitelist state through interop `git.exe`:
|
|
49
|
+
`ok` when the `\\wsl.localhost` share is whitelisted, a warn with the
|
|
50
|
+
copy-paste remedy when Windows git does not trust it, and an info (nothing
|
|
51
|
+
to do) when there is no Windows git — the distro fallback covers WSL change
|
|
52
|
+
review entirely.
|
|
53
|
+
|
|
54
|
+
### Documentation
|
|
55
|
+
|
|
56
|
+
- The architecture and upstream-promotion documents reflect the v6 protocol
|
|
57
|
+
and the shell-menu candidates; the promotion map records the measurement
|
|
58
|
+
that superseded the git one-shot decision.
|
|
59
|
+
- The full real-machine suite re-ran 15/0 on 2026-11-01 after the protocol
|
|
60
|
+
change landed, and again after the documentation refresh. The safe.directory
|
|
61
|
+
prerequisite for WSL change review is now documented with its one-time
|
|
62
|
+
remedy.
|
|
63
|
+
|
|
64
|
+
- **The shell menu now offers host shells instead of a single entry.**
|
|
65
|
+
`terminal-controller`'s `shellCandidates` grows from `[]` to
|
|
66
|
+
`[wsl.exe, powershell, cmd]` while the default profile stays
|
|
67
|
+
`shell: { path: wsl.exe, name: WSL }`. `wsl.exe` dedupes into the default's
|
|
68
|
+
`wsl` kind; `powershell` and `cmd` are launches this terminal really starts —
|
|
69
|
+
host sessions already run PowerShell outright (`hostSessions`), and a manually
|
|
70
|
+
selected shell is titled after itself, which moves the tab-title workaround
|
|
71
|
+
from a user-layer edit into the plugin's own composition
|
|
72
|
+
([#8769](https://github.com/deepseek-ai/deepseek-harness/discussions/8769)
|
|
73
|
+
remains the real fix for the default entry's title).
|
|
18
74
|
|
|
19
75
|
## [0.16.0] - 2026-10-31
|
|
20
76
|
|
|
Binary file
|
|
Binary file
|
package/agent/wsl-agent.sh
CHANGED
|
@@ -24,8 +24,12 @@
|
|
|
24
24
|
# SETENV|<key>|<b64 value> -> export the variable for later requests;
|
|
25
25
|
# <key> must be a POSIX identifier, else
|
|
26
26
|
# ERR|protocol
|
|
27
|
-
# EXEC|<id>|<b64 cwd>|<timeout seconds|0>|<n args>|<output cap bytes|0>
|
|
28
|
-
# followed by <n args> lines of base64, one argv word each
|
|
27
|
+
# EXEC|<id>|<b64 cwd>|<timeout seconds|0>|<n args>|<output cap bytes|0>|<stdin flag|0>
|
|
28
|
+
# followed by <n args> lines of base64, one argv word each, and — when
|
|
29
|
+
# the stdin flag is 1 — one more base64 line: the child's entire stdin.
|
|
30
|
+
# The agent feeds those bytes to the child and closes its stdin, so the
|
|
31
|
+
# child sees EOF at the payload's end. Absent or 0 means the child reads
|
|
32
|
+
# an empty stdin and immediate EOF (the v5 shape).
|
|
29
33
|
# -> ACK|<id> the moment the request is dequeued for execution — the host
|
|
30
34
|
# arms its stuck-request watchdog on this line, so time spent queued
|
|
31
35
|
# behind an earlier request never counts against a budget
|
|
@@ -62,7 +66,14 @@
|
|
|
62
66
|
|
|
63
67
|
set -u
|
|
64
68
|
|
|
65
|
-
|
|
69
|
+
# v6: EXEC gains an optional seventh header field flagging one stdin payload
|
|
70
|
+
# line after the argv words. One documented limitation, the same one SETENV
|
|
71
|
+
# and every argv word already carry: command substitution strips TRAILING
|
|
72
|
+
# NEWLINES from the decoded payload, so a stdin feed ending in `\n` reaches
|
|
73
|
+
# the child without it (NUL bytes — `check-ignore -z` separators — survive).
|
|
74
|
+
# Every known consumer parses line-oriented stdin that tolerates a missing
|
|
75
|
+
# final newline.
|
|
76
|
+
PROTO_VERSION=6
|
|
66
77
|
TMPDIR_AGENT=$(mktemp -d "${TMPDIR:-/tmp}/wsl-agent.XXXXXX")
|
|
67
78
|
OUT_FILE="$TMPDIR_AGENT/out"
|
|
68
79
|
ERR_FILE="$TMPDIR_AGENT/err"
|
|
@@ -254,16 +265,23 @@ run_capture() {
|
|
|
254
265
|
fi
|
|
255
266
|
fi
|
|
256
267
|
CURRENT_ID=$req_id
|
|
268
|
+
# Protocol v6 stdin: when the EXEC dispatch staged a payload file, the child
|
|
269
|
+
# reads its bytes and sees EOF at the file's end; otherwise /dev/null keeps
|
|
270
|
+
# the child off the agent's own stdin (the host pipe).
|
|
271
|
+
spawn_in=/dev/null
|
|
272
|
+
if [ -n "${EXEC_STDIN:-}" ]; then
|
|
273
|
+
spawn_in=$EXEC_STDIN
|
|
274
|
+
fi
|
|
257
275
|
# The inflight marker pauses the lease watchdog for this request and
|
|
258
276
|
# refreshes the temp dir's mtime for the boot sweep; removing it after the
|
|
259
277
|
# answer re-arms both.
|
|
260
278
|
: >"$TMPDIR_AGENT/inflight"
|
|
261
279
|
if [ "$mode" = subshell ]; then
|
|
262
|
-
( "$@" ) >"$spawn_out" 2>"$spawn_err" &
|
|
280
|
+
( "$@" ) >"$spawn_out" 2>"$spawn_err" <"$spawn_in" &
|
|
263
281
|
elif [ -n "$SETSID" ]; then
|
|
264
|
-
( exec setsid "$@" ) >"$spawn_out" 2>"$spawn_err" &
|
|
282
|
+
( exec setsid "$@" ) >"$spawn_out" 2>"$spawn_err" <"$spawn_in" &
|
|
265
283
|
else
|
|
266
|
-
"$@" >"$spawn_out" 2>"$spawn_err" &
|
|
284
|
+
"$@" >"$spawn_out" 2>"$spawn_err" <"$spawn_in" &
|
|
267
285
|
fi
|
|
268
286
|
CURRENT_PID=$!
|
|
269
287
|
# Dispatched: the child exists now, so the host may arm its execution
|
|
@@ -675,7 +693,18 @@ while IFS= read -r line; do
|
|
|
675
693
|
timeout_s=${rest%%|*}
|
|
676
694
|
rest=${rest#*|}
|
|
677
695
|
nargs=${rest%%|*}
|
|
678
|
-
|
|
696
|
+
rest=${rest#*|}
|
|
697
|
+
maxout=${rest%%|*}
|
|
698
|
+
# Protocol v6: an optional seventh field flags one stdin payload line
|
|
699
|
+
# after the argv words. Absent (a stale peer) or 0 keeps the v5 shape —
|
|
700
|
+
# the child reads an empty stdin and immediate EOF.
|
|
701
|
+
stdin_flag=0
|
|
702
|
+
case $rest in
|
|
703
|
+
*\|*) stdin_flag=${rest#*|} ;;
|
|
704
|
+
*) stdin_flag=0 ;;
|
|
705
|
+
esac
|
|
706
|
+
case $stdin_flag in ''|*[!01]*) stdin_flag=0 ;; esac
|
|
707
|
+
EXEC_STDIN=
|
|
679
708
|
cwd=$(b64dec "$cwd_b64")
|
|
680
709
|
if ! cd "$cwd" 2>/dev/null; then
|
|
681
710
|
printf 'ERR|%s|cwd|%s\n' "$req_id" "$(printf '%s' "$cwd" | base64 | tr -d '\n')"
|
|
@@ -704,9 +733,20 @@ while IFS= read -r line; do
|
|
|
704
733
|
printf 'ERR|%s|protocol|%s\n' "$req_id" "$(printf '%s' "an argv payload failed to decode" | base64 | tr -d '\n')"
|
|
705
734
|
continue
|
|
706
735
|
fi
|
|
736
|
+
if [ "$stdin_flag" = 1 ]; then
|
|
737
|
+
IFS= read -r stdin_b64 || exit 0
|
|
738
|
+
if ! b64dec_strict "$stdin_b64"; then
|
|
739
|
+
printf 'ERR|%s|protocol|%s\n' "$req_id" "$(printf '%s' "the stdin payload failed to decode" | base64 | tr -d '\n')"
|
|
740
|
+
continue
|
|
741
|
+
fi
|
|
742
|
+
EXEC_STDIN="$TMPDIR_AGENT/stdin.$req_id"
|
|
743
|
+
printf '%s' "$DEC" >"$EXEC_STDIN"
|
|
744
|
+
fi
|
|
707
745
|
# Dispatched: run_capture prints the ACK once the child exists, so the
|
|
708
746
|
# host's watchdog and USR1 cancel both target a real process.
|
|
709
747
|
run_capture "$req_id" "$timeout_s" "$maxout" exec "$@"
|
|
748
|
+
[ -n "$EXEC_STDIN" ] && rm -f -- "$EXEC_STDIN"
|
|
749
|
+
EXEC_STDIN=
|
|
710
750
|
;;
|
|
711
751
|
FS\|*)
|
|
712
752
|
header=${line#FS|}
|
package/cordis.patch.yml
CHANGED
|
@@ -36,17 +36,20 @@
|
|
|
36
36
|
# `wsl.exe -d <distro> --cd <linux dir>` for the directory the Session
|
|
37
37
|
# supplies. Ordinary `spawn()` — every command, the host ripgrep search,
|
|
38
38
|
# every other consumer — reaches the shipped implementation untouched;
|
|
39
|
-
# 2. the controller is given the
|
|
40
|
-
#
|
|
41
|
-
#
|
|
42
|
-
#
|
|
43
|
-
#
|
|
44
|
-
#
|
|
39
|
+
# 2. the controller is given the composition's default shell profile (the
|
|
40
|
+
# distro shell, so a directory-less launch lands in the distro rather than
|
|
41
|
+
# `cmd.exe` inside a UNC workspace) plus host candidates. The default must
|
|
42
|
+
# stay static — the controller resolves it without a Session — but the
|
|
43
|
+
# candidates make the shell menu honest: `wsl.exe` dedupes into the
|
|
44
|
+
# default's `wsl` kind, and `powershell`/`cmd` are launches this terminal
|
|
45
|
+
# really starts (host sessions run PowerShell outright, and a manually
|
|
46
|
+
# selected shell is titled after itself — the sanctioned bridge over the
|
|
47
|
+
# fixed tab title, see docs/UPSTREAM-TERMINAL-TITLE.md).
|
|
45
48
|
#
|
|
46
49
|
# Consequence: a terminal's EXECUTION WORLD follows the Session — a WSL-folder
|
|
47
50
|
# session runs the distro's shell, a Windows-folder session runs PowerShell in
|
|
48
|
-
# its own directory (`hostSessions` on `subprocess-wsl`) — while the
|
|
49
|
-
# and
|
|
51
|
+
# its own directory (`hostSessions` on `subprocess-wsl`) — while the DEFAULT
|
|
52
|
+
# entry and its title stay the composition's single `WSL` profile, which no
|
|
50
53
|
# Session identity reaches. The title that implies is documented in
|
|
51
54
|
# docs/LIMITATIONS.md and bridged upstream in docs/UPSTREAM-TERMINAL-TITLE.md.
|
|
52
55
|
# ═════════════════════════════════════════════════════════════════════════════
|
|
@@ -60,7 +63,10 @@
|
|
|
60
63
|
path: wsl.exe
|
|
61
64
|
name: WSL
|
|
62
65
|
args: []
|
|
63
|
-
shellCandidates:
|
|
66
|
+
shellCandidates:
|
|
67
|
+
- wsl.exe
|
|
68
|
+
- powershell
|
|
69
|
+
- cmd
|
|
64
70
|
|
|
65
71
|
- insert:
|
|
66
72
|
- id: subprocess-wsl
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -109,9 +109,15 @@ So the layer disables the shipped `subprocess` row and inserts
|
|
|
109
109
|
`dsh-plugin-wsl-env/subprocess` in its place at the app level. The subclass
|
|
110
110
|
overrides two methods: `spawnTerminal` rewrites a `wsl.exe` launch into
|
|
111
111
|
`wsl.exe -d <distro> --cd <linux dir>`, and `spawn()` rewrites two kinds of
|
|
112
|
-
launches into the distro — the file-search tool's packaged ripgrep
|
|
113
|
-
|
|
114
|
-
|
|
112
|
+
launches into the distro — the file-search tool's packaged ripgrep and git
|
|
113
|
+
snapshot commands (each agent-first on the warm resident — protocol v6 carries
|
|
114
|
+
a git call's stdin, and a one-shot call costs ~230 ms of `wsl.exe` startup
|
|
115
|
+
against the resident's ~2 ms — with the one-shot `wsl.exe --exec` handle as
|
|
116
|
+
the fallback when the agent is out). When the HOST has no git,
|
|
117
|
+
`resolveExecutable` answers git with a distro sentinel path, and the snapshot
|
|
118
|
+
command's discovery call (`rev-parse --show-toplevel ...`) routes too, its
|
|
119
|
+
absolute-path output translated back into UNC form — so full WSL change review
|
|
120
|
+
works without Windows git. Both spawn decisions are pure coordinate functions
|
|
115
121
|
(`lib/search-route.js`, `lib/git-route.js`); every other `spawn()` — including
|
|
116
122
|
the one-shot fallback's own `wsl.exe -lc <command>` (the default agent path
|
|
117
123
|
carries the command over the resident instead of spawning) — reaches the shipped
|
|
@@ -120,9 +126,11 @@ implementation untouched, as do the pwsh executor and the LSP host.
|
|
|
120
126
|
The terminal's execution world follows the session (`hostSessions`, default on):
|
|
121
127
|
a WSL-folder session runs the distro shell, a Windows-folder session runs
|
|
122
128
|
`powershell.exe` in its own directory. What still follows the app configuration
|
|
123
|
-
is the shell menu and
|
|
124
|
-
`WSL` shell profile — so a
|
|
125
|
-
|
|
129
|
+
is the shell menu's DEFAULT entry and its title, both derived from the
|
|
130
|
+
composition's single `WSL` shell profile — so a new tab reads `WSL` whatever it
|
|
131
|
+
runs, including a host session's PowerShell. The menu also lists host
|
|
132
|
+
candidates (`powershell`, `cmd`; `wsl.exe` dedupes into the default), and a
|
|
133
|
+
manually selected shell is titled after itself. See [LIMITATIONS.md](LIMITATIONS.md) and
|
|
126
134
|
[UPSTREAM-TERMINAL-TITLE.md](UPSTREAM-TERMINAL-TITLE.md).
|
|
127
135
|
|
|
128
136
|
## Path coordinates
|
package/docs/CONFIGURATION.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Configuration
|
|
2
2
|
|
|
3
|
+
*A Chinese translation lives at [CONFIGURATION.zh](CONFIGURATION.zh.md); the English version is authoritative.*
|
|
4
|
+
|
|
3
5
|
## Profile patches
|
|
4
6
|
|
|
5
7
|
Store profile settings in `$DSH_HOME/profiles/<name>/cordis.patch.yml`. Use
|
|
@@ -154,6 +156,27 @@ With `agent: true`, captured output is limited inside the distro and no spill
|
|
|
154
156
|
file is written. `maxSpillBytes` and `graceMs` apply when `agent: false` or
|
|
155
157
|
when the resident agent is unavailable and execution uses one-shot `wsl.exe`.
|
|
156
158
|
|
|
159
|
+
### zsh/fnm recipe for model-run commands
|
|
160
|
+
|
|
161
|
+
Commands run as `<shell> -lc`, so `~/.zshrc` is never read — see
|
|
162
|
+
[LIMITATIONS](LIMITATIONS.md#shell-startup-semantics-wsl-shell) for why. Put
|
|
163
|
+
fnm's hook in `~/.zprofile` instead, with a fallback for the sandbox, where
|
|
164
|
+
`/run` is read-only and fnm cannot create its multishell symlink directory:
|
|
165
|
+
|
|
166
|
+
```zsh
|
|
167
|
+
# ~/.zprofile — fnm for non-interactive login shells (mirrors the ~/.zshrc hook)
|
|
168
|
+
path=("$HOME/.local/share/fnm" $path) # the fnm binary itself
|
|
169
|
+
_fnm_env="$(command -v fnm &>/dev/null && fnm env --shell zsh 2>/dev/null)" && eval "$_fnm_env"
|
|
170
|
+
if [[ -z "${FNM_MULTISHELL_PATH:-}" || ! -e "${FNM_MULTISHELL_PATH}/bin/node" ]]; then
|
|
171
|
+
path=("$HOME/.local/share/fnm/aliases/default/bin" $path) # sandbox fallback
|
|
172
|
+
fi
|
|
173
|
+
unset _fnm_env
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Interactive terminals keep the hook in `~/.zshrc` and are unaffected; the
|
|
177
|
+
`aliases/default` fallback only takes over where the multishell link cannot
|
|
178
|
+
exist.
|
|
179
|
+
|
|
157
180
|
## Environment forwarding
|
|
158
181
|
|
|
159
182
|
WSL imports a host environment variable only when its name is listed in
|
|
@@ -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)。
|