DeepSeek Harness plugin

dsh-shell-wsl

Docker-container (WSL2 backend) shell executor for the DeepSeek Harness bash capability seam on Windows: every bash tool call runs in a disposable Linux container with the workspace bind-mounted.

Jump to install

Source facts

Repository
kaixinyujue/dsh-shell-wsl
Latest update
Aug 17, 2026
Category
Tools & Capabilities
GitHub stars
0
Format
plugin
Catalog evidence
Upstream dsh.bundle evidence
Evidence path
package.json#dsh.bundle
Checked against
0.1.0-rc.8
Upstream check date
2026-08-20

This evidence comes from the upstream catalog. This site has not installed, run, or security-reviewed the plugin.

Install

Start with a prompt that asks an agent to review the GitHub repository and source. Switch to the command if you want to install it yourself.

Copy this prompt into DSH, Codex, or another agent and ask it to review the GitHub repository and source first.

Do not install or run any commands yet. Read this plugin's GitHub repository, README, and relevant source code. Then answer the questions below clearly and directly so I can decide whether it fits my needs:

1. What is this plugin, and what problem does it solve?
2. Who is it for, and what are its typical use cases?
3. How is it used after installation? Include one minimal example.
4. What known limitations or privacy, security, compatibility, or maintenance risks does it have?
5. Give a clear recommendation: recommend, conditionally recommend, or do not recommend, with reasons.

Distinguish statements documented by the repository, inferences from source code, and unknowns. If evidence is insufficient, say so explicitly. Do not guess or simply repeat the README.

GitHub: https://github.com/kaixinyujue/dsh-shell-wsl
Plugin: dsh-shell-wsl
Author: kaixinyujue

Check the source files

Read the README and other files from this plugin directory before installing.

File explorer4 files
README.mdSource · read only
README language

dsh-shell-wsl

> English · 中文

A real-Linux bash execution environment for DeepSeek Harness (dsh) on Windows: every bash tool call is routed into a disposable Docker container (Docker Desktop / WSL2 backend), with the workspace bind-mounted for two-way file sharing and automatic integration with the official permission presets.

docker run --rm --name dsh-exec-<pid>-<seq> -i
  -v <session workspace>:/workspace[:ro] -w /workspace/<rel> -e ... ubuntu:24.04 bash -c <command>

The model keeps using the official bash tool (no new tools, no tool-layer changes); commands genuinely run in a container on the WSL2 Linux kernel. The executor declares the official sandboxMode capability bit (workspace-write), so permission presets (read-only / workspace-write / danger-full-access) integrate automatically — read-only sessions mount the workspace :ro, everything else mounts read-write.

See the design document: dsh-shell-wsl-design.md (checked line-by-line against the @deepseek-ai/dsh 0.1.0-rc.6 source contracts).

Why a container, instead of the other approaches

On Windows, dsh exposes only pwsh by default (the official tool-bash is disabled on win32). The common community approaches to getting bash back each come with trade-offs:

dsh-shell-wsl (this plugin)Git Bash (MSYS2) approachesWSL distro-direct approaches
RuntimeReal Linux: WSL2 kernel + Ubuntu 24.04 userspaceMSYS2 emulation layer, not LinuxReal Linux, but tied to a specific installed distro
IsolationDisposable container, destroyed via --rm after every callNo container isolation; runs as a host processDistro lives forever; state accumulates across sessions
Permission-preset linkageNative: read-only automatically mounts :roMost implementations require danger-full-access, or fail to start inside the sandboxCommon implementations let bash bypass the DSH file policy
PrerequisiteDocker Desktop onlyGit for WindowsWSL2 + at least one distro
State & reproducibilityStateless; every call starts from a pinned imageStatelessStateful; easy to pollute with earlier commands

The trade-off: one container cold start per command buys you real Linux, isolation, and permission linkage. If you want a zero-dependency, millisecond-start POSIX-ish environment, a Git Bash approach fits better. If you want a stateful Linux environment deeply tied to your distro, a WSL-direct approach fits better. If you want a clean, reproducible, permission-constrained real-Linux execution environment inside a Windows session, this plugin is exactly that.

  • Real Linux: genuine WSL2 kernel and Ubuntu userspace — Linux binaries, apt, pipes and process semantics work as-is
  • Zero pollution: every command runs in a brand-new container that is destroyed afterwards; no leftover cd, variables, or apt installs
  • Permission linkage: session permission presets map directly to read-only/read-write mounts (danger-full-access is equivalent to workspace-write under a container executor — see §7)
  • Complete lifecycle: background jobs, timeout tree-kill, orphan-container cleanup, and infrastructure-error classification, all inherited from the official executor contract
  • Zero tool-layer changes: the model keeps using the official bash tool; nothing new to learn

