dsh-wsl-workspace
WSL workspace support for DeepSeek Harness——无缝的 WSL 工作区使用体验,无需在 WSL 之中再安装一个dsh,安装该插件后在 GUI 里直接添加 WSL 工作区即可。WSL workspace support for DeepSeek Harness — Enjoy a seamless WSL workspace experience without needing to install dsh inside WSL. Once this plugin is installed, you can directly add a WSL workspace right from the GUI.
- Stars
- 50
- Language
- TypeScript
- Created
- Aug 14, 2026
- Updated
- Sep 19, 2026
Introduction
dsh-wsl-workspace
English · 中文 · 日本語 · 한국어 · Français · Deutsch · Español · Português · Русский
Add a WSL workspace from the DeepSeek Harness web GUI and run the whole agent session — bash commands and file reads/writes — inside a local WSL distribution with Linux paths. Nothing needs to be installed inside WSL. The session can reach both WSL and Windows at the same time: bash commands run inside the WSL distribution, while Windows files stay accessible via /mnt/<drive> (for example /mnt/c/Users/...).
Install
Pick one of the three ways below, then restart dsh web:
# 1) npm package
dsh plugin --profile web add dsh-wsl-workspace
# 2) GitHub repository (ships the prebuilt lib/, no local build required)
dsh plugin --profile web add https://github.com/6Mikao9/dsh-wsl-workspace
# 3) Local directory (development / self-hosted)
dsh plugin --profile web add D:\path\to\dsh-wsl-workspace
After restarting dsh web, a W button appears beside Settings at the sidebar foot.
Usage
Click the W button beside Settings at the sidebar foot to open the "Add WSL workspace" dialog. Pick a distribution from the list, then browse the directory tree or type an absolute Linux path (for example /home/me/proj) — use the Check button to verify the path exists before creating the workspace. The dialog follows the DeepSeek Harness UI language. The username field is optional: leave it empty to run commands as the distribution's default user, or name a Linux user of that distribution to run the session as that user instead (equivalent to wsl.exe -u <username>). The username only changes the bash tool's run identity — the file tools go through the Windows-side WSL share and are unaffected. Each workspace's username is kept in <dshHome>/wsl-workspaces.json; delete the entry (or recreate the workspace from the dialog) to return to the default user.
Click "Create & open" to start a new session in the workspace. In the new session the bash tool executes commands inside the chosen distribution and read/write/edit operate on WSL files, so every path the model sees is a Linux path. The mode picker keeps working as usual: Standard, PTC, Minimal and Creative each land on their WSL variant automatically (the WSL variant entries in the picker are bilingual, e.g. WSL · Standard mode(标准模式)), and Windows files stay reachable from inside the session under /mnt/<drive> (for example /mnt/c/Users/...). The dialog's "?" button opens a panel with the DSH releases this build declares, how the plugin is used, and the limitations it cannot fix.

