前言¶
DeepSeek Harness(DSH)使用同源策略来隔离不同页面的信任边界:Host/Origin 必须相等、Cookie 必须是 SameSite=Strict,不允许跨域请求。在本地开发或调试时,你通常会在不同端口(例如 3182、3183)启动多个 DSH Hub 实例,每个实例有独立的 origin。浏览器直接访问这些不同 origin 会触发同源限制,无法像访问单个 Hub 那样顺畅地切换。
dsh-remote-hub 是一个运行在 Hub 端的 host 插件:它为浏览器提供一个统一的 origin(即 Hub 自己的 origin),然后将所有注册的远端后端通过反向代理暴露在 /remotes/<id>/ 下。浏览器始终只与 Hub origin 通信,满足 DSH 的同源要求;远端之间的连接方式(例如 ssh -L、未来的代理通道)由插件底层处理,代理逻辑本身不关心具体传输。
插件定位与维护者¶
dsh-remote-hub 是一个 DSH Hub 端插件,由维护者 yhuan416 开发。它不改变 DSH 的运行时架构,只是扩展了 Hub 的能力,让一个 Hub 可以统一管理多个远端后端,并为用户提供一个无刷新的切换入口。
核心功能¶
1. 反向代理与同源访问¶
Hub 通过 dsh web 启动,浏览器访问 Hub origin。插件将每个远端(例如 n100)的 HTTP 服务反向代理到 /remotes/n100/ 下:
- 浏览器请求路径
/remotes/n100/api/x被转发到远端的/api/x - 请求的
Host和Origin被重写为远端的 authority(例如127.0.0.1:3182),远端的 Host/Origin fence 可以将其识别为 loopback - 远端返回的 Cookie 会被插件缓存,后续请求自动带上该远端的 Cookie
这样,浏览器只与 Hub origin 交互,满足 DSH 的同源要求。
2. 远端链路管理与 UI¶
在 DSH 的「Plugins」页面(Settings → Plugins → dsh-remote-hub)中,插件提供两个部分:
- 配置表单:从导出的
Config生成,包含upstreamTimeoutMs、probeIntervalMs、switcher.mode、switcher.dropdown等字段 - 远端链路:一个表格,列出所有已注册的远端,每行显示状态点、标签、传输方式、token 来源、延迟、最后错误。每行提供「打开 / 测试 / 删除」操作,以及一个添加远端的表单(label、ssh host、user、remote port 等)
表格还展示「远端工作区」列表:每个后端的工作区与会话由 Hub 聚合(通过 ssh 读取工作区存储和 session/list RPC;若存储不可读则按 cwd 分组)。点击会话会在 Hub 的顶部栏打开该后端,并通过消息机制打开对应的 iframe 页面。
3. 运行时存储¶
远端配置存储在 $DSH_HOME/remote-hosts.json(权限 0600,原子写入):
{
"version": 1,
"removed": ["old-id"],
"hosts": [
{
"id": "...",
"label": "...",
"ssh": {...},
"tokenFile": {...}
}
]
}
- 首次启动时,从
cordis.patch.yml中的remotes数组导入到存储中 - 后续启动以存储为准;新增的 seed id 会导入,除非已被标记为 tombstone(删除操作会在存储中添加 tombstone,防止 seed 重新出现)
4. 管理 API(Owner 端)¶
插件注册了以下 owner 端的 API 路由(通过 ctx.connection.fetch.register,框架会强制 Peer 为 owner 且浏览器信任墙生效):
GET /api/remote-hosts/list:返回已清理的远端列表,附带状态、健康、延迟、tunnel、token 统计信息POST /api/remote-hosts/save:保存远端配置,返回{ ok, id }POST /api/remote-hosts/remove:删除远端,返回{ ok }POST /api/remote-hosts/probe:探测指定远端,返回状态、延迟、token 是否解析、是否有 cookieGET /api/remote-hosts/workspaces:按远端返回工作区列表(workspaces[{id,title,path,sessions[]}])及孤儿工作区;远端通过 ssh 读取工作区存储,若不可读则按 cwd 分组
5. 统一入口与状态指示¶
插件通过 webserver/index-inject 钩子在 Hub 的索引页注入一个顶部栏:
- 默认模式
bar:持久显示「本机」+ 所有远端标签,点击后在下方的 iframe 中显示对应后端(只挂载一次,切换时不刷新页面) - 颜色使用应用的主题 token(
--dsw-alias-bg-base、--dsw-alias-label-primary等),跟随浅色/深色主题 - 每个远端标签有一个状态点,每 5 秒从
/api/remote-hosts/list轮询一次:绿色在线、琥珀色 pending/starting、红色离线、灰色未知;悬停可查看状态/tunnel 状态/最后错误
默认情况下,顶部栏已足够,侧边栏切换下拉框被关闭。可在配置中开启 switcher.dropdown: true 来启用它。
6. 入口路径与 token 处理¶
插件提供多个入口路径:
/remotes/?token=<hub launch token>:一次性入口。插件将 token 转发给 Hub 自己的/?token=交换机制,设置 Hub 的 cookie,然后通过 303 重定向到干净的/remotes/。注意:token URL 使用的 hostname 必须与设置 cookie 的 hostname 一致(例如 127.0.0.1 与 localhost 被视为不同的 cookie 主机)/remotes/:备用入口页,只提供纯链接,管理入口已移至 Plugins 页面/remotes/<id>/:远端的 SPA,由插件反向代理/remotes/_status:JSON 诊断信息,列出每个远端的 token 是否存在、cookie 是否存在、最后错误
7. 传输方式无关性¶
插件将远端定义为 Hub 可访问的 HTTP 基础 URL。传输方式由运维决定:
- 可以是手动
ssh -L隧道 - 可以是未来自动管理的隧道
- 也可以是未来的 agent 通道
代理代码本身不关心传输方式,只负责转发 HTTP 请求和 WebSocket 升级。
8. WebSocket 支持¶
插件为每个远端注册精确的 WebSocket 路由,并建立 socket-to-socket 的管道,将 WebSocket 升级请求转发到对应的远端。
安装与启用¶
插件通过 cordis.patch.yml 插入到 DSH:
- insert:
- id: remote-hub
name: dsh-remote-hub
config:
remotes:
- id: n100
label: yhuan@192.168.4.50
upstream: http://127.0.0.1:3182
token: <remote dsh launch token>
在 package.json 中,插件的入口为 lib/index.js,客户端入口为 lib/client.js。插件遵循 MIT 许可证,Node.js 要求版本 >=22.19.0。
典型用法¶
- 在 Hub 所在机器上,为每个远端后端启动 DSH 实例,并确保 Hub 可以通过 HTTP 访问远端(例如通过
ssh -L或内网地址)。 - 在
cordis.patch.yml中添加dsh-remote-hub插件,配置远端 id、label、upstream 和 token。 - 重启 DSH Hub,在浏览器中访问 Hub origin,顶部栏将显示「本机」和已注册的远端。
- 点击远端标签,iframe 中会显示对应后端,切换时不会刷新页面。
- 在 Plugins 页面的「远端链路」区域添加新远端、测试连通性、查看工作区与会话。
适用场景与注意¶
dsh-remote-hub 适合需要在同一个浏览器窗口中访问多个 DSH Hub 后端的场景,例如本地多实例调试、跨主机的远程开发环境统一入口。插件以当前 DSH 进程的权限运行,安装前应检查源码与许可证。插件仅管理远端配置与代理逻辑,不涉及 AI 模型推理或数据存储。
总结¶
dsh-remote-hub 通过反向代理和统一入口,将多个 DSH Hub 后端折叠到同一个 origin 下,使浏览器始终在同源环境下工作,满足 DSH 的信任墙要求。它提供了可视化的远端管理界面、工作区聚合、状态指示与探测功能,并支持多种传输方式,适合需要在 Hub 端统一管理多个远端后端的开发者。