Commit 6da2b2d99ab7c0ba2c5315129a7e0f17fe28a11a

Authored by yibo
1 parent d7b8a7e5

docs: update all WeChat auto-reply docs for SQLCipher + Frida refactor

- HERMES_WECHAT_ARCHITECTURE.md: v2.0 → v3.0, rewrite for Node.js plugin
- README.md: update architecture diagram, file list, API endpoint, env vars
- server/services/wechat/README.md: add sqlcipher tracker + frida sender
- SKILL.md: add Frida daemon and SQLCipher tracker usage
- wechat-auto-reply-status.md: mark SQLCipher + Frida as verified, update plans

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
md/wechat-auto-reply-status.md
1 # WeChat 自动回复项目 — 完整状态报告 1 # WeChat 自动回复项目 — 完整状态报告
2 2
3 -> 日期:2026-05-22 3 +> 日期:2026-05-23
4 > 设备:Pixel 10 Pro Fold 4 > 设备:Pixel 10 Pro Fold
5 > 微信版本:8.0.72 5 > 微信版本:8.0.72
6 > 环境:Termux + LSPosed (Zygisk) + Frida Server 17.9.10 6 > 环境:Termux + LSPosed (Zygisk) + Frida Server 17.9.10
@@ -40,20 +40,31 @@ adb shell &quot;am broadcast -a com.hermes.wechat.SEND_MSG --es toUser &#39;filehelper&#39; - @@ -40,20 +40,31 @@ adb shell &quot;am broadcast -a com.hermes.wechat.SEND_MSG --es toUser &#39;filehelper&#39; -
40 40
41 ## 3. 已尝试但不可行的方案 41 ## 3. 已尝试但不可行的方案
42 42
43 -### 3.1 直接查询 SQLite 数据库(失败) 43 +### 3.1 直接查询 SQLite 数据库(成功 — 已验证)
44 44
45 -**方案**:通过 `sqlite3` 命令行直接查询 `EnMicroMsg.db` 的 message 表。 45 +**方案**:通过 `sqlcipher` CLI 直接查询 `EnMicroMsg.db` 的 message 表。
46 46
47 -**结果**:❌ 完全失败 47 +**结果**:✅ 已验证,可直接解密并查询
48 48
49 -**原因**:  
50 -- 数据库使用 SQLCipher 加密,需要正确的 key 才能打开  
51 -- 通过 Frida hook `sqlite3_key`(offset 0x2687d8)捕获到 5 个 key  
52 -- 确认 EnMicroMsg.db 使用的 key 为 `e03ce81`  
53 -- 但使用 sqlcipher CLI 尝试 11 种不同配置(不同 KDF 迭代次数、HMAC 设置、page size),全部返回 "file is not a database"  
54 -- 通过 Frida 直接调用 `libWCDB.so` 的 `sqlite3_key`/`sqlite3_key_v2` 后执行 `PRAGMA schema_version`,同样返回错误码 26 49 +**关键参数**:
  50 +- SQLCipher key: `e03ce81`
  51 +- `PRAGMA cipher_compatibility = 1`(WCDB 兼容模式)
  52 +- `PRAGMA cipher_page_size = 1024`
  53 +- 无需复制 WAL(sqlcipher 可直接打开运行中的 DB,但可能遇到 `database is locked`)
55 54
56 -**结论**:捕获的 key 可能并非真正用于解密,或者 SQLCipher 参数(salt、KDF 算法、加密页大小等)与标准配置完全不同。 55 +**使用方式**(在 auto-reply.js 中通过 `AndroidWechatTrackerSQLCipher`):
  56 +```bash
  57 +sqlcipher /data/data/com.tencent.mm/MicroMsg/.../EnMicroMsg.db <<EOF
  58 +.headers off
  59 +.mode line
  60 +PRAGMA key="e03ce81";
  61 +PRAGMA cipher_compatibility=1;
  62 +PRAGMA cipher_page_size=1024;
  63 +SELECT msgId, type, isSend, talker, content, createTime FROM message WHERE msgId > ${lastId} AND isSend = 0 AND type IN (1, 10002) ORDER BY msgId ASC;
  64 +EOF
  65 +```
  66 +
  67 +**结论**:SQLCipher 解密成功,已成为主消息检测方案(替代 LSPosed 日志 tail)。
57 68
58 --- 69 ---
59 70
@@ -202,43 +213,37 @@ adb shell &quot;am broadcast -a com.hermes.wechat.SEND_MSG --es toUser &#39;filehelper&#39; - @@ -202,43 +213,37 @@ adb shell &quot;am broadcast -a com.hermes.wechat.SEND_MSG --es toUser &#39;filehelper&#39; -
202 213
203 | 功能 | 状态 | 备注 | 214 | 功能 | 状态 | 备注 |
204 |------|------|------| 215 |------|------|------|
205 -| 发送消息 | ✅ 可用 | LSPosed broadcast |  
206 -| 消息追踪 | ❌ 阻塞 | 所有已知 hook 点均失败 |  
207 -| 数据库解密 | ❌ 阻塞 | Key 正确但参数不匹配 |  
208 -| 自动回复 daemon | ⏳ 待联调 | 发送端可用,但缺少输入源 |  
209 -  
210 -**核心瓶颈**:消息追踪。没有可靠的消息来源,auto-reply.js 无法判断何时回复、回复给谁。 216 +| 发送消息 | ✅ 可用 | LSPosed broadcast(备用) |
  217 +| 发送消息(Frida) | ✅ 可用 | Frida daemon 队列注入(主推) |
  218 +| 消息追踪 | ✅ 可用 | SQLCipher DB 轮询(主推) |
  219 +| 数据库解密 | ✅ 可用 | sqlcipher CLI + WCDB 兼容参数 |
  220 +| 自动回复 daemon | ✅ 可用 | auto-reply.js 主流程已跑通 |
  221 +| 息屏唤醒 | ✅ 可用 | notification-list 兜底 + 自动亮屏解锁 |
  222 +
  223 +**当前主推方案**:Node.js plugin (`auto-reply.js`)
  224 +- 消息检测:`AndroidWechatTrackerSQLCipher`(sqlcipher 轮询)
  225 +- 回复生成:`callHermesApi()` → `hermes-agent` OpenAI-compatible API
  226 +- 消息发送:`sendTextMessageFrida()`(Frida daemon 队列)
  227 +- 息屏兜底:`pollNotifications()`(通知监听 → 唤醒微信)
211 228
212 --- 229 ---
213 230
214 ## 6. 下一步计划 231 ## 6. 下一步计划
215 232
216 -### 方案 A:LSPosed 模块扩展(推荐优先尝试)  
217 -  
218 -1. 在 Android Studio 中重新编译 LSPosed 模块(含 WCDB insert hook)  
219 -2. 安装新模块,重启 WeChat  
220 -3. 发送测试消息,检查 `/sdcard/hermes/wechat_msgs.jsonl` 是否有数据写入  
221 -4. 如果有数据,修改 auto-reply.js 读取该文件实现消息追踪  
222 -  
223 -### 方案 B:Frida :push 进程 hook  
224 -  
225 -1. 对 `:push` 进程(PID 18531)运行 Frida,hook WCDB insert  
226 -2. 发送消息到该设备,观察 push 进程是否有数据库操作  
227 -3. 如果成功,考虑将 hook 逻辑转为 LSPosed 模块(持久化)  
228 -  
229 -### 方案 C:AccessibilityService 233 +### 已完成(当前主流程)
230 234
231 -1. 开发一个 Android AccessibilityService  
232 -2. 监听微信通知栏或消息列表的文本变化  
233 -3. 将捕获的消息写入文件供 Termux 读取  
234 -4. 需要用户手动授权无障碍权限 235 +1. **SQLCipher DB 轮询** — `AndroidWechatTrackerSQLCipher` 已实现并验证,替代 LSPosed 日志 tail
  236 +2. **Frida Daemon 发送** — `sendTextMessageFrida` 已实现并验证,替代 am broadcast(绕过 Android 10+ 后台限制)
  237 +3. **hermes-agent API** — `auto-reply.js` 已切换到 OpenAI-compatible `/v1/chat/completions`
  238 +4. **息屏自动唤醒** — 通知监听 + `wakeWechat()` 点亮/解锁/启动微信流程已验证
235 239
236 -### 方案 D:妥协方案 — 基于通知的简化版 240 +### 待优化
237 241
238 -1. 使用 `dumpsys notification` 定期轮询  
239 -2. 仅检测"有新消息"事件(不获取具体内容)  
240 -3. 收到通知后,通过 ContentProvider 或数据库查询尝试获取完整内容  
241 -4. 如仍无法获取,可回退到"收到消息后固定回复"模式 242 +1. **息屏发送稳定性** — 当前息屏时 Frida daemon 可能因微信进程被冻结而失效,需要进一步验证
  243 +2. **群聊 @识别精度** — 依赖昵称匹配,考虑加入 wxid 匹配
  244 +3. **Frida daemon 持久化** — 目前需要手动注入,考虑 Termux:Boot 自动注入
  245 +4. **消息去重** — 息屏唤醒后 tracker 可能捕获到已处理过的消息,需优化 state 管理
  246 +5. **本地 LLM 部署** — 减少 API 延迟和网络依赖
