wechat-auto-reply-requirements.md 13.2 KB

微信自动回复 · 需求文档

日期:2026-07-09 设备:Pixel(59171FDCG0009N),微信 8.0.74,LSPosed + Termux + Hermes Agent 定位:Pixel 上的 Hermes Agent 自动处理"别人发给小火子的微信"——私聊 / 群里 @ / 群里引用——判断要不要回、怎么回、自动发还是人工审核。


0. 目标

有人给小火子发微信(私聊、群 @我、群引用我)时,Hermes 能:

  1. 接收到这条消息
  2. 读取会话上下文
  3. 基于上下文 + SOUL.md 生成回复
  4. 判断"直接发"还是"进人工审核队列"
  5. 发送回复(含微信原生引用气泡)

约束:尽量低电量 / 低资源;纯端上(Hermes 在设备 Termux 内跑,模型走 Kimi 或本地 llama-swap)。


1. 设备现状(建这套的地基,2026-07 核对过)

能力 现状 备注
微信 DB 解密读 ✅ 可用 主账号目录 ec4f423b316d41e9af5e8398155cf105,key 1986554(MD5("1234567890ABCDEF"+uin257966415)[:7]);参数 cipher_compatibility=1+page_size=1024。skill ~/.hermes/skills/wechat-db-decrypt/ 已就位
发送文本 ✅ 可用 LSPosed 内部 API(am broadcast com.hermes.wechat.SEND_MSG);WeChatHook.java 已打通文本/语音/图片
发送带原生引用气泡 ❌ 未实现 见环节 5
通知接收 待搭 NotificationListenerService / dumpsys,见环节 1
lifelog 人物库 明文 SQLite com.lemon.lifelog/databases/lifelog.db(4.8MB,Person 表),二期打通
SOUL.md ✅ 存在 ~/.hermes/SOUL.md(Hermes 人格)

⚠️ 过时提醒:旧文档 md/wechat-auto-reply-status.md(2026-05-23)里的 key e03ce81 / 目录 653cdc… 是次账号,已不是当前主账号;旧文档主推的 Frida daemon 发送已废弃(常驻注入微信触发反调试崩溃)。以本文件为准。


2. 五环节需求

环节 1 · 接收通知 —— ✅ 已实现并真机验证(2026-07-09)

落地:wechat_plain/scripts/auto-reply/wechat_reply_watcher.py(设备 ~/.hermes/wechat_reply/)。

  • 触发:纯 ctypes inotify 监听当前账号 DB 目录(SELinux 放行,零轮询);建 watch 失败自动退回 stat mtime 轮询。真机验证 [inotify] +N 事件驱动读取成立。
  • 账号自适应:运行时 MD5("mm"+uin) 定位当前登录账号(验证时账号已从 火星人 切到 小火子,daemon 自动跟上,无硬编码)。
  • 增量+去重+基线:持久化 last_msgId;只 isSend=0 且 msgId>last;首跑基线=当前最大 msgId,不重放历史。
  • 分类:private / group_at_me / group_quote_me / group_other;群消息剥 wxid:\n 前缀取 sender+body;自身 wxid/昵称从 userinfo 自动检测。
  • 输出:incoming.jsonl 事件队列(供环节 2-5 消费)。自检 --selftest、单读 --once、--tail N。
  • 常驻自启(已接):wechat_reply_daemon.sh 监督器(root + Termux 环境、单实例锁、等微信登录、看门狗崩溃重启);/data/adb/service.d/wechat-reply-watcher.sh 开机拉起。真机验证:纯 root(boot 上下文) selftest 通过、单实例防重、杀 watcher 自动重启。已在设备常驻运行。
  • 待接:env4 里过滤 gh_ 公众号与系统消息(撤回/拍一拍);daemon.log 无限增长需加轮转(ponytail:先不做,24/7 下每天几 MB)。

原始设计记录:

(原)环节 1 · 接收通知 —— 事件驱动,非定时轮询

根因:费电的不是 cron 循环,是"每次轮询都 su 拷 90MB 的 EnMicroMsg.db"。定 5s 紧循环拷 90MB → 电量扛不住。

  • 主触发:常驻 NotificationListenerService。系统主动 push,空闲近零成本;微信来消息才被唤醒 → 这时才读库。没消息 = 不动。
  • 兜底:Hermes cron 每几分钟扫一遍,捞免打扰 / 漏掉的消息。不要跑紧循环。
  • 读库:优先只读 in-place 打开(immutable);锁住再拷。因为改成事件驱动,拷库只在真有消息时发生,频率极低,可接受。
  • 消息源:主账号 ec4f…/EnMicroMsg.db 的 message 表,WHERE isSend=0 AND msgId>last_processed_id。
  • 分流(本环节难点):
    • 私聊:直接是一条
    • 群 @我:群消息 content 前缀 wxid:\n,需匹配我的昵称判 @
    • 群引用我:看 refermsg 是否指向我的 svrid

