dsh-remote-workspaces
English | 中文
Open folders on remote hosts over SSH as first-class DeepSeek Harness workspaces.
What it does
Once a remote directory is opened as a workspace, the agent's file tools (read / write / edit / grep / glob) and shell commands land directly on the remote host over SSH, not a local mirror. There is no mirroring and no sync: every operation is executed remotely in real time, and local workspaces keep their full sandbox behavior.
Features
- Open a remote folder as a workspace — browse the remote filesystem, pick a directory, and the harness adopts it as if it were local. All I/O routes to the remote.
- Multi-machine registry — add, edit, and remove SSH hosts (alias, host, port, user, key or password). Credentials are AES-256-GCM encrypted at rest and never sent back to the browser.
~/.ssh/configimport — list configured aliases and pre-fill the connection form.- Connection test — verify a host before using it.
- Transparent tool routing —
read/write/editgo through SFTP,grep/globrun ripgrep on the remote, andbash/pwshcommands execute over ssh2 exec. Local paths keep the harness's own sandbox. - Policy-aware remote mutations — the harness file policy applies to remote workspaces too:
read-onlydenieswrite/editand remote shell commands,workspace-writeconfineswrite/editto the remote workspace root (plus/tmp) while remote shell commands need adanger-full-accessapproval, anddanger-full-accessdelegates to the SSH account.
How it works
The bundle has a host half and a browser half:
- Host (
src/index.js) — an SSH/SFTP transport built onssh2, an encrypted machine store, the routing filesystem (ctx.fs) and shell (ctx.shell) providers, remote-awaregrep/globtools, and aremoteWorkspacesRemote namespace exposed through Typert. - Browser (
src/client.js) — a "Remote Workspaces" settings section (hosts grouped by machine) and a workspace-add picker.
Opening a remote directory creates an anchor: a real but empty local directory adopted by the harness as the workspace identity, plus a metadata file recording its remote origin. A registry (anchors.json) maps that anchor to its remote host and path, and every file/command operation is routed to the remote by that lookup:
$DSH_HOME/remote-workspaces/
├── machines.json # host registry (credentials encrypted at rest)
├── anchors.json # anchor → remote origin routing registry
└── <host>-<user>-<port>/ # per-host anchors
└── <encoded-path>/ # one empty anchor per remote path
│ └── .dsh-remote-meta.json
The full remote path is encoded into the anchor name (path separators and Windows-illegal characters become --), so /home/test and /data/test map to distinct directories (home--test vs data--test).
Requirements
- A DeepSeek Harness installation (the plugin resolves its host services at runtime).
- Node.js ≥ 18 (the harness itself runs on Node 22+).
ssh2is the only transport dependency; no externalsshbinary is needed.- The remote host needs
rg(ripgrep) forgrep/glob, andsha256sum(orshasum) for post-write verification — both degrade gracefully when absent.
Installation
Install from this repository:
dsh plugin --profile web add github:januory/dsh-remote-workspaces
Once published to npm, install the released package directly:
dsh plugin --profile web add dsh-remote-workspaces
Install from source:
git clone https://github.com/januory/dsh-remote-workspaces.git
cd dsh-remote-workspaces
pnpm install # install the ssh2 transport dependency
dsh plugin --profile web add .
Remove it with:
dsh plugin --profile web remove dsh-remote-workspaces
Maintainer note: the npm package name is now
dsh-remote-workspaces(the early git install usedremote-workspaces); older installs must first rundsh plugin --profile <name> remove remote-workspaces, then install under the new name.
Usage
- Open Settings → Remote Workspaces.
- Add an SSH host (or import one from
~/.ssh/config), then Test connection. - In the workspace-add flow, choose a remote host, browse to a directory, and open it. The harness adopts the empty anchor as a workspace, and reads, writes, searches, and shell commands all execute on the remote.
Remote API
The host exposes a remoteWorkspaces Remote namespace (Typert) with these invocations: listMachines, saveMachine, deleteMachine, listSshAliases, sshAliasDetail, testConnection, listRemoteDir, and openRemoteWorkspace.
Repository structure
src/ # the DSH bundle source
index.js # host entry (routing fs/shell, search tools, Remote namespace)
client.js # browser entry (settings UI + picker)
transport.js # ssh2 transport (SshClient, exec, sha256)
routing-fs.js # routing filesystem (remote SFTP / local fence)
fs-sftp.js # SFTP filesystem backend
local-backend.js # local filesystem backend
containment.js # local sandbox containment fence
shell-exec.js # SshShellExecutor (remote ssh2 exec / local subprocess)
search.js # remote-aware grep/glob tools
anchor.js # local anchor layout (empty dir + meta)
registry.js # anchors.json routing registry
machine-store.js # host registry (encrypted at rest)
ssh-config.js # ~/.ssh/config parser
ssh-uri.js # ssh:// URI parsing + detection
errors.js # error codes
cordis.patch.yml # DSH bundle patch (swap in the routing providers)
package.json # package + dsh manifest
test/ # unit + integration tests
Testing
Pure unit tests (no host required) run anywhere:
pnpm test
The integration suite exercises a real SSH host and expects the machine registry to be configured first (it targets the first machine in the registry, or the one aliased test):
pnpm test:integration
Publishing (maintainers)
Releases are fully manual with zero input: the version comes straight from package.json — commit the new version on main first, then GitHub Actions → release → Run workflow. The workflow runs the unit tests (npm test), guards that the v<version> tag does not exist yet (prevents duplicate releases), publishes to npm with provenance (npm publish --provenance), creates and pushes the v<version> tag, and creates a GitHub Release with --generate-notes.
Prerequisites: configure an NPM_TOKEN secret in Settings → Secrets and variables → Actions (an npm automation token, or a granular token with publish permission on the package); the publishing account must be the npm user owning the package name.
Equivalent manual flow:
npm login
npm publish --provenance
git tag "v$(node -p \"require('./package.json').version\")"
git push origin --tags
No comments yet. Be the first to write one.