242 247
243 --- 248 ---
244 249
server/services/wechat/README.md
@@ -119,8 +119,11 @@ hermes / adb shell content → 模块 Provider 进程 → 发送广播 @@ -119,8 +119,11 @@ hermes / adb shell content → 模块 Provider 进程 → 发送广播
119 |------|------|--------| 119 |------|------|--------|
120 | `wechat-sender.js` | 发送消息(adb) | Mac | 120 | `wechat-sender.js` | 发送消息(adb) | Mac |
121 | `wechat-tracker.js` | 跟踪消息(adb) | Mac | 121 | `wechat-tracker.js` | 跟踪消息(adb) | Mac |
122 -| `android-wechat-sender.js` | 发送消息(su) | Pixel |  
123 -| `android-wechat-tracker.js` | 跟踪消息(su) | Pixel | 122 +| `android-wechat-tracker-sqlcipher.js` | **主推** 跟踪消息(sqlcipher 轮询 DB) | Pixel |
  123 +| `android-wechat-sender-frida.js` | **主推** 发送消息(Frida daemon 队列) | Pixel |
  124 +| `android-wechat-sender.js` | 备用发送消息(am broadcast / content insert) | Pixel |
  125 +| `android-wechat-tracker.js` | 备用跟踪消息(tail -f LSPosed 日志) | Pixel |
  126 +| `auto-reply.js` | Pixel 自动回复 plugin(hermes-agent API) | Pixel |
124 | `index.js` | 统一入口,自动检测环境 | 通用 | 127 | `index.js` | 统一入口,自动检测环境 | 通用 |
125 | `test-phone.js` | 手机端一键测试 | Pixel | 128 | `test-phone.js` | 手机端一键测试 | Pixel |
126 | `deploy-to-phone.sh` | 推送到手机 | Mac | 129 | `deploy-to-phone.sh` | 推送到手机 | Mac |
server/services/wechat/SKILL.md
@@ -14,23 +14,38 @@ @@ -14,23 +14,38 @@
14 14
15 ``` 15 ```
16 server/services/wechat/ 16 server/services/wechat/
17 -├── android-wechat-sender.js # 消息发送  
18 -├── android-wechat-tracker.js # 消息跟踪  
19 -└── wechat-reader.js # 数据库读取(已有) 17 +├── android-wechat-tracker-sqlcipher.js # **主推** 消息跟踪(sqlcipher 轮询 DB)
  18 +├── android-wechat-sender-frida.js # **主推** 消息发送(Frida daemon 队列)
  19 +├── android-wechat-sender.js # 备用发送(am broadcast / content insert)
  20 +├── android-wechat-tracker.js # 备用跟踪(tail -f LSPosed 日志)
  21 +├── auto-reply.js # 自动回复主入口
  22 +└── wechat-reader.js # 数据库读取
20 ``` 23 ```
21 24
22 ## 发送消息 25 ## 发送消息
23 26
24 -### 单条发送(推荐) 27 +### 方式1:Frida Daemon(推荐)
  28 +
  29 +需要 frida-server 运行且 frida_daemon.js 已注入微信进程:
  30 +
  31 +```js
  32 +const { sendTextMessageFrida } = require('./android-wechat-sender-frida');
  33 +
  34 +const r = await sendTextMessageFrida('wxid_xxxxxx', '你好,来自 hermes');
  35 +console.log(r); // { ok: true }
  36 +// 写入 /data/local/tmp/frida_daemon/queue.jsonl,daemon 异步消费
  37 +```
  38 +
  39 +### 方式2:ContentProvider / Broadcast(备用)
25 40
26 ```js 41 ```js
27 const { sendTextMessage, sendTextMessageBroadcast } = require('./android-wechat-sender'); 42 const { sendTextMessage, sendTextMessageBroadcast } = require('./android-wechat-sender');
28 43
29 -// 方式1:通过 ContentProvider(与 Mac 端 API 一致)  
30 -const r1 = await sendTextMessage('filehelper', '你好,来自 hermes'); 44 +// ContentProvider(与 Mac 端 API 一致)
  45 +const r1 = await sendTextMessage('filehelper', '你好');
31 console.log(r1); // { ok: true, uri: 'content://.../send/0' } 46 console.log(r1); // { ok: true, uri: 'content://.../send/0' }
32 47
33 -// 方式2:直接广播(更可靠,绕过 Provider 进程) 48 +// 直接广播(绕过 Provider 进程)
34 const r2 = await sendTextMessageBroadcast('wxid_xxxxxx', '测试消息'); 49 const r2 = await sendTextMessageBroadcast('wxid_xxxxxx', '测试消息');
35 console.log(r2); // { ok: true } 50 console.log(r2); // { ok: true }
36 ``` 51 ```
@@ -56,37 +71,41 @@ console.log(s); // { status: &#39;pending&#39; } | { status: &#39;true&#39; } | { status: &#39;false @@ -56,37 +71,41 @@ console.log(s); // { status: &#39;pending&#39; } | { status: &#39;true&#39; } | { status: &#39;false
56 71
57 ## 跟踪消息 72 ## 跟踪消息
58 73
59 -### 轮询模式(轻量,推荐) 74 +### 方式1:SQLCipher 轮询(推荐)
  75 +
  76 +直接查询微信加密 DB,不依赖 LSPosed hook:
60 77
61 ```js 78 ```js
62 -const { AndroidWechatTracker } = require('./android-wechat-tracker');  
63 -const tracker = new AndroidWechatTracker(); 79 +const { AndroidWechatTrackerSQLCipher } = require('./android-wechat-tracker-sqlcipher');
  80 +const tracker = new AndroidWechatTrackerSQLCipher();
64 81
65 tracker.on('message', msg => { 82 tracker.on('message', msg => {
66 console.log(`[${msg.type.toUpperCase()}] ${msg.talker}: ${msg.content}`); 83 console.log(`[${msg.type.toUpperCase()}] ${msg.talker}: ${msg.content}`);
67 }); 84 });
68 85
69 -tracker.on('sendResult', result => {  
70 - console.log('发送结果:', result.success);  
71 -}); 86 +tracker.on('error', err => console.error('Tracker error:', err.message));
72 87
73 -tracker.startPolling(3000); // 每 3 秒轮询 88 +await tracker.init(); // 读取当前最大 msgId
  89 +tracker.startPolling(3000); // 每 3 秒轮询
74 // tracker.stopPolling(); 90 // tracker.stopPolling();
75 ``` 91 ```
76 92
77 -### 流式模式(更实时) 93 +### 方式2:LSPosed 日志 tail(备用)
78 94
79 ```js 95 ```js
  96 +const { AndroidWechatTracker } = require('./android-wechat-tracker');
80 const tracker = new AndroidWechatTracker(); 97 const tracker = new AndroidWechatTracker();
  98 +
81 tracker.on('message', msg => console.log(msg)); 99 tracker.on('message', msg => console.log(msg));
82 -tracker.startStream(); // tail -f 日志  
83 -// tracker.stopStream(); 100 +tracker.startPolling(3000);
  101 +// tracker.startStream(); // tail -f 模式(更实时)
  102 +// tracker.stopPolling();
84 ``` 103 ```
85 104
86 ### 一次性读取最近消息 105 ### 一次性读取最近消息
87 106
88 ```js 107 ```js
89 -const msgs = await tracker.fetchRecent(20); 108 +const msgs = await tracker.fetchRecent(20); // 仅 AndroidWechatTracker 支持
90 ``` 109 ```
91 110
92 ## 消息格式 111 ## 消息格式
wechat_plain/scripts/local-testkit/docs/HERMES_WECHAT_ARCHITECTURE.md
1 # Hermes WeChat 本地闭环架构方案 1 # Hermes WeChat 本地闭环架构方案
2 2
3 -> 版本: 2.0 (HTTP API 模式)  
4 -> 日期: 2026-05-22 3 +> 版本: 3.0 (Node.js Plugin + SQLCipher + Frida)
  4 +> 日期: 2026-05-23
5 > 目标设备: Pixel 10 Fold (Android) 5 > 目标设备: Pixel 10 Fold (Android)
6 > 运行环境: Termux + Ubuntu proot-distro 6 > 运行环境: Termux + Ubuntu proot-distro
7 > 微信版本: 8.0.72 7 > 微信版本: 8.0.72
@@ -13,15 +13,17 @@ @@ -13,15 +13,17 @@
13 在 Pixel 手机本地实现微信全自动闭环: 13 在 Pixel 手机本地实现微信全自动闭环:
14 14
15 ``` 15 ```
16 -收到消息 → 读取内容 → HTTP 调用 hermes → 生成回复 → 发送回复 16 +收到消息 → 读取内容 → HTTP 调用 hermes-agent → 生成回复 → 发送回复
17 ``` 17 ```
18 18
19 **不依赖 Mac 远程 adb**,所有组件运行在 Android 本地。 19 **不依赖 Mac 远程 adb**,所有组件运行在 Android 本地。
20 20
21 **核心设计原则**: 21 **核心设计原则**:
22 -- **daemon** 是常驻后台进程,只负责数据读写和消息收发  
23 -- **hermes** (lemonPCRM) 是独立的 AI 服务,负责 LLM 推理和回复生成  
24 -- 两者通过 HTTP API (`/api/master/chat`) 通信 22 +- **auto-reply.js** (Node.js plugin) 是常驻后台进程,负责消息检测、AI 调用、回复发送
  23 +- **hermes-agent** (lemonPCRM wrapper) 是独立的 OpenAI-compatible AI 服务,负责 LLM 推理
  24 +- 两者通过 HTTP API (`POST /v1/chat/completions`) 通信
  25 +- **SQLCipher DB 轮询** 替代 LSPosed 日志 tail,更稳定可靠
  26 +- **Frida Daemon** 替代 am broadcast,绕过 Android 10+ 后台限制
