DeepSeek Harness Plugin Hub

Publish and manage complete Harness Profiles. Discover Plugins for your next setup.

Explore

PluginsPresetsDocsNews

Community

Publish a pluginContactReport an issue

Resources

Plugin Hub on GitHubDeepSeek HarnessSystem statusPrivacy notice
© 2026 DeepSeek Harness Plugin HubPowered byPaxTech

Independent and unofficial. Not affiliated with, authorized by, or endorsed by DeepSeek.

Multi Remote — DSH Plugin for DeepSeek Harness
← Plugins
M

@jk-uzi/dsh-multi-remote

Multi Remote

DSH plugin: SSH / WSL / Docker all-in-one remote development—the agent’s file operations, command execution, search, and terminal all run in the target environment, while the brain stays local

The plugin will be installed here. Keep web if you are unsure.

npx -y @deepseek-ai/dsh plugin --profile web add github:jk-uzi/dsh-multi-remote#7e734cc22154f5551babc6b98fc1144d071b85a8
READMECompatibilityVersions

Compatibility and provenance

Multi Remote is published as @jk-uzi/dsh-multi-remote and currently resolves to version 0.1.1. The Hub verifies its manifest and preserves the exact installation source for reproducible installs.

DSH compatibility
*
Runtime surfaces
web
Release source
github
Registry updated
10/13/2026

Versions

0.1.1
stable
10/13/2026
0.1.0stable
10/9/2026

Related plugins

Loading related plugins…

Latest
0.1.1
DSH
*
HMR
Process restart
Tree shaking
Safe tree shaking not declared
Unpacked size
Unavailable
Files
Unavailable
Surface
web
License
MIT
Source
github
GitHub
★ 1
Weekly downloads
0
Last push
10/13/2026
View source ↗
README badge

Click the badge to copy Markdown for your README.

Do you maintain this Plugin?Claim benefit · Priority security scan

Verify the GitHub repository declared in package.json to manage this listing. After you claim it, Hub will prioritize a security scan of the current version and publish the result when it passes.

Claim this Plugin →
Report an issue
DeepSeek Harness Plugin Hub
ProfilesPluginsCategoriesNewsDocsSign inManage Profiles
ProfilesPluginsCategoriesNewsDocsSign in

Related plugins

More verified plugins in developer-tools.

Web App@deepseek-ai/dsh-web-appThe dsh browser-surface bundle: the web patch layer over dsh-base plus the runtime glue plugin (frontend dist serving, web-surface prompt, bash runtime variables, URL line)Sdk App@deepseek-ai/dsh-sdk-appThe dsh SDK profile bundle: stdio JSON-RPC serving and process lifecycle over dsh-baseExperimental Inspector@deepseek-ai/dsh-experimental-inspectorExperimental cross-realm CDP hub for Host debugging and Client Runtime inspectionExperimental Inspector Profile@deepseek-ai/dsh-experimental-inspector-profileOptional Web bundle for raw Session logs and Chat node inspection

README

dsh-multi-remote

DSH(DeepSeek Harness)插件:同时支持 SSH / WSL / Docker 三种远程环境。 连上之后,agent 的文件读写、bash 执行、搜索全部在目标环境里执行——大脑留在本地,文件和命令在远端。

当前状态:三种后端全部可用(SSH / WSL / Docker)。 见 CHANGELOG。

这是什么

在哪
模型调用、思考、决策本地(装了 DSH 的这台机器)
文件读写、命令执行远端(文件只有一份,就在远端)
界面要看的文件树 / @ 补全 / 改动对比本地锚点目录里的只读镜像

那个本地锚点目录是干什么的?会把我整个项目拉下来吗?

不会。 它是给界面看的:DSH 的文件树、编辑器、改动对比读的都是本地文件, 没有这份镜像,界面上就是空的。但它是有界的、只读的、且不是 agent 干活的地方。

分两个阶段,各有边界(都超出即停,并如实标记截断):

① 打开工作区时(要快 —— 用户在等着):

边界默认值超了会怎样
目录深度12 层更深的子树不搬
条目数20 000 个(文件 + 目录)剩下的不搬
单文件大小256 KiB本地留一个占位文件(见下)
墙钟时间30 秒到点就停,树可能只覆盖一部分

