Repository navigation
Releases: Big-Dao/dsh-plugin-wsl-env
Release list
v0.17.0
Changed
-
Git snapshot spawns are now agent-first with a one-shot fallback.
Protocol v6 adds a stdin payload to the EXEC frame (a seventh header field
flagging one trailing base64 line), which is what letsworkspace-changes'
snapshot calls — includingcheck-ignore -z --stdin— run on the warm
resident agent instead of a one-shotwsl.exe. Measured onubuntu
(2026-11-01): ~232 ms per one-shot git call (nearly allwsl.exeprocess
startup) against ~1.6 ms on the resident — a 6-8 call snapshot saves well
over a second. The per-stream caps map onto the agent's single wire cap with
a client-side per-stream slice (lib/git-exec.js), and the environment
rides the agent's per-requestenv(env --argv prefix, noSETENV
pollution) carrying only the translatedGIT_*names. Both agents speak v6
(the shell agent decodes the payload through command substitution, which
strips trailing newlines — NUL separators survive; the native binary
delivers bytes verbatim), so host library and shipped binaries upgrade
together; the doctor's handshake reports any mismatch. -
WSL change review no longer requires Windows git. When
resolveExecutable('git')fails on the host, the provider answers with a
distro sentinel (/usr/bin/git); every workspace-changes spawn — the
rev-parsediscovery included — then routes into the distro, and the
snapshot facade translates the discovery call's absolute-path output lines
into UNC form (translateDiscoveryOutput). A machine with Windows git is
unchanged (host discovery +safe.directorywhitelist). The
resolveExecutableoverride is scoped to the one name and only fires on
the super's failure. -
The doctor probes the Windows git trust. A new
git.win-safecheck
reports thesafe.directorywhitelist state through interopgit.exe:
okwhen the\\wsl.localhostshare is whitelisted, a warn with the
copy-paste remedy when Windows git does not trust it, and an info (nothing
to do) when there is no Windows git — the distro fallback covers WSL change
review entirely.
Documentation
-
The architecture and upstream-promotion documents reflect the v6 protocol
and the shell-menu candidates; the promotion map records the measurement
that superseded the git one-shot decision. -
The full real-machine suite re-ran 15/0 on 2026-11-01 after the protocol
change landed, and again after the documentation refresh. The safe.directory
prerequisite for WSL change review is now documented with its one-time
remedy. -
The shell menu now offers host shells instead of a single entry.
terminal-controller'sshellCandidatesgrows from[]to
[wsl.exe, powershell, cmd]while the default profile stays
shell: { path: wsl.exe, name: WSL }.wsl.exededupes into the default's
wslkind;powershellandcmdare launches this terminal really starts —
host sessions already run PowerShell outright (hostSessions), and a manually
selected shell is titled after itself, which moves the tab-title workaround
from a user-layer edit into the plugin's own composition
(#8769
remains the real fix for the default entry's title).
v0.16.0
The schema-validated routing row: the root-plane router stops silently
ignoring a mistyped or wrong-typed distro key, and the first pass of the
community-optimization plan stamps the routing documentation and the 1.0
roadmap with dated, current evidence.
Changed
fs-routingrow config is now validated at load time.
WslRoutingFileSystem.Configno longer blindly reuses
LocalFileSystem.Config: it is az.intersectof the host row's schema and
the distro-side keys (distro,wslPath,distroCwd,sandbox,
maskWindowsDrive,restrictToDistro,watchMaxDepth). The Loader now
refuses to mount the row when a key fails its schema — previously a typo or
a wrong type was silently ignored and the distro side ran on defaults. A
nullfield value still falls back to the schema default, which matches how
the Loader treats optional keys. Composition tests mount the row through the
real Loader to assert both the accepted defaults and the rejected values.
Fixed
docs/root-fs-routing.mdclaimed a 12-probe suite with no routing probe, a
unit file outside the test lists, and a 10/10 composition test. All three
had drifted: the suite is 15 probes with--include-fsand includes
fs-routing.sh,test/fs-routing.test.mjsentered both test lists on
2026-10-19 (OI-7), and the composition test is 12/12. A header note stamps
the 2026-10-31 review.docs/ROADMAP-1.0.mdstill described WSL-101 as implemented in the working
tree; it shipped in 0.13.0, and the status now records which acceptance leg
has real-machine evidence and which leg (concurrent two-distro) is still
missing. WSL-104's "routing probe has never been executed" claim is replaced
by the dated 2026-10-31 single-machine 15/0 result; the metrics row's probe
count moves to the current 15-with-fs breakdown.
v0.15.0
The compatibility-matrix release: the support table and its evidence, the
container and nightly legs that keep it current, two fresh-distro first-run
fixes the run surfaced, and the interop stance in one configuration key.
Changed
- WSL-310:
scripts/bootstrap-import.sh— a rootfs tarball becomes a
plugin-ready distro in one pass. The command automates the import traps
the compatibility-matrix run documented: ELF-header architecture detection
(walking bin/ candidates, tolerant of the SIGPIPE an early-exiting dd
causes),wsl --importwith an idempotent already-exists branch, the OOBE
wedge unstick,/etc/wsl.confwith[boot] systemdand[user] default
(an imported default-root distro makes the agent run as root), per-family
package-manager install includingsystemd-sysv— docker-export tarballs
strip/sbin/init, which is what broke the systemd boot chain — Linger as
root, and a final doctor pass. Verified on Debian 13.5 (systemd family:
every check green after the one mandatedwsl --shutdown) and the Alpine
minirootfs (OpenRC: the non-systemd profile per WSL-201, systemd checks
reported as the declared degradation).
Fixed
- WSL-307: the confined resident's spawn preparation is now a named
self-heal checklist. WSL-305 fixed the missing workspace root; the same
fresh-distro class has more members (nosh, nobase64, nosha256sum—
minirootfs ships almost none of this). The confined factory'spreparenow
runs onesh -cround trip whose first failing step names itself
(workspace-root,interpreter,base64,sha256sum), and the failure
reaches the caller asspawn checklist step '<name>' failed in <distro>
instead of a bare handshake code. The root rides as$1, never
interpolated into the script body. Verified end to end on Debian with an
absent workspace root: the checklist creates it and the first confined
write lands.
Fixed
- WSL-306: a directory name containing a newline lists as one exact row in
the picker. The picker's distro-side listing wasls -1ALpline-parsed —
a newline in a name split into two rows or dropped out entirely. The
listing is nowfind -L <dir> -maxdepth 1 -mindepth 1 -type d -print0
(NUL-separated,-type dfiltering in the distro where-Lresolves
symlinks), sorted host-side. Busybox find supports every flag — verified on
alpine:3, debian:stable-slim and fedora:latest with a newline-named
fixture, and the picker probe now carries that fixture against the real
composition. The LIMITATIONS entry retires.
Changed
-
The doctor gains a cross-distro sessions view (WSL-309) and marks live
residents (WSL-313's observable half). A new survey after the runtime
checks lists every running distro with itsdsh-wsl-*.scope, the scope
state and the resident pids the distro's own cgroup attests — the
concurrent-sessions coordinate routing made possible, made visible. The
agent.handshake check now notes when a live resident is already running in
the surveyed distro (and that its fresh probe cannot read that resident's
last error). The scope report records resident pids alongside the stale
verdict. Idle countdowns and lastError stay host-side — no IPC exists, and
the boundary is documented in LIMITATIONS. -
The real-machine probes now run nightly on the self-hosted runner. A
schedule trigger (21:30 UTC) fires only theprobesjob — every hosted job
skips the event — so the Tier 1 "probe suite 100%, nightly" metric collects
its evidence on its own instead of waiting for a manual dispatch. A
dedicated concurrency group folds a backlog of missed nights (runner
offline) into the newest run. The probes still require the harness app to
be closed; a night where it was open reports honestly red. -
The shell agent now runs in CI inside the non-Ubuntu containers it
claims to support.test/agent-container.mjsdrives the real
wsl-agent.shover the v5 frames insidedocker runfor
debian:stable-slim,alpine:3,fedora:latest,opensuse/tumbleweed
andarchlinux— seven checks each: the HELLO handshake with the
in-place script digest, PING, EXEC with exit code and both streams, SETENV
reaching later commands, and the presence ofsha256sum/base64where
the families actually differ (busybox on Alpine, coreutils elsewhere). The
agent-containersCI job runs one container per matrix leg. 35/35 on
first execution; this closes the container leg of WSL-202's acceptance.
v0.14.0
This release fixes the first-run trap the compatibility-matrix run found on a
freshly imported distro, and ships the measured support table the run produced.
Fixed
- WSL-305: the confined resident now creates the workspace root before its
first spawn. The bwrap profile binds the workspace root and bwrap refuses
a missing bind source, so on a freshly imported distro the first confined
write failed with a bareFS_IO_ERROR: agent exited during handshake (code=1)and a misleading remedy.agent-confined.jsgained aprepare
hook (workspace-write only — the only mode that binds the root) that runs
mkdir -pon the Linux-spelled root before bwrap runs, idempotently and
ignoring its own outcome: bwrap stays the authority, and a refusal still
reaches the caller. That refusal now carries evidence: the handshake
failure's message includes a bounded tail of the resident's stderr (the
half-day of diagnosis this cost came from it being swallowed). Three new
checks cover the prepare hook, its argv, and the stderr ride-on; a
fresh-root leg insubstrate.shexercises the absent-root spawn end to
end on a real distro.
Changed
-
Debian is verified end to end, and the run found a first-run trap.
The four harness-level probes pass against awslfs-debianprofile on a
freshly imported Debian 13.5, completing the full stack on top of the
distro-internal suite (docs/COMPATIBILITY.md). Getting there surfaced one
real finding: the confined resident refuses to start when the workspace
root does not exist yet — the bwrap profile binds the root, bwrap requires
the bind source to exist, and a fresh distro has nothing to bind. Creating
the root first turns all ten failing steps green; prior Ubuntu runs had it
left over from earlier probes. The finding is documented in the matrix page
with the suggested spawn-preparation fix. -
The compatibility matrix moves from prose to a measured page.
docs/COMPATIBILITY.mdrecords the WSL-202 evidence: six real WSL2 distros
(Ubuntu, Debian 13.5, Fedora 44, openSUSE Tumbleweed, Arch, Alpine 3.22)
executed the distro-internal probe suite on one machine on 2026-10-26, and
the page explains the two observed differences instead of hiding them — the
session-sandboxbackground-survival leg races WSL session teardown on
three systemd distros (probe artifact, not a plugin claim), and on Alpine
the same leg fails by declaration, because non-systemd distros get the
narrowersessionSandbox: autoscope exactly as P1.1/WSL-201 promises. The
page also records the per-distro import traps (Debian OOBE wedge, Alpine
minirootfs defaulting to root, busybox mktemp, zypper's interactive prompt)
and the reproduction commands. Not yet measured: real aarch64 hardware, the
container-image matrix. -
The aarch64 native agent is executed in CI, not only built. The
native packagesjob installs qemu's user-mode emulator and binfmt
registration (qemu-user+qemu-user-binfmt; noble has no real
qemu-user-staticpackage) and runs the full
18-check spike suite under qemu-user against both the freshly built aarch64
musl binary and the committedagent/native/linux-aarch64/dsh-wsl-agent—
the binary a Windows-on-ARM session would load. User-mode emulation
exercises every check (the static musl binary and the v5 frames are
host-OS-free), verified locally first: 18/18 underqemu-aarch64, with
/proc/self/exeresolving to the target so the HELLO digest binding holds
unchanged. This closes the "build-tested only" gap on WSL-202; a run on
real aarch64 hardware remains open.
v0.13.2
This release tracks DeepSeek Harness 0.2.1-rc.3: the peer declarations become
floor ranges so a newer harness installs the plugin unchanged, and the
lockfile's transitive undici moves past a security advisory that Dependabot
could not clear on its own.
Fixed
undiciin the lockfile moves to the security-fixed8.12.3via a pnpm
override.undicirides in only as an exact transitive pin of
@deepseek-ai/dsh-http-proxy— a package upstream controls — so Dependabot's
security job could resolve no patched version (it reports "the latest
possible version … is 8.11.2" and fails).pnpm-workspace.yamlnow carries
overrides: { undici: '>=8.12.3' }(the pnpm 11 settings home; the
package.jsonpnpmfield is no longer read), the lockfile swaps the one
package, and the override comment records that it is retired when upstream
moves the pin. Nothing undersrc/readsundici; the check chain is
unaffected.
Changed
-
The peer declarations become floor ranges, so a newer harness installs
the plugin unchanged. The@deepseek-ai/dsh-*entries in
peerDependenciesmove from the exact pin to>=0.2.1-rc.3, and
@deepseek-ai/cordis/@deepseek-ai/schemasterywiden from~to^
(^4.0.5,^3.18.5). An app that ships a newer harness release now
satisfies the peers without waiting for a plugin release — same-line rc
bumps and every stable release install cleanly; only a brand-new
prerelease line (0.2.2-rc.1against>=0.2.1-rc.3) still warns, because
semver admits a prerelease only against a comparator on its own tuple and
no range can promise an unpublished line. That residue is what the weekly
peer-canary is for: it still resolves thenextdist-tag into a scratch
tree and flags when the line moves past a floor — the signal to re-run the
docs/PEER-PARITY.mdcomparison and raise the floor in the commit that
matches the harness release. The devDependencies keep their exact pins, so
the gates stay reproducible and the parity audit keeps naming one line.
scripts/doctor.mjslearned the new shapes (peerSatisfiednow reads>=
and^, and itscompareVersionsorders prerelease identifiers by semver
§11 —rc.10aboverc.9),scripts/peer-canary.mjsparses and preserves
the shapes, andAGENTS.md,CONTRIBUTING.md,SUPPORT.mdand
docs/PEER-PARITY.mdstate the new policy. -
The dev pin line moves to DeepSeek Harness
0.2.1-rc.3. Every
@deepseek-ai/dsh-*pin indevDependenciesgoes from0.2.1-rc.2to
0.2.1-rc.3— the release thenextdist-tag resolves to — and
pnpm-lock.yamlrefreshes with it.@deepseek-ai/cordisand
@deepseek-ai/schemasterystay at4.0.5/3.18.5: upstream'svendor/
did not change on this line.No module under
src/changed, and that is the finding. The three peers
this plugin subclasses or replicates ship byte-identicallib/index.js
files on rc.3 —dsh-fs-local(the fsio dialect, still 909 lines),dsh-fs
(theFsVersionbrand) anddsh-bash-local(the spill machinery) — so
every line reference indocs/PEER-PARITY.mdstill names the same code;
only the tarballs' digests changed, and the parity doc now carries the
0.2.1-rc.3ones with the re-check dated 2026-10-26. That byte-identity is
also the evidence behind the new peer floor: the same code answers above
rc.3. Upstream's release notes for the line are UI- and LLM-side
(navigation entry customization, analytics, the official/models
listing): the one composition-visible change is a newentry-customization
UI row in the app's owncordis.patch.yml, which rides with the app's
bundle rather than this plugin's layer.preset-wslneeded no
regeneration —packages/bundle/web-app/presets/standard.patch.ymlis
byte-identical on rc.3 — and the provenance comment incordis.patch.yml
now names the rc.3 tree.
v0.13.1
A patch release restoring wsl-shell-env: the component mounts again, and the
managed DSH_* facts it contributes now reach commands on the default
resident-agent path.
Changed
- Upstream proposals filed. The session-notice seam
(#10263),
thespawnSpecseam (#10261), the fsio export (#10262), the UNC-workspace bug
(#10260) and the WSL-workspaces RFC (#10264) now point at their discussions;
the local drafts carry the links.
Fixed
-
wsl-shell-envno longer fails to activate. The row sweeps the
live-agent list at mount to warm facts for sessions that opened before it,
but declared onlyshellEnvininjectand readctx.agentsas a property.
Cordis 4 refuses that read —cannot get property "agents" without inject—
soapply()rejected and the plugin manager listed the component as 异常
(包含的组件 → 异常), with the whole contribution missing from shell calls.
The sweep now reads the service throughctx.get("agents"), the sanctioned
access for a service this row does not inject: activation no longer depends
on an agent loop, and the sweep still runs when one is composed.
wsl-preflightcarried the same property read inside atry/catch, where
the throw silently disabled its mount sweep; it gets the same fix.
test/shell-env.test.mjsand the real-cordis check in
test/preflight.test.mjsmount both rows against the guard. -
The managed
DSH_*namespace now reaches commands on the default
resident-agent path.ShellExecSpec.dshEnvwas forwarded only by the
one-shotwsl.exefallback;executeViaAgentdropped it before
agent.exec— after the mount fix above, the row registered its facts but
no model shell call could seeDSH_HOME,DSH_SESSION_IDor any
DSH_WSL_*value (env | grep '^DSH_'was empty). The handle now carries
the merged map (envfirst,dshEnvlast — the same order the one-shot
path uses) into the agent'sSETENVframes.test/agent-exec.test.mjsand
test/provider.test.mjspin the frame, and both checks fail against the
previous build.
v0.13.0
This release ships the P0/P1 hardening batch: session-scoped auto
sandboxing, resident-agent recovery with doctor coverage, the packed-install
E2E, measured GUI-preview and @-reference probes, and the docs/community
entry points.
Added
-
pnpm run preset— thepreset-wsloverride helper.wsl-shelland
wsl-fssit insidepreset-wsl'sconfig.plugins, so a patch that targets
them by id is ignored with only a loader warning. The helper prints the
completepreset-wslblock, appliesdistro/sessionSandbox/sandbox/
maskWindowsDrive/agentBackend, and--checkfails loudly with the exact
block to paste when a layer carries the nested id patch. See
docs/CONFIGURATION.md. -
Session-notice upstream payload ready. The exact title, body and
gh
command for the session-visible notice proposal are in
docs/upstream/proposal-session-notice.md; it remains unfiled (a maintainer
action). -
Configuration stability and migration policy.
docs/MIGRATION.mdrecords
the below-1.0 freeze (new keys preserve behavior, removals refuse at boot with
a migration, meaning changes get a note), the deprecation path with the
substrate: "share"anddistro: ''cases, and the release-gate table with
the manual legs (probe suite, clean machine, aarch64) marked unrun.
RELEASING and SUPPORT link it. -
Upstream promotion pack documented.
docs/upstream/promotion-pack.md
records thepackages/wsl/*layout (ten capability packages plus an
integration package), the seam/RFC index, what the composition boot test
covers and its gaps, and the install proof. Promotion stays packaging and
proof, not a dependency. -
@-reference traversal cost measured; seam deferred. The Windows walk
over the WSL share is ~56× the ext4 walk on a 520-entry tree (84.7 ms vs
1.5 ms) and is now reproducible withpnpm run probe:file-reference.
docs/UPSTREAM-FILE-REFERENCE-SEAM.mdrecords the three-function seam, the
ready (unwired) plugin consumer, and the decision to keep the 9p walk until
upstream exposes the funnel. -
Community loop: issue forms, triage targets and a contributor path.
Feature-request and setup-report forms (the setup form requires
doctor --json), a chooser that disables blank issues, SUPPORT's P1
three-business-day target and two-to-four-week cadence, and a
good first issuepath in CONTRIBUTING. -
Quickstart and performance entry pages.
docs/QUICKSTART.mdis the
five-minute path (prerequisites, install, first session, Node/Python/Rust/Go
and monorepo recipes, honest Tier 1/Tier 2 table);docs/PERFORMANCE.md
records the 9p-vs-agent numbers and the GUI-preview baseline with reproduction
commands. The visual demo is explicitly still missing. -
GUI-preview latency has a measured baseline.
pnpm run probe:gui-preview
drives the routed backend'sstat,listDirand 1 MiBreadByteRangewith
p50/p95 budgets (stat 100 ms, listing 500 ms, page 1 s/MiB). On this machine:
p95 2.7 ms / 5.1 ms / 63.5 ms per MiB. No optimization follows; the
workspace-filesrow is untouched. -
LSP seam researched and deferred.
docs/UPSTREAM-LSP-SEAM.mdrecords that
the harness shipsdsh-lsp/dsh-lsp-stdio/dsh-tool-lspbut the 0.2.1-rc.2
base composition mounts none of them, and that a WSL provider needs a
preset-scoped subprocess provider plus URI translation. The prototype is
deferred behind an explicit trigger; no LSP claim is made in the README. -
Peer-upgrade canary. A weekly workflow resolves the
nextdist-tag of
every pinned peer, tests that line in a scratch copy (pnpm test+ the
composition boot test), and publishes the exact version delta in the run
summary; a moved line that fails fails the run.pnpm run canary:peers
performs the check locally. -
The strict profile is documented and probed.
maskWindowsDrive: truewith
sandbox: truehides/mntfrom confined profiles; CONFIGURATION describes
the trade-offs and the visible/masked/disabled interop matrix, and
probe:sandboxnow asserts the masked leg (empty/mnt, unreachable Windows
binary, writable workspace). Enforcement stayspartial; the binfmt content
escape and the distro-level closure are stated. -
Install/upgrade/uninstall E2E.
pnpm run e2e:installpacks the tree,
installs it into a fresh%TEMP%DSH_HOME, composes--dump-config, removes
it, and upgrades from the previous minor with a marker proving the profile
layer survived.test/packaging.test.mjskeeps the packed file list honest in
pnpm test. -
The doctor probes the resident agent.
agent.handshakestarts the
shipped shell agent in the distro, reads its HELLO (pid, protocol, script
digest) and stops it, so a missing or stale agent script is a named check
instead of a first-command failure. It also reports the agent cache and the
live/staledsh-wsl-*.scopecounts. -
pnpm run doctor— one command for the environment prerequisites. Reports
Windows/WSL versions, the distro and its login shell, bubblewrap usability,
systemd / the user manager /systemd-run/ Linger, the optional tools, peer
versions, the native-agent cache, staledsh-wsl-*.scopeunits and the
profile-patch trap that silently ignoreswsl-shell/wsl-fsoverrides. Each
failure carries one copy-paste remedy;--jsonis the machine shape, and
--fixonly sweeps stale scopes unless--installplus a confirmation allows
a package install.diagnoseis now the read-only alias.
Changed
-
sessionSandbox: auto— the session runtime is an optimization, not a
prerequisite.wsl-shellandwsl-fsaccepttrue,falseor"auto";
autouses the systemd user scope when it is available and otherwise falls
back to the per-commandbwrapprofile — still confined, never unconfined —
reportingsessionScope: "session" | "per-command"in the command result.
A failed verdict is re-probed at most once a minute, so installing systemd or
enabling Linger upgrades without an app restart.truekeeps refusing with
the existing remedy;falseis unchanged. -
The session preflight also checks the session-runtime prerequisites. It
now probes systemd as PID 1, the user manager,systemd-runand Linger —
not onlybubblewrap— when a distro session opens, and publishes the same
advisory to the session's runtime context as well as the log.sessionSandbox: falseon thewsl-preflightrow skips the session half. DSH 0.2.1 has no
session-notice seam; the ask is recorded in
docs/UPSTREAM-SESSION-NOTICE.md and the
boundary in docs/LIMITATIONS.md.
Advisory only: nothing is installed and fail-closed behavior is unchanged. -
The distro follows the workspace.
distro: ''/'auto'now selects the
distro from the request workdir or target UNC;'default'forces WSL's
default, and an explicit name still pins. Shell, files, root-plane routing,
the GUI terminal andshell-envall use the same rule, and the shared agent,
substrate, home and session-runtime state are cached per distro. A pinned
profile still refuses a foreign UNC withFS_OUTSIDE_DISTRO. This changes the
previous meaning ofdistro: ''(WSL's default): set'default'to keep that
posture.
Fixed
-
A dead resident revives with bounded backoff instead of staying dead for
the process lifetime. A failure that spends the one permitted rebuild now
leaves the agentdeadwith a revival scheduled (2 s, 4 s, 8 s); the next
call after the delay gets a fresh transport and its own single rebuild, and
nothing from the dead generation is replayed. After three deaths the state is
permanent and the refusal names the restart remedy.status()exposes the
live state, pid, last error and pending count for diagnostics. -
A cancelled command's signal can no longer kill the next one. The
out-of-band USR1 carries no request id: a signal meant for a timed-out
command could land after the agent had moved on, and the next command then
died with exit 138 but reportedcompleted. A host timeout no longer sends
the signal at all (the in-distro watcher is the one that kills that
command), and an explicit cancel now waits briefly and is dropped once the
request answered or a newer request is running. -
A missing workdir is reported with its path on the native agent. The
native agent reports the failed spawn as reasonspawn, without naming the
directory; the executor now treats that as its own error class and retries
the command on the one-shot path, whosechdir(<path>) failedwarning names
the directory.
v0.12.0
The GUI file service keeps the shipped workspace-files row: its server-only
replacement dropped the browser file resource provider and broke Sidebar
previews. The shipped row now serves WSL paths through fs-routing. The
wsl-shell.maskWindowsDrive wiring fix ships with it.
Fixed
-
wsl-shell.maskWindowsDrivereaches the per-command sandbox. The shell
executor built itsWslSandboxwithout the configured key. With
sessionSandbox: falsethe per-command profile therefore left/mnt
visible after the operator asked for the mask; withsessionSandbox: true
the runtime still masked the drive, but the decorated result reported
windowsDrive: "visible". The executor now passes the value, matching
wsl-fsandfs-routing. -
Sidebar previews work: the GUI file service keeps the shipped
workspace-filesrow. The formerworkspace-files-wsltakeover replaced
that row with a server-only subclass, which dropped the browserfile
resource provider and made every Sidebar preview report "The file resource
service is unavailable." The shipped row now serves WSL paths through
fs-routing, so the server and client halves stay paired. The trade-off is
GUI-only: a listing,stator page read takes roughly four to six agent round
trips instead of one, and a deep page read streams the preceding lines through
the pipe. The model's file tools, search and@references are unaffected.
v0.11.0
The shared read agent is the native binary (A7): unconfined reads now use the
packaged static-musl agent with the shell as the fallback, cutting 16
concurrent reads from 342-614 ms to 2.4-8.2 ms. The A6 FS measurement and
metrics ship with it.
Added
-
The shared read agent is native (A7).
WslAgentgained an async
prepare()hook;sharedAgentuses it to select the packaged native binary
throughagentBackend(auto/native), with the shell as the fallback
(shell, a failed preparation, or a native HELLO/runtime failure; the reads
never fail closed). The A6 probe measures 16 concurrent shared reads at
2.4-8.2 ms, down from 342-614 ms on the shell. The native shared agent also
serves the picker, the subprocess activity probes and the non-session
command path, so those inherit EXEC concurrency. -
WslAgent.metrics(). Per-kind queue wait (sent -> ACK), execution time
(ACK -> settle), in-flight peak, rejected and cancelled counters; the
47-check lifecycle suite covers the counters and the reset. -
test/probe/fs-queue.mjs. A live measurement of both FS agents. The A6
run showed the shared shell reads were the batch bottleneck (16 concurrent
stat/read/listat 342-614 ms, queue up to 583 ms) while native FS ops
execute in 0.2-0.3 ms; the A7 run measures the native shared reads at
2.4-8.2 ms. v6's lock removal only takes 16 native writes from 7.7 ms to
~5 ms, so it stays deferred.
Changed
-
The plan after the A6 measurement: A7 (native shared reads) landed, v6
deferred.docs/ROADMAP.mdrecords the done, next and
deferred steps; ADR 0002, LIMITATIONS and the READMEs describe the native
shared reads and the v6 deferral. -
test/probe/fs-queue.mjsand the README/LIMITATIONS/ROADMAP describe the
native shared reads;test/agent-shared.test.mjscovers the shell selection.
v0.10.0
The native in-distro agent is the default session-runtime backend: agentBackend: auto
selects the packaged static-musl binary for the distro's architecture, the shipped
shell agent stays as the fallback, and native/shell keep the strict and forced
modes. The peer line moves to DeepSeek Harness 0.2.1-rc.2, which is the desktop
app's own version line. No module under src/ changed, and that is the
finding, not an omission: every peer artifact this plugin subclasses or
replicates is byte-identical to the one shipped for 0.2.0-rc.2, except one
added field in the dsh-api-workspace-files class that WorkspaceFilesWsl
extends (and that package's regenerated typert artifacts), so the seams the
code is built on did not move. What did move is the shipped composition — the
standard agent preset — and the plugin's embedded preset-wsl is regenerated
from it.
Added
-
The watcher uses inotify when it can. With
inotifywaitinstalled and an
unbounded directory target, the in-distro watcher now uses recursive inotify
watches and coalesces events to one callback per 0.5s instead of polling
find. A file target, a positivewatchMaxDepth, or a missing
inotify-toolskeeps the polling fallback, so the backend is optional. The
polling mtime-old blind spot does not apply to the inotify path. -
sessionSandboxruns one session's commands in one scope. With the key
on, the shell executor acquires the runtime for(distro, workspace, policy)on the first command, reuses it for the session, runs the command
WITHOUT the per-command bwrap wrap (the runtime's own mounts confine it),
and releases it from the preset scope's disposer — closing the agent and
stopping the unit, whose cgroup reaps the session's leftovers. On in the
shipped preset; requiresagent: true, systemd as PID 1, a reachable user
manager,systemd-runandLinger. The preflight refuses with the remedy
when one is missing (only a success is cached, so following the remedy
needs no app restart); setsessionSandbox: falsefor the per-command
shape. Validated on a live session:ps -p 1is systemd, the command's
cgroup is thedsh-wsl-*.scope, a background process and/tmpsurvive
across commands, and the unit disappears after the session ends. -
The session-runtime registry.
src/session-runtime.tsrefcounts one
agent per(distro, workspace, policy), composes the systemd user scope
prefix with a bwrap mount profile that avoids a PID namespace (and binds the
real/tmpunderworkspace-write), and tears a runtime down by closing the
agent and then stopping the scope.--unshare-pidis now a profile option
(default true), and the agent accepts an explicit lease independent of the
idle close. Tests cover the refcount, the mode-specific mounts, the mask in
the key, the explicit lease, and the close-then-stop order. It is wired into
the shell executor behindsessionSandbox, with a preflight that refuses
with a remedy; the filesystem provider's unification is next. -
The session-scoped sandbox's launcher primitives.
src/session-scope.ts
builds the systemd user unit name, thesystemd-run --user --scopeprefix,
the stop argv and the preflight probe argv; unit tests pin the shapes and the
sanitization. ADR 0001 records the runtime-ownership decision the wiring
still has to make (the agent is a per-distro singleton today). No behavior
change yet. -
docs/VALUE.zh.mdstates what the plugin is worth, in Chinese, for a
reader deciding whether to install it: the three misalignments it removes,
six values each with its mechanism and the evidence that backs it, the
comparison against no integration / desktop remoting / putting the CLI in
WSL, the costs and limits stated before the sale, how the claims are
proven, and a rule for when not to use it. It is linked from both readme
indexes and sits beside the reference docs, never in place of them.
Changed
- The peer pins move to the
0.2.1-rc.2line:@deepseek-ai/dsh-*at
0.2.1-rc.2,@deepseek-ai/cordisat~4.0.5,@deepseek-ai/schemastery
at~3.18.5, withpnpm-lock.yamlrefreshed. pnpm's release-age policy
records the exact closure it had to exempt inpnpm-workspace.yaml, the way
the harness repository does. The three seam packages were compared byte for
byte against the previous pin:dsh-fs-local'slib/index.js(the fsio
dialect this plugin replicates),dsh-fs's, anddsh-bash-local's (the
spill machinery and thespawnSpecseam) are identical files, so every line
reference indocs/PEER-PARITY.mdanddocs/UPSTREAM-SPAWN-SEAM.mdstill
names the same code — only the tarballs' digests changed, and
docs/PEER-PARITY.mdnow carries the0.2.1-rc.2ones.cordis@4.0.5and
schemastery@3.18.5likewise publish the same bytes as4.0.4and
3.18.4except for their manifests. Elsewhere:dsh-fs@0.2.1-rc.2drops its
unpublished./invariantexport subpath (nothing here imported it),
dsh-subprocess-local's new code is Linux systemd scope selection (this
plugin spawnswsl.exefrom a Windows host),dsh-api-workspace-filesadds
oneresourcefield to its own lookup registration, and the typert protocol
gained modules. preset-wslis regenerated from the shipped standard preset. The rows
that arrived are the clock-readingtime-contextrow,tool-schedule, and
the disabledtool-plugin-market/toolsrow; the rows that left are
tool-ralphand the disabledsubagent_codex/subagent_claude_code
entries, whose providers the preset no longer declares anywhere. The two
tool-subagentrows dropbackgroundMode, the plan-mode section's first
sentence now says the plan is approved throughexit_plan_mode, and
persona.suffixis gone: the host plane's newworking-directoryrow
reports the Session's cwd, so the "Your working directory is …" suffix is no
longer the mechanism. The three deliberate deviations from the shipped copy
— thewsl-envrealm wrapping every row, the fixed shell-tool choice, and
the preset's identity — are unchanged, and the transformation is now
scripts/build-preset-wsl.mjs rather than the
machine-local generator the block used to name: it takes the shipped file and
prints the row, and its output is the committed block byte for byte.docs/UPSTREAM-TERMINAL-FONT.mdcloses: the0.2.1-rc.2line made the
terminal font a user setting (--dsh-font-family-terminal/
--dsh-terminal-font-size), so the hardcoded stack it argued against is now
only the fallback appended after the user's list.
docs/UPSTREAM-TERMINAL-TITLE.mdwas re-checked against the new tag and is
unchanged, including after the terminal controller's 0.2.1 rework: the tab
title is still built fromshell.namealone.
Fixed
-
The GUI file service loads again from a linked install (
workspace-files-wsl
failed to import, and the Desktop host then died on awrite EOF). The module
imports@deepseek-ai/dsh-api-workspace-filesand
@deepseek-ai/dsh-typert-protocolat runtime, but both were declared only as
dev dependencies. The runtime's linked-plugin resolver routes a bare import
to the installation package only when the plugin's own manifest lists that
name underpeerDependencies(dsh-app-boot'sreadPeerNames/
routeLinked), so the import missed, the entry never activated, and Desktop
exited 1; the fatalwrite EOFis the shell writing to that already-exited
host. Both names are peers now, andtest/peers.test.mjsscans every
lib/*.jsimport so an undeclared runtime package fails the check chain
instead of the app. -
A warm native-agent cache no longer kills the Host on startup. The
in-distro copy script prints the cache path and exits without reading stdin
when the digest already matches.copyBinarystill streamed the packaged
binary into that closed pipe and left stdin'serrorevent unhandled; on
Windows the write surfaced aswrite EOFand took the Host down before the
UI could load, so the first run (cold cache) worked and every later start
crashed. The write error is absorbed now — the exit code and stdout already
decide the result — and a POSIX regression check runs an early-exit script
against a payload larger than any pipe buffer. -
The root-plane router survives its first distro target, and that path now
has a regression test. Three defects sat behind the same runtime failure:WslRoutingFileSystemkept its state in ES#privatefields. Cordis
invokes a service method with a shadow receiver (the traceable proxy
ReflectServicebuilds), and private members do not cross it, so the first
routed call threwCannot read private member #distro from an object whose class did not declare it. The fields are plain now, exactly like the host
backend's ownconfigandlocks.- Constructing the distro backend with the router's context re-registered
fsin the same scope (service "fs" has been registered). The backend
now receives anfs-isolated child scope, whilesandboxPolicystill
resolves from the parent. The router captures its construction context,
becausethis.ctxinside a method is the caller's context, not its own. - The row dropped the
static inject = ["sandboxPolicy"]declaration the
shippedfs-sandboxrow carries, although the backend it constructs reads
ctx.sandboxPolicy.defaultMode. That service is provided by a sibling row,
not an ancestor, so the fiber could not resolve it and construction failed
withcannot get property "sandboxPolicy" without inject. The declaration
is restored.
The
0.2.1-rc.2line's newworking-directoryrow resolves the session cwd
through the rootctx.fs, which is what first exercised the distro branch.
test/composition.test.mjsnow bo...