25 27
26 --- 28 ---
27 29
@@ -34,35 +36,35 @@ @@ -34,35 +36,35 @@
34 │ ┌───────────────────────────────────────────────────────────────────────┐ │ 36 │ ┌───────────────────────────────────────────────────────────────────────┐ │
35 │ │ Ubuntu proot-distro 容器 │ │ 37 │ │ Ubuntu proot-distro 容器 │ │
36 │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ 38 │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │
37 -│ │ │ hermes (lemonPCRM) │ │ │  
38 -│ │ │ ┌──────────────┐ ┌──────────┐ ┌──────────┐ │ │ │  
39 -│ │ │ │ Node.js │ │ Master │ │ Claude │ │ │ │  
40 -│ │ │ │ Express │ │ Session │ │ CLI │ │ │ │  
41 -│ │ │ │ :3010 │ │ Manager │ │ Protocol │ │ │ │  
42 -│ │ │ └──────────────┘ └──────────┘ └──────────┘ │ │ │ 39 +│ │ │ hermes-agent (:8642) │ │ │
  40 +│ │ │ ┌──────────────┐ ┌──────────┐ ┌──────────────────────────┐ │ │ │
  41 +│ │ │ │ Node.js │ │ OpenAI │ │ Claude CLI / Agents │ │ │ │
  42 +│ │ │ │ Express │ │ compat │ │ (wrapped via lemonPCRM) │ │ │ │
  43 +│ │ │ └──────────────┘ └──────────┘ └──────────────────────────┘ │ │ │
43 │ │ │ ↑ │ │ │ 44 │ │ │ ↑ │ │ │
44 -│ │ │ │ POST /api/master/chat (SSE) │ │ │  
45 -│ │ │ │ {message, sessionId} │ │ │ 45 +│ │ │ │ POST /v1/chat/completions │ │ │
  46 +│ │ │ │ {model, messages, stream} │ │ │
46 │ │ └─────────┼────────────────────────────────────────────────────────┘ │ │ 47 │ │ └─────────┼────────────────────────────────────────────────────────┘ │ │
47 │ │ │ │ │ 48 │ │ │ │ │
48 │ │ ┌─────────┴─────────────────────────────────────────────────────────┐ │ │ 49 │ │ ┌─────────┴─────────────────────────────────────────────────────────┐ │ │
49 -│ │ │ daemon (Python) │ │ │ 50 +│ │ │ auto-reply.js (Node.js) │ │ │
50 │ │ │ ┌──────────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │ │ │ 51 │ │ │ ┌──────────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │ │ │
51 -│ │ │ │ DB 轮询/ │→ │ 新消息 │→ │ HTTP │→ │ am broadcast │ │ │ │  
52 -│ │ │ │ 通知触发 │ │ 检测 │ │ hermes │ │ 发送回复 │ │ │ │ 52 +│ │ │ │ SQLCipher │→ │ 新消息 │→ │ HTTP │→ │ Frida Queue │ │ │ │
  53 +│ │ │ │ DB 轮询 │ │ 检测 │ │ hermes- │ │ /tmp/frida_ │ │ │ │
  54 +│ │ │ │ │ │ │ │ agent │ │ daemon/ │ │ │ │
53 │ │ │ └──────────────┘ └──────────┘ └──────────┘ └──────────────┘ │ │ │ 55 │ │ │ └──────────────┘ └──────────┘ └──────────┘ └──────────────┘ │ │ │
54 │ │ │ ↑ │ │ │ │ 56 │ │ │ ↑ │ │ │ │
55 │ │ │ │ │ │ │ │ 57 │ │ │ │ │ │ │ │
56 │ │ │ ┌──────────────┐ ┌──────────┴─────┐ │ │ │ 58 │ │ │ ┌──────────────┐ ┌──────────┴─────┐ │ │ │
57 -│ │ │ │ sqlcipher │ │ 回复内容 │ │ │ │  
58 -│ │ │ │ 解密读取 │ │ (SSE result) │ │ │ │ 59 +│ │ │ │ sqlcipher │ │ Frida Daemon │ │ │ │
  60 +│ │ │ │ 直接查询 │ │ (注入微信进程) │ │ │ │
59 │ │ │ └──────────────┘ └────────────────┘ │ │ │ 61 │ │ │ └──────────────┘ └────────────────┘ │ │ │
60 │ │ └──────────────────────────────────────────────────────────────────┘ │ │ 62 │ │ └──────────────────────────────────────────────────────────────────┘ │ │
61 │ │ ↑ │ │ 63 │ │ ↑ │ │
62 │ │ │ │ │ 64 │ │ │ │ │
63 │ │ ┌──────────────┐ ┌──────────┴──┐│ 65 │ │ ┌──────────────┐ ┌──────────┴──┐│
64 -│ │ │ su -c cp DB │ │ LSPosed ││  
65 -│ │ │ + WAL + SHM │ │ WeChatHook ││ 66 +│ │ │ EnMicroMsg.db│ │ com.tencent ││
  67 +│ │ │ (SQLCipher) │ │ .mm 主进程 ││
66 │ │ └──────────────┘ └─────────────┘│ 68 │ │ └──────────────┘ └─────────────┘│
67 │ └───────────────────────────────────────────────────────────────────────────┘ │ 69 │ └───────────────────────────────────────────────────────────────────────────┘ │
68 │ ↑ │ │ 70 │ ↑ │ │
@@ -70,14 +72,18 @@ @@ -70,14 +72,18 @@
70 │ ┌───────────────────────────────────────────────────────────────────────┐ │ 72 │ ┌───────────────────────────────────────────────────────────────────────┐ │
71 │ │ Termux 环境 (与 proot 共享 home) │ │ 73 │ │ Termux 环境 (与 proot 共享 home) │ │
72 │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ 74 │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │
73 -│ │ │ 通知监听线程 (辅助通道) │ │ │ 75 +│ │ │ 通知监听线程(息屏兜底) │ │ │
74 │ │ │ termux-notification-list 每 3 秒轮询 │ │ │ 76 │ │ │ termux-notification-list 每 3 秒轮询 │ │ │
75 -│ │ │ 发现微信通知 → 触发紧急 DB 轮询 │ │ │ 77 +│ │ │ 发现微信通知 → 点亮屏幕 → 解锁 → 启动微信 → tracker 恢复工作 │ │ │
76 │ │ └─────────────────────────────────────────────────────────────────┘ │ │ 78 │ │ └─────────────────────────────────────────────────────────────────┘ │ │
77 │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ 79 │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │
78 │ │ │ Termux:Boot 开机自启 │ │ │ 80 │ │ │ Termux:Boot 开机自启 │ │ │
79 │ │ │ ~/.termux/boot/start-hermes.sh │ │ │ 81 │ │ │ ~/.termux/boot/start-hermes.sh │ │ │
80 │ │ └─────────────────────────────────────────────────────────────────┘ │ │ 82 │ │ └─────────────────────────────────────────────────────────────────┘ │ │
  83 +│ │ ┌─────────────────────────────────────────────────────────────────┐ │ │
  84 +│ │ │ Frida Server (可选,用于注入 daemon) │ │ │
  85 +│ │ │ /data/local/tmp/frida-server │ │ │
  86 +│ │ └─────────────────────────────────────────────────────────────────┘ │ │
81 │ └───────────────────────────────────────────────────────────────────────┘ │ 87 │ └───────────────────────────────────────────────────────────────────────┘ │
82 │ ↑ │ │ 88 │ ↑ │ │
83 │ │ ▼ │ 89 │ │ ▼ │
@@ -98,75 +104,75 @@ @@ -98,75 +104,75 @@
98 ### 3.1 完整消息处理流程 104 ### 3.1 完整消息处理流程
99 105
100 ``` 106 ```
101 -1. 通知触发(或定时轮询) 107 +1. 息屏状态:通知监听线程发现微信通知
102 │ 108 │
103 ▼ 109 ▼
104 -2. daemon 复制 EnMicroMsg.db + WAL + SHM 到 /sdcard/hermes/dbs/ 110 +2. 点亮屏幕 → 解除锁屏 → 启动微信到前台(约 3-5 秒)
105 │ 111 │
106 ▼ 112 ▼
107 -3. daemon 用 sqlcipher 解密 DB 113 +3. auto-reply.js 的 SQLCipher tracker 轮询 EnMicroMsg.db
108 │ 114 │
109 ▼ 115 ▼
110 -4. daemon 查询 message 表:SELECT * WHERE msgId > last_msg_id 116 +4. 查询 message 表:SELECT ... WHERE msgId > last_msg_id AND isSend = 0
111 │ 117 │
112 ▼ 118 ▼
113 -5. daemon 提取消息内容 + 最近 5 条上下文 119 +5. 提取消息内容(单聊 / 群聊@ / 群聊引用)
114 │ 120 │
115 ▼ 121 ▼
116 -6. daemon 构造 prompt,HTTP POST 到 hermes /api/master/chat 122 +6. 构造 OpenAI messages 数组,HTTP POST 到 hermes-agent /v1/chat/completions
117 │ { 123 │ {
118 - │ "message": "收到消息(来自 xxx):...\n上下文: ...",  
119 - │ "sessionId": "wx-xxx" 124 + │ "model": "hermes-agent",
  125 + │ "messages": [{"role": "system", ...}, {"role": "user", ...}],
  126 + │ "stream": false
120 │ } 127 │ }
121 │ 128 │
122 ▼ 129 ▼
123 -7. hermes (lemonPCRM) 调用 Claude CLI 处理消息,返回 SSE 流  
124 - │ events: session → progress → agent_output → result 130 +7. hermes-agent (lemonPCRM) 调用 Claude CLI 处理消息,返回 JSON
  131 + │ {"choices": [{"message": {"content": "回复内容"}}]}