---

1. Environment requirements (P0 — verify before installing)

1. Docker Desktop running (WSL2 backend), with the docker CLI available on Windows: ``powershell docker version # both client and server sections must print docker context show # desktop-linux ` 2. Pre-warm the image (a first-time pull can exceed the default 120s timeout — pull it first): `powershell docker pull ubuntu:24.04 ` 3. Verify the mount path (replace the path with your workspace): `powershell docker run --rm -v E:\your\workspace:/workspace -w /workspace ubuntu:24.04 bash -c "uname -a && pwd && ls" ` Expected: Linux ... microsoft-standard-WSL2, /workspace, and ls showing your Windows-side files. 4. A distro is only required for the wsl transport (the default docker-cli transport needs none): in that case also run wsl --install -d Ubuntu` and enable WSL integration for that distro in Docker Desktop.

> Note: run the commands above in your own terminal. An agent's tool sandbox may block > docker/WSL probing (E_ACCESSDENIED / named pipes); the plugin itself runs in the host > process and is not affected.

2. Installation

dsh plugin --profile web add dsh-shell-wsl

This adds the package to ~/.dsh/profiles/web/package.json and writes dsh.profile.bundles; the dsh.bundle.patch bundle metadata brings cordis.patch.yml into the patch stack (layer order: bundle layer → profile layer → $DSH_HOME/cordis.patch.yml--patch layer).

The patch does two things: inserts the shell-wsl row (auto-disabled outside win32, stays portable), and sets the host pwsh-sandbox row disabled: true (ctx.shell can only have one provider — duplicate service registration fails loud).

Git-hosted plugins need the prepare script allowed in the profile's pnpm-workspace.yaml under allowBuilds, per pnpm's prompt.

3. Web UI: enable the wsl-container preset

Under the web surface the host tool rows are disabled by dsh-web-app and tools come from agent presets, so enable the preset after installing the plugin:

powershell -ExecutionPolicy Bypass -File <plugin dir>\scripts\install-preset.ps1 -SetDefault

Or manually: copy presets/wsl-container/ to ~/.dsh/.agent-presets/wsl-container/ and switch the default preset to WSL 容器模式 on the Web settings page. The preset changes exactly two rows relative to standard: tool-bashdisabled: false and tool-pwshdisabled: true (the latter is mandatory — tool-pwsh also consumes ctx.shell, and leaving it enabled would run PowerShell command strings through the container executor, which makes no sense).

4. tui / headless integration

For tui/headless, the agent plane lives in the host (base patch: tool-bash disabled on win32, tool-pwsh enabled). Flip the rows in the profile patch (not the bundle patch — under web that would double-register the bash tool name against the preset):

# ~/.dsh/profiles/<profile>/cordis.patch.yml
- id: tool-bash
  disabled: false

- id: tool-pwsh
  disabled: true

5. Configuration

5.1 Composition layer (cordis row config — edit this via the patch layers)

