DeepSeek Harness 插件

dsh-ssh

SSH remote-execution plugin: ProxyJump chain, SFTP filesystem, subprocess and PTY over ssh2.(英文原文)

跳到安装方式

来源信息

GitHub 仓库
UynajGI/dsh-ssh
最近更新
2026年8月16日
分类
工具与能力
GitHub stars
7
载体类型
plugin
目录证据
上游声明已找到 dsh.bundle
证据路径
package.json#dsh.bundle
核对版本
0.1.0-rc.8
上游核对日期
2026-08-20

该证据由上游目录提供。本站没有安装、运行或安全审核这个插件。

安装

默认先复制一段 Prompt,让 Agent 读 GitHub 仓库和源码;需要自己装时再切到命令。

复制这段 Prompt,发给 DSH、Codex 或其他 Agent,让它先读 GitHub 仓库和源码。

请先不要安装或执行任何命令。阅读这个插件的 GitHub 仓库、README 和关键源码,然后用清楚、直接的方式回答以下问题,帮助我判断它是否适合我的需求:

1. 这个插件是什么,解决什么问题;
2. 适合哪些用户和典型使用场景;
3. 安装后如何使用,并给出一个最小使用示例;
4. 有哪些已知限制,以及隐私、安全、兼容性或维护风险;
5. 给出“推荐 / 有条件推荐 / 不推荐”的明确建议和理由。

请区分仓库明确说明、根据源码推断和未知信息。证据不足时请明确说明,不要猜测或照抄 README。

GitHub:https://github.com/UynajGI/dsh-ssh
插件名:dsh-ssh
作者:UynajGI

检查来源文件

安装前先看这个插件目录里的 README 和其他文件。

文件资源管理器4 个文件
README.zh.md来源说明 · 只读预览
README 语言

dsh-ssh

English | 中文

<p align="center"> <img src="https://img.shields.io/npm/v/dsh-ssh" alt="npm 版本"> <img src="https://img.shields.io/npm/l/dsh-ssh" alt="许可证"> <img src="https://img.shields.io/badge/node-%3E%3D22-339933" alt="Node 版本"> <img src="https://img.shields.io/github/actions/workflow/status/UynajGI/dsh-ssh/ci.yml?label=CI" alt="CI 状态"> <img src="https://img.shields.io/github/stars/UynajGI/dsh-ssh" alt="GitHub Stars"> <img src="https://img.shields.io/badge/dsh-plugin-2ea44f" alt="dsh-plugin"> </p>

DeepSeek Harness 的 SSH 远程开发插件。把 Bash / 文件工具 / PTY 终端 / LSP 整体切到远程主机,支持跳板链(ProxyJump)、SFTP 上传下载、远程 subprocess 与交互终端。基于 ssh2

> dsh-plugin 生态中第一个(截至 2026-08 唯一)SSH 远程开发插件。已通过真实跳板环境(双跳、密钥认证、SFTP 读写)端到端验证。

架构:本地大脑,远程手脚

你的本机 (deepseek-harness)                    远程主机
┌───────────────────────────────────┐   SSH   ┌──────────────────────┐
│ agent loop(模型编排、会话、日志) │◄────────►│  bash / 命令执行      │
│ LLM API 调用(本机直连,不出网)   │ exec    │  文件系统 (SFTP)      │
│ 凭证 / 配置 / 会话状态             │ pty     │  PTY 交互终端         │
│ ctx.subprocess → dsh-ssh          │ sftp    │  LSP / git / 编译     │
│ ctx.fs → dsh-ssh                  │         │                      │
└───────────────────────────────────┘         └──────────────────────┘

不需要把 dsh 部署到远程。 dsh-ssh 实现 deepseek-harness 两个能力缝隙(capability seam)的远程 provider——ctx.subprocess(远程进程)与 ctx.fs(远程文件)。框架里所有消费这两个缝隙的工具(bash、文件读写、终端、LSP、子代理进程)零改动自动切到远端执行:模型在本地思考,命令在远程跑,结果回传本地进模型上下文。

安装

npm i dsh-ssh

快速开始(cordis.yml)

一行挂载全部——共享连接 + 两个远程 provider:

