还在让电脑吃灰?DeepSeek Harness 插件实战:把朋友闲置的电脑、局域网服务器、云上 ECS,组建成你自己的分布式 AI 算力池,一个人掌控多台 Agent
DSH 插件开发实战:把朋友的闲置电脑,组建成你的分布式 AI 算力池
一个"把每台闲置机器的算力凑一块儿"的闪光想法,烧了 2 亿 token 把它做成了真的。
本文记录 dsh-fleet 插件的完整开发实战:从架构设计、协议握手,到任务派发,再到一连串真实的踩坑记录。
一、为什么做这个插件?
DeepSeek Harness(DSH)是一个"一切皆插件"的 AI 编程 Agent。它跑在你的电脑上,吃你的模型 API,干你的活。
但我发现一个尴尬的事实:我有好几台机器——公司配的台式机、家里的旧笔记本、云上闲置的 ECS……它们大多数时候都在闲着。而 DSH 每次跑任务,都只吃一台机器的算力。
为什么不把它们串起来?
于是有了这个想法:
把朋友间、局域网里、云上 ECS 的每一份闲置算力,汇聚成你自己的分布式 AI 算力池。
一台机可以调用整个团队任意一台机的算力——朋友闲置的 PC、公司局域网服务器、云上闲置的 ECS,全都能跑起来。一个人,掌控多台 Harness,并行推进多个开发与研究项目。
这就是 dsh-fleet 插件:DSH 多机算力组网。
二、插件能干什么?
一句话:把你的多台机器组成一个"算力团队",任意一台可以给任意另一台派活。
具体能力:
| 能力 | 说明 |
|---|---|
| 机器身份 | 每台机安装后自动生成 machineId + 永久 SK,零配置 |
| 机器名片 | 一行可复制字符串(ID/名称/IP:端口/CPU/内存/NAT/SK),一键组队 |
| 多团队 | 每台机可同时属于多个团队,每个团队独立名录 |
| 信任握手 | challenge-response + HMAC-SHA256 证明 SK 持有,SK 永不明文过网 |
| 心跳监控 | 长连接 + RTT/内存上报,断线自动重连 |
| 任务派发 | fleet_dispatch 把任务发给任意在线成员,worker 自动跑 agent 并回流结果 |
| 会话保留 | worker 任务会话自动挂载到工作区,GUI 可见、记录完整、可回溯 |
三、快速上手
安装(已发布 npm)
dsh plugin --profile web add dsh-fleet
组队
"查看我的机器名片" → fleet_card
"把这张名片加入 XX 团队" → fleet_add
"有哪些团队?成员在线吗?" → fleet_teams
或者直接复制名片,粘贴给对方:
dsh-fleet://machineId|名称|IP:端口|CPU|内存|NAT|SK
派发任务
用 fleet_dispatch 把任务发给「XX舰队」的成员:
- 任务提示语:"用 bash 执行 echo hello 并输出结果"
- provider / model:deepseek-official / deepseek-v4-flash
- keyHint:inherit(worker 无 key 时用总机 key)或 self(worker 用自己的 key)
任务完成后返回:成员 / 任务 ID / 工作区 / 会话 ID / 完整回答 / 执行日志。worker 侧会话自动挂载到工作区,GUI 里可查看完整对话记录。
四、架构设计(四层模型)
┌─────────────────────────────────────────────────┐
│ 控制面:会话/工作区指令、心跳资源上报、名片上报 │
├─────────────────────────────────────────────────┤
│ 授权层:taskToken(HMAC 验签防伪造),worker 否决权 │
├─────────────────────────────────────────────────┤
│ 信任层:名片 / IP+SK 人工添加,双向可移除 │
├─────────────────────────────────────────────────┤
│ 身份层:machineId + 永久 SK,监听端口 │
└─────────────────────────────────────────────────┘
信任基础:知道对方永久 SK = 被授权调用。SK 永不通过线路明文传输,握手用 challenge-response 证明持有(HMAC-SHA256)。
协议(JSON-lines over TCP)
server → challenge { nonce, machineId, name } 服务器自证身份 + 发挑战
client → auth { machineId, name, proof } proof = HMAC(服务器SK, nonce)
server → welcome | error(AUTH_FAILED) 服务器本地验签,SK 永不出本机
之后周期 ping / pong(pong 携带内存空闲等资源统计)
任务派发协议
submit_task { taskId, token, prompt, provider, model }
→ worker 验签(HMAC(workerSK, taskId))
→ 新建工作区 → 建 agent 会话 → 执行
→ task_result { answer, workspace, sessionId }
五、核心实现要点
1. 身份层:永久 SK + 名片
// src/identity.js
sk: crypto.randomBytes(32).toString('hex'), // 64 hex 字符 = 256 bit
名片一行带走:machineId|名称|IP:端口|CPU|内存|NAT|永久SK
2. 任务派发:token 防伪造
const taskId = crypto.randomUUID()
const token = hmac(target.sk, taskId) // HMAC 绑定 worker 的 SK
3. worker 执行:新建工作区 + 跑 agent
// src/task-runner.js
await mkdir(wsPath, { recursive: true }) // 工作区必须已存在
const workspace = await workspaceRegistry.create(wsPath, title)
await workspace.attachSession(sessionId) // 会话挂到工作区
agent.followup({ id, role: 'user', content, source }) // 消息必须带 id + source
六、踩坑记录(真实血泪史)
开发过程中踩了 6 个真实 bug,每一个都值得记录:
🕳️ 坑 1:workspaceRegistry.create 要求路径已存在
现象:派发任务报 ENOENT: realpath '.../tasks/<id>'。
原因:workspaceRegistry.create() 内部会对路径做 fs.realpath 校验,路径必须已存在,而默认的 tasks/<taskId> 目录从未创建。
修复:创建前先 mkdir -p。
await mkdir(wsPath, { recursive: true })
const workspace = await workspaceRegistry.create(wsPath, title)
🕳️ 坑 2:followup 消息缺 source 字段,agent 首轮崩溃
现象:agent 第一轮就报 Cannot read properties of undefined (reading 'kind')。
原因:DSH 的 Message.source 是必填字段,agent-loop 会读 message.source.kind。headless 用 createUserMessage() 自动带 source,我手写对象时漏了。
修复:补 source: { kind: 'user' }。
🕳️ 坑 3:会话挂载缺失,任务会话落进"未分组"
现象:任务完成了,但 GUI 工作区里看不到会话,全堆在"未分组"。
原因:直接 agents.create() 不会自动把会话挂到工作区——GUI/API 层是创建后调 workspace.attachSession() 的。
修复:创建后手动 attach。
🕳️ 坑 4:followup 消息缺 id,落盘校验失败
现象:session.history 报 session event at seq 7 lacks an identified message,GUI 里会话记录是空的。
原因:消息缺 id 字段,持久化校验不过。
修复:补 id: crypto.randomUUID()。
🕳️ 坑 5:origin:'subagent' 导致会话被前端过滤
现象:后端数据全对(session.list / history 都正常),但 GUI 工作区就是显示不出来。
原因:前端 sessionVisible() 会过滤掉 origin=subagent 的会话。我以为"子任务"就该标 subagent,结果被前端藏了。
修复:不传 origin,作为普通会话创建。
🕳️ 坑 6:handle.dispose() 触发 host/session-removed,会话从 GUI 消失
现象:任务完成后会话从 GUI 列表实时消失。
原因:dispose 会触发 session/disposed → 前端收到 host/session-removed 把会话删掉。headless 从不在完成后 dispose(进程退出自然清理)。
修复:任务完成后不 dispose;高吞吐场景可用环境变量 DSH_FLEET_DISPOSE_AFTER_TASK=1 强制清理。
🕳️ 坑 7(附加):worker agent 没挂 preset,没有工具可用
现象:worker 上的 agent 只有 fleet 工具,没有 bash/fs——连 echo 都执行不了。
原因:agents.create() 直接创建不会自动 join preset,工具/prompt sections 全空。
修复:在 setup 里调 agentPresets.mount(agentCtx, presetId),参照 host-apiproxy 的 composeAgent()。
七、测试与验证
发布前做了完整的功能测试:
| 测试项 | 结果 |
|---|---|
| 基础任务(回答几个字) | ✅ |
| 带 bash 工具调用任务 | ✅ hello-from-vm |
| 多步工具调用(2 个 bash 命令) | ✅ step-one + step-two |
| keyHint=self(worker 用自己的 key) | ✅ |
| 失败路径(exit 7) | ✅ 退出码如实报告 |
| 超时路径(8s 超时) | ✅ 正确触发超时 |
| 双向派发(A→B 和 B→A) | ✅ |
八、未来规划
- 任务日志流式回流 / 产物回传
- 公网 IP / NAT 类型探测,名片补全公网信息
- UDP 打洞;邀请码入队
- 插件市场面板(浏览器内一键安装/升级)
九、开源信息
- GitHub: https://github.com/scriptsnet/dsh-fleet
- npm: https://www.npmjs.com/package/dsh-fleet
- 安装:
dsh plugin --profile web add dsh-fleet
dsh-fleet 始于一个闪光的想法:「把每台闲置机器的算力凑一块儿」,然后……就真的烧了 token 把它做出来了。开发调试全程烧了约 2 亿 token。
如果你也在做 AI Agent / 全栈开发,欢迎聊聊 —— 无论是合作、外包,还是单纯交流。
如果这篇文章帮到了你,欢迎去 GitHub 点个 ⭐,或者赏我点 token 继续造轮子 ☕
更多推荐


所有评论(0)