环节 2 · 读取上下文 —— ✅ 不写模块,走 skill 配方(2026-07-09)

结论:Hermes 是有 shell+SQL 的 agent,已有 wechat-db-decrypt skill,环节2 无需新代码。

  • 给 skill 补了「读会话上下文」一节(~/.hermes/skills/wechat-db-decrypt/SKILL.md,仓库留档 wechat_plain/scripts/auto-reply/skill/):一次拷库查齐 ①发送者身份 rcontact(显示名优先级 conRemark>nickname>alias)②最近 N 条 message 历史(剥群 wxid: 前缀、type→占位符、只喂文本)③引用 refermsg 解析 + 群名;末尾给出喂 env3 的上下文骨架。
  • 真机验证配方查询产出干净上下文(身份+时间正序历史)。
  • 架构澄清:环节 3(生成)= Hermes 这个 LLM 本身;环节 4(策略)= 它的推理;只有环节 4/5 的「审核队列 + viewer 接发送」还需写代码。
  • 二期加分:回链 lifelog Person 库做人物增强。一期不做。

环节 3 · 生成回复 —— ✅ 不写代码,Hermes CLI 生成,真机验证质量(2026-07-09)

  • 生成 = Hermes 本身(后端 deepseek-v4-pro,经 gateway)。调用走 hermes -z "<prompt>" 一次性(creds 已接好);不要直接打裸端点 127.0.0.1:8642(要鉴权,401)。
  • SOUL.md 已改为「火星人的手机助手」人设(原文备份 ~/.hermes/SOUL.md.bak.20260709_234432,仓库留档 wechat_plain/scripts/auto-reply/SOUL.md):直接/高效 + 替火星人回微信时用他口吻(短、口语、不客套)+ 涉钱/承诺/约时间交本人确认。
  • 端到端质量验证(真实上下文,只生成不发):
    • 陌生猎头冷开发 → 暂不考虑,谢谢(得体礼貌回绝)
    • 熟人约饭 → 可以啊,几点?在哪吃(热络自然接话)
    • 都在火星人口吻上,非 AI 助手长篇。
  • ⚠️ 观察:SOUL.md 那句「约时间交本人确认」是软约束,约饭案例里模型仍主动接话——真正的自动/审核安全门必须落在环节4 策略层,不能只靠人设。
  • 待办:一次结构化调用同时出 {should_reply, confidence, draft, risk_flags} 留给环节4 一起做。

环节 4 · 自动 vs 审核 —— ✅ 确定性策略引擎 + viewer 审批/白名单 UI(2026-07-10)

  • 策略引擎(代码,非 LLM 判)wechat_plain/scripts/auto-reply/wechat_reply_policy.py:decide(event,draft,confidence) → skip|auto|review。default-deny + 硬安全 override:群未@我/公众号/系统消息/自己发的/模型说别回 → skip;命中敏感词(钱/承诺/约时间/隐私,白名单也拉回)或低置信 → review;仅白名单+总开关开 → auto;其余 → review。demo 8/8 通过。
  • 配置 + 审批 UI 落在手机端 ~/wechat-viewer(Flask),非独立文件(详见 wechat-viewer-changes.md):
    • 新表 reply_whitelist + reply_queue(local_db.py)。
    • /whitelist 白名单管理页;/approvals 审批页(待审列表 + 可编辑草稿 + 【发送】【取消】)。
    • 发送复用 send_text_via_hook(LSPosed 内部 API)。
    • 真机验证:白名单增删查、审批发送到 filehelper(status=sent)、取消(cancelled) 全通。
  • ⚠️ 印证环节3 的发现:安全门是确定性代码(敏感词硬拦),不靠人设软约束。
  • 待接:编排器把环节1 队列串到策略+队列(见环节5)。

(原)环节 4 · 自动 vs 审核 —— 白名单直发,其余进 viewer 待回复

  • 白名单(联系人 / 群 / 群里某发言人)+ 生成成功 → 直接发送。
  • 非白名单 → 写入 Pixel 本地 wechat viewer 的"待回复列表",人工点击"发送"才真正发出。
  • 落地新增:①「待回复队列」存储(sqlite/json);② wechat viewer 的"发送"按钮接到发送链路。
  • 安全红线(贯穿):涉钱 / 承诺 / 约时间 / 低置信 → 即使白名单也建议进审核。

编排器 · 把 1→5 串起来 —— ✅ 已实现并真机验证(2026-07-10)

