| # | 问题 | 严重 | 类型 | 是不是你提的 |
|---|---|---|---|---|
| 1 | milestone 时间倒序 / 漂移 | HIGH | bug | ✓ 你说的 |
| 2 | 进行中的"当前节点"无呼吸灯 | MED | ux | ✓ 你说的 |
| 3 | TTS 朗读僵硬(系统 Tingting/Reed) | MED | ux | ✓ 你说的 |
| 4 | summary / verdict 行文僵硬 | MED | ux | ✓ 你说的 |
| 5 | milestone 时间挤一起(5 个都是 +13h19m) | HIGH | bug | 我扫出来的 |
| 6 | DM 根 chat 也进了 DB,每 5s tick 浪费 haiku token | MED | perf | 我扫出来的 |
| 7 | verdict 重复 summary 内容 | LOW | ux | 我扫出来的 |
| 8 | 超 1 天没动的 thread 仍在 tick(无 archive) | LOW | perf | 我扫出来的 |
| 9 | recent_actions 只存 10 条 → milestone 必然挤在尾部 | HIGH | bug | 我扫出来的 (#5 的根因) |
| 10 | modal 详情时间线 ASC(旧到新),可能不符你阅读习惯 | LOW | ux | 我猜的(需你确认) |
核心洞察:#1 #5 #9 是同一个根因链 — recent_actions 只有 10 条 + 这 10 条往往是最近几分钟的密集 tool-call → haiku 看不到任务的真实时间分布 → 所有 milestone 被挤到尾部,offset_s 还互相覆盖。表面是"时间不对",根上是"haiku 缺乏时间感"。
| 组件 | 位置 | 谁拉起 |
|---|---|---|
| fanout hook | ~/.hermes/hermes-agent/gateway/platforms/feishu.py:_wingman_fanout_event() | Hermes 主进程 |
| fanout 日志 | ~/.hermes/logs/wingman-fanout.jsonl | 追加写, 永不轮转 (隐患) |
| worker | ~/projects/wingman/worker.py (1176 行) | launchctl com.leway.wingman |
| DB | ~/.hermes/wingman.db | SQLite |
| cockpit | ~/projects/hermes-cockpit/app.py + static/index.html | launchctl com.leway.cockpit |
| haiku | eagle proxy https://api.eagle.openclaws.co.uk/v1/messages | worker.py 5s tick |
| 列 | 类型 | 含义 |
|---|---|---|
thread_id | TEXT PK | omt_xxx (话题) 或 oc_xxx (DM 兜底) |
card_msg_id | TEXT | 飞书卡片消息 ID (v0.4 dashboard 模式不用) |
status | TEXT | idle / active / stalled / done |
thread_name | TEXT | haiku 给的 4-12 字标题 |
thread_first_msg_id | TEXT | 首条 om_xxx (给 reply-in-thread 当 anchor) |
last_user_msg | TEXT | 最近 1 条爸爸消息原文 |
recent_actions | TEXT (JSON) | 仅最近 10 条 · ts/direction/sender_type/msg_type/content |
summary | TEXT | haiku ≤30 字 一句话现在干啥 |
verdict | TEXT | haiku ≤40 字 进展判断/警告/卡点 |
milestones | TEXT (JSON) | haiku 给的 3-5 条 [{event, offset_s}] |
changes | TEXT | haiku ≤50 字 改了哪些文件 |
tick_count | INTEGER | 累计 tick 次数 |
started_at / updated_at | INTEGER | unix 时间戳 |
done_notified | INTEGER | 1 = ✅ 已发送, 防重复 |
last_haiku_action_count / _ts | INTEGER | 节流键 (actions 没变就不烧 haiku) |
thread_name summary
───────────────────────── ──────────────────────────────────────────
CC MVP Bridge项目进度询问 Hermes 正在改代码 4 处并编写测试,typecheck...
drone-picker项目bug修复完成 调研剪映是否有 MCP 接口,已给出结论:有但不切
摄影公司网站导航和内容重组 完成首页合作伙伴上移和合作页重排,修复底栏...
任务已取消,驾驶舱链接共享 技能已固化完成,用通用 Python 脚本替换旧流程...
设备输入问题咨询 补全生成「DJI Mic Mini语音输入」话题的正式上下文卡片
GitHub Fork项目longcut 完工 (15min 无动作兜底)
创建Azure China命令并检查 SSH 端口从 22 迁移到 18822,NSG 规则已生效...
oc_ffa37ba1bef2fb... 已收到chat_id,等待后续指示 ← 这条不该存在!
每张卡片自上而下:
| 位置 | 字段来源 | 排序逻辑 |
|---|---|---|
| 卡顶渐变色 | status | active橙 / stalled琥珀 / done绿 |
| BADGE | status → RUNNING/STUCK/DONE | — |
| 标题 | thread_name (haiku 生成 4-12 字) | — |
| 右上时长 | elapsed_s = now - started_at | — |
| 新进展(主角) | summary | — |
| 判断(副内容) | verdict (若 ≠ summary) | — |
| 时间线 3 栏 | milestones[-5:] | 仅 filter + slice,不 sort |
| "每 5 秒自动刷新" | 仅 status=active 显示 | 有呼吸灯 (但在卡底不在节点) |
| 快捷回复按钮 | backend parser 抠 A/B/C/1/2/3 | — |
// 时间线 3 栏 (最近 5 条 milestone)
const ms = Array.isArray(t.milestones) ? t.milestones.filter(m => m && m.event) : [];
const shown = ms.slice(-5); // 没 sort!
let tl = "";
if (shown.length) {
const base = shown[0].offset_s || 0; // 用第 0 条做 base
const rows = shown.map((m, i) => {
const tm = t.started_at ? fmtTime(t.started_at + (m.offset_s || 0)) : "—";
const off = i === 0 ? "起点" : fmtOffset((m.offset_s || 0) - base);
return `<div class="tl-row"><span class="tl-t">${tm}</span>...`;
}).join("");
tl = `<hr class="card-rule"><div class="timeline">${rows}</div>`;
}
static/index.html:917 · 你提的症状:你看到"摄影公司"卡片 timeline +0h22m → +0h26m → +0h28m → +0h22m,最后一条回到 22 分钟。
根因:haiku 偶尔不按时序返回 milestones,前端 .filter().slice(-5) 不 sort 直接渲染。
前端加 .sort((a,b) => (a.offset_s||0) - (b.offset_s||0));后端 parse_haiku_text() 也加保险 sort。
worker.py:_humanize_action + haiku prompt · 我扫的症状:看 "CC MVP Bridge":
+13h19m CC 派发任务开始
+13h19m 识别 Hermes 注入点需求
+13h19m 代码 4 处改完
+13h19m 进入测试阶段
4 条全是 +13h19m。看不出先后。
根因:started_at 是 13h 前但 recent_actions 只存了 10 条(都集中在最近 1 分钟内),haiku 看不到中间 12h59m 在干什么,5 个 milestone 只能从最近 10 条里抠 → 全集中尾部。
三条路并行:(a) prompt 显式要求"如果只能从最近行抠,offset_s 用 1 分钟间隔模拟分布而不是全部相同";(b) 加 milestones_archive 列,每次 haiku 输出后把"最早被挤掉的"那条存档,下次 prompt 把存档作为"已知过往节点"喂给 haiku;(c) 把 RECENT_ACTIONS_MAX 从 10 升到 20-30 (代价:每次 tick prompt 长 2x)。
worker.py:44 RECENT_ACTIONS_MAX=10 · 我扫的 (#5 根因)症状:长任务(>30 分钟)的 milestone 永远只反映最后几分钟,开始/中段细节全丢。
根因:最初设计是给短任务 (10-30 min) 的,没考虑长跨度任务 (多小时)。10 条 tool-call 在密集阶段 1 分钟就刷完。
改进 #5 的 (b) milestones_archive 是更优雅的解法;最简单的是把 RECENT_ACTIONS_MAX 提到 25 + 加一个 action_summary_so_far 字段让 haiku 自己累积长任务记忆(增量摘要模式)。
static/index.html:917-927 .tl-row · 你提的症状:active 卡的 timeline 几行里看不出哪条是"刚刚发生"的;卡底虽然有"● 每 5 秒自动刷新"呼吸灯,但跟当前进度节点没视觉关联。
根因:renderCard 没给 timeline 最后一条标"current",CSS 也无 .tl-row.current 样式。
active 卡的最后一条 .tl-row 加 .current class,CSS 里给 .tl-t 前贴一个 ::before 呼吸灯(橙色,2px 圆点 + pulse)。
static/index.html:1194-1215 _speak() · 你提的症状:系统选 Tingting/Eddy/Flo 这种机械嗓门读"摄影公司网站完成",断句生硬、情绪扁平。
根因:用的是浏览器自带 SpeechSynthesisUtterance,调的是 macOS Tingting (vintage neural)。新一代 neural voice 在 macOS 默认列表里就没。
三条路:
(a) 最便宜:换 webkit "Sin-ji" / "Mei-Jia" 香港/台湾女声更自然,调 rate=0.95 + pitch=1.05 软一点。
(b) 推荐:cockpit 加 /api/tts?text=xxx,后端调 Azure Speech (你 voicetyper 项目 key 在 vault) 的 zh-CN-XiaoxiaoNeural 流式返回 mp3,前端 <audio> 播放。延迟 ~500ms 但音色质感天差地别。
(c) 本地:cockpit 调本地 Qwen3-TTS(你 HF cache 已有 4.3GB CustomVoice),完全离线 + 可自定义音色。
worker.py:371-401 build_haiku_prompt · 你提的症状:看真实输出
summary: HERMES 改完代码 4 处,现在写测试,等 CC 跑完后连测
verdict: 正常推进。CC 任务运行中(10/90 迭代),代码改动已完成...
"HERMES"/"正常推进"/"已完成"这种公文腔重复出现。
根因:prompt 没人格化,要求"≤30 字 一句话现在干嘛"诱导出口播体;haiku-4-5 在没人格 prompt 下默认这种 PR-merge-message 风格。
prompt 加人格 + few-shot 示例:
"你是爸爸的副驾驶,跟他聊天一样汇报。用 30 字内、口语、像同事说话——不要写 '正常推进/已完成/HERMES 在...' 这种官腔。
示例:
✓ '改完代码了,现在跑 typecheck 等结果'
✓ '卡在 NSG 规则,要不要换思路?'
✗ '正常推进。代码改动已完成'"
预期效果:summary 像同事口语而不是 release notes。
worker.py:168 db_upsert 用 chat_id 兜底 · 我扫的症状:DB 里有一条 thread_id=oc_ffa...(DM 根)的 active 记录,summary 写"已收到 chat_id 等待后续指示"。cockpit 已 HIDDEN_THREAD_IDS 屏蔽不显示,但 worker 仍每 5s tick 烧 haiku。
根因:db_upsert 把没 thread_id 的事件兜底到 chat_id(DM 单条消息)。SKIP_THREAD_IDS 在 tick_one_thread 里有判断,但 _select_active_rows 没过滤 → tick 头部才 return,已经烧了 SQL。
_select_active_rows SQL 加 WHERE thread_id NOT IN (?, ?),或 db_upsert 直接拒绝 oc_ 开头的写入。
症状:看 "drone-picker":summary "调研剪映是否有 MCP 接口,已给出结论",verdict "任务已回复完成,给出了技术方案对比" — 信息 80% 重叠。
根因:prompt 说"不要重复 summary"但太软。haiku 偷懒就把 summary 换种说法塞 verdict。
prompt 改:verdict 改名 "judgment",强制写"该不该担心 / 卡点 / 下一步建议"中**至少一个**。没担心写空字符串,前端就不渲染那行。
worker.py:_classify_status · 我扫的症状:状态机里没有"archive"概念。stalled 30s 一次也是花费,老 thread 永远占着 active/stalled 槽。
加第 5 个状态 archived,stalled > 24h 自动归档。归档的 thread cockpit 也不显示。
static/index.html:1004 · 我猜的症状:详情 modal 的"时间线"是 ASC(旧→新,需要往下滚才看到最新)。你提到"时间不是倒序",**不确定指的是哪个 — 卡片 milestone(已答 #1)还是 modal 详情?
如果你想要 DESC(最新在顶),改 line 1004 为 (b.ts||0) - (a.ts||0)。等你确认。
打开 cockpit 时默认页面顶部是 stalled,需要往下滚才看到 active。打开时滚动到第一张 active 卡。
左滑 = 标记 done(强制收尾);右滑 = 暂停 ticking(短期 mute)。比点叉按钮快。
卡顶 BADGE "RUNNING" + 右上"已 2h6m" + 卡底"每 5 秒自动刷新" 三处都告诉你它在跑。可以折掉卡底那行(既然 BADGE 已经说了)。
当前 TTS 读"摄影公司项目 更新。完成首页合作伙伴上移..."。其实你只关心"发生了什么新事"。改成读最近一条 milestone (新事件),比读 summary 全量更短更有信号。
飞书 DM 根 "✅ 完成 + 链接" 多任务并发时刷屏。加个"今天 N 条完成"批量推送 + 个别可 mute。
当前只显示"event 描述",看不出是 USER 说的还是 HERMES 做的。加 1px 左侧条 / icon 区分(橙=爸爸说话 / 灰=Hermes 动作)。
已 836KB / 1696 行。半年后撑大→ tail 性能可能掉。加个 logrotate 配置或每周自截留近 7 天。
| 批次 | 包含 | 工作量 | 价值 |
|---|---|---|---|
| 批 1 立刻能见效 |
#1 timeline sort + #2 当前节点呼吸灯 + #6 SKIP SQL 过滤 | 15 min | 3 个改动都是 5-15 行代码,立刻见 |
| 批 2 haiku 表达改造 |
#4 prompt 加人格 + few-shot + #7 verdict 强制写差异化 | 30 min (改 worker.py 重启 daemon, 等 5min 看几个 thread 出新 summary) |
summary/verdict 从公文转口语,朗读出来也舒服 |
| 批 3 TTS 升级 |
#3 选一条路:先 (a) 调系统 voice 参数,不满意再上 (b) Azure Speech 后端 | (a) 5min · (b) 2h | 朗读体验质变 |
| 批 4 长任务记忆 |
#5 + #9 加 milestones_archive 列,长任务里程碑分布化 | 1-2h schema 改 + worker.py prompt 改 |
"+13h19m × 4" 这种现象消失 |
| 批 5 清扫 |
#8 archived + fanout 日志轮转 | 30 min | 系统长期健康 |
我自己推荐先做批 1+2——20 分钟内能看到:"时间不会倒序了 + 当前节点有呼吸灯 + 文字像同事说话了"。批 3 你看完批 2 再决定 TTS 走 (a) 还是 (b)。
批 4 是最大的工程量也最有架构价值("长任务记忆"是个通用机制,以后任何 LLM-tick-loop 都用得上,可以沉淀进现成的 llm-tick-loop-pitfalls skill)。但不急。
end · 2026-06-10 · ~/Desktop/wingman-analysis-2026-06-10.html