flymysql/dsh-remote

flymysql/dsh-remote

多机远程工作区:管理多台 SSH 主机,在原生「添加工作区」流程里选本机系统文件夹或远程目录,把远程工作区镜像成真实本地文件夹并用 rw_* 工具操作。选择器是居中弹窗,默认落在本机页签,远程路径自动预填 `/` 并逐级自动补全目录。

工具与能力deepseek-harnessdshremote

English · 中文

dsh-remote

由 @flymysql 维护 · 主页 · 用量统计 · 博客 · 讨论区 · Issue · English

为 DeepSeek Harness(DSH)打造的远程工作助手。

维护多台 SSH 机器,然后在「选择工作区」时选一个远程工作区(或本地工作区),Agent 就能在不离开 harness 的情况下直接操作——列文件、读代码、在远程主机上跑构建/命令,并把远程目录镜像成一个真实的本地工作区对象。

DSH 的 Web 界面刻意只监听 127.0.0.1(CLI 为安全拒绝 --host 0.0.0.0)。本插件反过来:由你主动连出到你维护的机器,选一个工作区,然后通过 DSH 原生的工作区 + 文件流来工作——不改动 `dsh-workspace` 核心。

反过来也成立:你可以把远端那台机器的 DSH 网页界面,当成本机浏览器里的一个页面打开——同样不需要在远端开放任何端口(详见 在本地打开远程机器上的 DSH 界面)。连不上时还有一键体检与部署,它会替你把远端环境修好并验证。

界面预览

设置 → 远程工作区 —— 多机列表、高级配置(私钥/跳板机/agent)、连接体检、远程 DSH 界面、端口转发、审计日志、更新:

原生 「Add workspace / 选择工作区」 流程 —— 居中弹窗、两个 tab,默认落在「本机」;切到「远程」:

  • 路径框实时补全;Windows 主机根级显示「此电脑」多盘视图;「浏览…」浮层选中只回填、不直接提交。
  • 确定后创建真实本地镜像并被 harness 收养,同时通过 SFTP 保持同步;所选工作区持久化到该机器。

功能

上图是能力总览;下面只列上图没说清、但用起来需要知道的部分。

