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 继续造轮子 ☕

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