Hermes 旁路视图系统

Wingman.

爸爸跟 Hermes 聊天时,每个话题会刷几十条 tool_call 中间态。僚机把 「当前话题在干啥 / 进度多少 / 完工没 / 在等谁拍板」实时提炼出来,落进 SQLite, 由网页驾驶舱呈现,关键里程碑浏览器语音播报。

前后端合一 monorepo daemon 纯 stdlib web FastAPI · React 两进程 · 一份代码 · 一份契约
飞书云
  
Hermes gateway · fanout hook wingman-fanout.jsonl
  ↓ tail -F
daemon/worker.py ── tail → SQLite · 5s tick → haiku 提炼
  ↓ 读同一个 SQLite
web/app.py ── 只读驾驶舱 :7421 · React 双栏 · 语音播报
01

后端守护 · 实时提炼

daemon/worker.py — tail + tick + haiku,纯标准库,无第三方依赖

📡

Fanout 消费

持续 tail -F Hermes 网关写出的 fanout 日志,逐条 upsert 进 SQLite 的 thread_state,按 thread_id 分桶维护每个话题的动作流。

tail jsonlrecent_actions
⏱️

5s tick 提炼

每 5 秒对每个 active 话题调一次 haiku,提炼 summary / verdict / milestones / changes / choices / done —— 纯数据,决策与副作用分离,可单测。

haiku 4.52-model fallback
🚦

状态机判定

基于 bot 沉默时长推进状态:5 分钟静默 → stalled,15 分钟 → 自动收尾 done。在等拍板的话题永不误判完工。

active stalled done idle
🏷️

话题标题生成

首条消息冒号开头视为用户自取(直接采用);否则喂对话上下文给 haiku,起一个 4–12 字的中文标题,缓存进库不重复调模型。

名词短语size-cached
🅰️

待拍板选项抽取

从 Hermes 最后一条问句里抠出 A/B/C、1/2/3 或「是/否」式快捷选项,驱动驾驶舱的一键回复按钮。模型抠的优先,正则兜底。

strict A/B/Cloose X/Y
🔔

完工通知

话题 done 时往飞书 DM 根发一行「✅ 完成 · 用时 · 驾驶舱链接」文本,去重只发一次。可选回退到飞书原地更新的交互卡片模式。

notify_doneCARD_ENABLED 可选
02

多源融合

三个独立 runtime 的话题/任务,统一成同一种卡片呈现

💬

Hermes 话题

主数据源。飞书话题的实时状态、动作流、里程碑、待拍板选项,全部来自 wingman 自己的 SQLite。

🤖

Pi agent 话题

第二套完全独立的 runtime(~/.pi/agent),只读其会话 jsonl,额外解析每话题的 token 用量与成本,后台 haiku 独立提炼。

total_tokenscost_usd
🔗

CC / MCP Bridge 任务

同源代理本地 claude-code-mcp-bridge,展示派出去的 CC 任务实时进度,并按 cwd 模糊关联回对应的飞书话题。带缓存,桥挂了也不拖慢请求。

recent_eventscached 5s
🗂️

本地 CC 会话浏览

代理 bridge 的全盘会话扫描,浏览磁盘上所有 Claude Code 会话;点开任意会话拉取扁平化 transcript(路径严格限定在 projects 目录内)。

sessions/alltranscript
03

网页驾驶舱

web/app.py + React·Vite·TS — 只读、无鉴权、localhost · :7421

🎛️

暗色命令中心

宽屏双栏:上墙是「现在在忙」的 active/stalled 卡片 + 实时时间轴动作流;下方收纳已完成 / 空闲话题与 CC 会话。一眼看清谁在跑、谁在等你拍板。

Wall 主墙Recede 收纳区Drawer 详情抽屉
🔊

里程碑语音播报

浏览器 speechSynthesis 播报关键里程碑,去重不重复念。声音模式可循环切换(全部 / 仅完工 / 静音)。

useSound
↩️

一键快捷回复

点选项按钮,以「用户身份」经 lark-cli 把回复发回对应飞书话题(Hermes 与 Pi 各一条通道),不写任何对方存储。文本上限 200 字。

/api/respond/api/pi_respond
📊

轮询 + 省流

5 秒客户端 tick;服务端弱 ETag + 304(话题空闲时几乎零传输)+ gzip 压缩快照(35kB → 几 kB)。空闲就是大多数时候。

ETag 304gzip
📜

会话 Transcript 详情

抽屉里浏览单个 CC 会话的完整对话流,text / tool_use / tool_result 分块渲染,带短缓存按需拉取。

on-demand
04

架构与工程

两个进程、一份代码、一份契约

📦

共享逻辑收口

haiku prompt / parser / 凭据、线程 id、schema 全收进 shared/,daemon 与 web 共用,消除曾经「KEEP IN SYNC」的整段抄写。

shared/haiku.pyidentities.py
🔒

ThreadStore 深模块

独占一个 SQLite 连接 + 一把锁,对外只暴露领域动词。所有读写必经此口、内部串行化;WAL 模式让只读 web 不撞 daemon 写锁。

WAL类型化 ThreadState
🪆

防套娃

wingman 主话题在 tick 层被跳过,避免「网关写卡片 → fanout → 又被重新提炼 → 再写卡片」的反馈循环。

SKIP_THREAD_IDS
🚀

launchd 托管

daemon 与 cockpit 各一个 plist,开机自启、崩溃重拉。前端改动只需 npm run build 出 dist,部署无构建步。

com.leway.wingmancom.leway.cockpit