ParallelAgent 一期实现记:给多 AI 会话并行工作流加上编排层
ParallelAgent 一期实现记:给多 AI 会话并行工作流加上编排层
从写下第一行代码,到全端点联调通过——编排器插件实现与踩坑记录 开源地址:https://github.com/YorickFin/ParallelAgent (Apache 2.0)
背景
上一篇介绍了 ParallelAgent 的方案设计:一套"仲裁者 + 多轨会话 + 文件信箱"的多 AI 并行开发工作流,以及用编排层把"建会话、派活、收工判定"自动化的思路。方案冻结、仓库开源之后,下一步就是按实现路径把编排器插件和两个 Skill 做出来。
一期的定位是手动触发版——仲裁者 Skill 出建议、用户确认,编排器插件负责把脏活自动跑起来。这篇记录一期的实现过程。
技术选型:能薄则薄
开工前先把技术选型定死,原则就一条:能薄则薄。
| 项 | 选择 | 理由 |
|---|---|---|
| CDP 客户端 | ws 直连 WebSocket | 不用现成的 chrome-remote-interface——它假设标准 Chrome,Electron 衍生版未必兼容,而 ws 直连已被探针验证 |
| HTTP 网关 | Node 内置 http | 零依赖,手写极薄 body 解析 |
| 文件监听 | 轮询 fs.stat 为主 + fs.watch 唤醒 | Windows 的 fs.watch 不稳定;轮询天然拿 mtime,恰好契合"回执文件出现 + mtime 稳定 = 完成"的协议设计 |
| 构建 | esbuild 打包 + tsc 类型检查 | 微软官方推荐的 VS Code 扩展构建方式,ws 直接打进 dist,装扩展连 node_modules 都不用 |
最终交付:15 个 TypeScript 源文件,运行时第三方依赖只有 ws 一个。HTTP 网关六个端点(/health、/new、/ping、/dispatch、/watch、/delete),对应编排器的全部动作:查状态、建轨、切轨、派活、等回执、删轨。
架构上的铁律没变:控制面只传"轨号 + 轮次 + 文件指针",任务内容 100% 走文件信箱;每个动作前都做 sid 身份断言,断言失败立即 fail-stop 停机告警——宁可停,不盲发。
联调:真实 DOM 的连番毒打
代码写完只是上半场。真正的重头戏是联调——当 CDP 脚本第一次碰到真实运行的应用界面(第一个适配宿主是 Trae Work),教科书式的假设会一个接一个碎掉。记几个最有代表性的坑,其中大部分对任何"用 CDP 驱动 Electron 应用 UI"的场景都适用。
坑一:扩展装了就是不生效。 插件文件放进扩展目录,重启宿主,命令死活不出现。翻遍配置才发现扩展目录下有个 .obsolete 文件,里面赫然写着新扩展的条目——宿主的扩展生命周期管理把它标记成"待废弃"了。还得在 extensions.json 里正经注册一条记录才算数。教训:扩展的"安装"不只是把文件拷进去。
坑二:重启脚本把自己杀了。 实现"带调试端口重启宿主"命令时,脚本逻辑是:杀掉宿主进程 → 等 8 秒 → 带参拉起。结果脚本一杀宿主,自己也被连带终止——因为它是从宿主里启动的,和宿主在同一个 Windows Job Object 里,爹死了全家陪葬。换 Start-Process 也没用。最后用 WMI 的 Win32_Process.Create 拉起:父进程是 WmiPrvSE,彻底脱离宿主的进程树,这才活了下来。
坑三:"新建任务"按钮,点哪个有讲究。 任务面板顶部有个"新建任务"按钮,但它走的是繁琐路径:新建后还要搜索项目、选文件夹挂载。而每个项目分组行右侧还有一个小小的"新建任务"图标——一步创建,自动挂上该项目。轨道会话必须挂在项目下才能读到文件信箱,所以这个不起眼的分组行按钮才是正确入口。这种发现,坐在那里想方案是想不出来的,必须盯着真实界面看。
坑四:sid 去哪了? 点击新建按钮后,输入界面就绪了,但任务列表里没有新会话、也拿不到 sid。观察之后才理解了"草稿会话"机制:点击后先进入草稿态,首条消息真正发出后,会话才落库、sid 才存在。所以时序必须是"点击 → 等输入框聚焦 → 注入话术 → 发送 → 等新 sid 出现",顺序反了就永远拿不到 sid。
坑五:点不动的按钮,和会变脸的类名。 删除会话要走"更多菜单",但那个菜单按钮常态是 display:none,hover 才显示——CDP 按坐标点击,拿到的是 (0,0),点了个寂寞。反直觉的是:el.click() 这种合成点击,对 display:none 的元素照样能触发 React 的 onClick。于是整条删除链全部改用合成点击。另一个坑是确认弹窗:它的类名带构建哈希,今天的 dialog-Di866XR 明天可能就变了——最终全部改为按文本匹配("删除任务"菜单项、"确认删除"标题、弹窗内"删除"按钮),彻底不碰哈希类名。
验证:全端点跑通,多轨切换也没问题
坑填完之后,就是一条链一条链地验:
/health:CDP 连通性 + 轨道身份表 ✅/new:分组行按钮建轨 → 草稿会话 → 首条话术注入发送 → sid 落库进表 ✅/ping:切轨 + 读状态,来回切换任务名正确跟随 ✅/dispatch:切轨 → 身份断言 → 注入转达话术 → 发送 ✅/watch:轮询回执文件,收到格式完整的回执(裁决编号 + 自检字段)✅/delete:菜单链 + 确认弹窗全自动点掉,轨道同步移出身份表 ✅
最后专门做了多轨切换测试:建两条轨道,交替 ping,两条会话的任务名随 sid 正确切换;在多轨状态下执行删除,身份表同步干净。收工后把联调期的 30 多个临时探针脚本、测试会话、信箱文件全部清理干净,正式探针只留两个,连同全部源码推上 GitHub。
至此,上一篇里那个"人肉邮差"的活——建轨、派活、等回执、删轨——这几段已经真的不用人干了。
一期的"手动触发版",手动指的是流程的起点和关键决策点,而不是让人手敲命令。实际体验是:在任意一个会话里说出"把这个任务拆开并行跑"之类的关键词,仲裁者 Skill 就被激活了。它会先判奠基阶段:契约没冻结、边界没定义、骨架没就位的,它自己动笔写奠基文档;奠基就绪的,直接按拆分判据输出决策建议。用户确认拆分方案后,仲裁者持笔写好转达单,然后自己去调编排器的 HTTP 接口:建轨、派发、监听回执,全程它自己完成。回执收齐,仲裁者独立实检、写快照、输出自检报告和收回评估。
整条"串行 → 并行 → 串行"的工作流走下来,人要做的只有决策:拆不拆(确认拆分建议)、收不收(确认收回决策)、出事了要不要接管(异常升级)。派活、盯梢、催收工这些体力活,一个都不用过手。
后续规划:先把链路跑可靠,再加智能
一期特意只做"手动触发版",这是定稿里明确的节奏——不要一期就做自动判定,先把 Skill + 插件 + 文件信箱这条链路跑可靠,再加智能。三期的演进路线:
| 期 | 触发方式 | 决策方式 | 状态自检 |
|---|---|---|---|
| 一期(已跑通) | 手动 | 仲裁者给建议 + 用户确认 | AI 自评报告 |
| 二期 | 自动判定 + 建议式 | 仲裁者自动评估,需用户确认 | 插件采集指标 |
| 三期 | 自动 + override | 仲裁者自动拆,用户可拦截 | 同 + 自动交接 |
二期方向:拆分与收回的判定自动化——仲裁者自动评估拆分时机与收回时机,用户确认后执行;T-0 轨自动完成奠基任务;插件自动采集轨道健康指标。三期才是全自动,同时给用户留 override 拦截权。
另外两条顺路的演进:把首个宿主(Trae)的适配经验整理成适配层接口,未来平级扩展 Cursor 等其他宿主;DOM 锚点全部外置化,宿主大版本更新后重跑探针即可修复,不用动逻辑代码。
写在最后
从方案定稿到"编排器真的能自动建轨派活",整个一期实现的节奏比预想快了不少。回头看,联调那几场 DOM 毒打反而是最有价值的部分:所有假设都被真实界面修正了一遍,这些教训已经全部写进仓库的方案文档和锚点清单里——下一个适配其他宿主的人,不用再踩一遍。
这些坑大多不是某个产品特有的,而是"用 CDP 从外层驱动 Electron 应用 UI"这类工程的共性问题:进程树归属、草稿态时序、合成点击、哈希类名。希望这份记录对做类似事情的人有参考价值。
项目地址:https://github.com/YorickFin/ParallelAgent ,欢迎 Issue / PR 交流。