125 │ 132 │
126 ▼ 133 ▼
127 -8. daemon 解析 SSE,提取 result.output 作为回复文本 134 +8. auto-reply.js 提取 content 作为回复文本
128 │ 135 │
129 ▼ 136 ▼
130 -9. daemon 执行 am broadcast 发送回复到微信 137 +9. 写入 /data/local/tmp/frida_daemon/queue.jsonl
131 │ 138 │
132 ▼ 139 ▼
133 -10. LSPosed 模块拦截广播,调用微信内部 API 发送消息 140 +10. Frida Daemon(注入微信进程)读取队列,调用微信内部 API 发送消息
134 ``` 141 ```
135 142
136 ### 3.2 Session 管理 143 ### 3.2 Session 管理
137 144
138 -每个微信联系人 (`talker`) 对应一个独立的 hermes session: 145 +每个微信联系人 (`talker`) 对应一个独立的 hermes-agent session:
139 146
140 -| talker | sessionId | 作用 |  
141 -|--------|-----------|------| 147 +| talker | sessionId(X-Hermes-Session-Id) | 作用 |
  148 +|--------|--------------------------------|------|
142 | `filehelper` | `wx-filehelper` | 文件传输助手 | 149 | `filehelper` | `wx-filehelper` | 文件传输助手 |
143 | `wxid_abc123` | `wx-wxid_abc123` | 个人联系人 | 150 | `wxid_abc123` | `wx-wxid_abc123` | 个人联系人 |
144 | `123456789@chatroom` | `wx-123456789@chatroom` | 群聊 | 151 | `123456789@chatroom` | `wx-123456789@chatroom` | 群聊 |
145 152
146 **好处**: 153 **好处**:
147 -- hermes 自动维护每个对话的上下文记忆 154 +- hermes-agent 自动维护每个对话的上下文记忆
148 - 不同联系人的对话互不干扰 155 - 不同联系人的对话互不干扰
149 -- session 10 分钟空闲后自动清理  
150 156
151 --- 157 ---
152 158
153 ## 4. 消息监听双通道 159 ## 4. 消息监听双通道
154 160
155 -### 4.1 通道 A: DB 轮询(主推) 161 +### 4.1 通道 A: SQLCipher DB 轮询(主推)
156 162
157 -**原理**: 定期复制微信加密 DB → sqlcipher 解密 → 查询 message 表 → 检测 msgId 变化 163 +**原理**: sqlcipher CLI 直接查询微信加密 DB 的 message 表,检测 msgId 变化。
158 164
159 **流程**: 165 **流程**:
160 -```python  
161 -while True:  
162 - su -c "cp /data/data/com.tencent.mm/MicroMsg/<hash>/EnMicroMsg.db /sdcard/hermes/dbs/"  
163 - su -c "cp /data/data/com.tencent.mm/MicroMsg/<hash>/EnMicroMsg.db-wal /sdcard/hermes/dbs/"  
164 -  
165 - sqlcipher EnMicroMsg.db "PRAGMA key='e03ce81'; ..."  
166 -  
167 - SELECT * FROM message WHERE msgId > last_msg_id ORDER BY msgId  
168 - UPDATE last_msg_id = max(msgId)  
169 - sleep(10) 166 +```js
  167 +// AndroidWechatTrackerSQLCipher
  168 +await tracker.init(); // SELECT MAX(msgId) 初始化 lastMsgId
  169 +tracker.startPolling(3000); // 每 3 秒轮询
  170 +
  171 +// 每次轮询执行:
  172 +SELECT msgId, type, isSend, talker, content, createTime
  173 +FROM message
  174 +WHERE msgId > ${lastMsgId} AND isSend = 0 AND type IN (1, 10002)
  175 +ORDER BY msgId ASC
170 ``` 176 ```
171 177
172 **关键参数**: 178 **关键参数**:
@@ -175,22 +181,37 @@ while True: @@ -175,22 +181,37 @@ while True:
175 | SQLCipher key | `e03ce81` | 7 字符字符串密码 | 181 | SQLCipher key | `e03ce81` | 7 字符字符串密码 |
176 | cipher_compatibility | `1` | WCDB 兼容模式 | 182 | cipher_compatibility | `1` | WCDB 兼容模式 |
177 | cipher_page_size | `1024` | 页大小 | 183 | cipher_page_size | `1024` | 页大小 |
178 -| 轮询间隔 | `10s` | 可配置 |  
179 -| WAL 文件 | **必须复制** | 否则丢失最新数据 | 184 +| 轮询间隔 | `3000ms` | 可配置 |
  185 +| 查询条件 | `isSend = 0` | 只查收到的消息 |
  186 +| 消息类型 | `type IN (1, 10002)` | 文本消息 + 撤回通知 |
180 187
181 -### 4.2 通道 B: 通知监听(辅助) 188 +**优势**:
  189 +- 不依赖 LSPosed hook(WeChat 8.0.72 上 Xposed 不稳定)
  190 +- 数据完整(含群聊名称、发送者等)
  191 +- 不需要复制 WAL 文件
182 192
183 -**原理**: 通过 Termux:API 的 `termux-notification-list` 读取系统通知栏,发现微信通知后立即触发一次 DB 轮询。 193 +### 4.2 通道 B: 通知监听(息屏兜底)
184 194
185 -**流程**:  
186 -```python  
187 -while True:  
188 - notifs = termux-notification-list  
189 - wechat_notifs = [n for n in notifs if n.pkgName == "com.tencent.mm"]  
190 - for n in wechat_notifs:  
191 - if is_new(n):  
192 - trigger_urgent_db_poll() # 不等 10 秒周期  
193 - sleep(3) 195 +**原理**: 通过 Termux:API 的 `termux-notification-list` 读取系统通知栏,发现微信通知后执行屏幕唤醒流程。
  196 +
  197 +**唤醒流程**:
  198 +```
  199 +检测到微信通知
  200 + │
  201 + ▼
  202 +屏幕关闭? → input keyevent 224 (KEYCODE_WAKEUP)
  203 + │
  204 + ▼
  205 +锁屏状态? → wm dismiss-keyguard (无密码设备)
  206 + │
  207 + ▼
  208 +微信不在前台? → am start -n com.tencent.mm/.ui.LauncherUI
  209 + │
  210 + ▼
  211 +轮询确认 screen on + unlocked + WeChat foreground(最多 20 秒)
  212 + │
  213 + ▼
  214 +tracker 恢复 DB 轮询,捕获新消息
194 ``` 215 ```
195 216
196 **权限要求**: 217 **权限要求**:
@@ -201,82 +222,102 @@ while True: @@ -201,82 +222,102 @@ while True:
201 ### 4.3 双通道协同 222 ### 4.3 双通道协同
202 223
203 ``` 224 ```
204 -正常状态: DB 轮询每 10 秒执行一次  
205 -  
206 -收到新消息:  
207 - 通知栏产生通知 ──→ termux-notification-list 在 3 秒内发现  
208 - │  
209 - ▼  
210 - 触发紧急 DB 轮询(不等 10 秒周期)  
211 - │  
212 - ▼  
213 - 获取完整消息内容 ──→ HTTP POST hermes ──→ 发送回复 225 +正常状态: SQLCipher tracker 每 3 秒轮询 DB
  226 +
  227 +息屏后微信被冻结:
  228 + 微信消息到来 → 系统通知栏产生通知
  229 + │
  230 + ▼
  231 + termux-notification-list 在 3 秒内发现
  232 + │
  233 + ▼
  234 + 执行 wakeWechat() 唤醒流程(约 3-5 秒)
  235 + │
  236 + ▼
  237 + 微信前台运行 → SQLCipher tracker 恢复轮询
  238 + │
  239 + ▼
  240 + 获取完整消息内容 → HTTP POST hermes-agent → 发送回复
214 ``` 241 ```
215 242
216 --- 243 ---
217 244
218 -## 5. Hermes HTTP API 调用 245 +## 5. Hermes-Agent HTTP API 调用
219 246
220 ### 5.1 API 端点 247 ### 5.1 API 端点
221 248
222 | 端点 | 方法 | 说明 | 249 | 端点 | 方法 | 说明 |
223 |------|------|------| 250 |------|------|------|
224 -| `POST /api/master/chat` | SSE | 发送消息,流式返回处理结果 | 251 +| `POST /v1/chat/completions` | HTTP | OpenAI-compatible,发送消息,返回 JSON |
225 | `GET /api/health` | HTTP | 健康检查 | 252 | `GET /api/health` | HTTP | 健康检查 |
226 253
227 ### 5.2 请求格式 254 ### 5.2 请求格式
228 255
229 ```json 256 ```json
230 -POST /api/master/chat 257 +POST /v1/chat/completions
231 Content-Type: application/json 258 Content-Type: application/json
  259 +X-Hermes-Session-Id: wx-filehelper
