爸爸跟 Hermes 聊天时,每个话题会刷几十条 tool_call 中间态。僚机把 「当前话题在干啥 / 进度多少 / 完工没 / 在等谁拍板」实时提炼出来,落进 SQLite, 由网页驾驶舱呈现,关键里程碑浏览器语音播报。
daemon/worker.py — tail + tick + haiku,纯标准库,无第三方依赖
持续 tail -F Hermes 网关写出的 fanout 日志,逐条 upsert 进 SQLite 的 thread_state,按 thread_id 分桶维护每个话题的动作流。
每 5 秒对每个 active 话题调一次 haiku,提炼 summary / verdict / milestones / changes / choices / done —— 纯数据,决策与副作用分离,可单测。
基于 bot 沉默时长推进状态:5 分钟静默 → stalled,15 分钟 → 自动收尾 done。在等拍板的话题永不误判完工。
首条消息冒号开头视为用户自取(直接采用);否则喂对话上下文给 haiku,起一个 4–12 字的中文标题,缓存进库不重复调模型。
从 Hermes 最后一条问句里抠出 A/B/C、1/2/3 或「是/否」式快捷选项,驱动驾驶舱的一键回复按钮。模型抠的优先,正则兜底。
话题 done 时往飞书 DM 根发一行「✅ 完成 · 用时 · 驾驶舱链接」文本,去重只发一次。可选回退到飞书原地更新的交互卡片模式。
三个独立 runtime 的话题/任务,统一成同一种卡片呈现
主数据源。飞书话题的实时状态、动作流、里程碑、待拍板选项,全部来自 wingman 自己的 SQLite。
第二套完全独立的 runtime(~/.pi/agent),只读其会话 jsonl,额外解析每话题的 token 用量与成本,后台 haiku 独立提炼。
同源代理本地 claude-code-mcp-bridge,展示派出去的 CC 任务实时进度,并按 cwd 模糊关联回对应的飞书话题。带缓存,桥挂了也不拖慢请求。
代理 bridge 的全盘会话扫描,浏览磁盘上所有 Claude Code 会话;点开任意会话拉取扁平化 transcript(路径严格限定在 projects 目录内)。
web/app.py + React·Vite·TS — 只读、无鉴权、localhost · :7421
宽屏双栏:上墙是「现在在忙」的 active/stalled 卡片 + 实时时间轴动作流;下方收纳已完成 / 空闲话题与 CC 会话。一眼看清谁在跑、谁在等你拍板。
浏览器 speechSynthesis 播报关键里程碑,去重不重复念。声音模式可循环切换(全部 / 仅完工 / 静音)。
点选项按钮,以「用户身份」经 lark-cli 把回复发回对应飞书话题(Hermes 与 Pi 各一条通道),不写任何对方存储。文本上限 200 字。
5 秒客户端 tick;服务端弱 ETag + 304(话题空闲时几乎零传输)+ gzip 压缩快照(35kB → 几 kB)。空闲就是大多数时候。
抽屉里浏览单个 CC 会话的完整对话流,text / tool_use / tool_result 分块渲染,带短缓存按需拉取。
两个进程、一份代码、一份契约
haiku prompt / parser / 凭据、线程 id、schema 全收进 shared/,daemon 与 web 共用,消除曾经「KEEP IN SYNC」的整段抄写。
独占一个 SQLite 连接 + 一把锁,对外只暴露领域动词。所有读写必经此口、内部串行化;WAL 模式让只读 web 不撞 daemon 写锁。
wingman 主话题在 tick 层被跳过,避免「网关写卡片 → fanout → 又被重新提炼 → 再写卡片」的反馈循环。
daemon 与 cockpit 各一个 plist,开机自启、崩溃重拉。前端改动只需 npm run build 出 dist,部署无构建步。