拉取有多快(2026-10-09 实测,WSL、197 个文本文件的项目): 首次全量 ~2.3 秒(批量读:一次调用拉一批,而不是每文件一次); 重连 ~1.1 秒(指纹命中跳过);改动一两个文件后重连,只拉变化的那几个。 SSH 走并发拉取(SFTP 长连接天然支持流水线),Docker 的文件内容本来就走 Engine API 的 archive(一次请求拿一批)。

② 连上之后的「后台补拉」(可以慢 —— 不阻塞任何操作):

边界默认值说明
只拉文本类文件按扩展名(源码/配置/文档/脚本…)网格、数据库、压缩包、图片不拉 —— 它们本来也不适合在界面里读
单文件大小2 MiB比首轮大得多,覆盖绝大多数源码与文档
总字节上限64 MiB到点就停(不会因为连了个 8 GB 的工程就往本地灌 8 GB)
墙钟时间120 秒每次连接都会补一次;上一轮拉过的会跳过,所以会一轮比一轮覆盖得多

为什么要后台补拉:界面的读文件路径是宿主独占的(我们插不进手), 所以「点开文件时自动从远程拉」做不到。退而求其次 —— 在点开之前把 你真正会看的文件先补到本地。实测:一个 1.5 GB 的代码仓库,后台补拉 拉回 138 个文本文件(3.1 MB),README / 配置 / 源码点开就有内容。

占位文件:超过单文件上限的文件,本地会留一个几百字节的说明文件,内容形如:

<!-- dsh-multi-remote:not-synced -->
这个文件没有同步到本地镜像。

远端大小:69.8 MB(单文件上限 256.0 KB)
远端路径:/home/user/my-project/data/mesh.msh

它只在远端 —— 内容没有拉下来(避免为了显示一个文件把几十 MB 搬过来)。
要看或改它,直接在会话里让 agent 读(agent 的文件读写走的是远端,不受这里限制),
或者在会话里用 bash 查看(head / less / file / python 都行)。

这样文件在界面的文件树里看得见(以前是直接跳过 → 树里根本不显示,用户以为不存在), 点开也能立刻知道「它在哪、多大、怎么看」。

⚙️ 后台补拉可以关掉或调参:插件配置 mirrorDeepen = { enabled, maxFileBytes, maxTotalBytes, maxDurationMs }。

实测例子(一个 8.2 GB / 44 662 个文件的工程):

远端:8.2 GB,44 662 个文件
本地锚点:0.37 MB,30 个文件      ← 0.005%,且 >256 KiB 的内容一个字节都没拉

上面量的是首轮格局层(①)的边界效果。之后的后台补拉(②)会在此基础上再补进 「文本类 + ≤ 2 MiB」的文件,所以实际落到本地的会比这个数字多一些 —— 但仍然有界。

⚠️ 代价要如实说(2026-10-09 按代码复核过):

  • 项目特别大时,镜像会被边界截断 —— 被截掉的那部分子树本地根本没有, 界面的文件树里也不显示(首轮的深度 / 条目数 / 30 秒墙钟这三个边界)
  • 首轮超限(> 256 KiB)的文件,本地只有占位:文件树里看得见、点开有一句说明, 但内容没有拉下来 —— 所以界面里看不到内容、也不能编辑 (2026-10-07 之前是直接跳过:那些文件在树里根本不显示,用户以为不存在)
  • 后台补拉会把占位换成真内容 —— 只要它是文本类且 ≤ 2 MiB; 非文本类(网格 / 数据库 / 压缩包 / 图片)或超过 2 MiB 的,保持占位

这几条都不影响 agent 干活(读写、命令、搜索全在远端,不受镜像边界约束), 只影响界面里能看到多少。想看得更全,就把工作区指向更小的目录(比如指向代码仓库本身, 而不是包含备份和数据集的上层目录)。

远端不需要装任何东西:

  • SSH:只要有 sshd 和一个能登录的账号 —— 不需要联网、不需要 API key、不需要装 DSH
  • WSL:只要有发行版本身(本机进程,连凭据都不需要)
  • Docker:只要容器已在运行(连凭据都不需要)

支持的后端