Behavior notes
- bash tool: runs inside the WSL distribution as the configured username (empty = the distro default user, often
root), so it can read and write anywhere in the distro. The Windows ACL sandbox cannot wrapwsl.exe— its children run on the Linux kernel side — so WSL itself is the isolation boundary and the DSH file policy does not apply to bash. - File tools (
read/write/edit): go through the Windows-side WSL 9P share and run under the DSH file policy. Underworkspace-write, reads work anywhere but writes are restricted to the session workspace; switch the file policy todanger-full-accessto also allow writes outside it. The username field does not affect the file tools. - Skill catalog: the session's skill catalog is discovered starting at the session cwd's nearest
.gitancestor (falling back to the cwd itself), then scanning downward for.dsh/skills/.agents/skills— including nested projects — bounded to 4 directory levels, 64 skill directories and 4096 visited directories. Register the workspace at the project root you work in; if the registered workspace itself sits inside a larger git repository, the scan starts at that repository's root (matching the host's own rule) and sibling projects may surface. Results are cached for 10 seconds per scan root, so freshly added skills appear within that window; skill bodies always load live. One substrate limit to know: the Windows-side\\wsl.localhostshare cannot resolve Linux symlinks (they read back as unresolvable entries), so a project linked into the workspace vialn -sis not discoverable — the scan walks past it without failing; register the workspace at a level that contains the real project directories instead. Fixed in 0.4.3: a UNC workspace used to deliver no catalog at all. The host skill provider watches the workspace withfs.watch, which fails withEISDIRon\\wsl.localhost\...; that observation is then reported incomplete anddsh-tool-skillwithholds the whole catalog message while a snapshot is incomplete. The generated preset therefore pinswatch: falseon theskill-filesystemrow, so the catalog is scanned once at session start and injected as usual. The only trade-off is live refresh: a skill added while a session is running shows up in the next session, not in the running one (skill bodies are still read live byget). - The garbled
localhostport-forwarding bannerwsl.exeprints to stderr when the distro was not running yet is harmless.
Changelog
0.4.4 — 2026-09-19
- A preset built on top of a WSL variant could not be used at all: this generator recognises its own output by id prefix (
wsl-), so a user preset that started life as a copy ofwsl-standardorwsl-cordis— a "data mode" that carries its own world, say — was treated as a plain source preset and had a second world group appended to it. DSH refuses a composition carrying twowsl-worldrows, so choosing that mode failed outright with无法切换到「WSL · <name>」:duplicate loader entry id: wsl-world; on a release that mounts the group before validating row ids the same duplication surfaces one step later astool "str replace editor" is already registered in this scope(the report in #24). The generator now replaces the world group it finds — identified by the mountedshell-wsl/fs-wslprovider ids, so a copy whose group was renamed is caught too — and every variant ends up with exactly one world pointing at this installation's providers. A top-level row id that appears twice in a source is reduced to its first occurrence as well, because DSH rejects the whole preset on a duplicate id rather than the offending row. tool-str-replace-editorrows are replaced like the olderstr-replace-editorrow (#24): newer rosters name the editor row that way, and it registers the samestr_replace_editortool as the world group's own editor row, so the source row is dropped just like its predecessor and the WSL-aware editor the variant injects stays.- Variant display names are no longer double-quoted: the variant's
preset.ymlcopied the source'sname:scalar verbatim, so a quotedname: 'Data mode'reached the mode picker asWSL · ''Data mode''. The scalar is unquoted before it is re-emitted. - Not adopted from #24: disabling the
tool-cordisrow to avoid a duplicate inspect-provider registration. Adisabledrow never applies, so the WSL variant of Creator mode silently lostcordis_inspect_list/cordis_inspect_query(checked against 0.4.3, where both are present and answer with the host and the client providers); the PR's own description that the model "can still see the tools in the catalog" is not what happens. The registration their report shows needs that row applied twice, which the row-id reduction above now prevents where a copied preset caused it. - Help panel tidied up: the panel now opens with a greeting line and the repository link, carries a "What's new" section for this build, and lists only the limitations that still apply — the historical "fixed in 0.4.3" note and the per-generation API walkthrough are gone. The compatibility chips are untouched: they are the manifest this build declares, not history.
- Verification: the eight declared releases (
0.1.0-rc.7…0.1.5-rc.2) pass the same 8/10 harness checks as 0.4.3 — only the documentedtypecheckbaseline and the check that needs a live server fail; 136 transforms over every shipped preset of the 17 installed runtimes are unchanged apart from the repair, and all 68 "copied variant" cases resolve to a single fresh world group. - Per-mode matrix with a real model (every WSL variant, not just the default one): on
0.1.0-rc.7,0.1.1-rc.2,0.1.3-alpha.2and0.1.5-rc.2each of the four variants — Standard, PTC, Minimal, Creator — was driven through the browser and asked to write a file with its file tool, rununame -r; pwd; whoamiin bash and land that output in the workspace, then read the file back. Every mode producedMODE-<mode>-OKand a WSL2 kernel line in/home/mille/<workspace>/notes/with no loader error; the follow-up bash call lands in the workspace again, which is the documented per-call shell (the PTY group stays dropped).0.1.2-rc.1and0.1.5-rc.1were driven through all four modes without the file/bash assertions. - Browser + real-model spot checks of the copied-variant mode (mounts and answers), Creator mode (inspect tools intact) and the
0.1.0-rc.7standard flow complete the pass.
0.4.3 — 2026-09-11
- The persona text moved in
0.1.3-alpha.2(#22): DSH renamed the persona's model-facing scalar fromtextto an inlinesuffixplus a foldedprefix, and the variant generator only recognisedtext: >-. On that line the WSL environment sentence was never appended - the session still ran inside the distribution, but the model was never told that its working directory is a Linux path reachable from Windows as/mnt/<drive>. The generator now amendssuffix,textorprefix(folding an inline scalar into a block scalar when needed, so the sentence joins the working-directory line exactly where the legacytextblock put it), and a persona carryingcomplete: trueis still left alone. Verified on seven releases: the five older ones keep their persona block byte-identical, and the two newer ones now carry the sentence into the model's system message. - Help panel: the dialog gained a "?" button that opens an in-place panel - the DSH releases this build declares (read from
package.jsonthrough the host route, so the list can never drift from the manifest), how the plugin is used, its features, and the limitations it cannot fix. - The skill catalog now reaches UNC workspaces: the host skill provider watches a workspace through
chokidar, and watching a\\wsl.localhost\...path fails; the failed watcher makes the skill snapshot reportcomplete: false, anddsh-tool-skillwithholds the entire catalog message while a snapshot is incomplete — so a WSL session's model saw no skills at all, not even the ones the plugin had discovered. The variant generator now pinswatch: falseon theskill-filesystemrow (merged into an existingconfig:block when there is one, and left alone when the source declareswatchitself), which makes the host collect the catalog once at session start instead. Verified end to end on0.1.5-rc.2: the model's context carries the<available_skills>list. Trade-off: a skill added mid-session appears in the next session rather than the running one; skill bodies are still read live. verify-libhardening: its comment/string stripper could pair a lone apostrophe inside a comment with a later one and swallow the rest of the bundle, which made everynode:*import look tree-shaken. The quote rules now stop at a newline, exactly as a JavaScript string does.
0.4.2 — 2026-09-10
- Create & open in a
0.1.2-rc.1workspace: the session starter is now resolved when the dialog writes, not when the plugin applies. This plugin applies before the UI domain that publishesuiWorkspaceregisters its service, so the lookup cached at apply time stayedundefinedfor the whole page life:Create & opencreated the workspace and then silently opened no session, leavingsessionIdsempty while the dialog reported success. A release exposing neitheruiWorkspace.startSessionnorworkspaces.startSessionnow fails before anything is written, instead of leaving an orphaned workspace behind. - Skill body integrity: skill bodies no longer lose their first character.
findFrontmatterEndalready returns the index of the body's first character (the closing delimiter's newline plus one), so the slice must start there; the previous offset dropped that character and made the one after the delimiter look like the body. The existing fixtures always put a blank line after the delimiter, which is exactly what hid it. - UTF-8 BOM skills are no longer dropped: a
SKILL.mdsaved with a leading BOM (Notepad, VS Code's "UTF-8 with BOM", PowerShell redirection) did not match the opening---and disappeared from the catalog entirely. The parser strips the BOM before the fence check. - Binding converges on late inputs: the agent-preset roster and the registered
/mnt/<drive>workspace set are both inputs to binding, and both land asynchronously after the plugin's first pass. Each now re-runs the pass when it arrives instead of waiting for a session-store event that may never come. - Compatibility manifest corrected:
0.1.3-alpha.1is not published (npm view @deepseek-ai/dsh@0.1.3-alpha.1is a 404), so the declaration could never be verified; it is replaced by the published0.1.3-alpha.2. - Reproducible publishes: a new
.gitattributes(* text=auto eol=lf,lib/** -text) pins line endings.core.autocrlf=trueused to rewrite text files to CRLF on checkout, and sincelib/is committed and published verbatim the same commit produced different npm tarballs depending on the machine; the repository already stored LF, so no renormalisation was needed. - Closed-loop tests:
tests/client-lifecycle.test.mjsdrives the browser half through the shippedlib/client.jsfor both service shapes — legacy (connection.api.agentPresets+workspaces.startSession) and current (remote.agentPresets+uiWorkspace) — and assertsCreate & openfor the normal, late-registration and no-starter cases. The skill tests now cover a body that starts on the delimiter's next line, for LF and CRLF files.
0.4.1 — 2026-09-03
- DSH v0.1.2-rc.1 compatibility: Added backward compatibility support for DSH v0.1.2-rc.1 and later versions through feature detection and compatibility wrappers. The plugin now automatically detects the DSH version at runtime and uses the appropriate API:
uiWorkspace.startSession()for v0.1.2-rc.1+workspaces.startSession()for v0.1.1-rc.2 and earliersummary.projectionValues?.agentPresetfor v0.1.2-rc.1+summary.agentPresetfor v0.1.1-rc.2 and earlier- Projection-based auto-sync for v0.1.2-rc.1+
sessions.noteAgentPreset()for v0.1.1-rc.2 and earlier
- Updated compatibility manifest: Added v0.1.2-rc.1 to the
dsh.compatibility.dshReleasesdeclaration. - Fixed
without injectcrash on v0.1.2-rc.1+: the agent-preset roster is read through theremote.agentPresetsnamespace service viactx.get('remote.agentPresets')(topology-free store lookup) instead of theremoteaggregate'sagentPresetsproperty, which Cordis' associate proxy rejects when the dotted property is not declared ininject.injectstays limited to the services both DSH generations share (slots,locale,sessions,workspaces). - Compatibility manifest: declared v0.1.3-alpha.1 compatible (its plugin-facing API surface matches v0.1.2-rc.1). Final adaptation notes consolidated in
docs/COMPATIBILITY_SUMMARY.md(supersedes the root-level draft plans).
0.4.0 — 2026-08-29
Follow-ups from the #12 limitation list and the #13 compatibility work:
- Lookup cache: completed skill-catalog lookups are cached per scan root for 10 seconds, so repeated catalog builds no longer rescan the workspace over the slow 9P share;
get()keeps reading skill bodies live, and freshly added skills appear within the TTL window. - Symlinked projects — investigated, substrate-limited: the discovery walk now recognizes directory symlinks explicitly and prunes them safely (no crashes, no loops). Following them is not possible over the
\\wsl.localhost9P share — the Windows side cannot resolve Linux symlink targets (probed:readlink→EISDIR,stat/readdir→ENOENT) — so a project linked into the workspace vialn -sstays undiscoverable; a name+body fingerprint dedupe also guarantees aliased skill files can never publish twice on substrates that do resolve links. - Block-scalar frontmatter:
description:/whenToUse:written as YAML block scalars (|literal,>folded) now parse — such skills were silently dropped before. - Compatibility manifest:
dsh.compatibility.dshReleasesdeclares per-release compatibility with the official DSH versions, backed by reproducible disposable-Profile install/start/uninstall evidence (scripts/verify-dsh-compat.sh), andenginesdeclares the Node.js floor. - Guard scripts:
scripts/check-rank-parity.mjsfails the release when the copied project-rank constants drift from the host'sdsh-skill-filesystem.
0.3.2 — 2026-08-29
- WSL workspace sessions now inject nested-project skill catalogs (#10):
.dsh/skillsand.agents/skillsdirectories of projects nested below the registered workspace root are discovered and published with the host's project ranks and sources, so the model sees the same skill catalog it would see when the session cwd is the project folder itself. Discovery is depth- and budget-bounded, prunesnode_modules/dot-directories, and leaves non-WSL sessions untouched. - Host-parity scan root: lookups from inside a project subtree resolve the nearest
.gitancestor first, so the enclosing project's skills stay visible from deeper cwds; skills above that ancestor do not leak. - Hardening: the skill-root budget is enforced per push, and the
skills.registerProvidercall is guarded so a host whoseskillsservice has a different shape can no longer break plugin load. - Housekeeping: removed stale prebuilt
lib/chunks that shipped dead vendor code (including an inlined schemastery copy that triggered dsh.so'snew Functionstatic rule); addedscripts/repro-setup.shplus a nested skill-catalog regression suite, and a matching TESTING.md section.
License & attribution
MIT — see LICENSE and NOTICE. The NOTICE precisely lists:
- Adapted/inherited source code: DeepSeek Harness (MIT) —
dsh-bash-local(executor mechanics),dsh-fs-local(WslFileSystemsubclasses it), and the shipped agent presets (read and transformed by the variant generator); - Design references (no source copied): dsh-bash-terminal (MIT, wsl argv / WSLENV approach), dsh-side-panel (BSD-3-Clause, host-route pattern), vpshub (MIT, roadmap reference).
Keep LICENSE and NOTICE when redistributing.
Acknowledgments
Special thanks to dsh-deep-whale (DSH Web 鲸鱼娘 skin series · 深海女仆工坊 maid-atelier, CC BY-NC-SA 4.0): the whale girl skin plugin brings a full set of adorable skins to the DeepSeek Harness Web UI and makes daily use of DSH a warmer experience.