232 260
233 { 261 {
234 - "message": "收到消息(来自 filehelper):你好\n请回复:",  
235 - "sessionId": "wx-filehelper" 262 + "model": "hermes-agent",
  263 + "messages": [
  264 + {"role": "system", "content": "你是 lemonAI 助手..."},
  265 + {"role": "user", "content": "收到微信私聊消息(来自 filehelper):你好\n请给出自然、简短、得体的中文回复。只输出回复内容,不要加任何解释。"}
  266 + ],
  267 + "stream": false
236 } 268 }
237 ``` 269 ```
238 270
239 -### 5.3 SSE 响应格式 271 +### 5.3 响应格式
240 272
  273 +```json
  274 +{
  275 + "choices": [
  276 + {
  277 + "message": {
  278 + "role": "assistant",
  279 + "content": "你好!很高兴收到你的消息。"
  280 + }
  281 + }
  282 + ]
  283 +}
241 ``` 284 ```
242 -event: session  
243 -data: {"sessionId": "wx-filehelper"}  
244 -  
245 -event: progress  
246 -data: {"step": "initializing", "message": "🤖 Master Agent 启动中..."}  
247 285
248 -event: agent_output  
249 -data: {"agent": "master", "message": "你好!很高兴收到你的消息。"} 286 +auto-reply.js 提取 `choices[0].message.content` 作为最终回复。
250 287
251 -event: result  
252 -data: {"sessionId": "wx-filehelper", "type": "text", "output": "你好!很高兴收到你的消息。"}  
253 -``` 288 +---
254 289
255 -daemon 提取 `result` 事件的 `output` 字段作为最终回复。 290 +## 6. 消息发送
256 291
257 -### 5.4 Prompt 构造 292 +### 6.1 发送机制(Frida Daemon — 主推)
258 293
259 -daemon 发送给 hermes 的消息格式: 294 +Frida Daemon 注入微信主进程,通过 Java.perform 调用微信内部 API:
260 295
261 ``` 296 ```
262 -你是一个微信智能助手。根据收到的消息,给出简短、自然、得体的中文回复。  
263 -  
264 -最近对话上下文:  
265 - 对方: 在吗?  
266 - 我: 在的,有什么可以帮你的?  
267 -  
268 -收到新消息(来自 filehelper):帮我查一下明天的天气  
269 -  
270 -请直接给出回复内容,不要加任何解释或格式标记。 297 +auto-reply.js
  298 + │
  299 + ├── 写入队列文件 /data/local/tmp/frida_daemon/queue.jsonl
  300 + │ {"talker": "filehelper", "content": "回复内容", "id": "msg_xxx"}
  301 + │
  302 + ▼
  303 +Frida Daemon(注入微信进程)
  304 + │
  305 + ├── 读取 queue.jsonl
  306 + │
  307 + ▼
  308 +Java.perform → 调用微信内部发送 API
  309 + │
  310 + ▼
  311 +微信服务器
271 ``` 312 ```
272 313
273 ----  
274 -  
275 -## 6. 消息发送 314 +**前置条件**:
  315 +1. frida-server 运行中
  316 +2. frida_daemon.js 已注入微信进程
276 317
277 -### 6.1 发送机制 318 +### 6.2 发送机制(LSPosed Broadcast — 备用)
278 319
279 -LSPosed 模块 `HermesWeChatModule` 注入微信进程,暴露 BroadcastReceiver: 320 +当 Frida Daemon 不可用时,回退到 am broadcast:
280 321
281 ```bash 322 ```bash
282 /system/bin/am broadcast \ 323 /system/bin/am broadcast \
@@ -285,13 +326,13 @@ LSPosed 模块 `HermesWeChatModule` 注入微信进程,暴露 BroadcastReceive @@ -285,13 +326,13 @@ LSPosed 模块 `HermesWeChatModule` 注入微信进程,暴露 BroadcastReceive
285 --es content "回复内容" 326 --es content "回复内容"
286 ``` 327 ```
287 328
288 -### 6.2 发送限制 329 +### 6.3 发送限制
289 330
290 | 限制 | 说明 | 331 | 限制 | 说明 |
291 |------|------| 332 |------|------|
292 -| 仅文本消息 | 当前模块只支持文本 |  
293 -| 主线程调用 | NetScene 提交必须在主线程 |  
294 -| 微信前台 | 建议保持微信在前台或后台运行 | 333 +| 仅文本消息 | 当前只支持文本 |
  334 +| Frida 依赖 | 需要 frida-server + 注入的 daemon |
  335 +| 微信主进程 | 发送必须在微信主进程存活时 |
295 336
296 --- 337 ---
297 338
@@ -304,7 +345,7 @@ LSPosed 模块 `HermesWeChatModule` 注入微信进程,暴露 BroadcastReceive @@ -304,7 +345,7 @@ LSPosed 模块 `HermesWeChatModule` 注入微信进程,暴露 BroadcastReceive
304 ```bash 345 ```bash
305 #!/bin/bash 346 #!/bin/bash
306 347
307 -# 启动 frida-server(可选) 348 +# 启动 frida-server
308 if [ -f "/data/local/tmp/frida-server" ]; then 349 if [ -f "/data/local/tmp/frida-server" ]; then
309 su -c "/data/local/tmp/frida-server &" 350 su -c "/data/local/tmp/frida-server &"
310 fi 351 fi
@@ -315,21 +356,21 @@ if ! su -c &quot;pgrep -x com.tencent.mm&quot; &gt; /dev/null 2&gt;&amp;1; then @@ -315,21 +356,21 @@ if ! su -c &quot;pgrep -x com.tencent.mm&quot; &gt; /dev/null 2&gt;&amp;1; then
315 sleep 5 356 sleep 5
316 fi 357 fi
317 358
318 -# 启动 hermes (lemonPCRM) — 在 proot 内 359 +# 启动 hermes-agent (lemonPCRM) — 在 proot 内
319 export PATH=/data/data/com.termux/files/usr/bin:$PATH 360 export PATH=/data/data/com.termux/files/usr/bin:$PATH
320 -proot-distro login --shared-home ubuntu -- bash -c \  
321 - "cd /root/aipcrm && npm start &" 361 +proot-distro login --shared-home ubuntu --user ubuntu -- bash -c \
  362 + "cd /data/data/com.termux/files/home/hermes-wechat && . .env && node server/index.js &"
322 363
323 sleep 5 364 sleep 5
324 365
325 -# 启动守护进程  
326 -proot-distro login --shared-home ubuntu -- bash -c \  
327 - "cd /root/local-testkit && python3 hermes_wechat_daemon.py --daemon" 366 +# 启动 auto-reply plugin
  367 +proot-distro login --shared-home ubuntu --user ubuntu -- bash -c \
  368 + "cd /data/data/com.termux/files/home/hermes-wechat/server/services/wechat && MY_WXID=xxx MY_NICKNAME=xxx node auto-reply.js &"
328 369
329 # 发送启动通知 370 # 发送启动通知
330 termux-notification \ 371 termux-notification \
331 --title "Hermes WeChat" \ 372 --title "Hermes WeChat" \
332 - --content "守护进程已启动" \ 373 + --content "自动回复已启动" \
333 --priority low 374 --priority low
334 ``` 375 ```
335 376
@@ -346,38 +387,50 @@ chmod +x ~/.termux/boot/start-hermes.sh @@ -346,38 +387,50 @@ chmod +x ~/.termux/boot/start-hermes.sh
346 ## 8. 目录结构 387 ## 8. 目录结构
347 388
348 ``` 389 ```
349 -~/local-testkit/ # daemon 目录  
350 -├── hermes_wechat_daemon.py # 主守护进程  
351 -├── config.json # 运行时配置  
352 -├── test_env.py # 环境预检  
353 -├── test_db_poll.py # DB 轮询测试  
354 -├── test_send_msg.py # 发送消息测试  
355 -├── test_termux_api.py # Termux API 测试  
356 -├── test_hermes_api.py # hermes HTTP API 测试  
357 -├── run_all_tests.sh # 一键测试  
358 -├── setup_termux_boot.sh # 开机自启脚本  
359 -└── HERMES_WECHAT_ARCHITECTURE.md # 本方案文档  
360 -  
361 -~/aipcrm/ # hermes (lemonPCRM) 目录  
362 -├── server/ # Express 后端  
363 -├── packages/ # ACP 协议 + Agent 运行时  
364 -├── package.json  
365 -└── ...  
366 -  
367 -/sdcard/hermes/ # 运行时数据  
368 -├── dbs/ # DB 副本  
369 -│ ├── EnMicroMsg.db  
370 -│ ├── EnMicroMsg.db-wal  
371 -│ └── EnMicroMsg.db.decrypted.db  
372 -├── wechat_msgs.jsonl # 消息日志  
373 -└── daemon.log # 守护进程日志 390 +~/hermes-wechat/server/services/wechat/ # 主 plugin
  391 +├── auto-reply.js # 主入口
  392 +├── android-wechat-tracker-sqlcipher.js # **主推** 消息跟踪
  393 +├── android-wechat-sender-frida.js # **主推** 消息发送
  394 +├── android-wechat-tracker.js # 备用消息跟踪
  395 +├── android-wechat-sender.js # 备用消息发送
  396 +├── index.js # 统一入口
  397 +└── README.md # plugin 文档
  398 +
  399 +~/local-testkit/ # 备用 daemon / 工具
  400 +├── hermes_wechat_daemon.py # 备用 Python 守护进程
  401 +├── config.json # 运行时配置
  402 +├── keepalive.sh # 保活脚本
  403 +├── test_env.py # 环境预检
  404 +├── test_send_msg.py # 发送消息测试
  405 +├── run_all_tests.sh # 一键测试
  406 +└── docs/
  407 + ├── README.md # 使用文档
  408 + ├── HERMES_WECHAT_ARCHITECTURE.md # 本方案文档
  409 + ├── REVERSE_ENGINEERING_LOG.md
  410 + ├── NOTIFICATION_STRATEGY.md
  411 + └── plan.md
  412 +
  413 +/sdcard/hermes/ # 运行时数据
  414 +├── dbs/ # DB 副本(备用方案用)
  415 +├── wechat_msgs.jsonl # 消息日志(备用方案用)
  416 +└── auto-reply.log # auto-reply plugin 日志
