Repository navigation
Releases: MingTXin/dsh-remote-workstation
Release list
v0.6.0 - remote shell routing (opt-in)
dsh-remote-workstation 0.6.0 — remote shell routing
0.6.0 adds an opt-in remote shell: with one extra patch, the agent's own bash / pwsh
commands run on the SSH host whenever their working directory is inside a mount anchor, so builds and
test suites execute next to the code instead of in the empty local anchor directory.
Added
lib/shell-remote-core.js,lib/shell-remote.js,lib/pwsh-remote.js— remote executors for the
shellservice. They subclass the stock sandboxed executors and only take over commands whose
workdiris inside an anchor; every other command keeps the stock local behaviour and sandbox.lib/ssh-exec-helper.mjs— the helper the host spawns per remote command. It connects with the
shared SSH pool, opens an exec channel, and pipes stdin/stdout/stderr so the host keeps its own
timeout, output cap, spill file and background-job semantics.examples/cordis.remote-shell.yml— the two loader rows that enable routing (they rewrite
bash-sandbox/pwsh-sandboxin place, so theshellservice keeps a single provider).test/shell-remote.test.mjs— routing, the confined-session fence, and a realctx.shell
execution against an SSH host (skipped unlessDSH_RW_REMOTE_HOSTis set).
Safety model
A process on another machine cannot be confined by the host sandbox, so a remote command only runs
when the call's sandbox mode is danger-full-access. In workspace-write / read-only the executor
refuses with an explanation, and the model can retry once with
sandbox_permissions: "danger-full-access" + a one-sentence justification — the standard approval
prompt. remote_exec / remote_git keep the same gate.
Not covered
The interactive terminal (PTY), built-in glob / grep (host ripgrep), LSP, @ mentions and turn
diffs stay host-local; use remote_search / remote_exec / remote_git for remote work.
Upgrade notes
Nothing changes unless you add the rows from examples/cordis.remote-shell.yml to your profile's
cordis.patch.yml. In the Windows desktop app that patch replaces the launcher's ACL-confined pwsh
executor, which is why it stays opt-in.
Verification
npm test— 41 tests: 34 offline + 7 SSH-gated (DSH_RW_REMOTE_HOST=<alias>) all green.- Real host:
ctx.shell.execute({ command: "uname -s && pwd", workdir: <anchor> })returns
Linux\n/tmp/dsh-remote-workspace-fixturethrough the SSH host (≈1 s including connect). - Isolated profile boot with the shell rows enabled: no provider conflict, only the pre-existing
plugin-import warnings.
v0.5.0 - remote tools honour sandbox mode and approvals
Remote operations now obey the session's sandbox mode and approval policy
Until 0.4.x the three remote tools bypassed DSH's permission model: in a workspace-write session with
ask approvals, remote_exec would run any command on the server without a prompt — the filesystem
provider was fenced, the shell was not.
0.5.0 closes that hole and makes a remote workspace behave like a local one:
remote_execand mutatingremote_gitcalls (stash,--output) are refused when the session
runs inworkspace-writeorread-only. The error tells the model how to retry, and the retry —
sandbox_permissions: "danger-full-access"plus a one-sentencejustification— goes through
ctx.approvalexactly like abashescalation:askshows the standard prompt,neverfails closed.danger-full-accesssessions (完全权限) run remote commands directly, as before.workspace-writeis rejected as an escalation target: no host-side sandbox can confine a process
on another machine, so the only meaningful target isdanger-full-access.- Reads never prompt —
remote_search, read-only git queries and the Files tab stay available in
every mode, mirroring "reads are allowed in every mode" for local files. - Auto review (自动审查, preset
auto) needs no plugin support: it hookstools/pre-executefor
every tool, so the remote tools are judged by the same reviewer asbash.
Tests: 41 (34 offline + 7 SSH-gated), including an end-to-end gate test that runs echo on a real
host, refuses an un-escalated call, runs it again after allowed-once, and keeps a rejected call
closed.
v0.4.1 - no more crashes on connection failures
Crash fix
A handshake timeout is reported by ssh2 as an error event on the connection object. The pool listened for it only while waiting for ready and detached the listener on its own timeout, so ssh2's timer then emitted into an emitter with no error listener — an uncaught exception that killed the DSH host (fatal uncaught exception: Error: Timed out while waiting for handshake, DSH Host exited (1)).
Fixed by owning the guard for the whole connection lifetime, disabling ssh2's handshake timer, guarding the SFTP session and read streams, and rejecting the caller with a normal cannot connect to "<alias>" (...): connection timed out after <n> ms error. test/pool-errors.test.mjs reproduces the old crash and now passes.
Also in this release: resolveRemote(host, path) so the panel previews an explicitly named host instead of the first configured one.
v0.4.0 — polling watch, git over SSH, binary previews
新增 / New
- 远端 watch(轮询):
watchIntervalMs(默认 2000ms,最小 250)。Files 标签不再报workspace-file/watch-unsupported,外部改动最多延迟一个轮询周期出现。
Polling remotewatch: the Files tab no longer fails withworkspace-file/watch-unsupported. remote_git工具 //api/remote-workspace/git:在远端仓库执行只读 git 查询(status/diff/log/show/branch/rev-parse/ls-files/blame/stash),其它子命令直接拒绝;面板新增「Git 状态」按钮。
remote_gittool plus a Git status button in the panel; only read-only subcommands are allowed.- 图片/二进制预览:
readBytes/readByteRange走真实 SFTP 字节窗口,图片、PDF、Excel、Office 预览对远端文件可用。
Binary and image previews work through real SFTP byte windows.
验证 / Verification
- 23 个测试:离线 19 通过 + 4 个 SSH 门控用例(本地真实主机 23/23,22.1s)
- 真实宿主:
readBytesmultipart 返回远端 70 字节 PNG(version0:0:70:…、eof:true);git status --short --branch→## master+M app.txt(309ms);git diff --stat正常;push被白名单拒绝 - 客户端 bundle 已确认包含「Git 状态」按钮与主机管理区
- CI:syntax-check +
npm pack --dry-run
升级 / Upgrade
dsh plugin --profile web add github:MingTXin/dsh-remote-workstation宿主侧代码有变化,升级后需重启 DSH Desktop;客户端 bundle 只需刷新页面。
Host-side code changed: restart DSH Desktop after upgrading (a page refresh is enough for the client bundle).
v0.3.0 - remote writes, remote_exec, remote_search
0.3.0 — 阶段 2:远端可写 + 远端命令/搜索工具
新增
- 远端写入:
write/edit工具经ctx.fs落到远端,语义与本地后端一致createIfAbsent(目标已存在报FS_NOT_OBSERVED)replaceIfVersion(版本令牌 = dev:ino:size:mtime:ctime,过期报FS_STALE_VERSION)- 字面量
editText(多处命中未replaceAll报FS_AMBIGUOUS_EDIT,0 命中报FS_EDIT_NOT_FOUND) - 沙箱围栏保留:
read-only拒、workspace-write限锚点子树 +/tmp、danger-full-access放行
remote_exec工具:在服务器上执行命令(host/command/cwd?/timeoutMs?),返回exitCode、stdout、stderr、durationMs—— 用它跑构建、测试、git。remote_search工具:远端递归grep -rInE(排除.git),支持glob、ignoreCase、maxResults,返回path:line: text命中。
变更
remoteReadOnly默认改为false;设为true可继续当只读挂载用。- 新增
enableTools(默认true)控制两个工具的注册。
验证
- 单元/集成测试 23 例全通过(含真实 SSH:读写编辑、版本冲突、二进制拒绝、只读围栏、工具转发)。
- 真实 DSH 宿主(隔离 profile)实测:
POST /api/remote-workspace/exec{host, command:"uname -srm && cat …/hello.txt", cwd}→ exitCode 0,输出Linux 5.10… aarch64+ 远端文件内容(290 ms)POST /api/remote-workspace/search→./hello.txt:2:second line;带glob:"*.md"→./sub/notes.md:1:# notes
已知限制
- 远端仍无
watch(Files 标签不自动刷新)。 - 内置
glob/grep仍在宿主执行,远端搜索请用remote_search。
v0.2.0 - 远程工作区(只读 MVP)+ 主机管理
远程工作区,第一阶段(只读)
把远程服务器目录挂进 DeepSeek Harness 的侧栏工作区,像本地工作区一样浏览、读文件,让 agent 在远端代码上工作。
新增
- Remote Explorer 面板(侧栏「远程」):主机下拉、目录浏览(上一级 / 刷新 / 直接输入路径)、一键「挂载为工作区」。挂载后会在
~/.dsh/remote-workspaces/<host>/<path>建一个空的锚点目录并注册为工作区——DSH 要求工作区是宿主真实目录,锚点把二者对上。 - 联合文件系统提供方:接管
ctx.fs,锚点前缀下的路径走 SFTP,其余路径仍是本地。它继承SandboxedFileSystem,read-only / workspace-write / full-access 沙箱围栏照旧生效。 - 插件自带主机管理:面板里增 / 改 / 删 / 测主机,支持密码、私钥(含 passphrase)、
SSH_AUTH_SOCKagent。主机存~/.dsh/remote-workspaces-hosts.json(0600,明文凭据)。优先级:插件存储 >~/.dsh/dsh-ssh.json——所以不再强制依赖 dsh-ssh 插件。 - 凭据不落映射表:锚点映射表只存主机名与远程路径,凭据每次连接时现读;面板 API 只回
alias/host/port/user/source。 - 可观测的错误:连接超时、认证失败、目录不存在、二进制/非 UTF-8 文件、超大文件都返回明确的错误码与文案。
安装
dsh plugin --profile web add github:MingTXin/dsh-remote-workstation装完重启 DSH 宿主(宿主侧代码只在进程启动时挂载;浏览器侧刷新页面即可)。
要求
- DeepSeek Harness
>= 0.2.1-rc.2,webprofile - 可用的 SSH/SFTP 服务器 + 密码 / 私钥 / agent 之一
- Node.js
^22.19.0 || >=24
已知限制(本阶段)
- 远端只读:写入报
remote workspaces are read-only in this build - 无远端
watch:Files 标签不会自动刷新(收起再展开文件夹) @提及与回合 diff 仍走宿主本地路径;内置glob/grep跑的是宿主 ripgrep,看不到远端文件(阶段 2 补remote_search)- 插件会接管所装 profile 的
ctx.fs(不碰锚点的会话行为不变)
验证
- 单元测试 21 例(离线 18 通过,3 例 SSH 集成按环境变量跳过)
- 在真实 DSH 宿主上验证:
workspace/create→ 会话 cwd=锚点 → Files 标签workspaceFiles/list返回远端条目、workspaceFiles/read返回远端内容(version 令牌为远端派生格式) - 改名回归:
dsh-remote-workstation的 loader id、patch 行名、包名三者一致后在隔离 profile 中正常启动
完整版(0.2.0)