后端状态说明
SSH✅ 可用用 ssh2 库;host key TOFU,变更一律拒绝
WSL✅ 可用驱动 wsl.exe;不需要凭据
Docker✅ 可用直连 Engine API(不依赖 docker CLI);只连已运行的容器,不需要凭据

三者共用同一套上层(镜像层 / 预设层 / 会话绑定 / 工具)—— 加一个后端只需要写一个 transport 文件,上层零改动。

模型能用上的能力

工具干什么
remote_connect / remote_profiles / remote_list / remote_disconnect / remote_refresh连接管理
宿主自带的 bash / read / write / edit自动落到远程(在隔离域内换掉了 fs 与 shell)
glob / grep也在远程执行(我们自己实现,用远程的 find / grep;见下)
人设 / AGENTS.md / 技能 / 目标 / 计划模式 / 后台任务与本地会话同一套(预设里逐项对齐)
remote_terminal远程交互式终端(真 PTY):步进调试器、REPL、需要跨调用保留状态的探索

remote_terminal 的要点:

  • 真 PTY(Docker / WSL 实测拿到 /dev/pts/N),不是管道 —— sudo、密码输入、 全屏 TUI 都正常
  • 状态跨调用保留:cd、导出的变量、函数、后台任务都留着
  • 有界滚动缓冲 + 分页读,不会因为一条 yes 把内存吃光
  • SIGINT 真的能打断前台命令(走 PTY 控制字符,命中前台进程组)
  • 返回的 waitReason 告诉你「跑完了」有多可靠:session_exit 最确定, inferred_idle 是推断,timeout 表示可能还在跑

有界的一次性命令请用 bash —— 它更省事,而且有输出上限与超时保护。 终端是给「状态必须留在终端里」的场景用的。

搜索也是在远程执行的

glob / grep 由本项目自己实现(src/world/search.js),在隔离域内通过 ctx.shell 调用远程的 find / grep。

之所以不用宿主自带的那两个:它们用的是打包在宿主里的 ripgrep 可执行文件, 通过 subprocess 接缝运行(宿主源码注释明确写着 "never ctx.shell")—— 也就是说跑在你本机、搜的是本地镜像:大于 256 KiB 的文件、 超过 12 层或 2 万条目之外的部分根本搜不到,而且不会告诉你漏了。

自己实现之后,搜索与读文件、跑命令一样是原生远程的 —— 实测在一个 328 KB 的文件里搜到了内容,而那个文件本地镜像里压根没有。

与宿主那份的差异(已写进工具描述,不静默):

项说明
正则方言用远程的 grep -E(POSIX 扩展正则),不是 ripgrep 语法;rg 特有的写法(如 lookaround)不支持
忽略规则用内置剪枝名单(.git、node_modules、__pycache__…),不读远程的 .gitignore
结果上限超过上限截断并如实标注(宿主那份会写 spill 文件)
include支持简单 glob 与大括号(*.{js,jsx} 会展开成多个 -name)

需要的话仍可用 bash 跑 rg(若远程装了)—— 那是另一条路,不受上面的方言限制。

连接远程工作区

有两个入口,它们最终走同一套内部实现,所以行为不会漂移。

入口一:侧边栏的「远程开发」弹窗(推荐)

侧边栏里(和官方的 插件 并列)有一行 远程开发 —— 点开是一个固定大小的弹窗: 左边选后端,右边是识别出来的目标(不用自己填名字):

┌────────────────────────────────────────┐
│  远程开发                            ✕ │
│  连上 SSH / WSL / Docker 机器…          │
├───────────┬────────────────────────────┤
│ WSL  2 个 │ 选择发行版                  │
│ SSH  用过 │ ○ Ubuntu-24.04             │
│ Docker    │ ○ docker-desktop           │
├───────────┴────────────────────────────┤
│                       [取消]  [连接]    │
└────────────────────────────────────────┘

三个后端各自怎么选目标:

后端怎么选
WSL自动识别已安装的发行版(wsl -l -q,不会启动发行版)—— 一个就直接点,多个就在列表里挑。列表与 wsl -l -q 完全一致;Docker Desktop 自己装的那个系统发行版(docker-desktop)只是排在最后。busybox 发行版也支持(没有 bash 就用 sh,find/stat 走 busybox 兼容写法)
Docker自动识别容器(运行中的排在前面并标出状态);守护进程没跑时如实说明原因
SSH没有「自动识别」这回事(那需要凭据),所以列出用过的连接 —— 点一条就把主机 / 端口 / 账号 / 私钥路径填好,只需再给密码;也可以「新建连接」

