DuckFleet(鸭群)
中文 | English
一群机器,一声令下。
DuckFleet 是一个 DSH 插件:往主控机上装一次,把多台机器接进同一群,
每台机器自动获得一个派活工具,然后你就可以用自然语言让「某个 / 某几个 / 全部」机器干活。
你:让所有机器汇报当前的 CPU 和内存占用
DuckFleet:→ 并发派给 3 台机器 → 逐台汇总结果回来
它提供什么
- 机器清单一处维护:
$DSH_HOME/fleet.json,或设置页里的可视化编辑。
- 加机器后立即注册好它的 ACP provider 与派活工具,不用重启。
- 改
host 一个字段即可换地址,运行时当场重注册。
- 被控机不需要装这个插件:它只需要 sshd、
dsh、一个 acp profile。
安装
插件只装在主控机上。
从 npm
dsh plugin --profile desktop add dsh-duck-fleet
从本地检出
git clone git@github.com:new-sailfish/duck-fleet.git
dsh plugin --profile desktop add /path/to/duck-fleet
dsh plugin add 会把包装进 profile 并同步到 dsh.profile.bundles。
装完要重启一次 DSH。
面板属于客户端半,而客户端插件的 bundle 是应用启动时建立并缓存的,
所以刷新页面没用 —— 正在跑的进程不会重读磁盘上的新 bundle。
服务端那一半(fleet_* 工具、派活)在插件行重新加载后即时生效,重启只是为了面板。
判断标准:工具能用 = 服务端已更新;面板还是旧的 = 需要重启。
手工挂载(等价做法)
profile 的 package.json:
{
"dependencies": { "dsh-duck-fleet": "link:/path/to/duck-fleet" },
"dsh": { "profile": { "bundles": ["…", "dsh-duck-fleet"] } }
}
快速开始
1. 主控机:装上插件
装好之后,插件会在主控机上:
- 在设置里加一个鸭群页面;
- 往系统提示注册一段用法说明,让模型知道「某个 / 某几个 / 全部机器」该怎么派活;
- 注册一批管理工具(
fleet_list / fleet_add / fleet_test / fleet_setup / …)。
打开设置 → 鸭群。
2. 被控机:只需要装好 DSH
| 需要 | 说明 |
|---|
| sshd 在跑 | 主控机要能 ssh 进去 |
dsh 已安装 | 这是唯一的前提 |
一个 acp profile | 由提示词创建 |
| 主控机的公钥 | 由提示词写入 |
这四件事都交给提示词里的被控机 agent 自己去查、去装,不需要手工配。
3. 两种送达方式
「自动获取配置」生成的那段提示词,可以这样送到被控机:
| 方式 | 怎么做 | 适用 |
|---|
| 复制粘贴 | 点**「复制提示词」**,粘到被控机的 DSH 或终端里执行 | 你能直接操作那台机器 |
| 局域网链接 | 点**「改用局域网链接」,得到一个形如 http://192.168.1.10:<port>/abcd 的地址,在被控机上打开**它取提示词 | 被控机不在手边 |
也可以在任意会话里说:「用 fleet_setup 生成被控端配置提示词,用户 dev、主机 192.168.1.10」。
局域网链接是临时的:只在取提示词之前有效,取到即止,最多 1 分钟;
路径是 4 位小写字母,只有取走它的那一台机器能访问(其他 host 一律 403)。
它只在本机监听,不经过任何外部服务。
提示词自己是自包含的:让被控机的 agent 先查现状再动手,只装缺的东西,
所以对新机器和配了一半的机器都适用。它只带公钥,私钥永远留在主控机。
profile 那一步不能省。 acp profile 默认带沙箱,而在由 ssh 拉起的无人值守会话里,
受限沙箱会让子进程创建失败。症状是「能连上、能对话,但什么命令都执行不了」,
看起来像机器坏了,其实只差配置文件里的两行。
4. 加机器
设置 → 鸭群 →「添加机器」 有两条路,选一条即可:
| 路径 | 适合 |
|---|
| 推荐 | 「自动获取配置」 | 把提示词交给被控机执行,它查完自己回报,主控机自动写入并验证 |
| 备选 | 「手动填写配置」 | 你已经知道主机名、账号、端口,或想直接编辑 JSON |
自动获取配置(推荐)
- 点**「自动获取配置」,把提示词送到被控机 —— 点「复制提示词」粘贴,或点「改用局域网链接」**让被控机打开链接自取;
- 被控机执行完,六个阶段的进度会实时显示在面板上;
- 它最后回报完整配置,主控机自动加进列表并验证。地址多于一个时,面板让你选。
手动填写配置
- 点**「手动填写配置」**,填
label / host / user / port(其余字段有默认值);
- 也可以切到 JSON 视图直接编辑这台机器的记录;
- 保存后点**「测试」**验证 SSH + ACP 握手。
名称的 ASCII 字母决定工具名(Home Server → pc_home_server),随时可改。
也可以让 agent 做
用 fleet_add 加一台机器:名称 laptop-01,主机 192.168.1.10,用户 dev
或直接改 $DSH_HOME/fleet.json,改完不用重启。
5. 派活
| 你要的 | 你说 |
|---|
| 某个 | 让 desktop-01 汇报一下磁盘占用 |
| 某几个 | 让 laptop-01 和 desktop-01 都检查一下 Node 版本 |
| 全部 | 所有机器都把日志目录打包发我 |
多台机器并发执行,不会等一倍的时间。长任务加 run_in_background: true,之后用 job_output 收结果。
每次派活都会在任务前面自动附加一段「环境与工作规范」——那台机器的环境事实、由事实推出的硬约束、以及跨平台的工作通则。写任务时不要重复这些内容。哪台机器带哪一版由它的 platform 字段决定。
机器字段
机器清单在 $DSH_HOME/fleet.json。共享设置设一次所有机器继承;单台字段每台独立,新建时继承共享设置。
共享设置(defaults)
| 字段 | 默认 | 说明 |
|---|
keyFile | 空 | SSH 私钥路径。必须对,否则 ssh 会静默用别的身份 |
sshCommand | ssh | ssh 可执行文件 |
profile | acp | 远端 profile 名 |
autoArchive | 关闭 | 实验性。会话清理规则,见下 |
单台字段
| 字段 | 默认 | 说明 |
|---|
label | 必填 | 显示名,也是工具名的来源 |
host / user / port | 必填 / 必填 / 22 | SSH 目标 |
toolName | 由 label 派生 | 模型调用的工具名,可随时改 |
remoteCommand | dsh | 怎么在那台机器上启动 agent。非交互式 SSH 的 PATH 里通常没有 dsh,所以实际要填绝对路径,如 C:/Users/dev/AppData/Local/Programs/DeepSeek Harness/resources/runtime/cli/bin/dsh.cmd |
cwd | 空 | 那台机器上的工作区。空 = 让远端用自己的默认工作区 |
permission | allow | 那台机器上授权提示怎么答 |
description | 空 | 一句话说明这台的用途,会附到工具描述里 |
platform | windows | 那台机器跑什么系统,决定派活前置词带哪一版:windows 全带、posix 只带工作通则、none 不带。不是 Windows 的机器要显式设置 —— 默认值是假设 |
extraArgs | [] | 额外的 ssh 参数 |
remoteCommand 不在共享设置里,因为它的可用值是被控机上 dsh 的绝对路径,
而那个路径带着那台机器的用户名 —— 共享出去等于让别的机器去跑另一台机器的程序。
agent 管理工具
| 工具 | 用途 |
|---|
fleet_list | 列出机器:toolName user@host label + 工作区 + 权限 + 注册状态 |
fleet_add | 加机器或改机器(按 id 或 label 匹配) |
fleet_remove | 删机器,并注销它的 provider 和工具 |
fleet_test | 只做 SSH + ACP 握手,不花 token |
fleet_setup | 生成给被控机的配置提示词(附公钥) |
fleet_defaults | 读/改共享设置 |
fleet_version | 报告当前加载的是哪个版本的实现 |
插件往系统提示里注册了一段用法说明,告诉模型:先调 fleet_list 拿「机器名 → 工具名」的映射,
然后在同一条消息里并发调用。工具 schema 只描述「怎么调」,从不描述「什么时候调」。
能力边界
DuckFleet 是给自己用的、内网的工具。下面是设计上就没做的事 —— 不是 bug,是边界。
越过它们,安全性由你自己负责。
网络:只面向你信得过的内网
- 插件假设主控机和被控机在同一个你能控制的网络里。它没有做任何面向公网的设计。
- 局域网链接只做三件事:4 位随机路径、只允许那一台机器读取、取到或超时即关闭。
它没有 TLS、没有账号体系、没有速率限制、没有审计日志。
- 不要把它反向代理到公网、不要做端口转发对外暴露。 4 位路径不是密码。
- 主控机与被控机之间没有本插件引入的额外认证,信任完全建立在 SSH 上:
谁能用那把私钥 ssh 上被控机,谁就能在那台机器上派活。私钥保管是你的责任。
- 主控机上的面板路由挂在 harness 已有的监听地址上(默认
127.0.0.1)。
改了 harness 的监听范围,面板也跟着变 —— 插件不额外做访问控制。
权限:被控端默认不问
被控机的 ACP profile 默认关掉审批。那条会话对面没有人可以回答提示,问了只会白等一轮;
授权判断由主控机这边承担(记录的 permission 字段)。
后果:派过去的任务在被控机上以那个 SSH 账号的全部权限运行,不会中途停下来问你。
派活之前,你要对那条命令本身负责。
数据:清单是明文,提示词含公钥
- 机器清单在
$DSH_HOME/fleet.json,明文,含主机名、用户名、私钥路径。
- 配置提示词含主控机的公钥(公钥本就可公开),私钥永远不进去。
- 局域网链接把这段提示词挂在局域网上一小会儿 —— 里面没有私钥,但有主机名、账号名和公钥。
它不做什么
- 不装到被控机上。 插件本身只跑在主控机。
- 不做增删改以外的编排。 没有任务队列、没有重试、没有失败自动转移。
- 不代理文件传输。 要传文件就在任务里让被控机的 agent 自己处理。
会话清理(实验性,仅限 Windows)
它解决什么
每次派活都会在被控机上开一个新的 ACP 会话,而 ACP 那条路径不把会话归入任何 workspace ——
它只带一个 cwd。结果是这些会话全部落进侧边栏的**「未分组」**里,用一次多一个。
主控机这边没有办法事后补救:能完成挂载的操作既没有 ACP 等价物,也没有 HTTP 路由。
能动的只有归档。
怎么用
# 预演:只探测平台,不改动任何东西
fleet_prune --inspect
# 清理全部机器,保留最近 5 个
fleet_prune
# 只清理一台,保留最近 2 个
fleet_prune --id huawei-vm --keep 2
面板里也有对应入口(共享设置下方的虚线框),可以逐台点「清理」。
规则
| 项目 | 行为 |
|---|
| 候选范围 | 只处理「裸 UUID」目录名的会话 |
| 保留数量 | 最近 N 个(默认 5,可配 autoArchive.keepLast) |
| 备份 | 首次运行写 workspace.json.bak-before-archive,不覆盖已有备份 |
| 可逆 | 归档可在侧边栏「已归档」里取消 |
判据是「裸 UUID」:ACP 建的会话 ID 是裸 UUID,而 DSH 界面里建的是 session- 前缀。
只按目录名筛选不够 —— 被控机的 cwd 往往就是你自己也在用的目录。
这个功能会打断正在跑的任务
清理需要停掉被控机的 DSH,归档后再自动启动。
整个过程约 30 秒,期间那台机器上的派活会中断。
平台支持
| 平台 | 状态 |
|---|
| Windows | ✅ 已在真机上反复验证 |
| macOS / Linux | ❌ 未实现也未测试,遇到会直接拒绝,不会尝试 |
拒绝而不是「试一试」,是因为两个环节都是平台相关的:注册表路径不同,
而且停掉之后如何把 GUI 应用放回交互式桌面,POSIX 的等价做法还没有写过。
已知的误判风险:如果 DSH 将来改了会话命名规则,或者你在同一目录里手工建了裸 UUID 会话,
它们会被当作派活会话归档。备份和可逆性是为这个准备的。
已知限制
- 升级插件后要重启 DSH,面板才会换新(原因同安装一节)。工具能用 = 服务端已更新。
cwd 分两侧:本机 ssh 进程的工作目录永远是本机会话目录;
被控机的工作区走 ACP 的 session/new 参数。
- 被控机必须真的能执行命令:握手成功不等于能干活。受限沙箱下 ACP 会话能起来、
能对话,但任何子进程都会失败。
fleet_test 只验证到握手,实际执行一次才算验证。
- 一台机器一个工具:没有单独的广播工具。「全部」=
fleet_list 报告出来的每一台,
由模型在同一轮里逐个调用。
许可证
MIT