Commit e431ff7
新增用户活动追踪器(main_logic/activity/),重构主动搭话 Phase 2 决策 (#1015)
* 新增 main_logic/activity/ 用户活动追踪器,把窗口/进程/CPU/idle/GPU/语音/对话信号聚合成结构化 ActivitySnapshot 注入 proactive_chat Phase 2 prompt,解决 pending reflection 几乎不被采用、AI 两极化打断/沉默、对话连贯性差三个老问题
主要内容:
- config/activity_keywords.py:943 titles + 692 processes + 518 domains 关键词库,EN/简/繁/JP/KR 多语言别名 + 词边界匹配防短缩写撞普通词
- 9 状态规则机(gaming / focused_work / casual_browsing / chatting / voice_engaged / idle / transitioning / stale_returning / away)+ unfinished_thread 5min 跟进窗口(最多 2 次)+ GPU 兜底未识别游戏
- emotion-tier LLM 增强:soft scores + 活动叙述 + 语义 open_threads 检测,三者均为 advisory,规则路径仍为 propensity 决策权威
- reminiscence 注册为 source channel + 配套 weight 衰减;focused_work / gaming 状态自动 bypass Phase 1 unified LLM 调用
- Phase 2 prompt 5 lang 重构:A/B/C/D 决策框架替代 9 条规则墙,结构性 token 开销 ~700 → ~370(47% off)
- 远程部署降级:自动检测平台 + NEKO_ACTIVITY_TRACKER_REMOTE 环境变量强制降级 + 前端推信号 API(push_external_system_signal)预留
- 设计文档 docs/design/user-activity-tracker.md(650 行)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* 按 github-code-quality bot 反馈修 system_signals.py 的 5 处代码质量问题:
- start() 里 psutil prime 失败的 except 加 logger.debug 和注释,不再裸 pass
- stop() 里 task await 异常拆分为 CancelledError(预期)和 Exception(异常路径打 log)
- _read_idle_seconds / _read_active_process_name 里的 ctypes 导入统一成 'from ctypes import ...' 单一风格,避免同作用域里同时 import + from import 的 lint warning
无功能变化,仅风格 / 可观测性。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* 按 codex bot 反馈修两处 race / 副作用问题
- P1 (main_logic/core.py): handle_input_transcript 仅在 transcript.strip() 非空时
才调 on_user_message。空 transcript(VAD 误触发或转录失败)不应当成"用户消息"
处理——之前会清掉 unfinished_thread、bump _user_msg_seq(让 open_threads 缓存
失效)、往 buffer 加空条目。on_voice_rms 仍无条件调用,维持 voice_engaged 状态。
- P2 (main_routers/system_router.py): Phase 2 渲染 state_section 前重拉 tracker
当前缓存的 enrichment 字段(activity_scores / activity_guess / open_threads)。
kickoff_open_threads_compute 是 Phase 1 起点 fire-and-forget 跑的,结果会在
Phase 1 中途陆续落到缓存——早期捕获的 activity_snapshot 看不到这些更新,
早先版本要等下一轮 proactive 才能用上。决策性字段(state / propensity /
unfinished_thread)仍取自早期 snapshot,避免 Phase 1 中途状态变化导致
gating 不一致。
P1 已加 smoke 验证:空 / 纯空格 transcript 不动 unfinished_thread / seq;
真实 transcript 正常清除并递增。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* 按 CI lint + coderabbit 反馈修若干 bug 与代码质量问题
CI 阻塞修复:
- 新建 config/prompts_activity.py 收纳 ACTIVITY_GUESS_PROMPTS / OPEN_THREADS_PROMPTS / OS_DEGRADED_MARKER 三个多语言 dict(按 prompt-hygiene 约定多语言 str→str 表必须放 config/prompts_*.py),llm_enrichment.py 与 snapshot.py 改为 import 使用
bug 修复:
- finish_proactive_delivery 没 flush _current_ai_turn_text 给 tracker。/api/proactive_chat 成功路径不走 _emit_turn_end / handle_proactive_complete,buffer 会污染下一轮用户消息。在 lock 里 send_lanlan_response 之后立即 on_ai_message + 清 buffer
- _dispatch_openclaw_handoff 通过 handle_input_transcript 复用文本——会让 on_voice_rms 假触发 voice_engaged,且 on_user_message 在文本入口已调过一次再调一次会双计 _conv_seq + 重复进 buffer。给 handle_input_transcript 加 is_voice_source 参数,handoff 传 False 时跳过 tracker hooks(保留 queue/cache 主流程)
- open_threads 缓存只盯 _user_msg_seq,AI 新开的线程(promise / 半截话)触发不到重算。改 _user_msg_seq → _conv_seq,on_user_message 与 on_ai_message 都 bump
- get_snapshot_sync 直接读 _collector.snapshot(),绕过 _select_system_snapshot 选外部信号的逻辑——远端部署同步路径会拿到服务器侧降级信号。改走 _select_system_snapshot 与 async 路径一致
- reminiscence 重复写 _proactive_chat_history(用 'reminiscence' channel)会污染 _format_recent_proactive_chats / _is_similar_to_recent_proactive_chat 两条 dedup/相似度链路。新建独立 _reminiscence_usage_history buffer + _record_reminiscence_usage 工具函数;_compute_source_weights 单独读这个 buffer 把 reminiscence 当 channel 衰减,不再污染共享 history
- Phase 2 prompt 5 lang 决策框架里写死 [CHAT] 标签与 get_proactive_format_sections 在无外部素材时输出格式段("直接输出正文,不需要来源标签")冲突。改成不指定具体 tag,由下方 {output_format_section} 决定输出形式
文档:
- 顶部 Status 改为 "v1 (rules-primary, LLM advisory)",避免和后面 LLM enrichment 章节自相矛盾
- 接线点章节修正:on_user_message 来自 handle_input_transcript(语音)+ _process_stream_data_internal(文本),不是 handle_new_message;on_ai_message 来自 _emit_turn_end / handle_proactive_complete / finish_proactive_delivery 三处
E2E smoke 全过:empty transcript 不再误清 unfinished_thread / 不 bump _conv_seq;handoff 路径不再误触发 voice_engaged;AI 消息现在能 bump _conv_seq 让 open_threads 重算;prompt-hygiene + ruff + 其他 4 个 lint 全 pass
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* 按 coderabbit 反馈再修两处
- main_logic/activity/tracker.py _do_open_threads_compute:加 in-flight 守卫。
LLM 调用期间(1-3s)用户/AI 可能发新消息推进 _conv_seq;老 task 完成时
原本无脑覆盖缓存——本轮 prompt 看到的是 stale 结果。改成 LLM 返回后比对
seen_seq vs 当前 _conv_seq,不一致就丢弃结果(不更新 _open_threads_computed_at_seq),
下次 kickoff 看到 seq mismatch 自动重算。
- main_logic/core.py 抽 _flush_ai_turn_text_to_tracker helper:三个出口
(_emit_turn_end / handle_proactive_complete / finish_proactive_delivery) 共用,
消除 DRY。语义不变。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* 按 coderabbit 反馈再修一处真 bug + 三处文档错配
- main_logic/activity/tracker.py _activity_guess 后台 loop:和 _do_open_threads_compute 一样,给 LLM 调用加 in-flight guard。先捕获 seen_conv_seq + buffer 快照,await 后比对 _conv_seq;不一致就丢弃结果,不更新 last_conv_seq。否则在 LLM 调用期间用户/AI 发新消息会让旧上下文的 result 把 last_conv_seq 推到新值,下次 tick 看到 seq 没动会被错误跳过。
- docs/design/user-activity-tracker.md:
- 架构图里 propensity == screen_only 改为 restricted_screen_only(之前是错的枚举值)
- open_threads 缓存失效条件从 "_user_msg_seq" 更新到 "_conv_seq + AI 侧也 bump + in-flight guard",反映当前实际语义
- Phase 2 快照流程改为"双层快照"准确描述:早期 snapshot 用于 gating,渲染前 fresh-fetch 用 dataclasses.replace 拼 enrichment 字段。原来的"only get_snapshot once"已经过时
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* 按 coderabbit 反馈给 doc 5 处 fenced code block 加 text 语言标识
markdownlint MD040:line 102/128/288/311/325 都是无 language tag 的
``` 代码块,markdownlint-cli2 会重复报 warning。改成 ```text(5 处都是
file tree / dataflow 图 / snapshot 例子,不是真实可执行代码,统一 text 即可)。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* 按 coderabbit 反馈再修两处文档
- "Future work" 段时态错配:open_threads / activity_guess / 整段 emotion-tier
接入都已经在当前 PR 上线,原文写成"v2 will fold"/"will enter"会让读者误判
当前能力边界。改成两条 v2 quality upgrade(recall/排序、稳定性/cost),
以及把"emotion-tier finally enters"改成"already integrated"。
- _dispatch_openclaw_handoff 接线点说明:原文只说"without re-firing tracker
hooks"被 coderabbit 误读为只 skip on_voice_rms。补 WHY:on_voice_rms 是
voice-only 在文本路径下会误判 voice_engaged;on_user_message 也跳过是因为
上一步 _process_stream_data_internal 已经 on_user_message(text=data) 调过
同一份 payload,再调会 double-bump _conv_seq + 重复进 buffer。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* 按 review 反馈:把 activity 模块剩余 4 张嵌套 i18n 表搬进 config/prompts_activity.py
snapshot.py 里原本以 _STATE_LABELS / _PROPENSITY_DIRECTIVES / _REASON_TEMPLATES /
_STATE_SECTION_LABELS 形式存放的 5 语言嵌套 dict,规模虽然小但仍是 i18n 内容,
应按项目约定全部落在 config/prompts_*.py,方便后续扩展语言时一次 grep config/
就能补全。lint (check_prompt_hygiene.py) 只扫平铺多语言 dict,嵌套结构需要按
约定自觉搬。
同时更新 .agent/rules/neko-guide.md 把这条规则显式写出来,避免下次再遗漏。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* 按 review 反馈:清理 activity 进程名列表的跨池重复,加 import-time 去重断言
背景:state_machine 设计是"启动器(subcategory='launcher')不触发 gaming 状态"
(逛 Steam 商店 ≠ 在玩游戏,见 state_machine.py:506-510),但若同一个 .exe 既
出现在 GAME_PROCESS_NAMES 又出现在 GAME_LAUNCHER_PROCESS_NAMES,_build_process
_table 先迭代前者,需求被静默绕过。审查发现 6 处这种 launcher 误入 game pool
的情况,再用断言扫了一遍发现还有更多跨池冲突和池内 case-fold 冗余。
修改:
A. game ↔ launcher 跨池冲突(6 项)—— 这些都是启动器,从 GAME_PROCESS_NAMES
删除,仅保留在 GAME_LAUNCHER_PROCESS_NAMES:
- wegame.exe / WeGameLauncher.exe (Tencent WeGame)
- TenioDL.exe (Tencent 下载器)
- Battle.net.exe (Blizzard 启动器)
- ffxivlauncher.exe (FFXIV 启动器;ffxivboot.exe 留下,是游戏内 boot loader)
- NGM.exe (Nexon Game Manager)
B. 其他跨池冲突 —— 一个 exe 名属于一个 category:
- Origin.exe: EA Origin(launcher)/ OriginLab(work)共用同名是历史问题。
EA Origin 用户面更广,从 WORK_PROCESS_NAMES 删除;OriginLab 用户的现代
64 位版 Origin64.exe 仍归 work。
- OUTLOOK.EXE: 邮件 = communication,不是 office,从 WORK 删除(COMM 已有)。
- sumatrapdf.exe: PDF 阅读器主用途是工作文档,从 ENT 删除(WORK 已有)。
- discord/telegram/whatsapp/line/kakaotalk: IM 应用归 communication,从 ENT
删除(COMM 已有)。
- steam.exe / steamwebhelper.exe: 启动器归 launcher pool,从 ENT 删除。
C. 池内 case-fold 冗余 —— _make_needle 已 lowercase,重复列只会让查找表变胖:
- GAME: re4.exe/RE4.exe → re4.exe;re8.exe/RE8.exe → re8.exe
- LAUNCHER: Launcher.exe + launcher.exe → 单条
- WORK: OneNote/ONENOTE、Lightroom/lightroom、PaintDotNet/paintdotnet、
CINEMA 4D/Cinema 4D、MATLAB/matlab → 各保留单条
D. 加 _assert_no_process_dups(),模块 import 时调用,覆盖跨池 + 池内两种情况。
下次有 PR 误把 launcher 塞进 game pool(或诸如 outlook.exe 误入 work)时,
立即在 CI / 启动期抛 AssertionError,不再静默错分类。
实测:清理后 game=218 / launcher=41 / work=265 / comm=96 / ent=48,断言通过;
合成回归测试(往 GAME_PROCESS_NAMES 注入 'steam.exe')能正确抛出。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* 按 review 反馈:同步 activity tracker 设计文档与最新代码
去重清理(commit 9ebeedf)后,关键词库的池规模变化没有同步到文档:
- 总 title 行数 943 → 965(先前数错)
- 进程名(不含 launcher 池)692 → 627(去重 + 重新统计)
- launcher 进程 64 → 41(先前数错;和实际不符)
同时给"Extending the keyword library"章节补一段 dedup 不变量说明:
- 一个 exe 只能在五个进程池里出现一次(含 case-fold)
- 解释为什么这一约束是真的需求(_build_process_table 的优先级 +
state_machine.py:506 的 launcher 豁免会被绕过)
- 指向 _assert_no_process_dups() 作为 import-time 守门
- 给 Origin.exe 这种"同名不同产品"的特殊处理案例
源码侧把"=== GAMES (... titles / ... processes / ... launchers) ==="
区段头也校正到当前值。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* 按 review 反馈:Phase 2 prompt 决策段加水印 + 三条 instruction 格式统一 + external 瘦身
A. 决策段加水印(5 langs)
将 "向{master_name}搭话决策:" 改成 "======以下为向{master_name}进行搭话的
决策方式======",{meme_instruction} 之后加 "======以上为向{master_name}进行
搭话的决策方式======" 配对水印,与该文件其他 system prompt 段落统一
(======以下为对话历史====== 等模板)。en/ja/ko/ru 同步对应翻译。
B. 三条 instruction 格式统一为 bullet(5 langs)
原状态:source_instruction = "- 你可以结合..."(bullet),
music_instruction = "10. 关于音乐:..."(编号 10,但前面只有 1-5),
meme_instruction = "11. 关于表情包:..."(编号 11,同样断层)。
风格断裂感很强。改成:
补充:
- 重复判定:...
- 风格:...
- source bullet
- 关于音乐:... (有素材时)
- 关于表情包:... (有素材时)
prompt 模板里去掉 {source}{music}{meme} 之间的 "\n",改为
{source_instruction}{music_instruction}{meme_instruction};music/meme 值
自带前导 "\n" 当存在、空字符串当不存在——所以三种组合(4/3/0 个素材)
都不出多余空行。en 走 "Additional rules:",ja 走 "補足:",ko 走
"보조 규칙:",ru 走 "Дополнительно:"。
C. external section 瘦身
1) EXTERNAL_TOPIC_HEADER 去掉 "你注意到一个有趣的话题:" 前导行(5 langs),
与 SCREEN_SECTION_HEADER / MUSIC_SECTION_HEADER / MEME_SECTION_HEADER 对齐——
原前导行是 external 还是主信号源时的叙事框架,现在 vision/music/meme/
reminiscence 平行存在,不对称的口吻浪费 token 也显得别扭。
2) PROACTIVE_PHASE1_TOTAL_TOPICS: 20 → 12。早期 external 是主通道,候选池
开得很大;现在 Phase 2 多通道并行后 external 相对权重下降,多看 8 条
边际候选无助于 Phase 1 挑出更好的 top-1,反而把单次 LLM call 的 prompt
推向 2k 上限。
3) PROACTIVE_EXTERNAL_TOTAL_MAX_TOKENS: 2000 → 1500(同步收紧 Phase 1 拼合
兜底;12 × 200 = 2400 max 仍兜底截断到 1500,留 ~250 token 富余)。
4) docs/design/llm-prompt-budget.md 的对应表格同步到新值。
Phase 2 generate prompt 实测:4 种素材组合渲染均干净(无多余空行/编号断层),
水印对所有 5 langs 闭合。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* 按 review 反馈:水印 ====== 数量统一为 6 + 修正前导空格 + 音乐/表情包补充加搭话条件前缀
A. 水印等号数量统一为 6(5 langs 全覆盖)
- config/prompts_activity.py 的活动状态段从 ===活动状态=== / ===状态结束===
升级到 ======活动状态====== / ======状态结束======(zh/en/ja/ko/ru
均改),与 config/prompts_*.py 其他段落保持视觉一致。
- config/prompts_memory.py 和 config/prompts_proactive.py 的
MEMORY_RECALL_HEADER / MEMORY_RESULTS_HEADER 把右侧 5 个等号补成 6
个;前者原本写成 "======...=====" 的非对称水印,是历史遗漏。
B. 移除多余前导空格
- 英文版 Phase 2 prompt 里 "★ When the activity state lists..." 的
第二行带 2 空格缩进,把整句合并成单行(其他 4 语言本来就是单行)。
C. 音乐 / 表情包补充指令加 "当你决定结合 xxx 进行搭话时" 前缀(5 langs)
- 原本 _P2_MUSIC_INSTRUCTION 以 "如果提供了音乐素材..." 起头是段
条件描述,但 instruction 本身就是仅在 has_music=True 时注入,
"如果提供了" 是冗余条件。改成 "当你决定结合音乐推荐进行搭话时"
直接告诉模型这条规则在它选 [MUSIC] 输出时才适用,语义更清晰。
- _P2_MEME_INSTRUCTION 同步改为 "当你决定结合表情包进行搭话时,系统
会自动发送...",主语回到 AI 决策。
- en: "When you decide to combine ... with your message, ..."
- ja: "音楽のおすすめを取り入れて話しかけると決めたとき" /
"ミームを取り入れて話しかけると決めたとき"
- ko: "음악 추천을 결합하여 말을 걸기로 결정했을 때" /
"밈을 결합하여 말을 걸기로 결정했을 때"
- ru: "когда вы решаете включить музыкальную рекомендацию..." /
"когда вы решаете включить мем..."
prompt-hygiene lint 通过;4 种素材组合(max / mid / min)下决策段渲染干净,
无空行断层。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* 按 review 反馈对齐 Phase 2 规则措辞与回忆线索 header(仅 zh 一处错字)
CodeRabbit 指出 Phase 2 的 "记忆线索 / Memory cues" 与实际 prompt 渲染对不上;
我去 trace 了一下,CodeRabbit 给的修复方向(改 MEMORY_RECALL_HEADER /
MEMORY_RESULTS_HEADER)实际上方向错了——那两个常量只在 /search_for_memory/
里用,而该端点 docstring 直接写着 "语义记忆已下线,返回空结果占位",proactive
Phase 2 根本不走那条路径。
实际链路:proactive Phase 2 拿 memory_context 走 /new_dialog/ 端点,里头是
PERSONA_HEADER + INNER_THOUGHTS_HEADER + 原始 recent dialog 行;老话题
followup 由 PROACTIVE_FOLLOWUP_HEADER(标签是 [回忆线索] / [Memory cues] /
[記憶の手がかり] / [기억 단서] / [Подсказки памяти])独立拼上去。
en/ja/ko 的规则措辞和 header label 已经精确一致;ru 的 "Тема из «Подсказок
памяти»" 是规则一致的属格变形,俄语模型可识别。唯一真正的错配是 zh:规则
写 "记忆线索"("记"),实际 header 是 "[回忆线索]"("回")——一个字之差,
看起来是早期手抖。改规则那侧对齐到 "回忆线索"。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* 按 review 反馈:决策段水印 5 语言统一为中文 + 外部话题→网络话题
A. 决策段水印 5 语言统一中文(per .agent/rules/neko-guide.md 约定)
"翻译 system prompt 时即使出于其他原因也应当保留 ====== 这是水印"——前
一版我把 en/ja/ko/ru 各自翻译了水印文字,违反了水印不变性。改回所有
语言都用中文 "======以下为向{master_name}进行搭话的决策方式======" /
"======以上为向{master_name}进行搭话的决策方式======",水印只是结构
标记,不参与本地化。决策内部内容仍按各语种翻译。
B. EXTERNAL_TOPIC_HEADER / FOOTER 5 langs 改名 外部话题 → 网络话题
- zh: 外部话题 → 网络话题
- en: External Topic → Web Topic
- ja: 外部の話題 → ウェブ話題
- ko: 외부 주제 → 웹 화제
- ru: Внешняя тема → Веб-тема
理由:该 channel 实际只装 web sources(news / video / social),prompt
别处又把 vision / music / meme 也归类为 "external material",光说
"外部话题" 容易和它们混。renamed 为 web 直接对应通道实际抓取范围。
C. _material_labels (5 langs) + system_router.py 两处 dev 注释同步改名。
_material_labels 决定 source_instruction 里 "你可以结合 X、Y 来搭话"
的素材列表表述,跟着 header 改才对得上。
注意 1-5 优先级里的 "外部素材" / "external material" 文案保持不变——
那是包含 web + music + meme 的总称,不是单指网络话题这一个 channel。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* 按 review 反馈修日文 priority #1 用词错误:未収 → 未完
CodeRabbit 抓到 ja 优先级列表第 1 条 "前回未収のスレッド → 継続",未収 是
"未收/未回收"(如"料金未収"),不是 unfinished thread 的语义。该条要表达的
是"上轮挂着没收尾的话题",正确用词是 未完,正好和上面 ★ 行 "未完話題" 对齐。
改成 "前回の未完スレッド → 継続"。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* 删除 prompts_proactive.py 里的死复制 MEMORY_RECALL_HEADER / MEMORY_RESULTS_HEADER
这两个常量在 prompts_proactive.py:2484-2498 与 prompts_memory.py:719-733 完全
重复,且 prompts_proactive.py 这份零 importer——memory_server.py 只 from
config.prompts_memory import 那份。属于"配置/复制粘贴未清理"的死代码。
不动 prompts_memory.py 那份和 memory_server.py 的 /search_for_memory/ 端点;
那条链路是 plugin SDK 的 query_memory capability 唯一后端,给未来插件开发者
保留(哪怕当前 returns "语义记忆已下线" 占位也不是这次清理的范围)。
发现起源:proactive Phase 2 prompt 对偶性盘点(======X======/======X 结束======
配对)。该文件其他对偶性都对得齐:5 对 section header/footer、CONTEXT_SUMMARY_TASK
header/footer 都齐;CONTEXT_SUMMARY_READY / SYSTEM_NOTIFICATION_TASKS_DONE 是
自封闭 banner,AGENT_PLUGINS / AGENT_TASKS 走【...】内联标签风格——这三类故意
不配 footer。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* 按 review 反馈修 snapshot.py docstring 里 ===活动状态=== 仍是三等号的过时 example
format_activity_state_section 的 docstring layout example 还在用 ===活动状态=== /
===状态结束===,是 cbd59e0 把实际标签从三等号升级到六等号时漏改的。仅文档字符
串,不影响任何运行时行为;修齐使读者对得上实际渲染。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* 按 review 反馈给 _REMINISCENCE_USAGE_MAX = 50 加 calibration 注释
CodeRabbit 提示这个魔术数字应该解释或合并。trace 后选择"加注释"路径——
不能合 PROACTIVE_CHAT_HISTORY_MAX(=10),两个 buffer 服务相反的 sizing 约束:
- chat_history.maxlen=10 限的是去重内存,10 条足够
- reminiscence.maxlen=50 限的是衰减信号完整性,必须 > _SOURCE_WEIGHT_WINDOW
(1h) 内最坏使用次数,否则 _compute_source_weights 的指数和会 under-count
也不放进 config/__init__.py—— 跟同 module 已有的 _SOURCE_WEIGHT_* 私有常量
(_DECAY_LAMBDA / _K / _FLOOR / _WINDOW)保持风格一致:这一组都是 weight 模型
calibration,不是用户面向的 config knob,跟模型一起调才有意义。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* 按 review 反馈修 unfinished-thread 配额错误消耗 (CodeRabbit L4197)
原逻辑:本轮 snapshot 里有 _has_unfinished_thread 就 mark_used,无视 AI 实际
是否选了 [CHAT] 路径。原 comment 写"防 AI 反复忽略 override 烧光配额",但
trace 后发现这个理由站不住——UNFINISHED_THREAD_WINDOW_SECONDS=300 的自动过期
已经兜底了 thread 总暴露时间,旧逻辑实际上让两次选了外部素材的 proactive 轮
就能把"最多跟进 2 次"的配额提前烧光,真正想续接时 thread 已被 tracker 停止
暴露。
新逻辑:仅当 source_tag == 'CHAT'(或 build_proactive_response 落到 chat 通道)
才 mark_used。[PASS] 已在 4079 早 return,到这里 source_tag 只可能是
CHAT/WEB/MUSIC/MEME,分支干净。CHAT 也不严格等于"用了 unfinished thread
override"——AI 可能用 CHAT 走 priority 2-3(回忆线索 / 屏幕评论)——但这是当前
最接近的可靠信号,也比"无脑曝光即计数"接近真实意图。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* 按 review 反馈补 unfinished-thread 配额漏算路径 (CodeRabbit L4201)
CodeRabbit 指出 8cde3c8 的修复在 source_tag 为空串时仍然漏掉 mark_used。
trace 确认:source_tag 只在 L4004-4008 regex 命中 [CHAT|WEB|PASS|MUSIC|MEME]
时才赋值,而 _of_none output_format_section 明确告诉 AI"不带 source tag"
(无外部素材时的合法输出路径)。这种情况下 AI 真在跟进 unfinished thread
但 source_tag="" / primary_channel='unknown',新条件 (source_tag == 'CHAT'
or primary_channel == 'chat') 不命中,配额被静默绕过,只剩 5 min 自动过期兜底。
修复:在 abort early return 之后、build_proactive_response 之前补一个兜底——
非 abort 且 full_text.strip() 非空意味着 Phase 2 实际产出了文本,无标签时按
CHAT 处理。这样下游 primary_channel 也变 'chat',mark_used 条件正常命中。
对照 music-cooldown 分支 (L4109) 已有的 source_tag = 'CHAT' 降级先例,语义一致。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Hongzhi Wen <cartabio.coder1@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>1 parent b14079f commit e431ff7
16 files changed
Lines changed: 7124 additions & 215 deletions
File tree
- .agent/rules
- config
- docs/design
- main_logic
- activity
- main_routers
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
| 10 | + | |
10 | 11 | | |
11 | 12 | | |
12 | 13 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1139 | 1139 | | |
1140 | 1140 | | |
1141 | 1141 | | |
1142 | | - | |
| 1142 | + | |
1143 | 1143 | | |
1144 | 1144 | | |
1145 | | - | |
| 1145 | + | |
| 1146 | + | |
| 1147 | + | |
| 1148 | + | |
| 1149 | + | |
| 1150 | + | |
1146 | 1151 | | |
1147 | 1152 | | |
1148 | 1153 | | |
| |||
1152 | 1157 | | |
1153 | 1158 | | |
1154 | 1159 | | |
1155 | | - | |
1156 | | - | |
| 1160 | + | |
| 1161 | + | |
1157 | 1162 | | |
1158 | 1163 | | |
1159 | | - | |
| 1164 | + | |
| 1165 | + | |
| 1166 | + | |
| 1167 | + | |
| 1168 | + | |
1160 | 1169 | | |
1161 | 1170 | | |
1162 | 1171 | | |
| |||
Large diffs are not rendered by default.
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
717 | 717 | | |
718 | 718 | | |
719 | 719 | | |
720 | | - | |
721 | | - | |
722 | | - | |
723 | | - | |
724 | | - | |
| 720 | + | |
| 721 | + | |
| 722 | + | |
| 723 | + | |
| 724 | + | |
725 | 725 | | |
726 | 726 | | |
727 | 727 | | |
728 | | - | |
729 | | - | |
730 | | - | |
731 | | - | |
732 | | - | |
| 728 | + | |
| 729 | + | |
| 730 | + | |
| 731 | + | |
| 732 | + | |
733 | 733 | | |
734 | 734 | | |
735 | 735 | | |
| |||
742 | 742 | | |
743 | 743 | | |
744 | 744 | | |
| 745 | + | |
| 746 | + | |
| 747 | + | |
745 | 748 | | |
746 | | - | |
747 | | - | |
748 | | - | |
749 | | - | |
750 | | - | |
| 749 | + | |
| 750 | + | |
| 751 | + | |
| 752 | + | |
| 753 | + | |
751 | 754 | | |
752 | 755 | | |
753 | 756 | | |
| |||
0 commit comments