列表长了会自己出现筛选框(容器可能几十个);标题栏的 ↻ 是「重新识别」—— Docker Desktop 是按需启动的,启动完点一下就有容器了,不用关掉弹窗再开。

「探测失败」和「成功但没有」是两回事:探测失败(守护进程没跑、API 不对…) 会显示原因并给一条手填兜底;而探测成功、只是没有目标(没有容器 / 没装发行版)时, 给的是空状态 + 引导(docker run / wsl --install,然后点 ↻)—— 那种情况下给输入框只会让人以为「填对了就能连」。

然后分两步(和 VS Code / ZCode 一样):

  1. 连机器 —— 只连机器,不用填项目路径
  2. 选项目文件夹 —— 连上后显示远程真实的目录树:点进去、返回上一层, 找到项目后点「就用这个文件夹」。

选好之后插件自动建好工作区(镜像 + 预设 + 锚点),关掉弹窗并切回会话 —— 你只要在那个工作区下面新建会话。

弹窗里没有「打开本机文件夹」:那是 DSH 自己「添加工作区」的活。 本地目录不属于远程开发流程,混在一起只会让人以为它也是远程的一步。

为什么不用一次填完:那要求你盲填远程项目根。填错了(比如漏写一层 桌面/)连接照样「成功」,但之后每条命令都切不到目录 —— 非常难排查。 改成点选之后,路径不再靠猜。

⚠️ 为什么入口在侧边栏,而不是「工作区」旁的 + 按钮里: 我们试过把 + 按钮的选目录流程换成自己的界面 —— 结果官方的 选文件夹插件激活失败,整个 DSH 打不开(那个流程已经有官方归属, 插件之间会冲突)。所以改成完全加法式的入口:多一行导航,谁也不影响谁。

实现上用的是宿主自己的三个槽位:sidebar.panellist(列表型 —— 多一行导航)、 main(keyed —— 那一行必须对应一格,用一个新 key 就是加法)、 shell.overlay(全窗口浮层 —— 弹窗挂在这里才盖得住整个框架)。 图标由宿主画,我们只交一个 currentColor 线框 SVG, 所以选中态、配色、留白都和官方一致;弹窗配色走 --dsw-alias-* 主题变量。

入口二:让 agent 调用 remote_connect

连到 WSL 的 Ubuntu-24.04,远程目录 /home/user/我的项目

agent 会调用 remote_connect(kind 为 ssh / wsl / docker), 然后把返回的锚点目录告诉你。

连上之后(两个入口都一样)

  1. 把返回的锚点目录作为工作区打开
  2. 在那个工作区里新建会话 —— 预设只能在会话第一轮之前选定

这一步不能省:旧会话已经定型了。漏了它,你会发现命令还是跑在本机。

关于「模式」:列表里为什么会多一个「远程模式」

新建会话时可以选模式(标准 / PTC / 极简 / 创造)。连上远程之后, 列表里会多出一个「远程模式」 —— 那不是另一种功能,而是远程会话的载体:

  • DSH 要求一个会话的插件组合来自一个已注册的预设; 插件要把会话切到「文件与命令都在远程执行」,就必须先注册这么一个预设
  • 它的功能与标准模式逐项对齐(读/写/编辑、命令、搜索、终端、计划、目标、技能、 后台任务、AGENTS.md 全都有)—— 差别只在「底下的文件系统与命令执行换成了远程版」
  • 它排在最后,是因为连接时才注册(前四个是 DSH 开机就有的)
  • DSH 没有「注册但不显示」的开关,所以它一定会出现在列表里

名字固定叫「远程模式」,「是哪台机器、项目在哪」写在它下面那行说明里:

远程模式
WSL docker-desktop · /root/.docker/desktop      ← 发行版/容器/主机 · 项目根

