dsh-machine-modes
Verifieddsh-machine-modes Β· v1.0.0 Β· MIT Β· Web UI
Session-level machine modes for DeepSeek Harness: run commands and file operations on the local host, a WSL distribution, a Docker container, or an SSH host, switchable mid-session, with tty and root escalation.
Install
dsh plugin add dsh-machine-modes Confirm the layer applied with dsh --profile default --dump-config β see the install guide.
Source
Published to npm without a public repository. Inspect the package contents before installing.
Tags
Creators
Readme
dsh-machine-modes
Session-level machine modes for DeepSeek Harness.
Pick a machine β the local Windows host, a WSL distribution, a Docker container, or an SSH host β and that session's commands, background jobs and file operations run there. The model is told which machine it is on, how paths map, and how to switch, on every step.
β ε½εζΊε¨ my-container /machine εζ’
Requirements
| Harness | DeepSeek Harness 0.2.0-rc.2 (@deepseek-ai/dsh-fs, @deepseek-ai/dsh-fs-sandbox) |
| Host OS | Windows only β the transport is wsl.exe, so Linux/macOS hosts are not supported |
| Node | >= 22.12 |
| Optional | Docker Desktop (for kind: docker), an SSH client (for kind: ssh) |
Install
Ask your DSH agent to install it, or use the plugin manager directly with the npm spec:
plugin_manager action: install_bundle target: dsh-machine-modes
Then restart dsh and refresh the page. The plugin does not build anything: both halves are plain
ES modules loaded at startup.
Read this before installing
This bundle replaces the filesystem provider: its patch disables the shipped fs-sandbox row and
mounts dsh-machine-modes/fs instead. The replacement extends the stock class and delegates every path
without a machine prefix to it, so ordinary local reads and writes (including the sandbox fence) behave
exactly as before β but it is a core service swap, and you should know that before installing.
Two hard rules follow from that:
- Never hot-swap it. Applying the patch in a running process tears
fsdown under services that still hold it; that cascades into a dead back end. Install, then restart. - Do not delete the package directory while it is installed β
dshwill fail to start with nofsprovider. Remove the bundle first, then delete.
Configure machines
Either edit the machine-modes row's config, or β simpler β create $DSH_HOME/machine-modes.json
(%USERPROFILE%\.dsh\machine-modes.json on Windows). That file is re-read on every call, so adding a
machine needs no restart:
{
"default": "local",
"machines": [
{ "id": "local", "label": "This Windows host", "kind": "local" },
{ "id": "wsl-ubuntu", "label": "WSL Ubuntu-24.04", "kind": "wsl", "distro": "Ubuntu-24.04" },
{ "id": "my-container", "label": "Docker container", "kind": "docker", "container": "my-container", "distro": "Ubuntu-24.04" },
{ "id": "vm-a", "label": "SSH host", "kind": "ssh", "host": "vm-a", "cwd": "/home/dev/project" }
]
}
| Kind | Fields | Shell |
|---|---|---|
local |
β | Windows PowerShell 5.1 |
wsl |
distro |
bash, started in the session cwd mapped to /mnt/<drive>/... |
docker |
container, optional distro (WSL distro that hosts the docker CLI) |
sh, preferring bash; the session cwd is not mounted, pass cwd or set it on the entry |
ssh |
host (an ~/.ssh/config alias with key auth), optional cwd |
bash over ssh -o BatchMode=yes |
What the model gets
| Tool / command | Purpose |
|---|---|
machine / /machine |
Report the active machine and the roster, or switch ({"id":"β¦"}, /machine my-container) |
exec |
Run a command on the active machine: command, cwd?, tty?, root?, background?, timeout_ms? |
machine_auth |
Hold the sudo credential so exec {root:true} can escalate |
Switches also show up in the conversation as a divider row that names both ends β
local β my-container (Docker container) β and the composer keeps a small card with the current machine.
Both read the durable session history, not a guess.
Every step's prompt states the active machine and asks the model to batch cross-machine work: switch once per machine, run that machine's commands and file operations together, and switch again only when the next operations belong elsewhere. The switch is session-wide, so it governs everything after it.
File addressing
File tools (read/write/edit) keep their normal Windows paths for anything shareable, and take a
machine URI for paths that only exist on the target:
read wsl://Ubuntu-24.04/etc/os-release
write docker://my-container/tmp/notes.txt
glob and grep are local-only (they spawn ripgrep on the host); on a remote machine use exec with
find / grep.
tty and root
exec {tty:true} allocates a pseudo-terminal inside the target (the command runs under script; its
stdin is redirected to /dev/null so the pty does not echo the script back).
exec {root:true} escalates: docker exec -u 0 in a container, sudo -S in WSL/SSH with the credential
from machine_auth. A passwordFile holds the account on its first line and the password on its
second (a single line is treated as the password). Passwordless sudo is detected first. The local
Windows host cannot be escalated from a tool.
Availability
WSL distributions and Docker containers come and go, so every entry point probes the target first: a WSL
distribution must appear in wsl.exe -l -q, and a Docker container must report State.Running == true.
Probes are cached for 15 seconds, warmed at startup, and invalidated as soon as a command fails.
When a machine is unavailable:
- switching to it is refused, with the probe's own reason
(
WSL distribution "Ubuntu-99.04" is not installed (have: Ubuntu-24.04, docker-desktop)); execrefuses while the active machine is unavailable;- the roster marks it
[unavailable: β¦]; - the per-step prompt tells the model not to switch there.
SSH hosts are deliberately not probed β a probe would cost an SSH timeout every TTL β so they are always offered. The gate sits at the entry points: a container that stops while you are already inside it fails the next command for real, and the availability view can lag by up to the cache TTL.
Limitations
- Windows host only (
wsl.exetransport). - Replacing the filesystem provider is mandatory for the file layer; see the warning above.
- Writes to remote paths are not fenced by the sandbox (local paths still are).
- Container
ttyneedsscriptinside the image (present in Debian/Ubuntu images, usually absent in Alpine). - The SSH kind has no remote filesystem layer: only
execworks there. - The composer card derives from the loaded conversation window (400 nodes back); older switches fall back to a grey "observed/default" marker instead of claiming authority.
Uninstall
plugin_manager action: remove_bundle target: dsh-machine-modes
Restart afterwards; the shipped fs-sandbox row returns.
License
MIT