374 ``` 417 ```
375 418
376 --- 419 ---
377 420
378 ## 9. 配置说明 421 ## 9. 配置说明
379 422
380 -`config.json`: 423 +### auto-reply.js 环境变量
  424 +
  425 +| 变量 | 默认值 | 说明 |
  426 +|------|--------|------|
  427 +| `MY_WXID` | — | 你的微信 wxid(必填) |
  428 +| `MY_NICKNAME` | — | 你的微信昵称(必填,用于判断群聊@) |
  429 +| `HERMES_HOST` | `127.0.0.1` | hermes-agent API 地址 |
  430 +| `HERMES_PORT` | `8642` | hermes-agent API 端口 |
  431 +| `NOTIFICATION_ENABLED` | `true` | 是否启用通知监听兜底 |
  432 +
  433 +### config.json(Python daemon 备用)
381 434
382 ```json 435 ```json
383 { 436 {
@@ -390,9 +443,7 @@ chmod +x ~/.termux/boot/start-hermes.sh @@ -390,9 +443,7 @@ chmod +x ~/.termux/boot/start-hermes.sh
390 "max_msgs_per_poll": 50, 443 "max_msgs_per_poll": 50,
391 "hermes": { 444 "hermes": {
392 "api_url": "http://localhost:3010/api/master/chat", 445 "api_url": "http://localhost:3010/api/master/chat",
393 - "timeout_sec": 120,  
394 - "default_session_prefix": "wx-",  
395 - "system_hint": "你是一个微信智能助手。根据收到的消息,给出简短、自然、得体的中文回复。" 446 + "timeout_sec": 120
396 }, 447 },
397 "auto_reply": { 448 "auto_reply": {
398 "enabled": false, 449 "enabled": false,
@@ -412,7 +463,7 @@ chmod +x ~/.termux/boot/start-hermes.sh @@ -412,7 +463,7 @@ chmod +x ~/.termux/boot/start-hermes.sh
412 } 463 }
413 ``` 464 ```
414 465
415 -**注意**:daemon 不配置 LLM API key,所有 LLM 配置在 hermes (lemonPCRM) 中。 466 +**注意**:主流程(auto-reply.js)不读取 config.json,所有配置通过环境变量传入。
416 467
417 --- 468 ---
418 469
@@ -426,51 +477,54 @@ pkg update @@ -426,51 +477,54 @@ pkg update
426 pkg install python sqlcipher termux-api 477 pkg install python sqlcipher termux-api
427 478
428 # 安装 Termux:Boot 和 Termux:API 应用(F-Droid 或 GitHub) 479 # 安装 Termux:Boot 和 Termux:API 应用(F-Droid 或 GitHub)
  480 +# 关闭电池优化(Termux / Termux:API / 微信)
429 ``` 481 ```
430 482
431 -### Step 2: 部署 hermes (lemonPCRM) 483 +### Step 2: 部署 hermes-agent (lemonPCRM)
432 484
433 ```bash 485 ```bash
434 # 在 proot Ubuntu 中 486 # 在 proot Ubuntu 中
435 -cd ~/aipcrm 487 +cd ~/hermes-wechat
436 npm install 488 npm install
437 # 配置 .env 中的 Claude API key 489 # 配置 .env 中的 Claude API key
438 npm start # 或 npm run dev(热重载) 490 npm start # 或 npm run dev(热重载)
439 ``` 491 ```
440 492
441 -确认 hermes 监听 `localhost:3010`。  
442 -  
443 -### Step 3: 部署 daemon  
444 - 493 +确认 hermes-agent 监听 `127.0.0.1:8642`:
445 ```bash 494 ```bash
446 -cd ~/local-testkit 495 +curl http://127.0.0.1:8642/v1/chat/completions \
  496 + -X POST -H "Content-Type: application/json" \
  497 + -d '{"model":"hermes-agent","messages":[{"role":"user","content":"hi"}]}'
447 ``` 498 ```
448 499
449 -### Step 4: 运行测试 500 +### Step 3: 部署 Frida Daemon
450 501
451 ```bash 502 ```bash
452 -chmod +x run_all_tests.sh  
453 -./run_all_tests.sh  
454 -``` 503 +# 1. 确保 frida-server 运行
  504 +su -c "/data/local/tmp/frida-server &"
455 505
456 -预期结果:  
457 -- `test_env.py`: 全部 [OK]  
458 -- `test_db_poll.py`: DB 复制、解密、查询通过  
459 -- `test_send_msg.py`: am broadcast 发送成功  
460 -- `test_termux_api.py`: 通知读取成功  
461 -- `test_hermes_api.py`: hermes API 调用成功,收到回复 506 +# 2. 注入 daemon 到微信进程(从 Mac 或手机端)
  507 +frida -U -n com.tencent.mm -l frida_daemon.js
  508 +```
462 509
463 -### Step 5: 启动守护进程 510 +### Step 4: 部署 auto-reply plugin
464 511
465 ```bash 512 ```bash
466 -# 单次测试  
467 -python3 hermes_wechat_daemon.py --once 513 +cd ~/hermes-wechat/server/services/wechat
  514 +export MY_WXID=wxid_xxxxxx
  515 +export MY_NICKNAME=你的昵称
  516 +export HERMES_HOST=127.0.0.1
  517 +export HERMES_PORT=8642
468 518
469 -# 正常运行(前台)  
470 -python3 hermes_wechat_daemon.py 519 +nohup node auto-reply.js > auto-reply.log 2>&1 &
  520 +```
  521 +
  522 +### Step 5: 启动保活脚本
471 523
472 -# 后台运行  
473 -python3 hermes_wechat_daemon.py --daemon 524 +```bash
  525 +cd ~/local-testkit
  526 +chmod +x keepalive.sh
  527 +nohup ./keepalive.sh > keepalive.log 2>&1 &
474 ``` 528 ```
475 529
476 ### Step 6: 设置开机自启 530 ### Step 6: 设置开机自启
@@ -484,29 +538,34 @@ chmod +x ~/.termux/boot/start-hermes.sh @@ -484,29 +538,34 @@ chmod +x ~/.termux/boot/start-hermes.sh
484 538
485 ## 11. 故障排查 539 ## 11. 故障排查
486 540
487 -### Q: DB 解密失败 541 +### Q: DB 轮询报错 "database is locked"
  542 +**A**: 微信正在写入 DB 时 sqlcipher 尝试打开。tracker 已对此静默处理,会在下次轮询重试。如频繁出现,可增大轮询间隔。
  543 +
  544 +### Q: sqlcipher 解密失败
488 **A**: 545 **A**:
489 -1. 确认 key 正确  
490 -2. 确认 WAL 文件已复制:`ls -la /sdcard/hermes/dbs/EnMicroMsg.db*`  
491 -3. 微信运行时 DB 被锁定,必须通过 `su -c cp` 复制后再解密 546 +1. 确认 key 正确:`e03ce81`
  547 +2. 确认 cipher_compatibility=1 和 cipher_page_size=1024
  548 +3. 直接测试:`sqlcipher /path/to/EnMicroMsg.db "PRAGMA key='e03ce81'; PRAGMA cipher_compatibility=1; SELECT COUNT(*) FROM message;"`
492 549
493 -### Q: hermes API 调用超时 550 +### Q: hermes-agent API 调用超时
494 **A**: 551 **A**:
495 -1. 确认 hermes 已启动:`curl http://localhost:3010/api/health`  
496 -2. 检查 hermes 日志是否有 Claude CLI 调用错误  
497 -3. 增加 `hermes.timeout_sec`(Claude 推理可能需要 30-60 秒) 552 +1. 确认 hermes-agent 已启动:`curl http://127.0.0.1:8642/api/health`
  553 +2. 检查 hermes-agent 日志是否有 Claude CLI 调用错误
  554 +3. 增加 `HERMES_TIMEOUT`(Claude 推理可能需要 30-60 秒)
498 555
499 -### Q: hermes 返回空回复 556 +### Q: Frida 发送失败
500 **A**: 557 **A**:
501 -1. 检查 hermes 的 `system/agents/` 目录是否有微信相关 Agent  
502 -2. 查看 hermes 日志中的 SSE 事件流  
503 -3. 确认 Claude CLI 进程正常运行(非 mock 模式) 558 +1. 确认 frida-server 运行中:`ps aux | grep frida-server`
  559 +2. 确认 frida_daemon.js 已注入微信进程
  560 +3. 检查队列文件权限:`ls -la /data/local/tmp/frida_daemon/queue.jsonl`
  561 +4. 备用方案:回退到 `sendTextMessageBroadcast()`
504 562
505 -### Q: am broadcast 发送成功但微信没收到 563 +### Q: 息屏后完全失效
506 **A**: 564 **A**:
507 -1. 检查 LSPosed 模块是否启用且作用域包含微信  
508 -2. 检查日志:`su -c "cat /data/adb/lspd/log/modules_*.log | grep HermesWX | tail -20"`  
509 -3. 确认微信在主进程运行 565 +1. 确认电池优化已关闭(Termux / Termux:API / 微信)
  566 +2. 检查 `keepalive.sh` 是否运行
  567 +3. 检查通知权限:Termux:API 是否有通知访问权限
  568 +4. 检查日志:`tail -f auto-reply.log` 看是否有 `[NOTIF]` 或 `[WAKE]` 日志
510 569
511 ### Q: termux-notification-list 返回空 570 ### Q: termux-notification-list 返回空
512 **A**: 571 **A**:
@@ -516,16 +575,26 @@ chmod +x ~/.termux/boot/start-hermes.sh @@ -516,16 +575,26 @@ chmod +x ~/.termux/boot/start-hermes.sh
516 575
517 --- 576 ---
518 577
519 -## 12. 安全与风险提示 578 +## 12. 版本演进
  579 +
  580 +| 版本 | 日期 | 主要变更 |
  581 +|------|------|---------|
  582 +| 1.0 | 2026-05-20 | 初始方案:Mac adb + Python daemon |
  583 +| 2.0 | 2026-05-22 | Pixel 本地方案:LSPosed 日志 tail + am broadcast + SSE API |
  584 +| **3.0** | **2026-05-23** | **SQLCipher DB 轮询 + Frida 发送 + OpenAI-compatible API** |
  585 +
  586 +---
  587 +
  588 +## 13. 安全与风险提示
520 589
521 1. **微信封号风险**: 自动化操作存在被检测风险,建议使用测试小号 590 1. **微信封号风险**: 自动化操作存在被检测风险,建议使用测试小号
522 2. **Root 权限**: 需要 Magisk root 591 2. **Root 权限**: 需要 Magisk root
523 -3. **LSPosed 模块**: 仅作用于微信进程  
524 -4. **API Key**: Claude API key 仅在 hermes 中配置,daemon 不持有 592 +3. **Frida 注入**: 需要 frida-server 常驻运行
  593 +4. **API Key**: Claude API key 仅在 hermes-agent 中配置,plugin 不持有
525 594
526 --- 595 ---
527 596
528 -## 13. 扩展方向 597 +## 14. 扩展方向
529 598
530 - [ ] 支持群聊 @识别和回复 599 - [ ] 支持群聊 @识别和回复
531 - [ ] 支持图片/语音消息处理 600 - [ ] 支持图片/语音消息处理
wechat_plain/scripts/local-testkit/docs/README.md
@@ -11,19 +11,13 @@ @@ -11,19 +11,13 @@
11 ### 主流程:Node.js Plugin(推荐) 11 ### 主流程:Node.js Plugin(推荐)
12 12
13 ``` 13 ```
14 -LSPosed 模块 hook WCDB insert 14 +sqlcipher 直接查询 EnMicroMsg.db
15 │ 15 │
16 ▼ 16 ▼
17 -┌─────────────────────────┐  
18 -│ XposedBridge.log() │────→ /data/adb/lspd/log/verbose_*.log  
19 -│ writeMsgToFile() │────→ /sdcard/hermes/wechat_msgs.jsonl  
20 -└─────────────────────────┘  
21 - │  
22 - ▼  
23 -┌─────────────────────────┐ ┌─────────────────────────┐  
24 -│ AndroidWechatTracker │────→│ tail -f 日志 │  
25 -│ (Node.js EventEmitter) │ │ 实时事件驱动 │  
26 -└─────────────────────────┘ └─────────────────────────┘ 17 +┌─────────────────────────────────┐
  18 +│ AndroidWechatTrackerSQLCipher │────→ 轮询 message 表 (msgId > last)
  19 +│ (Node.js EventEmitter) │ PRAGMA key="e03ce81"
  20 +└─────────────────────────────────┘
27 │ 21 │
28 ▼ 22 ▼
29 ┌─────────────────────────┐ 23 ┌─────────────────────────┐
@@ -31,50 +25,49 @@ LSPosed 模块 hook WCDB insert @@ -31,50 +25,49 @@ LSPosed 模块 hook WCDB insert
31 │ handleMessage() │ 25 │ handleMessage() │
32 │ - 判断单聊/群聊@/引用 │ 26 │ - 判断单聊/群聊@/引用 │
33 │ - 检查 cooldown │ 27 │ - 检查 cooldown │
  28 +│ - 息屏唤醒微信 │
34 └─────────────────────────┘ 29 └─────────────────────────┘
35 │ 30 │
36 ▼ 31 ▼
37 ┌─────────────────────────┐ ┌─────────────────────────┐ 32 ┌─────────────────────────┐ ┌─────────────────────────┐
38 -│ callHermesApi() │────→│ POST localhost:3010 │  
39 -│ buildHermesPrompt() │ │ /api/master/chat │  
40 -│ 构造 prompt + session │ │ SSE 流式响应 │ 33 +│ callHermesApi() │────→│ POST 127.0.0.1:8642 │
  34 +│ buildHermesMessages() │ │ /v1/chat/completions │
  35 +│ OpenAI messages 格式 │ │ (OpenAI-compatible) │
41 └─────────────────────────┘ └─────────────────────────┘ 36 └─────────────────────────┘ └─────────────────────────┘
42 │ │ 37 │ │
43 │ ▼ 38 │ ▼
44 │ ┌─────────────────────────┐ 39 │ ┌─────────────────────────┐
45 - │ │ hermes (lemonPCRM) │  
46 - │ │ MasterSession │  
47 - │ │ → Claude CLI / Agents │ 40 + │ │ hermes-agent │
  41 + │ │ (lemonPCRM wrapper) │
48 │ └─────────────────────────┘ 42 │ └─────────────────────────┘
49 │ │ 43 │ │
50 ▼ ▼ 44 ▼ ▼
51 ┌─────────────────────────┐ ┌─────────────────────────┐ 45 ┌─────────────────────────┐ ┌─────────────────────────┐
52 -│ parseSSE() 提取 output │────→│ 回复文本 │ 46 +│ JSON 提取 content │────→│ 回复文本 │
53 └─────────────────────────┘ └─────────────────────────┘ 47 └─────────────────────────┘ └─────────────────────────┘
54 │ 48 │
55 ▼ 49 ▼
56 ┌─────────────────────────┐ ┌─────────────────────────┐ 50 ┌─────────────────────────┐ ┌─────────────────────────┐
57 -│ 微信在前台? │ No →│ am start 唤醒微信 │  
58 -└─────────────────────────┘ │ 等待 2s │ 51 +│ 微信在前台? │ No →│ wakeWechat() 点亮+解锁 │
  52 +└─────────────────────────┘ │ + am start 启动微信 │
59 │Yes └─────────────────────────┘ 53 │Yes └─────────────────────────┘
60 ▼ 54 ▼
61 ┌─────────────────────────┐ 55 ┌─────────────────────────┐
62 -│ sendTextMessageBroadcast │────→ am broadcast  
63 -│ (android-wechat-sender) │ action: com.hermes.wechat.SEND_MSG 56 +│ sendTextMessageFrida │────→ 写入 /data/local/tmp/frida_daemon/queue.jsonl
  57 +│ (android-wechat-sender-frida) │
64 └─────────────────────────┘ 58 └─────────────────────────┘
65 │ 59 │
66 ▼ 60 ▼
67 ┌─────────────────────────┐ 61 ┌─────────────────────────┐
68 -│ LSPosed 模块 │────→ u01.r1.a() → builder → m1.a()  
69 -│ HermesWeChatModule │ setIntField f=0, i=4 62 +│ Frida Daemon (注入微信) │────→ Java.perform → 调用微信内部发送 API
70 └─────────────────────────┘ 63 └─────────────────────────┘
71 ``` 64 ```
72 65
73 **优势**: 66 **优势**:
74 -- 事件驱动(tail -f),比 DB 轮询实时性高  
75 -- 不需要 sqlcipher 解密,不需要复制 WAL  
76 -- 通过 hermes API 享受完整的 Agent 编排、工具调用、记忆能力  
77 -- hermes 不可用时自动 fallback 到直接 LLM API 67 +- 不依赖 LSPosed hook(WeChat 8.0.72 上 Xposed hook 不稳定)
  68 +- sqlcipher 直接查询,数据完整(含群聊名称、发送者等)
  69 +- Frida 发送比 am broadcast 更可靠(绕过 Android 10+ 后台限制)
  70 +- 通过 hermes-agent API 享受完整的 Agent 编排、工具调用、记忆能力
78 71
79 ### 备用流程:Python Daemon 72 ### 备用流程:Python Daemon
80 73
@@ -118,9 +111,11 @@ isWechatForeground()? No → wakeWechat() → sleep 3s @@ -118,9 +111,11 @@ isWechatForeground()? No → wakeWechat() → sleep 3s
118 111
119 | 文件 | 用途 | 环境 | 112 | 文件 | 用途 | 环境 |
120 |------|------|------| 113 |------|------|------|
121 -| `../../server/services/wechat/auto-reply.js` | **主 plugin**:tracker → hermes API → sender | proot Ubuntu |  
122 -| `../../server/services/wechat/android-wechat-tracker.js` | 手机端消息跟踪(tail -f LSPosed 日志) | proot Ubuntu |  
123 -| `../../server/services/wechat/android-wechat-sender.js` | 手机端消息发送(am broadcast) | proot Ubuntu | 114 +| `../../server/services/wechat/auto-reply.js` | **主 plugin**:tracker → hermes-agent API → sender | proot Ubuntu |
  115 +| `../../server/services/wechat/android-wechat-tracker-sqlcipher.js` | **主推** 消息跟踪(sqlcipher 轮询 DB) | proot Ubuntu |
  116 +| `../../server/services/wechat/android-wechat-sender-frida.js` | **主推** 消息发送(Frida daemon 队列) | proot Ubuntu |
  117 +| `../../server/services/wechat/android-wechat-tracker.js` | 备用消息跟踪(tail -f LSPosed 日志) | proot Ubuntu |
  118 +| `../../server/services/wechat/android-wechat-sender.js` | 备用消息发送(am broadcast / content insert) | proot Ubuntu |
124 | `hermes_wechat_daemon.py` | **备用 daemon**:DB 轮询 → hermes/LLM API → sender | Termux / proot | 119 | `hermes_wechat_daemon.py` | **备用 daemon**:DB 轮询 → hermes/LLM API → sender | Termux / proot |
125 | `config.json` | 运行时配置 | Termux / proot | 120 | `config.json` | 运行时配置 | Termux / proot |
126 | `keepalive.sh` | **保活脚本**:wake-lock + 进程守护 | Termux | 121 | `keepalive.sh` | **保活脚本**:wake-lock + 进程守护 | Termux |
@@ -156,9 +151,9 @@ cp -r /sdcard/hermes/local-testkit ~/local-testkit @@ -156,9 +151,9 @@ cp -r /sdcard/hermes/local-testkit ~/local-testkit
156 cd ~/local-testkit 151 cd ~/local-testkit
157 ``` 152 ```
158 153
159 -### 3. 启动 hermes server(如果还没运行) 154 +### 3. 启动 hermes-agent(如果还没运行)
160 155
161 -**必须在 proot Ubuntu 内以 ubuntu 用户运行**(Claude CLI 安全机制拒绝 root + `--dangerously-skip-permissions`): 156 +hermes-agent 是 lemonPCRM 的 OpenAI-compatible wrapper,默认监听 `127.0.0.1:8642`:
162 157
163 ```bash 158 ```bash
164 proot-distro login ubuntu --user ubuntu -- bash -c \ 159 proot-distro login ubuntu --user ubuntu -- bash -c \
@@ -172,19 +167,36 @@ nohup proot-distro login ubuntu --user ubuntu -- bash -c \ @@ -172,19 +167,36 @@ nohup proot-distro login ubuntu --user ubuntu -- bash -c \
172 > hermes-server.log 2>&1 & 167 > hermes-server.log 2>&1 &
173 ``` 168 ```
174 169
175 -### 4. 启动 auto-reply plugin(推荐主流程) 170 +确认 agent 就绪:
  171 +```bash
  172 +curl http://127.0.0.1:8642/v1/chat/completions -X POST \
  173 + -H "Content-Type: application/json" \
  174 + -d '{"model":"hermes-agent","messages":[{"role":"user","content":"hi"}]}'
  175 +```
  176 +
  177 +### 4. 启动 Frida Daemon(发送端依赖)
  178 +
  179 +```bash
  180 +# 确保 frida-server 在运行
  181 +su -c "/data/local/tmp/frida-server &"
  182 +
  183 +# 注入 daemon 到微信进程(从 Mac 端执行)
  184 +frida -U -n com.tencent.mm -l frida_daemon.js
  185 +```
  186 +
  187 +### 5. 启动 auto-reply plugin(推荐主流程)
176 188
177 ```bash 189 ```bash
178 cd ~/hermes-wechat/server/services/wechat 190 cd ~/hermes-wechat/server/services/wechat
179 export MY_WXID=wxid_xxxxxx 191 export MY_WXID=wxid_xxxxxx
180 export MY_NICKNAME=你的昵称 192 export MY_NICKNAME=你的昵称
181 -export HERMES_HOST=localhost  
182 -export HERMES_PORT=3010 193 +export HERMES_HOST=127.0.0.1
  194 +export HERMES_PORT=8642
183 195
184 nohup node auto-reply.js > auto-reply.log 2>&1 & 196 nohup node auto-reply.js > auto-reply.log 2>&1 &
185 ``` 197 ```
186 198
187 -### 5. 启动保活脚本(推荐) 199 +### 6. 启动保活脚本(推荐)
188 200
189 ```bash 201 ```bash
190 cd ~/local-testkit 202 cd ~/local-testkit
@@ -196,7 +208,7 @@ chmod +x keepalive.sh @@ -196,7 +208,7 @@ chmod +x keepalive.sh
196 nohup ./keepalive.sh > keepalive.log 2>&1 & 208 nohup ./keepalive.sh > keepalive.log 2>&1 &
197 ``` 209 ```
198 210
199 -### 6. 备用:启动 Python daemon 211 +### 7. 备用:启动 Python daemon
200 212
201 ```bash 213 ```bash
202 cd ~/local-testkit 214 cd ~/local-testkit
@@ -213,9 +225,9 @@ python3 hermes_wechat_daemon.py --daemon @@ -213,9 +225,9 @@ python3 hermes_wechat_daemon.py --daemon
213 |------|--------|------| 225 |------|--------|------|
214 | `MY_WXID` | — | 你的微信 wxid(必填) | 226 | `MY_WXID` | — | 你的微信 wxid(必填) |
215 | `MY_NICKNAME` | — | 你的微信昵称(必填,用于判断群聊@) | 227 | `MY_NICKNAME` | — | 你的微信昵称(必填,用于判断群聊@) |
216 -| `HERMES_HOST` | `localhost` | hermes server 地址 |  
217 -| `HERMES_PORT` | `3010` | hermes server 端口 |  
218 -| `NOTIFICATION_ENABLED` | `true` | 是否启用通知监听兜底 | 228 +| `HERMES_HOST` | `127.0.0.1` | hermes-agent API 地址 |
  229 +| `HERMES_PORT` | `8642` | hermes-agent API 端口(OpenAI-compatible) |
  230 +| `NOTIFICATION_ENABLED` | `true` | 是否启用通知监听兜底(息屏唤醒) |
219 231
220 ### Python daemon config.json 232 ### Python daemon config.json
221 233
@@ -244,12 +256,13 @@ python3 hermes_wechat_daemon.py --daemon @@ -244,12 +256,13 @@ python3 hermes_wechat_daemon.py --daemon
244 256
245 | 问题 | 排查步骤 | 257 | 问题 | 排查步骤 |
246 |------|---------| 258 |------|---------|
247 -| auto-reply 收不到消息 | 检查 LSPosed 模块日志:`su -c "cat /data/adb/lspd/log/verbose_*.log \| grep HermesWX \| tail -20"` |  
248 -| hermes API 返回空 | 检查 hermes server 是否运行:`curl http://localhost:3010/api/health` |  
249 -| 息屏后完全失效 | 确认电池优化已关闭;检查 `keepalive.sh` 是否运行 |  
250 -| 发送失败 | 检查微信是否在前台;检查 LSPosed 模块是否启用 | 259 +| auto-reply 收不到消息 | 检查 sqlcipher 能否查询:`su -c "sqlcipher /data/.../EnMicroMsg.db 'PRAGMA key=\\\"e03ce81\\\"; SELECT COUNT(*) FROM message;'"` |
  260 +| hermes-agent API 返回空 | 检查 hermes-agent 是否运行:`curl http://127.0.0.1:8642/v1/chat/completions ...` |
  261 +| 息屏后完全失效 | 确认电池优化已关闭;检查 `keepalive.sh` 是否运行;检查通知权限 |
  262 +| 发送失败(Frida) | 确认 frida-server 运行中;确认 frida_daemon.js 已注入微信进程;检查队列文件 `/data/local/tmp/frida_daemon/queue.jsonl` |
  263 +| 发送失败(broadcast 备用) | 检查微信是否在前台;检查 LSPosed 模块是否启用 |
251 | Python daemon 查询不到新消息 | 检查微信是否在前台;检查 `sqlcipher_key`;确认 WAL 已复制 | 264 | Python daemon 查询不到新消息 | 检查微信是否在前台;检查 `sqlcipher_key`;确认 WAL 已复制 |
252 -| Claude CLI 报错 `root/sudo privileges` | hermes server 必须以 proot 内 **ubuntu 用户** 运行,不能是 root | 265 +| Claude CLI 报错 `root/sudo privileges` | hermes 必须以 proot 内 **ubuntu 用户** 运行,不能是 root |
253 266
254 --- 267 ---
255 268
@@ -271,9 +284,11 @@ local-testkit/ @@ -271,9 +284,11 @@ local-testkit/
271 └── plan.md 284 └── plan.md
272 285
273 server/services/wechat/ # 主 plugin(Node.js) 286 server/services/wechat/ # 主 plugin(Node.js)
274 -├── auto-reply.js # 主入口:tracker → hermes → sender  
275 -├── android-wechat-tracker.js # 消息跟踪  
276 -├── android-wechat-sender.js # 消息发送 287 +├── auto-reply.js # 主入口:tracker → hermes-agent → sender
  288 +├── android-wechat-tracker-sqlcipher.js # **主推** 消息跟踪(sqlcipher 轮询 DB)
  289 +├── android-wechat-sender-frida.js # **主推** 消息发送(Frida daemon 队列)
  290 +├── android-wechat-tracker.js # 备用消息跟踪(tail -f LSPosed 日志)
  291 +├── android-wechat-sender.js # 备用消息发送(am broadcast / content insert)
277 ├── index.js # 统一入口 292 ├── index.js # 统一入口
278 └── README.md # plugin 文档 293 └── README.md # plugin 文档
279 ``` 294 ```