- id: ssh-remote
  name: dsh-ssh
  config:
    host: server.example.com  # 目标主机(必填;也可用 ~/.ssh/config 别名,如 prod)
    port: 22
    username: root            # 必填
    privateKey: ~/.ssh/id_ed25519   # 私钥文件路径,或直接写 PEM 内容
    # password: 'xxx'               # 密码认证(可与 privateKey 并存)
    # agent: 'pageant'              # Windows Pageant;Unix 填 SSH_AUTH_SOCK 路径
    cwd: /root/workspace           # 远程工作目录(必填,绝对 POSIX 路径)
    # --- 跳板链(可选,按序:先连第一个跳板,最后连目标)---
    jump:
      - host: bastion.example.com
        # port: 22             # 缺省跟随目标机
        # username: ubuntu     # 缺省跟随目标机
        privateKey: ~/.ssh/id_ed25519
      # - host: 第二级跳板 ...
    # --- 连接与安全 ---
    readyTimeout: 45000        # 等价 ConnectTimeout(毫秒,默认 45s,中继链路常见慢握手)
    keepaliveInterval: 0       # 等价 ServerAliveInterval(毫秒,0 禁用)
    keepaliveCountMax: 3       # 等价 ServerAliveCountMax
    strictHostKeyChecking: false   # true 时校验主机指纹
    knownHosts:                    # strictHostKeyChecking: true 时必填
      - 'SHA256:xxxxxxxx...'

聚合行等价于三个子路径行——只有需要单独组合 provider 时才分开挂载:

- id: ssh
  name: dsh-ssh/ssh            # ctx.ssh 连接(上面的 config)
- id: subprocess-ssh
  name: dsh-ssh/subprocess     # ctx.subprocess 远程 provider
- id: fs-ssh
  name: dsh-ssh/fs             # ctx.fs 远程 provider(SFTP)

界面上的「添加工作区」走 SSH(Web GUI)

Web 界面的添加工作区流程(对话首屏的工作区选择器、侧边栏的工作区浏览) 由 dsh-ssh 的客户端 UI 接管,布局为左侧连接侧栏 + 右侧目录浏览(VS Code Remote Explorer 式):侧栏依次列出「SSH 配置主机」「已保存连接」和「本机目录」, 右侧是与当前选中目标对应的目录浏览器。本机列表继续走 ctx.directoryPickerbrowse 能力;远程列表、连接管理与远程目录浏览走 dsh-ssh 自己的 /dsh-ssh RPC 通道。浏览本机时,工具栏还有「系统选择器」按钮(local.pickNative 端点,复用宿主的 OS 原生文件夹对话框)——弹窗里选中的目录直接成为工作区, 不必在列表里逐层点开。

~/.ssh/config 主机直达(config.hosts

侧栏的「SSH 配置主机」分区由 config.hosts 端点驱动:每次打开对话框都会 重新读取宿主机的 ~/.ssh/config,列出其中的精确 Host 别名(通配符 模式如 *.example.com 不列出),每个条目带解析出的 user@host:port、是否 配置 IdentityFile、是否有 ProxyJump:

  • 点击一个别名:解析其完整配置(用户名、端口、私钥、跳板链)→ **自动注册

进连接注册表并直接进入该主机的目录浏览**,免表单(VS Code Remote-SSH 式); 已注册过的别名标「已添加」,点击直接切换。

  • 别名未配置 User:不自动注册,而是打开预填好的表单(端口 / 私钥 /

跳板已填入),只需补用户名。

  • 别名未配置 IdentityFile:可以注册,但连接会在认证处失败——右栏会把

All configured authentication methods failed 翻译成可读提示,并提供 「补全认证」按钮打开预填表单。

「新建连接」表单本身也是别名优先:主机字段填 ~/.ssh/config 别名,失焦或 粘贴时自动解析预填(「识别 ssh 配置」按钮保留为兜底),解析成功后表单内会 显示一行摘要(别名 → user@host:port、私钥路径、跳板链)。

选中远程目录后,客户端先经 /dsh-sshsession.route 拿到一个本地占位 目录<DSH_HOME>/dsh-ssh-routes/<连接id>/<远程路径>,宿主侧自动创建), 再用它创建会话:

const { cwd } = await rpc('session.route', { id: connectionId, path: remotePath })
ctx.sessions.create({ cwd })

之所以绕这一步:宿主的 session 服务会用 node:fs 对项目目录做本地 mkdirssh://… 形式的 cwd 过不了这一关;而 mkdir 对已存在的目录静默 成功。ctx.subprocessctx.fs 同时识别 ssh://<id>/<path> 与这个本地 占位前缀,把该会话的 bash / 文件 / 终端操作路由到对应注册连接的对应目录。 远程会话不会写入 DSH 本地 workspace 注册表(见「已知限制」);删除连接时会 一并清掉它的占位目录树。