wechat_plain/scripts/auto-reply/wechat_reply_orchestrator.py(设备 ~/.hermes/wechat_reply/):

  • watcher 读到新消息 → 写 incoming.jsonl → fire-and-forget 触发 orchestrator --drain(watcher _trigger_orchestrator,独立进程不阻塞采集)。
  • drain(非阻塞单实例锁 + cursor 增量 + 首跑基线):对候选事件(私聊/群@我/群引用我)→ env2 查上下文(rcontact 身份 + 最近历史,一次拷库全批复用)→ env3 hermes -z 生成 → env4 policy.decide()(白名单读 circle.db reply_whitelist)→ env5 落地:auto → send_text_via_hook + reply_queue(auto_sent) / review → reply_queue(pending)。
  • 安全:生成超时/失败 → 无草稿 → policy skip → 不发(失败即静默,绝不误发)。
  • 真机验证:接线(watcher inotify → 自动拉起编排器 drain)通过;真实上下文(猎头 婷婷)生成「哈哈婷婷,我不是什么总,就是个写代码的,不负责招人这块哈」→ 正确进 /approvals 待审。

环节 5 · 可靠发送 —— ✅ 打通并端到端验证(2026-07-11 打通 / 07-12 加固+闭环)

经历一场硬仗才让 API 发送真正送达。两个坑 + 一轮 flush 加固:

  1. 中文被弄乱:send_text_via_hook 用 --es text 传中文被 shell/am 弄乱→服务器拒收→卡 status=1。hook 早已要求 --es text_b64,发送端漏改。→ 改用 text_b64。
  2. z2.b 只入队不派发(设备机制,非代码 bug):sendViaHighLevel 的 z2.b 把消息入队(status=1 转圈),WeChat NetSceneQueue 要一次真实的 app 切换(切走→切回微信=onPause→onResume)才派发。发送代码跨版本逐字节相同,不是回归。
  3. flush 加固(2026-07-12):单次切换+被动等常卡几十秒~分钟。三个经验值(_send_text_verified):① 发送后 sleep(4) 让消息稳稳落库(切太快微信没登记这条)② 切换给足时间(HOME 背景 2.5s → am start LauncherUI 切回停 3s)③ 切回后停前台连查多轮,不立刻再切走(否则打断正在进行的网络发送),没到 2 再来一轮。真机实测屏亮/灭都 ~15s flush 到 status=2。
  4. 落地 _send_text_verified(orchestrator):前台微信 → text_b64 发送 → sleep(4) → 切换 cycle + 前台停留轮询 → status 到 2。超时仍 1 视为"已入队将送达"(不误报失败)。
  5. 详见 memory wechat-api-send-onresume-flush。
  6. ✅ 端到端闭环验证(2026-07-12):火星人私聊 → 自动回复送达;延墨/丁鹏群里 @我 → 自动回复送达。私聊 + 群@我两条链路全通。
  7. 待深挖(下次):onResume 到底改变了什么让队列派发(疑似后台暂停网络场景/长连接休眠)——要 hook NetSceneQueue/网络模块在前后台切换点打 log 拿根因。

(原)环节 5 · 发送回复带引用(原生气泡)

需求 = 点原文→引用→发送后显示引用气泡。两条实现路:

  • (a) A11y UI 自动化(一期推荐):长按目标气泡 → 点"引用" → 输入 → 发送。驱动微信自己 UI → 产出货真价实的原生引用气泡,零逆向。代价:需微信前台、脚本略脆、比 API 慢。已有 A11y 发送服务底子。
  • (b) 内部 API 构造 refermsg(二期优化):微信引用 = type 49 appmsg + refermsg(type 57) 指向被引 svrid。一整块新逆向,量级同当初啃发图,重。留作提速。

3. 贯穿层(不做会出事)

  • 防循环/去重:只处理 isSend=0;持久化 per-talker last_processed_msgId;绝不回自己发的、绝不重放。
  • 限流:每人/每群回复冷却,防抖防刷屏。
  • 安全红线:陌生人 / 涉钱 / 承诺 / 约时间 → 永不自动发,硬编码兜底。

4. 一期最小可跑(MVP)

通知触发(NotificationListener)
  └→ 读库(主账号 ec4f/1986554, message 表, isSend=0 & msgId>last)
     └→ 分流(私聊/群@我/群引用我) + 取上下文(近 N 条 + rcontact 身份)
        └→ Hermes 生成(上下文 + SOUL.md → {should_reply,confidence,draft})
           ├→ 白名单 → A11y 发送(纯文本+@;引用气泡)
           └→ 非白名单 → 写入 wechat viewer 待回复队列 → 人工点发送

二期:lifelog Person 人物增强;内部 API refermsg 引用提速;cron 兜底调优。


5. 待决策 / 待确认

  • [ ] 白名单如何维护(配置文件 / viewer 里管理 / 从 lifelog 拉)
  • [ ] 待回复队列 + wechat viewer 的具体形态(现有 wechat-web-viewer skill 扩展?)
  • [ ] SOUL.md 是否加"小火子语气"段
  • [ ] 群 @我 / 引用我的判定用昵称还是 wxid(昵称易误判,wxid 更准)
  • [ ] in-place 只读打开能否稳定绕过 WCDB 锁(否则回退拷库)
  • [ ] 环节 2 lifelog join key:Person 里存的微信标识字段(二期确认)