# 微信自动回复 · 需求文档 > 日期: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 ""` 一次性(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 · 可靠发送 —— ✅ **API 发送打通(2026-07-11)** 经历一场硬仗才让 API 发送真正送达(火星人跨账号收到确认)。两个坑: 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 **要一次 onResume(后台→前台切换)才派发**。发送代码跨版本逐字节相同,不是回归。→ 发送后自动 `input keyevent KEYCODE_HOME` + `am start LauncherUI` 触发 onResume,消息 flush 到 status=2。 - 落地 `_send_text_verified`(orchestrator):前台微信 → text_b64 发送 → 后台前台切换 → 轮询 status 直到 2。status=1 超时视为"已入队将送达"(不误报失败)。 - 详见 memory `wechat-api-send-onresume-flush`。 - **待深挖(下次)**: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` 里存的微信标识字段(二期确认)