挂载三行:聚合 provider 行 + 本机/远程目录 browse 后端 + 连接注册表与 RPC 通道。补丁层的 name校验字段(名字对不上会跳过整条补丁,不是替换), 所以要用 disabled 按 id 关掉 Web 包默认挂载的 @deepseek-ai/dsh-host-directory-picker-auto 行(它动态挂载的界面随之 消失)。在 Web profile($DSH_HOME/profiles/web/cordis.patch.yml)中:

# 关闭启动时自动选择的 picker(它动态挂载的界面一起消失)
- id: directory-picker
  name: '@deepseek-ai/dsh-host-directory-picker-auto'
  disabled: true

- insert:
    - id: ssh-remote
      name: dsh-ssh
      config: { ...同快速开始的 config... }

    # 本机目录 browse 后端(dsh-ssh 客户端会注册两个 directoryFlow 槽位)
    - id: directory-picker-ssh
      name: dsh-ssh/picker
      config:
        maxEntries: 1000

    # 多连接注册表 + /dsh-ssh RPC(连接持久化与远程目录浏览)
    - id: ssh-web-channel
      name: dsh-ssh/web
      config:
        maxEntries: 1000

远程会话打开后,bash / 文件 / 终端工具都跑在所选连接的 ssh:// 路径上。