为什么名字不拼目标:拼出来是 远程:<派生 id>(WSL <发行版>), 而派生 id 又长又没信息量,会把真正有用的部分挤出显示宽度(实测被截断成 远程:wsl-root-docker-desktop(WSL docker-de)。现在把目标放进说明里,整行都是它的。

你不用管它。 在那个远程工作区里新建会话时,插件会自动把会话切到它:

会话创建 → 工作目录在某个远程工作区里吗?
   ├─ 不在 → 什么都不做(本地会话完全不受影响)
   └─ 在   → 切到该工作区的远程预设 → **回读确认**真的生效
             → 失败就拒绝这一步并报错,**绝不让你在本机跑**

⚠️ 所以:在远程工作区里选别的模式会被切回来 —— 这是故意的。 不切的话,那个会话的文件读写会落在本地镜像上: 你以为在改远程的代码,其实改的是本地那份副本(改了、以为生效了、远程根本没变)。

工作区的名字形如 ssh:my-proj、wsl:test-proj —— 后端 + 最后一层目录名。 (完整路径不挤进名字里:选目录那一步的弹窗里已经显示过。)

安装

用自带安装器(推荐)

node scripts/install.mjs --profile <你的 profile>

安装器会按顺序做完三件事,并把每一步的结果打出来:

  1. 往你的 profile 写入 pnpm 构建脚本表态(本插件零原生依赖,见下)
  2. 调用 dsh plugin --profile <p> add @jk-uzi/dsh-multi-remote
  3. 回读校验插件真的被登记进了 dsh.profile.bundles,没登记就非零退出

为什么需要第 1 步和第 3 步:pnpm 11+ 默认拒绝任何「没提前表态」的依赖构建脚本, 并且以非零退出码失败;而 dsh plugin 只在安装成功时才把插件登记进 bundle 栈。 结果是包装进了 node_modules、却永远不会被加载,而且没有任何提示。 安装器就是用来堵这个洞的。

手动安装

dsh plugin --profile <profile> add @jk-uzi/dsh-multi-remote

手动装完之后请确认 dsh.profile.bundles 里有 @jk-uzi/dsh-multi-remote。 没有的话插件不会生效。

从源码(开发用)

git clone https://github.com/jk-uzi/dsh-multi-remote.git
dsh plugin --profile <profile> add "link:$PWD/dsh-multi-remote"

卸载

dsh plugin --profile <profile> remove @jk-uzi/dsh-multi-remote

使用

连上一台机器,然后用 agent 的常规工具干活。

SSH

> 连接 user@example.com:22,项目根 /home/user/proj,用 ~/.ssh/id_ed25519

(调用 remote_connect 工具,kind='ssh')

已连接 …(user@example.com:22)
远程项目根:/home/user/proj
本地镜像锚点:<本地锚点目录>/mr-…
预设:multi-remote-…(可用)

下一步:把上面的「本地镜像锚点」作为工作区打开,然后新建会话。

SSH 掉线了怎么办? 长连接会被网络抖动、服务端重启 sshd、NAT 表项过期打断。 断掉之后插件会自动重连一次,通常你不需要做任何事。 重连时 host key 校验照旧生效(不会因为重连就放行陌生主机)。

WSL

> 连上本机的 Ubuntu-24.04,项目根 /home/user/proj

(调用 remote_connect 工具,kind='wsl', distro='Ubuntu-24.04')

已连接 …(WSL Ubuntu-24.04)
远程项目根:/home/user/proj
本地镜像锚点:…
预设:multi-remote-…(可用)

WSL 不需要任何凭据 —— 它是本机进程。用 wsl -l -q 可以列出可用发行版。

Docker

> 连上容器 my-app,项目根 /app

(调用 remote_connect 工具,kind='docker', container='my-app')

已连接 …(容器 my-app)
远程项目根:/app
本地镜像锚点:…
预设:multi-remote-…(可用)

Docker 不需要任何凭据 —— 它连的是本机守护进程。用 docker ps 看有哪些容器在跑。 只连已运行的容器,不会替你创建或启动。

Docker Desktop 重启过怎么办? 引擎重启会让旧连接失效,插件会自动重连一次, 通常你不需要做任何事。但注意引擎重启会把容器也停掉 —— 此时报错会说清 「容器没有在运行」并提示 docker start,而不是丢一个看不懂的错误给你。

⚠️ 用户流程有硬约束:必须先选远程工作区(= 锚点目录)、再新建会话。 远程预设只能在会话第一轮开始之前选定,反过来换不了。 走反了的话,插件会拒绝这一步并明确报错,而不是让命令静默落在本机。

工具

工具作用
remote_connect连接一台机器,建立本地镜像 + 注册远程预设
remote_profiles列出 / 删除已保存的连接档案(list / delete)
remote_list列出当前已连接的远程工作区
remote_disconnect断开(本地镜像保留,重连后界面立刻有内容)
remote_refresh重新拉取远程目录结构到本地镜像

凭据

  • 连接档案只保存非敏感字段(主机、端口、账号、路径、私钥路径)
  • 口令与私钥内容永不落盘,每次连接现给
  • 档案文件权限 0600

设置

设置 → 远程开发 里可以直接改这几项(不用编辑配置文件):

项说明默认
连上之后在后台补拉镜像把「文本类 + 上限内」的文件补到本地镜像,界面里点开就有内容开
后台补拉的单个文件上限只管后台补拉那一趟(MiB)。首轮遍历另有一套更小的常量,不受这项影响2
后台补拉的总量上限同上(MiB)64
审计日志条数上限GET /multi-remote/audit 的环形缓冲容量500

想彻底关掉插件? 用 DSH 自己的插件开关(插件管理器里关掉它, 或往 profile 的 cordis.patch.yml 写 disabled: true)—— 宿主会整行不加载。 我们不自造第二个「显示 / 隐藏入口」的开关:既和宿主重复,又半残 (客户端半边跑在浏览器里,改了配置要刷新页面才生效)。

⚠️ 保存后会重载插件:已连接的远程工作区会断开,重新连一次即可。 这是宿主 configEditor 的正常行为 —— 配置变更走 Loader 重新应用。

改动不自己写文件:走宿主的 configEditor 服务(校验 + 持久化 + 重新应用), 落点是 profile 的 cordis.patch.yml 里一条 id: multi-remote 的定向覆盖。

如果那个 profile 没有 configEditor 服务(精简 profile),这一页会如实说明改不了, 并给出手工改 cordis.patch.yml 的办法 —— 不会假装有个能用的开关。

本地开发

不要动你日常在用的 profile。 用独立的 DSH_HOME 建一个隔离 profile:

# 1) 找一个仓库外的目录当隔离 DSH_HOME
export DSH_HOME="$HOME/.dsh-multi-remote"        # Windows PowerShell: $env:DSH_HOME = "$HOME\.dsh-multi-remote"

# 2) 从内置模板建一个隔离 profile
dsh --profile dev --from-default-profile web --dump-config

# 3) 挂载本仓库
dsh plugin --profile dev add "link:$PWD"

# 4) 启动
dsh --profile dev --no-open --port 19388

本仓库无构建步骤——宿主直接加载 src/*.js,改完代码重启 profile 即可生效。

自检

检查期望
GET http://127.0.0.1:19388/multi-remote/ping{"ok":true,...},含已连接工作区
GET http://127.0.0.1:19388/multi-remote/selfcheck5 个工具 registered: true、visibleToModel: true,并报告三种后端各自的可用性
GET http://127.0.0.1:19388/multi-remote/workspaces已连接工作区的安全描述(不含凭据)
GET http://127.0.0.1:19388/multi-remote/audit审计日志(?limit= / ?event= 过滤,?clear=1 清空)

审计日志

记面向操作的轨迹:连过哪些机器、成没成、失败原因、栅栏拒绝(安全事件)。

三条约束:有界(环形缓冲,默认 500 条,超界会报 dropped)、 绝不落凭据(统一脱敏 —— 字段名、错误信息、连 URL 里的 ?token= 都会抹掉)、 绝不抛错(审计坏了不能让连接失败)。

curl 'http://127.0.0.1:19388/multi-remote/audit?limit=20'
curl 'http://127.0.0.1:19388/multi-remote/audit?event=connect-failed'
curl 'http://127.0.0.1:19388/multi-remote/audit?clear=1'

自检最有用的部分是 backends 那一段 —— 它先回答「这台机器上哪种后端能用」:

{
  "ssh":    { "status": "client-ready", "detail": "客户端可用;能不能连上取决于目标 sshd 与账号" },
  "wsl":    { "status": "available", "detail": "找到 2 个发行版", "distros": ["Ubuntu-24.04"] },
  "docker": { "status": "available", "detail": "守护进程 29.8.1,API 1.56",
              "endpoint": "\\\\.\\pipe\\dockerDesktopLinuxEngine" }
}

status 的取值:available / unavailable / unsupported / client-ready / unknown。 探不到时会带上原始错误,不会只说一句「失败」。

SSH 那条永远只说「客户端可用」,不会说「你的服务器连得上」—— 自检不该要凭据,也不该在你没让它连的时候发连接。

探测默认开启(三个并行,实测约 50 ms)。脚本里只想要工具状态可以加 ?backends=0 跳过。

跑测试

node --test          # 静态闸门 + 镜像层 + 预设 + transport + 安装器

其中 SSH / WSL / Docker 的端到端测试需要真机,默认跳过:

# SSH(需要一台能登录的机器)
DSH_MR_SSH_TEST=1 \
DSH_MR_SSH_HOST=<host> DSH_MR_SSH_PORT=<port> DSH_MR_SSH_USER=<user> \
DSH_MR_SSH_KEY=<私钥路径> DSH_MR_SSH_SANDBOX=/tmp/dsh-mr-e2e \
node --test

# WSL(需要 Windows + WSL2)
DSH_MR_WSL_TEST=1 DSH_MR_WSL_DISTRO=Ubuntu-24.04 \
DSH_MR_WSL_SANDBOX=/tmp/dsh-mr-wsl-e2e \
node --test

# Docker(需要一个已在运行的容器)
DSH_MR_DOCKER_TEST=1 DSH_MR_DOCKER_CONTAINER=<容器名> \
DSH_MR_DOCKER_SANDBOX=/tmp/dsh-mr-docker-e2e \
node --test

端到端测试只会在 *_SANDBOX 指定的远程目录里读写,不会碰其他文件。

结构

路径作用
src/index.js宿主半边入口:组装各层、注册 remote_* 工具与自检路由
src/diagnostics.js后端可用性探测(自检里的 backends 那一段)
src/audit.js审计日志:有界、脱敏、绝不抛错
src/transport/Transport 契约 + 三个后端:SSH(ssh.js)/ WSL(wsl.js)/ Docker(docker.js)+ 最小 tar(tar.js)
src/mirror/锚点目录的只读镜像(格局层 / 照片层 / 路径围栏 / git 基线)
src/session/连接档案、连接管理、预设注册(preset.js:单一「远程模式」预设)、会话绑定、工作区路由(workspace-routes.js:路径→连接)
src/session/plan-mode-section.js计划模式的提示词段(由 scripts/sync-plan-mode-section.mjs 生成,别手改)
src/world/隔离域内的 provider:fs / shell / search(远程 glob/grep)、终端后端与工具的挂载点(terminal.js);按路径动态路由(见 session/workspace-routes.js)
src/terminal/交互式终端:与后端无关的会话机械(session.js)、三个后端的 PTY(backends.js)、宿主 TerminalBackend 适配(backend.js)、模型工具(tool.js)
src/http/fence.jsHTTP 路由的 Host / Origin 栅栏(fail-closed)
src/client.js客户端半边:设置页小节(文案走宿主 i18n,中英双语)
scripts/install.mjs安装器(构建脚本表态 + 安装 + 回读校验)
scripts/sync-plan-mode-section.mjs从宿主的默认预设同步计划模式提示词段

依赖

只有一个运行时依赖:ssh2(纯 JavaScript)。

它的可选原生加速 cpu-features 被显式拒绝构建(见 pnpm-workspace.yaml): 允许它会让没有 C++ 工具链的用户直接装不上,换来的只是一点特性探测。 所以本插件零原生依赖,任何平台都能直接装上。

兼容性与测试矩阵

宿主与运行时

项要求实测于
DSH见 peerDependencies(@deepseek-ai/dsh-tools >=0.2.0-rc.2 <0.3.0)0.2.0-rc.2
Node>=20(engines)v24.14.0
插件主机Windows / macOS / LinuxWindows 11 25H2
目标环境POSIX shell(原生 Windows 远端不在范围)Ubuntu / Debian / Alpine

三个后端各自的前置条件

后端需要什么不需要什么可用平台
SSH可达的 sshd + 一个能登录的账号远端不装任何东西(无 DSH、无 API key、不联网)全部
WSLWindows + WSL2 + 一个发行版凭据、网络、远端安装仅 Windows
Docker可达的 Docker 守护进程 + 已在运行的容器docker CLI(我们直连 Engine API)、凭据全部(按平台自动选端点)

Docker 的端点按平台解析:Windows 用命名管道(dockerDesktopLinuxEngine → docker_engine), 类 Unix 用 /var/run/docker.sock → /run/docker.sock;DOCKER_HOST 显式给出时优先。 tcp:// 与 ssh:// 不在支持范围(需要 TLS 与额外协议),会明确报错而不是假装能连。

WSL 的交互式终端额外需要发行版里有 util-linux 的 script (用来分配 PTY)。没有的话终端会明确报错,其他功能不受影响。

目标环境的实测覆盖

目标文件读写命令执行交互式终端
真实 OpenSSH 服务器(Linux,有登录 banner)✅✅✅ PTY /dev/pts/N、resize 生效
OpenSSH 服务器(容器夹具,CI 可复现)✅✅✅ PTY /dev/pts/0、resize 生效
Ubuntu-24.04(WSL)✅✅✅ PTY /dev/pts/N
node:22-bookworm-slim(Docker)✅✅✅ PTY /dev/pts/N
alpine:3.20(Docker)✅✅未测
scratch(无 shell、已停止)✅ 内容读写仍可用⛔ 明确报错⛔ 明确报错

真机比夹具多测出三件事(夹具环境太干净,这些都不出现): 登录 banner、中文 motd 造成的终端折行、以及 shell 启动期间的就绪竞态。 其中就绪竞态是个真 bug,已修(见 CHANGELOG)。

alpine 上实测发现 busybox 的 find 没有 -printf、stat 没有 --printf, 所以内部脚本用的是两者都支持的写法(见 CONTRIBUTING §4.11)。

交互式终端的窗口大小变更:SSH / Docker 支持;WSL 不支持 (util-linux 的 script 没有这个能力),resize 是空操作 —— 如实的能力差异。

交互式终端首次发送前会先等登录 banner 打完再写命令 (否则命令输出会被 banner 的间隙吞掉)。已经安静的终端几乎不增加延迟; 一直不安静也会照常发送,并在结果里如实标 readyReason: 'not-quiet-yet'。

实测过的宿主版本

组件版本
DSH0.2.0-rc.2
Nodev24.14.0
宿主 OSWindows 11 25H2(build 26200.9457)
WSL2.7.3.0 / kernel 6.6.114.1-1 / Ubuntu-24.04
DockerDesktop 4.93.0 / Engine 29.8.1 / API 1.56

上表是实测过的组合,不是支持范围的下界。engines 声明的是 node >=20; 其他平台(macOS / Linux)上 SSH 与 Docker 应当可用,但没有实测过 —— 按本项目惯例,没测过的就标成没测过。

测试怎么跑

node --test                      # 全部单元与契约测试(不需要任何外部环境)

真机端到端测试默认跳过,按环境变量开关(见下一节)。当前规模 (2026-10-13 复核:node --test → 521 通过 / 14 跳过 / 0 失败):

类别数量
单元与契约测试521
真机端到端(默认跳过)14
合计535

SSH 的多数测试不需要靶机:断线重连那组用 ssh2 自带的 Server 在进程内 起一个真 SSH 服务端(真握手、真认证、真断线),所以不需要网络也不需要 Docker, 已经算在上面「单元与契约测试」里。只有「容器里跑 sshd」那组真机端到端默认跳过。

⚠️ 同时打开多个真机端到端时请加 --test-concurrency=1: Node 的测试运行器默认并行跑不同测试文件,而 wsl.exe 在并发下会明显退化 (见 CONTRIBUTING §4.6),会把 Docker 的用例拖到超时。 实测:两个都开 + 默认并发 → Docker 用例 32 秒超时;--test-concurrency=1 则通过。 这不是代码问题,是测试环境互相干扰。

贡献

欢迎参与。动手前请先读 CONTRIBUTING.md——里面写了开发环境、 插件 API 契约,以及几个很容易踩的坑。

许可证

MIT