FieldDefaultDescription
transportdocker-clidocker-cli (Windows-side CLI) or wsl (wsl.exe -d <distro> -- docker)
distroUbuntuUsed only by the wsl transport
imageubuntu:24.04Must include bash (alpine/busybox don't — that surfaces as a runner failure)
workspaceMount/workspaceMount point inside the container
workspaceRootnone (fallback)Fixed workspace root; normally the per-call policy's workspaceRoot is used
containerPrefixdsh-execContainer name prefix: <prefix>-<pid>-<seq>, ≤63 chars
# Example: switch images (rewrite the row config in the profile patch — patch replaces the
# whole row config, so restate every field)
- id: shell-wsl
  config:
    transport: docker-cli
    image: debian:bookworm-slim

5.2 Settings layer (settings.yaml, hot-reload, budget fields only)

# ~/.dsh/settings.yaml
bash:
  timeoutMs: 120000
  maxTimeoutMs: 600000
  maxOutputBytes: 64000
  maxSpillBytes: 67108864
  graceMs: 3000

Environment identity fields (transport/image/…) must not go into settings.yaml — the base constructor registers the bash settings namespace with the base schema, and unknown keys are rejected by the schema.

6. Behavior semantics

  • Normal exit costs nothing extra: --rm is trusted; no additional docker command is spawned.
  • kill / timeout / cancel: the docker.exe tree is killed with taskkill /T /F on the Windows

side, then a best-effort docker rm -f <name> is appended; the lazy reaper is armed.

  • Lazy reaper: before the next spawn (only when a kill happened, or on the first spawn of this

executor instance — covering host-crash recovery) a docker ps -a prefix scan force-removes every leftover container not in the live-handle table. The reaper never runs during normal operation.

  • Infrastructure error classification: daemon down, docker CLI missing, image pull failure,

image without bash, distro missing → WslContainerUnavailableError (isError, the model stops retrying); a command's own nonzero exit stays an ordinary [exit code: N].

  • read-only: mounts :ro; writing /workspace inside the container is an ordinary EROFS-class

command error. workspace-write / danger-full-access: both mount read-write (a container executor cannot grant host-wide access, so danger-full-access is equivalent to workspace-write).

  • workdir: Windows absolute paths map to /workspace/<rel> by case-insensitive prefix match

against the workspace root; container paths already under /workspace pass through; paths outside the workspace fall back to /workspace with a one-line stderr warning (non-blocking).

  • env: ENV_OVERRIDES (NO_COLOR/TERM/PAGER/GIT_PAGER) + spec.env + spec.dshEnv materialize as

-e KEY=VALUE (passed as argv parameters, no quoting issues), and LANG=C.UTF-8 is forced. Under the wsl transport, Windows paths in DSH_* are translated to /mnt/<drive>/....

  • stdin: docker run -i keeps stdin open, so hooks' stdin data channel works.
  • Background jobs, timeouts, output caps, spill files, and the [exit code: N] marker contract

are all inherited from LocalBashExecutor / ctx.subprocess — zero reimplementation.

7. Known limitations

  • No interactive PTY / persistent shell: every call is a brand-new container (matching the

official fresh-shell semantics; apt installs, cd and variables don't survive across calls). If you need a persistent shell / PTY, extend via the official terminal capability family — this plugin deliberately keeps the one-shot semantics.

  • Occasional timeouts on short commands: usually a first-time image pull; pre-warm with

docker pull (see §1).

  • Mount I/O is slower than native Windows: inherent to Docker Desktop file sharing

(9p / grpcfuse).

  • rm -rf inside the mount is not blocked by ACL: the container can only see the mounted

volume and has no write access to the system drive — the same risk surface as workspace-write mode.

  • danger-full-access is equivalent to workspace-write: a container executor cannot grant

host-wide access; both mount read-write (see §6).

  • No docker / wsl.exe inside the container, on purpose: do Docker/WSL troubleshooting

in the user's own terminal (probing from inside the agent sandbox is unreliable too).

  • Private image registries: the docker-cli transport shares Docker Desktop credentials; the

wsl transport needs a credential helper configured inside the distro.

  • Chinese / UTF-8 output: the container forces LANG=C.UTF-8; rendering is normal.

8. Acceptance checklist (mirrors design doc §9)

> This plugin has completed a full acceptance round: the runtime chain (tool table / real Linux / > mount / workdir mapping / two-way file sharing / background jobs / timeout / no orphan > containers / read-only mount enforcement / file-tool regression) and the unit + integration > test suites all pass. The checklist below is kept for re-verification and regression runs.

1. The bash tool appears; uname -sLinux. 2. With workdir set to E:\…\sub, pwd inside the container → /workspace/sub; files are visible in both directions. 3. run_in_background + job_output / job_kill work end-to-end. 4. After timeout/kill, docker ps -a --filter name=dsh-exec- is empty; also empty after normal runs. 5. With Docker Desktop stopped, a bash call surfaces as an infrastructure error (runner failure), not a command failure. 6. In a read-only session, writing /workspace inside the container fails (ro mount); workspace-write can write. 7. Under the wsl-container preset there is only bash, no pwsh. 8. Windows-side read/write/edit file tools behave unchanged (regression). 9. npm test is fully green (6 test files, 49 cases); $env:DSH_WSL_INTEGRATION="1"; npm run test:integration is fully green (9 cases, requires Docker Desktop running).

9. Development

# One-time environment prep: junction the deps to the local DSH install's node_modules
# (avoids installing peer dependencies)
New-Item -ItemType Directory -Force node_modules | Out-Null
cmd /c mklink /J node_modules\@deepseek-ai "$env:APPDATA\npm\node_modules\@deepseek-ai\dsh\node_modules\@deepseek-ai"
cmd /c mklink /J node_modules\@types       "$env:APPDATA\npm\node_modules\@deepseek-ai\dsh\node_modules\@types"
# TypeScript lives outside the plugin dir (npm reify ELOOPs on junctioned node_modules)
npm install --prefix ..\.dsh-dev-tools --no-save --no-package-lock typescript

npm run build                    # tsc → lib/
npm test                         # unit tests (pure functions + executor tests with a fake subprocess)
$env:DSH_WSL_INTEGRATION = "1"
npm run test:integration         # real-docker integration tests (requires Docker Desktop running)

Layout: src/ (pure functions: paths/env/naming/classify/argv + the executor index) → compiled to lib/; tests/ (unit + optional integration); cordis.patch.yml (bundle patch layer); presets/wsl-container/ (web preset); scripts/install-preset.ps1.

License

MIT