核心能力 · Five Capabilities
每个能力先说"业务方原本要付什么代价",再补一行技术细节。共同前提:用户消息从公众号 / Feishu / Web Widget 三个渠道进来,共用一套 Conversation / Ticket / Message 抽象,bot 在 Mattermost DM 端跑,admin 在 panel 端审核。
架构 · Three channels × One core × Three domains
微信公众号 callback 不直连 cspy,先走上游 wx-gateway-pucs 网关分流;Feishu 通过 channel adapter 接入;Web Widget 直接打到 cspy 自有 API。核心仅 1 个 Next.js 进程,内部分工单 / 对话 / 项目运营三主域;依赖外部 Mattermost(bot 通讯)、Uptime Kuma(健康)、Azure OpenAI(AI 推理)。
关键里程碑 · Timeline
从 4 月底 wechat-bot-tickets 立项,5 月一边扩 Project/Environment/Feature 一边把 bot 通讯从 Clawline relay 切到 Mattermost DM;5 月底完成 Next 14→16 升级 + Feishu/Widget Phase 1。⭐ 标记结构性里程碑。
名场面 · 1 个最值钱的坑(深度)
从 Clawline relay 切到 MM DM 那天看上去就是改个 transport,实际踩到两个相互独立的坑——一个出现在传输层(bot 多次 edit 同一条 post),一个出现在应用层(MM channel 本来就持久,cspy 还在塞历史摘要)。两个坑加起来导致用户和 bot 都"看上去说话不正常"。
sendAndWait edit-aware 静默窗口 + 删 buildHistoryDigest
- 症状 1
- admin 在工单聊天面板看到 bot 回复只剩半截("在呢 👋"),但拿 MM 客户端打开同一个 DM channel 是完整长文。两边数据不一致,看起来像 cspy 在裁字。
- 根因 1
- OpenClaw bot 模仿流式样式:先发一条短占位 post,再多次 edit 同一条 post 把内容补全。lib/mm/client.ts 原版抓到第一条带内容 post 就立刻 return,后续 edit 全丢。
- 修法 1
- edit-aware 静默窗口 — Map<id, MmPost> 记 collected,每轮 poll 覆盖,候选 message 连续 5s 不变才 return(247b82c)。后又改成 WS 订阅 + 双 idle(typing 5s + post 1.5s,aa95cc5),省掉轮询。
- 症状 2
- admin 跟 bot 多轮对话,bot 反复回复"我之前已经分析过了" / "如前所述",像被洗脑。每次都得重新 kick off 一遍。
- 根因 2
- lib/feature/bot-chat.ts 每次都拼一段 markdown 历史摘要 buildHistoryDigest 塞 prompt;但 MM DM channel 本来就持久,bot 在 MM 端能看完整 channel 历史 → 双重历史困惑。
- 修法 2
- 删 buildHistoryDigest,reply 路径改裸消息 [feature:title]\n${msg} 单行 tag,kickoff 仍带 brief(e24d1e7)。
// lib/mm/client.ts — edit-aware 静默窗口(后续被 WS 单例替代) const collected = new Map<string, MmPost>() while (Date.now() - lastChange < QUIESCENCE_MS) { const posts = await fetchSince(channelId, sinceTs) for (const p of posts) collected.set(p.id, p) // 覆盖式合并 新 post + edit if (changed) lastChange = Date.now() } return [...collected.values()].filter(matchesCandidate).pop()
📊 当前现状 · Status
不写"一切正常 / 反馈正面"这类空话。下面五个分组每条都对应一个可 grep 到的真实事实:生产部署 / 数据规模 / 接入渠道 / 最近 7 天变动 / 运维状态。
生产部署
- 域名:cspy.mvp.restry.cn(mvp-deployer 项目名沿用 cspy)
- pm2 进程:cspy · port 3791 · online
- 栈:Next.js 16.2.6 + React 19.2.6(2026-05-10 升级完)
- 部署链路:mvp-deployer 异步 build + prisma migrate deploy,通用 deployment guide 已写
- 双 push:origin 配 GitHub Restry/nexora-loop + GitLab 内网,一条 push 同步两端
数据规模
- 30+ Prisma 表 / 19 model:Bot · WechatUser · Conversation · Session · Message · Ticket · TicketEvent · AdminUser
- 运营域:Project · Environment · Notification · SystemConfig · ProjectNote
- 需求域:Feature · FeatureEvent
- 多渠道:FeishuUser · WidgetApp · ChannelEventLog(2026-05-30 Phase 1 新增)
- 表前缀 wbt_:历史遗留(原 repo 名 wechat-bot-tickets),新表沿用一致,改名无价值
接入渠道 + bot 名册
- 公众号 callback:配在 wx-gateway-pucs(wxmsg.mvp.restry.cn)· 按 scene_str 分流到 cspy /api/wechat
- OpenClaw bot:nexora-mazu(客服 + 工程师双角色)· 妈祖客服 · 喜铺客服 · 通用客服 — 每个 Bot 带 categoryTags + mmBotUserId
- Mattermost:mm.cn.restry.cn · WS 单例 + DM 通信 + SSE 转推浏览器
- Web Widget(2026-05-30):JS snippet 嵌第三方网站 · Feishu channel(同期):飞书机器人桥接进工单
最近 7 天主要变动(git log)
- 552d0cf docs: 通用 deployment guide — 解耦 mvp-deployer,允许 self-host
- 06f89ac merge: Feishu Phase 1 + Widget Phase 1(★ 部署到 cspy 2026-05-30)
- 7ab02d1 feat(widget): Phase 1 — 嵌入式 web chat widget + Admin panel
- 6c39263 feat(feishu): Phase 1 — Feishu channel + shared abstractions
- 2be09fa refactor(ui): bots & features 面板镜像 consult-panel streaming 模型
- fa0473d feat(ui): typing indicator + streaming render 统一三面板
- 211e5b7 feat(mm): SSE endpoint 流式转推 WS 事件到前端
- aa95cc5 refactor(mm): sendAndWait 改 WS 订阅(替换轮询)
运维状态
- MM 通信稳定:edit-aware 静默窗口 + WS 单例 + 双 idle(typing 5s / post 1.5s)三件套生效,流式截断不复发
- engineer bot 自动指派:categoryTags 命中 → 派对应 bot,无匹配走 default fallback,工单不卡死
- 4 态状态机(P8 简化后)稳定运行:open / processing / waiting / closed · ESCALATE/ANALYSIS 解析正常
- GitNexus 已索引:2098 symbols · 3637 relationships · 175 flows,bot 改代码前先查上下文
- 集成测试(真 DB + 真 MM)持续跑,关键路径必过;少量纯函数 unit 走 vitest.unit.config.ts
🚧 未结清债务 · Open Debt
不写"P1 待办 N 条"这种模糊罗列。每条 4 列:优先级(P0 立刻 / P1 本周 / P2 排期) · 工作量(S < 0.5d · M 1-2d · L 3d+) · 待办 · 现状是什么。来源:.hermes/memory.md 踩坑历史 / 近 30 commits / 本地 skill NEEDS-DECISION 段。
| 优先级 | 工作量 | 待办 | 现状(还差什么) |
|---|---|---|---|
| P0 | M | prod 泄漏 secrets 轮换 | 2026-04-28 部署日志被 dump 进外部聊天,SEED_ADMIN_PASSWORD / MM_USER_TOKEN / WECHAT_APPSECRET 等明文外泄。需逐个 rotate + 同步到 mvp-deployer env + 重启 pm2,且核对 prod admin 实际密码(可能已被手改未同步)。 |
| P1 | M | need_human 不进 ACTIVE_STATUSES → 重复开单 | 用户人工兜底后再发消息,因 need_human 不在活跃工单匹配集,会开第二张单。memory.md 已知坑 #1。修法:扩 ACTIVE_STATUSES 或新增 reuse 路径。 |
| P1 | S | 复用 existing ticket 不刷 summary/category | 同一用户连续发新消息复用旧工单时,Ticket.summary 和 category 保持初版,工程师 bot 拿到的上下文过期。需在 reuse 分支重跑 AI 分诊。 |
| P1 | L | 跨 conversation / 跨用户工单合并 | 同一业务问题被不同用户分别提,产生 N 张独立工单,admin 无合并入口;需先建 TicketGroup 抽象 + admin 合并 UI(可拖拽 / 多选合并)。 |
| P1 | M | P9.2 AI 建议自动首次生成 + 拆 DIAGNOSIS/REPLY 两块 | 当前 AI 建议需 admin 手动触发,且 DIAGNOSIS(诊断思路)和 REPLY(给用户的话)合一团,admin 编辑成本高。需 ESCALATE 后自动跑一次,并把两块拆开分别可编辑。 |
| P1 | L | P9.3 admin ↔ 工程师 bot 多轮 | 进行中 · 当前 admin 给 bot 提问只能单轮,bot 回完就结束;需在 Feature 面板复用 ChatBubble/ChatComposer,沿用 MM DM persistent channel 直接对话。 |
| P2 | M | prod admin 密码漂移自动检测 | SEED_ADMIN_PASSWORD env 跟 DB 实际密码长期不同步,登录失败每次都要先 reset。需启动时对比 DB hash,不一致告警。 |
| P2 | S | Widget / Feishu 渠道接入业务方验证 | Phase 1 已发,目前仅 cspy 自用样例;需找 2-3 个真实接入跑一轮,验证 ChannelEventLog 抽象是否够、widget JS snippet 文档够不够。 |