dsh-remote-subagent
English · 简体中文
DSH(DeepSeek Harness)远端子代理插件:本地会话用 remote_agent 工具把任务派给远端 DSH(SSH 上的另一台机器),远端 agent 用它自己的模型、工具与沙箱干活,思考 / 工具调用 / 工具结果 / 最终答复实时流回本地会话,并可在同一远端会话上追加消息——用法与内置 subagent 类似,但执行发生在远端机器里。
本地会话 ── remote_agent 工具 ──SSH──> 远端 dsh headless --json <task>
▲ │
└──── 事件流(thinking / tool_call / tool_result / final)◄──┘
为什么用远端 dsh headless --json
DSH 自带 headless 入口,--json 会把一次 run 的过程按行输出为 JSON 事件;--session-id 可续跑同一个 Session:
dsh headless --json '<task>' # 新远端会话
dsh headless --json --session-id <id> '<msg>' # 同一会话续跑
插件通过 SSH 的 exec 通道直接读这个流,因此:
- 不需要在远端开公网端口,也不需要 token / WebSocket 协议;
- 远端 agent 用它自己的模型配置、工具、沙箱与 session 存储(session 落在远端
$DSH_HOME/sessions,在远端 DSH Web UI 里同样可见);
- 追加消息用
--session-id,语义与 subagent 的 send 一致;
- 事件流本身就是「工作过程」,不需要额外埋点。
安装
# 从 npm 安装到当前 profile(bundle 形式)
dsh plugin --profile <profile> add dsh-remote-subagent
# 或在 DSH 插件市场/plugin_manager 里安装 dsh-remote-subagent
依赖会自动安装(ssh2)。启用后:左侧栏出现「远端代理」图标,模型侧多出 remote_agent 工具。
机器从哪来
不重复配置 SSH 清单:目标机器读取 dsh-remote 的注册表
$DSH_HOME/remote-workspaces/machines.json
解析顺序:插件 config.machineId → 注册表 currentId → 第一个已保存机器 → config.machine(独立部署无 dsh-remote 时用)。支持密码、私钥(含 passphrase)、SSH agent。主机密钥采用 TOFU:首次连接把指纹记录到 $DSH_HOME/dsh-remote-subagent/known_hosts.json,之后必须匹配。
工具:remote_agent
| action | 作用 | 关键参数 |
|---|
spawn | 起一次新的远端 run(独立远端会话) | task, machine?, cwd? |
wait | 阻塞等 run 结束或超时(默认 30s,上限 120s;超时不影响远端继续跑) | runId, waitSeconds? |
get | 状态 + 最近 N 条过程事件 + 结果 | runId, eventLimit? |
send | 给同一远端会话追加消息(新 run,--session-id 续跑,turn+1) | runId, task |
list | 最近的 run 列表 | limit? |
cancel | 终止远端进程(按 pid 文件 kill -TERM) | runId |
machines | 可用的远端机器 | — |
典型用法:
remote_agent({action:"spawn", task:"在 /opt/app 跑一次只读健康检查:磁盘、内存、监听端口,最后总结"})
→ runId
remote_agent({action:"wait", runId, waitSeconds:120})
remote_agent({action:"get", runId, eventLimit:30})
remote_agent({action:"send", runId, task:"把监听端口按大小排序再列一次"})
复用语义:spawn 永远新建远端会话(等于 subagent 的 spawn);send 复用该 run 所属的同一远端会话(等于 subagent 的 send_message)。多个 spawn 并发 = 多个互不共享上下文的远端会话;同一会话同一时刻只能跑一个 turn。run 历史与 sessionId 持久化在本地,DSH 重启后仍可 send 续跑。
面板(Client 半)
左侧栏「远端代理」→ 主面板:
- 左列:新建任务(选机器 + 任务文本 + 派活)、run 列表(状态点 / 任务摘要 / 机器 / 用时);
- 右列:run 详情(状态、远端 session id、cwd、轮次、事件数、token)、实时事件时间线(1.5s 轮询增量、自动滚底)、结果框、终止 / 刷新、追加消息输入框。
数据接口(同源;Desktop 走 /api 前缀,Web 走裸路径):
GET /dsh-remote-subagent/runs
GET /dsh-remote-subagent/run?id=<run>&from=<seq>&limit=<n>
POST /dsh-remote-subagent/action {action: spawn|send|cancel, ...}
配置(可选)
插件配置在 profile 的 cordis.patch.yml 的 config: 下,全部可省略:
| 字段 | 默认 | 说明 |
|---|
machineId | '' | 默认机器(id/名称/host) |
machinesFile | '' | 自定义机器注册表路径 |
headlessCommand | dsh headless | 远端命令前缀 |
cwd | '' | 远端工作目录(空 = 机器 home) |
maxConcurrent | 4 | 并行 run 上限 |
timeoutSeconds | 900 | 单 run 上限,超时自动终止远端进程 |
maxEvents | 4000 | 每个 run 保留的事件上限 |
connectTimeoutMs | 20000 | SSH 连接超时 |
本地数据
$DSH_HOME/dsh-remote-subagent/runs/<runId>.json:每个 run 的元数据 + 事件 transcript(重启后历史仍在;重启瞬间仍在跑的 run 标记 failed/interrupted,但保留 session id,可 send 续跑);
$DSH_HOME/dsh-remote-subagent/known_hosts.json:TOFU 主机指纹。
实测状态(v0.1.0,2026-11-04)
在一个 Linux 远端(DSH 0.2.0-rc.2,SSH 密码登录)上跑通:
spawn → 11s / 23 条事件 / turn 1,含 tool_call 与 tool_result;
send 续跑同一 session-*,turn 2 正确引用上一轮上下文,回答数值与 turn 1 一致;
- DSH 重启 + 插件热重载后
send 仍能续上同一远端会话(turn 3);
cancel 后远端无残留 dsh headless 进程,pid 文件清理;
- 面板槽位注册(
sidebar.panellist id=remote-agents、main key=remote-agents)与三条面板接口的处理器行为均有单测/实测覆盖。
单测覆盖(离线,无网络):机器解析顺序与报错、事件流解析/分页/续跑/取消/持久化、面板路由、SSH 引用与 TOFU 指纹、Client 槽位注册。
已知边界
- 远端需要
dsh 在 SSH 登录 PATH 上,且 headless profile 可用;远端版本过老、没有 --json / --session-id 时不可用;
- 只支持 POSIX 远端(命令用
sh -c 包装);Windows 远端未实现;
- 不支持 dsh-remote 的
useSshConfig 别名机器与 ProxyJump 跳板(请用直连条目);
- 远端 run 的模型/工具/沙箱完全由远端 profile 决定,本地不能改;
wait 超时不会终止远端 run;运行中不支持插话,需 wait 或 cancel 后再 send;
- 事件流覆盖 headless 单 turn;远端 agent 若把活丢进后台 job,其后续输出不在本 run 流里。
回滚
- 卸载:从 profile 移除
dsh-remote-subagent bundle;
- 清数据:删除
$DSH_HOME/dsh-remote-subagent/(run 历史 + TOFU 指纹);
- 对远端无持久改动:仅
/tmp/dsh-remote-subagent-*.pid(正常结束自动清理)与远端 session 目录。
License
MIT