其余要点:

  • 22 个模型工具(便于复制/检索):rw_info、rw_connect、rw_machines、rw_pick_workspace、rw_list_dir、rw_stat、rw_read_file、rw_write_file、rw_edit、rw_append、rw_mkdir、rw_remove、rw_move、rw_exec、rw_search、rw_download、rw_upload、rw_sync、rw_push、rw_forward、rw_disconnect、rw_deploy_probe。
  • 会话列表里区分本地/远程(0.8.43+)—— 远程会话的行首有一个绿点,悬停显示 user@host:port 与远端路径;打开该会话后,标题栏还会常显一个主机标签(本地会话两者都没有)。判定只看会话 cwd 是否落在 $DSH_HOME/remote-workspaces/… 镜像里,所以「保存过但没在用」的机器不会给本地会话打标。
  • 把远端机器的 DSH 界面挂到本机(0.8.36+)—— 不用在远端开任何端口:插件主动 SSH 连过去,在远端只监听 `127.0.0.1` 地起一个 dsh web,再把端口经隧道搬回本机的一个 loopback 端口。地址带一次性登录令牌,只在本机这次跳转里用一次。
  • 连不上时一键体检与部署(0.8.36+)—— 连不上大多不是插件的问题,而是远端 dsh 的版本或环境不对。体检(只读)会报出平台/node/npm/dsh 版本、原生模块能否启动、代理与 npm 源,并给出修复建议;「部署并验证」把它装到远端私有目录(不写系统目录、不改 PATH、不覆盖你在用的版本,删目录即回滚)并逐步校验。按钮不再需要先体检——直接点即可(0.8.40+),部署过程自己会重新体检。失败还能交给内置的 dsh-remote-deploy 技能排查。
  • 远端跨平台 —— 文件访问走 SFTP 协议层(不依赖 POSIX shell),Linux/macOS/Windows 远端都能列/读/写/搜索/同步。
  • Windows 主机 —— 自动探测平台并定位 Git Bash,命令经 bash -s 走 stdin 执行,不受引号/反斜杠转义困扰(config.shell 可指定或设 native 关闭);C:\Users\dev 与 /c/Users/dev 两种写法都接受。
  • 长任务异步化 —— rw_sync/rw_push 传 async: true 返回 taskId,可查询进度/结果/取消。
  • 数据跟随 Harness 根目录 —— 机器清单与镜像在 $DSH_HOME/remote-workspaces;0.6 之前的数据首次启动自动迁移。
  • `~/.ssh/config` 是连接四字段的唯一来源(0.8.50+,issue #50)—— 保存机器时把 主机/用户/端口/私钥 写入 ~/.ssh/config 的受管块(# dsh-remote: managed 标记,只动自己的块;你手写的同名 Host 块绝不覆盖,会提示冲突)。之后改这个文件立即生效:任何 Host 块点名的主机(含字面 IP),连接都按文件解析(裸 Host * 是通用默认,不算点名);机器记录里的副本只做兜底,插件自己的状态(最近工作区/延迟等)仍存注册表。
  • 不改动 `dsh-workspace` 官方代码 —— 全部作为普通插件实现。

安装

DSH 版本兼容性

同时支持 0.1.x 与 0.2.x 两条 DSH 线。DSH 会在导入 bundle 之前校验所有 @deepseek-ai/dsh-* 的 peer 范围,任一条不匹配就整包丢弃(没有设置页、没有 rw_* 工具):

dsh: skipping profile bundle "dsh-remote": Error: Plugin dsh-remote@… is incompatible …

caret 在 0.x 上会锁死小版本线(^0.1.x 容不下 0.2.x,反之亦然),所以自 0.8.29 起改为跨线区间 >=0.1.0-rc.6 <0.3.0。低于 0.8.29 请在升级 DSH 前先升级本插件。

官方 Desktop 兼容适配(实验性)

对 DeepSeek 官方 Desktop 的适配(以 0.1.5-rc.2 Host 协议验证,不修改 Harness 核心):

  • 经 ctx.connection.fetch 注册 /api/dsh-remote/*,由 Desktop 的 dsh-app: 通道承载,不启动 Web Server。
  • 经 sidebarRightTabs 提供原生「远程文件」入口,不把远端路径传给本地预览器。
  • dsh-better-sidebar 不再内置;官方 Desktop 用原生右侧栏,不需要它。

已验证 Host 启动、IPC 请求、真实 SSH 的只读连接/列目录/读文件,以及双机会话路由(侧栏 /ls /read /write /fs 带 sessionId 时按会话选机)。原生文件 tab 的完整 GUI、失败/取消交互、非 macOS 宿主仍属实验性。Desktop 安装器可能需为 ssh2 / cpu-features 可选构建脚本配置策略。

已发布的 Web bundle

dsh plugin add dsh-remote

自 v0.8.18 起只安装并挂载自身;Web 侧边栏(dsh-better-sidebar)改为可选。需要 Web 版远程文件浏览/编辑时再单独装它;不装时 rw_* 工具、设置页、同步、审计与转发均照常工作。

从 0.7.2–0.8.17 升级: 内嵌侧边栏会消失,旧 profile 里 id: dsh-remote-sidebar 的覆盖可以删除。

(或 npm install dsh-remote,再在 cordis.patch.yml 加 - id: dsh-remote / name: dsh-remote。)

快速上手

  1. 加一台机器 —— 设置 → 远程工作区 → 填 host/port/user + 密码或 key → 设为当前。
保存 ≠ 激活:保存只是备用连接;只有「设为当前」(或 Agent 调 rw_connect)才进入会话的 remote context。
  1. 选工作区 —— 点侧边栏/会话的 Add workspace:
  • 本机 → 系统文件夹选择(或手输路径)→ 本地工作区。宿主没有可用系统对话框时改用插件内置浏览器。
  • 远程 → 选机器 → 浏览到远程目录(或输入 /path)→ 「设为远程工作区」⇒ 创建并收养本地镜像工作区。
  1. 让 Agent 工作 —— 当作普通工作区使用,例如 rw_read_file / rw_write_file / rw_edit / rw_exec / rw_search / rw_sync / rw_push / rw_forward(完整列表见上文)。
Remote context 是 session 级的:只有当前 session 的工作区是某个远程镜像时,system prompt 才注入「Remote workspace」段落;普通本地 session 不受影响,模型也不会主动调 rw_*。

在本地打开远程机器上的 DSH 界面

远程机器上那个 dsh web 也可以当成本机的一个页面打开(issue #46)。方向仍然是「本地主动连出去」:

远端不需要开放任何端口。DSH 刻意拒绝 --host 0.0.0.0(它会把远程代码执行能力暴露到网络上),
所以正确的做法是由本机 SSH 连过去,在远端只监听 127.0.0.1 地起一个 dsh web,
再把它的端口经 SSH 隧道搬回本机的一个 loopback 端口。

用法:设置 → 远程工作区 → 「远程 DSH 界面」 → 选一台机器 → 连接并打开。 浏览器会新开一个标签页,显示的就是那台远端机器的完整 DSH 界面(聊天、工具树、设置都在)。

  • 界面地址形如 http://127.0.0.1:3088/?token=…。那个 token 是远端进程启动时打印的一次性凭据,

只在本机的这次跳转里用一次:DSH 会把它换成 30 天有效的签名 cookie 并跳转到干净的 /。 请不要把这个带 token 的地址外发。

  • 「断开」只关隧道,不会杀掉远端进程(它可能是你在别处也在用的实例);

要连远端进程一起停,用「断开并停止远端」——它只会结束本次由插件启动的那个进程。

  • 若远端已经在跑一个界面(比如你自己在终端里起的),可以选机器后把它的

http://127.0.0.1:/?token=… 粘进输入框,点「连接已有实例」,插件不会再去启第二个。

  • 远端 dsh 不在 SSH 登录 PATH 时,用 webAttachCommand 指向它的绝对路径;

想让它别污染远端用户自己的会话/设置,用 webAttachDshHome 指一个临时目录。 按机器设:设置 → 远程工作区 → 机器卡片 → 展开「高级」→「远端 dsh 命令」, 填绝对路径即可(例如 /home/you/.nvm/versions/node/v22.19.0/bin/node /home/you/.dsh-remote/dsh/node_modules/@deepseek-ai/dsh/lib/bin.js); 留空则用全局的 webAttachCommand。「部署并验证」成功后也会自动写进这个字段。

  • 隧道跟着 SSH 连接走:连接断了界面会显示「连接已断开」,不会影响本机 DSH 进程;

池自动重连后插件会重新解析远端端口(必要时重启它自己启动的那个远端进程),界面点「重连」即可继续用。

  • 远端 node 需要 ≥ 20.12(dsh 启动要用 node:util 的 parseEnv,该导出从 20.12 起才有)。

node 太旧时体检会直接报 blocker:npm 只会给 EBADENGINE 警告、照样把包装完, 直到启动才崩在 does not provide an export named 'parseEnv',所以必须在装之前拦住。

连不上?先体检,再一键部署

连不上大多不是插件的问题,而是远端 dsh 的版本或环境不对。 最典型的一种:dsh 0.1.0-rc.6 依赖 node-pty@1.1.0,而那个版本发布的包里没有 `linux-x64` 预编译产物 ⇒ dsh web 在 Linux 上根本起不来。这种情况以前只能看到「没拿到启动令牌」,无从判断。

设置 → 远程工作区 → 「远端 dsh 体检与部署」:

  1. 体检(只读) —— 探测远端平台/node/npm/dsh 版本/原生模块能否启动/是否认识

--no-open/代理与 npm 源,给出结论与修复建议。不写任何东西,可以随便点。

  1. 部署并验证 —— 只在上一步判定「需要且可以自动装」时才出现。它会装到远端

私有目录(默认 ~/.dsh-remote/dsh):不写系统目录、不改 PATH、不覆盖你在用的版本, 删掉那个目录即完全回滚。装完逐步校验(二进制 / 原生模块 / web 子命令), 成功后按机器记住这条 dsh 路径,之后连接就用它。

  1. 让 AI 排查 —— 只在失败后出现。它会创建一个会话,让内置的 dsh-remote-deploy

技能去处理确定性流程覆盖不到的长尾(没有 npm、要 sudo、代理、内网镜像源、Windows 远端等)。 只有你点它才会创建,因为这会消耗模型额度。

相关配置:webInstallPrefix(装哪,默认 $HOME/.dsh-remote/dsh)、 webInstallVersion(装哪个版本,默认 0.1.5-rc.2——第一个在 Linux 上能起 web 的版本)、 webInstallRegistry(npm 源;留空则沿用远端自己的配置,不会覆盖你的内网镜像)。

可选:CLI 默认机

可在 cordis.patch.yml 提供默认机:

# 示例:请换成你自己的机器
- id: dsh-remote
  name: dsh-remote
  config:
    host: 203.0.113.10   # 或你的真实主机 / hostname
    port: 22
    username: dev
    privateKeyPath: ~/.ssh/id_rsa
    # 或用密码登录:
    # password: '…'
    workspace: ~/project

若 host 为空,插件启动时处于断开状态,在 UI 里配置机器即可。

常用命令(安装 / 查看 / 启动)

DSH 的 dsh 可能不在某些 shell 的 PATH(比如 Windows PowerShell 里在某个仓库目录下),所以同时列出 dsh 与 npx 两种写法。操作都要用 --profile 指定 profile(一般 web):

# 安装(从 npm 拉到 profile)
dsh plugin --profile web add dsh-remote
# 同一效果:当 `dsh` 不在 PATH 时用 npx
npx --yes @deepseek-ai/dsh plugin --profile web add dsh-remote

# 确认已装
dsh plugin --profile web list
npx --yes @deepseek-ai/dsh plugin --profile web list

# 启动 web 界面(重载 profile,新插件在启动时生效)
dsh --profile web
npx --yes @deepseek-ai/dsh --profile web   # 访问 http://127.0.0.1:3080

# 迭代用本地源码替换 npm 版(便于改 dsh 插件代码后即测)
npx --yes @deepseek-ai/dsh plugin --profile web add D:/path/to/dsh-remote
npx --yes @deepseek-ai/dsh plugin --profile web remove dsh-remote   # 恢复用发行版

启动成功后,设置 →「远程工作区」会出现;「Add workspace」流程会带「本机 / 远程」两个 tab(见上方效果图)。

开发(沙箱优先,勿改产品)

迭代一律在沙箱里做——手工改产品 profile 会被插件管理器在重装时还原:

scripts/dev-run.sh --restart   # 启动 / 重启隔离沙箱
scripts/dev-run.sh --stop      # 停止
scripts/dev-run.sh --status    # 是否在运行
  • 沙箱自带独立 DSH 实例(仓库内 dev-harness/harness),UI 在 http://127.0.0.1:50599。
  • 宿主半(lib/index.js)改动需 --restart;客户端半(lib/client.js)改动刷新页面即可。
  • 脚本用硬链接拷贝把 lib/ 放进沙箱而非软链——软链会破坏 @deepseek-ai/* 的解析。
  • 提交前跑 node check.mjs(框架约束闸门)与 npm test;scripts/boot-smoke.sh 证明插件仍能启动。
  • 完整规则见 scripts/dev-standards.md。

部署到产品 profile 是单独的受控动作(./sync.sh),只在确定要发布时做。

配置

键 · 类型 · 默认 · 说明 host · string · '' · 默认 SSH 主机(空=断开) port · int · 22 · 默认 SSH 端口 username · string · '' · 默认 SSH 用户 password · string · '' · 默认 SSH 密码(非空覆盖 key) privateKeyPath · string · '' · 私钥路径(仅在显式提供时使用) passphrase · string · '' · 加密私钥的 passphrase workspace · string · '' · 默认远程工作区路径 shell · string · '' · 远程命令终端策略:''=自动检测(Windows 找 Git Bash)、'git-bash'=优先 Git Bash、'native'=不包装、其他=显式 bash.exe 路径(如 C:\Program Files\Git\bin\bash.exe) commandTimeoutMs · int · 20000 · 单条远程命令超时 connectTimeoutMs · int · 15000 · SSH 连接超时 maxOutputChars · int · 200000 · 单条远程命令捕获的 stdout/stderr 上限 maxFileBytes · int · 52428800 · 镜像同步时跳过超过该大小的文件(0=不设上限) hostKeyMode · string · accept-new · 主机指纹策略:accept-new(首次信任)、verify(拒绝未知主机)、off(跳过校验) useAgent · bool · false · 用 OpenSSH agent(SSH_AUTH_SOCK)认证 keyboardInteractive · bool · false · 允许 keyboard-interactive 认证(OTP/MFA)并复用配置的密码 backend · string · native · SSH 传输后端(issue #51)。native=内置 ssh2(密码/私钥/agent/动态码);openssh=调用本机 `ssh` 命令,因此支持 GSSAPI/Kerberos(kinit 后免密)以及 ProxyCommand、PKCS11Provider、Match 等 OpenSSH 专有特性。可按机器单独设置。代价:每条命令新建一次连接(比 native 慢);SFTP 仍复用单条长连接。主机名会被原样交给 ssh(~/.ssh/config 是配置唯一来源),主机密钥也由 ssh 用你自己的 known_hosts 校验 sshBinary · string · '' · openssh 后端调用的 ssh 可执行文件(留空=用 PATH 里的 ssh) proxy · object · — · 跳板机:{ host, port?, username?, password?, privateKeyPath? };语义是「在跳板机上连目标 host:port」,所以也能用来连跳板机上 docker 容器的 sshd(见「常见问题」里的 issue #56) autoPush · bool · false · 镜像内文件被编辑后自动推回远端(watcher,带防抖) auditLog · bool · true · 把执行的命令追加到 $DSH_HOME/remote-workspaces/audit.log encoding · string · utf-8 · 远程文件读写的文本编码(如 gbk) fileReference · bool · true · 远程 @ 补全:远程会话的 @ 列出远端目录树(issue #39);关闭则只有本地镜像 fileReferenceMaxResults · int · 20 · 一次 @ 查询最多返回多少候选 fileReferenceMaxEntries · int · 3000 · 一棵远程工作区索引最多保留多少条目 fileReferenceExcludedDirectories · string[] · [.git, node_modules, dist, build, out, coverage, target, .next, .nuxt, .turbo, .venv, __pycache__, .pytest_cache, .mypy_cache, .gradle] · 远程 @ 遍历跳过的目录名 fileReferenceTimeoutMs · int · 4000 · 一次远程索引遍历的墙钟预算(超时用已扫到的部分结果,不让光标等) searchTimeoutMs · int · 60000 · rw_search 的协作式预算(ms):既作为工具声明的 timeoutMs 交给 DSH 的 timeout-policy,也是搜索自身的墙钟上限;到点返回部分结果并标 TRUNCATED(issue #44)。 searchMaxEntries · int · 50000 · rw_search 在返回部分结果前最多扫描多少个文件。 updateMode · string · auto · 自更新模式:auto=加载时及每 6 小时检查并自动应用、manual=仅在手动检查时查、off=完全不查。0.8.27 起默认 `auto`——之所以现在才安全,是因为 0.8.24 补上了宿主半热切换 updateCheckIntervalMs · int · 21600000(6h) · auto 模式检查 npm 的间隔(下限 60000) updateAutoReload · bool · true · 更新落地后自动热切换宿主半;false 则留到下次启动,设置页会显示 pendingReload webAttachPortStart · int · 3088 · 「远程 DSH 界面」在本机监听的起始端口(占用则顺延,仅监听 127.0.0.1) webAttachCommand · string · dsh · 在远端启动 dsh web 用的命令;远端 dsh 不在 SSH 登录 PATH 时改这里(也可按机器在设置页「高级」里单独设,机器级优先) webAttachDshHome · string · '' · 远端启动时导出的 DSH_HOME;留空则复用远端用户自己的 harness home webAttachWaitSeconds · int · 45 · 等待远端 dsh web 打印启动令牌的秒数 webInstallPrefix · string · '' · 自动部署装到远端的哪个目录;留空用 $HOME/.dsh-remote/dsh(不写系统目录、不改 PATH) webInstallVersion · string · 0.1.5-rc.2 · 自动部署安装的 dsh 版本;默认值是第一个在 Linux 上能启动 web 的版本 webInstallRegistry · string · '' · 安装用的 npm 源;留空沿用远端自己的配置,不会覆盖内网镜像 localTools · string · bootstrap · 本地 session(cwd 不在远程镜像里)暴露哪些 rw_* 工具(issue #63):bootstrap=只暴露配置闭环 rw_machines/rw_connect/rw_pick_workspace(其余 19 个在本地 session 必然报 "this session is LOCAL",暴露只是白占 schema);none=完全不暴露 rw_*(远程配置只能走设置页);all=旧的 full 暴露。远程 session(cwd 在镜像里)不受影响,始终全量;非法值按 bootstrap 处理

权威清单是 lib/index.js 里的 Config schema,本表与之一致。