选择器配置(dsh-ssh/picker

字段类型默认说明
maxEntriesnumber1000单层目录行数上限(隐藏行计入;超出时 truncated 标记截断)
remoteLabelstring保留字段:当前客户端流程不再使用钉住入口,远程入口在左侧连接侧栏里

dsh-ssh/picker 现在只承担 ctx.directoryPicker 的 browse 后端(Windows 上 本机目录照常可用,POSIX 绝对路径走聚合 SSH 连接)。客户端 UI 的远程连接 列表与目录浏览改走 dsh-ssh/web 的 RPC。

配置参考(dsh-ssh/ssh

字段类型默认说明
hoststring目标主机(必填)
portnumber22目标 SSH 端口
usernamestring登录用户(必填)
passwordstring密码认证
privateKeystringPEM 私钥内容或本地私钥文件路径
passphrasestring加密私钥的密码
agentstringssh-agent socket 路径或 pageant
jumpJumpConfig[][]跳板链,每级可独立配 port/username/认证
cwdstring远程工作目录(必填,绝对 POSIX 路径)
readyTimeoutnumber45000连接超时(毫秒)
keepaliveIntervalnumber0SSH 层保活间隔(毫秒)
keepaliveCountMaxnumber3保活失败判定次数
strictHostKeyCheckingbooleanfalse是否校验主机指纹
knownHostsstring[][]信任的主机指纹(SHA256:…)或原始 base64 公钥

OpenSSH ~/.ssh/config 映射

OpenSSH 配置dsh-ssh 字段
HostName / Port / Userhost / port / username
IdentityFile / IdentitiesOnlyprivateKey(路径或 PEM)
PasswordAuthenticationpassword
ForwardAgentagent
ProxyJump(逗号分隔多级)jump 数组(逐级)
ConnectTimeoutreadyTimeout
ServerAliveInterval / ServerAliveCountMaxkeepaliveInterval / keepaliveCountMax
StrictHostKeyChecking + UserKnownHostsFilestrictHostKeyChecking + knownHosts
RemoteCommand / RequestTTYspawnTerminal(PTY 由消费者请求)

能力

能力实现
跳板链jump 数组,多级跳板(direct-tcpip,等价 OpenSSH ProxyJump),每级独立认证
认证密码、私钥(PEM 内容或路径)、passphrase、ssh-agent / Pageant;全部未配置时回退 ~/.ssh 默认私钥(id_ed25519 / id_ecdsa / id_rsa,等价 OpenSSH 行为)
近端上传SFTP 原子写(同目录临时文件 + rename,保留原 mode)
远端下载fs provider 全套:read / streamText(流式解码)/ readBytes(限量)/ listDir / stat / lstat
远程命令subprocess provider:collect(tail 保留 + 本地 spill 文件)、pipe、inherit、批量 stdin
交互终端PTY(spawnTerminal),输入输出 + TERM→KILL 清理
添加工作区 GUIdsh-ssh/picker:directory-picker 接缝的 browse 后端;客户端 UI 为左侧连接侧栏(~/.ssh/config 主机直达 + 已保存连接 + 本机)、右侧目录浏览
环境隔离远端登录环境 scrub(剔除 DSH_* 与凭据形变量)+ 显式 env 覆盖,env -i 启动
并发安全fs 写操作按 targetKey 串行化(防并发写同一文件)
主机校验strictHostKeyChecking + knownHosts(SHA256 指纹或原始公钥)

性能

  • 连接复用:三个 provider 共享一个 SSH 连接(含跳板链);SFTP 通道懒打开、复用,断线自动失效重建。
  • 环境缓存:远程登录环境只读一次并缓存(env -0 一次开销),每次 spawn 不再重复探测。
  • 输出本地 spill:collect 模式的内存 tail + 本地 spill 文件,与官方本地 provider 同语义。
  • 零轮询:spawn 一条 exec 通道完成命令(cd && exec env -i -- …),无轮询、无中间状态文件。

可靠性

  • 退出事实权威:exit code / signal 来自 SSH channel close 事件(真实远端进程事实)。
  • UTF-8 安全:exec 输出整段 buffer 后统一解码,SSH 分包不会损坏多字节字符。
  • 失败即报错:连接失败、认证失败、跳板失败、SFTP 错误都 fail loud,携带可读信息。
  • 清理兜底:插件卸载时终止全部活动进程/终端并关闭连接;临时文件(staging dir、spill)随写失败清理。

故障排查

症状原因与处理
All configured authentication methods failed认证配置错误:核对 username / privateKey 路径 / passphrase;私钥权限过宽(chmod 600)
Cannot read private keyprivateKey 不是 PEM 内容且文件路径不存在
跳板连接超时检查跳板 host/port 可达性、readyTimeout;跳板机的 User/认证单独核对
Host key verification failedstrictHostKeyChecking: trueknownHosts 未含目标指纹;用 ssh-keyscan 获取后填入
exec 返回 127远程命令不存在;确认远程 PATH(scrubbed 环境保留远端 PATH)
写文件报 FS_NOT_OBSERVED文件已存在且用了 createIfAbsent(防覆盖语义,非 bug)

已知限制

  • 远端 pid 不可见:SSH channel 不暴露远端 pid,SubprocessHandle.pid 恒为 -1
  • 终止不保证进程树terminate 通过 channel 信号(SIGTERM → grace → SIGKILL)作用于远程直接进程,不保证覆盖其子进程树(SSH 协议固有,与本地 provider 的进程组语义有差距)。
  • 终端前台进程组inspectForeground 返回 undefinedsignalForeground 不可用(SSH channel 无法解析远端前台进程组)。
  • 单连接不重连:连接断开后需重启插件。
  • 远程目录以会话落地:多连接界面选中远程目录后,经 session.route 取本地占位目录并 session.create({ cwd }) 打开远程会话(占位目录形如 <DSH_HOME>/dsh-ssh-routes/<id>/<path>,会话列表里 cwd 显示的就是它);它不会在 DSH 的本地 workspace 注册表里创建 workspace 记录(dsh-workspace 仍只接受本地 fs.realpath 目录)。
  • POSIX 主机上选择器仅远程:任何绝对路径都是远程路径,本机文件系统无法与远程共用选择器(Windows 主机通过盘符/UNC 路由两者共存)。
  • streamText 仅文本:二进制文件抛 FS_NOT_TEXT(与官方 provider 一致)。

开发

npm i
npm run typecheck
npm run build       # 产出 lib/ —— harness 加载器实际导入的编译产物
  • Git hooks(husky):pre-commit 跑 typecheck;commit-msg 强制 Conventional Commitspre-push 拒绝与 package.json 版本不一致的版本 tag。
  • CI(GitHub Actions):每次 push/PR 跑 typecheck + 发布载荷检查。
  • 发布(GitHub Actions):推送版本 tag 自动发布 npm 并生成 GitHub Release:
npm version patch -m "chore(release): v%s"   # 改版本 + 提交 + 打 tag 一步完成
git push origin main && git push origin --tags

tag 必须与 package.jsonversion 字段一致(本地 hook 与 release workflow 双重强制)。发布使用仓库的 NPM_TOKEN secret(npm Automation token,CI 发布可绕过 2FA)。

License

MIT