# ai-group-chat — 让 DSH、opencode、WorkBuddy 在同一个群里聊天 一个技能包:三个 agent 共享一条**文件总线**当群聊,互相发消息、`@` 点名,并且能**真的把对方叫醒**回答 (不是留言等人看)。零依赖,只用 Node 标准库。另带一个**本地 Web 群聊台**,你可以在里面直接发号施令, 默认发给主代理(DSH),由它决定自己做还是把活分给 opencode / workbuddy。 ``` ┌──────────────── ~/.ai-groups/rooms//messages.jsonl ────────────────┐ │ (append-only 日志 + 每个成员自己的游标) │ └───▲────────────▲────────────▲────────────▲──────────────────────────────┘ │ │ │ │ dsh ── dsh --profile headless │ │ │ opencode ── opencode run --format json -m big-pickle │ workbuddy ── cbc -p --output-format json --model hy4-preview-f qoder ── qodercli -p -o json -m Qwen3.8-Flash ▲ │ Web 群聊台 http://127.0.0.1:3099/ ← 你在这里说话(human,默认发给主代理) ``` ## 快速开始 ```bash # 1) 安装到本机各 agent 的技能目录(DSH/opencode 共用 ~/.agents/skills) node E:\deepseek\ai-group-chat\scripts\install.mjs --host all # 2) 自检(离线,49 项,不调用任何模型) node E:\deepseek\ai-group-chat\scripts\selfcheck.mjs # 3) 体检:房间 + 三家的唤醒通道 node ~/.agents/skills/ai-group-chat/scripts/aig.mjs doctor # 4) 开群、试一句 node ~/.agents/skills/ai-group-chat/scripts/aig.mjs init node ~/.agents/skills/ai-group-chat/scripts/aig.mjs ask opencode "说一句你在群里报到了" --as dsh # 5) 打开 Web 群聊台(你自己发号施令的入口) node ~/.agents/skills/ai-group-chat/scripts/aig-web.mjs --port 3099 --workspace E:\deepseek # → http://127.0.0.1:3099/ ``` 之后在任何一家里直接说「**拉个群,让 opencode 看看这个改动**」就会触发这个技能。 ## Web 群聊台 **暗色 IM 群聊**(微信/飞书式观感,按 linear 配方落地:暖黑 #08090A + 发丝线 + 单一强调色 #5E6AD2)。 页面主体在 `scripts/web/ui.html`,服务端 `scripts/aig-web.mjs` 只做两件事:读总线(直接读 JSONL)+ 调 `aig.mjs`(send / ask / wake),所有业务规则只在 `aig.mjs` 一处。 **「谁正在干什么」四处同时可见**:左侧成员卡(染色 + 强调条 + 「正在回答:… 12s」)、顶部状态条 (`成员 · 回答中 · 秒数 · 进度条`)、会话流里的**「正在输入」气泡**(三点动画 + 中止按钮)、头像行光环。 - **默认发给主代理**:输入框里直接说话 → 以 `human` 身份进群 → 主代理被真的唤醒去落实, 它可以用 `aig ask` 把活分给其他成员,再汇总回群。 - **自驱模式**:只对用户发出的消息自动唤醒被点名的人(agent 之间的往返不自驱,避免重复唤醒)。 - **点击立刻有反应**:按下即本地改状态;消息乐观回显;成员卡与按钮只建一次(早期版本每 3 秒重建成员栏, 会把 mousedown/mouseup 之间的按钮换掉,click 根本不触发——这就是"点了没反应"的真根因)。 - 右上角可切明/暗;只绑定 `127.0.0.1`,无鉴权。 ## 命令一览 | 命令 | 作用 | |---|---| | `init` / `rooms` / `use ` | 建房间 / 列房间 / 设默认房间 | | `join ` / `members` / `leave ` | 成员管理(含通道与模型画像) | | `send <文本> [--to a,b] [--file p]` | 发言(正文里 `@某人` 自动定向) | | `read [--all] [--peek] [--json]` | 读未读 / 读历史 / 看结构 | | `wait [--timeout 60]` | 阻塞等新消息(拿到=0,超时=3) | | **`ask <成员> <文本>`** | 发问 → 无头唤醒对方 → 回答贴回群里 | | **`wake <成员>`** | 只把该成员的未读交给它处理 | | `status` / `detect` / `doctor` | 房间概况 / 我是谁 / 环境体检 | 完整协议见 `references/protocol.md`,通道细节见 `references/transports.md`,踩坑记录见 `references/troubleshooting.md`。 ## 设计取舍 - **文件总线而不是网络服务**:任何一方没开、崩了、不在线,消息都不丢;谁后起来谁能读到。不需要端口,不需要 daemon。 - **唤醒 = 调用对方 CLI 的无头模式**:这是「群聊」和「留言板」的分界线。三家都实测可用(`dsh --profile headless` / `opencode run` / WorkBuddy 随附的 `cbc -p`)。 - **一律真 EXE 或 `node `,从不拼命令行字符串**:Windows 的 `.cmd` 垫片 + cmd 转义会把中文提示词搞坏。 - **防互相唤醒死循环**:`AIGROUP_DEPTH`/`AIGROUP_MAX_DEPTH` + 子进程注入 `AIGROUP_NO_WAKE=1`。 - **失败必须进群**:唤不起来会写一条 `dispatch-error` 消息,而不是静默返回非零码。 - **模型全部免费档**(用户要求):opencode 用 Zen 免费档(默认 `big-pickle`,实测同分最快,掉线自动退到 `nemotron-3.5-lightning-free`),workbuddy 用 `hy4-preview-f`。选型依据是两轮同题实测,不是印象。 ## 验证证据 | 项目 | 结果 | |---|---| | 离线自检 `scripts/selfcheck.mjs` | **49/49 通过**(房间、游标、定向隔离、8 并发写、wait、dry-run、深度护栏、失败进群、主模型退档、坏行不静默、诊断命令) | | UI 交互验收 `tools/ui-check.mjs` | **13/13 通过**,CDP 真实鼠标事件测得「点击→可见变化」**64–119 ms**;含节点不被重建、压力下连点 8/8 不丢事件 | | UI 观感(免费视觉模型三轮审图) | 7.0 → 7.5 → 8.0,并逐条确认「谁正在干什么」一眼可辨 | | Web 群聊台端到端 | 只发一条号令 → 主代理自动派活给 opencode + workbuddy → 两条回复落群 → 主代理汇总 | | Qoder 接入 | `aig ask qoder "…"` → `Qwen3.8-Flash` 3–4 秒作答、cost 0、回复落群 | | 免费档选型 `tools/model-shootout*.mjs` | 两轮同题实测,见 `tools/shootout*-result.json`;选定 `opencode/big-pickle` | | 真机记录 | `VERIFY.md`:三方对话原文、真 bug 修复、接口实测、UI 诊断与验收 | ## 目录 ``` ai-group-chat/ ├── SKILL.md # 技能入口(DSH / opencode / WorkBuddy 三家共用同一份) ├── scripts/ │ ├── aig.mjs # 总线 CLI(房间、消息、游标、唤醒) │ ├── install.mjs # 四目标安装器(agents / workbuddy / codebuddy / opencode) │ └── selfcheck.mjs # 44 项离线自检 ├── references/ │ ├── protocol.md # 消息结构、游标语义、锁、唤醒协议、退出码 │ ├── transports.md # 三家 CLI 的确切调用方式与免费档清单 │ └── troubleshooting.md # 真实踩过的坑 └── tools/ ├── model-shootout.mjs # 免费档第一轮(基础题) └── model-shootout2.mjs # 免费档决赛轮(难题 + 跑代码断言) ```