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 1 # WeChat 自动回复项目 — 完整状态报告
2 2  
3   -> 日期:2026-05-22
  3 +> 日期:2026-05-23
4 4 > 设备:Pixel 10 Pro Fold
5 5 > 微信版本:8.0.72
6 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 40  
41 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 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 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 119 |------|------|--------|
120 120 | `wechat-sender.js` | 发送消息(adb) | Mac |
121 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 127 | `index.js` | 统一入口,自动检测环境 | 通用 |
125 128 | `test-phone.js` | 手机端一键测试 | Pixel |
126 129 | `deploy-to-phone.sh` | 推送到手机 | Mac |
... ...
server/services/wechat/SKILL.md
... ... @@ -14,23 +14,38 @@
14 14  
15 15 ```
16 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 41 ```js
27 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 46 console.log(r1); // { ok: true, uri: 'content://.../send/0' }
32 47  
33   -// 方式2:直接广播(更可靠,绕过 Provider 进程)
  48 +// 直接广播(绕过 Provider 进程)
34 49 const r2 = await sendTextMessageBroadcast('wxid_xxxxxx', '测试消息');
35 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 71  
57 72 ## 跟踪消息
58 73  
59   -### 轮询模式(轻量,推荐)
  74 +### 方式1:SQLCipher 轮询(推荐)
  75 +
  76 +直接查询微信加密 DB,不依赖 LSPosed hook:
60 77  
61 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 82 tracker.on('message', msg => {
66 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 90 // tracker.stopPolling();
75 91 ```
76 92  
77   -### 流式模式(更实时)
  93 +### 方式2:LSPosed 日志 tail(备用)
78 94  
79 95 ```js
  96 +const { AndroidWechatTracker } = require('./android-wechat-tracker');
80 97 const tracker = new AndroidWechatTracker();
  98 +
81 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 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 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 5 > 目标设备: Pixel 10 Fold (Android)
6 6 > 运行环境: Termux + Ubuntu proot-distro
7 7 > 微信版本: 8.0.72
... ... @@ -13,15 +13,17 @@
13 13 在 Pixel 手机本地实现微信全自动闭环:
14 14  
15 15 ```
16   -收到消息 → 读取内容 → HTTP 调用 hermes → 生成回复 → 发送回复
  16 +收到消息 → 读取内容 → HTTP 调用 hermes-agent → 生成回复 → 发送回复
17 17 ```
18 18  
19 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 36 │ ┌───────────────────────────────────────────────────────────────────────┐ │
35 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 72 │ ┌───────────────────────────────────────────────────────────────────────┐ │
71 73 │ │ Termux 环境 (与 proot 共享 home) │ │
72 74 │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │
73   -│ │ │ 通知监听线程 (辅助通道) │ │ │
  75 +│ │ │ 通知监听线程(息屏兜底) │ │ │
74 76 │ │ │ termux-notification-list 每 3 秒轮询 │ │ │
75   -│ │ │ 发现微信通知 → 触发紧急 DB 轮询 │ │ │
  77 +│ │ │ 发现微信通知 → 点亮屏幕 → 解锁 → 启动微信 → tracker 恢复工作 │ │ │
76 78 │ │ └─────────────────────────────────────────────────────────────────┘ │ │
77 79 │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │
78 80 │ │ │ Termux:Boot 开机自启 │ │ │
79 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 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 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 149 | `filehelper` | `wx-filehelper` | 文件传输助手 |
143 150 | `wxid_abc123` | `wx-wxid_abc123` | 个人联系人 |
144 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 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 181 | SQLCipher key | `e03ce81` | 7 字符字符串密码 |
176 182 | cipher_compatibility | `1` | WCDB 兼容模式 |
177 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 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 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 252 | `GET /api/health` | HTTP | 健康检查 |
226 253  
227 254 ### 5.2 请求格式
228 255  
229 256 ```json
230   -POST /api/master/chat
  257 +POST /v1/chat/completions
231 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 322 ```bash
282 323 /system/bin/am broadcast \
... ... @@ -285,13 +326,13 @@ LSPosed 模块 `HermesWeChatModule` 注入微信进程,暴露 BroadcastReceive
285 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 345 ```bash
305 346 #!/bin/bash
306 347  
307   -# 启动 frida-server(可选)
  348 +# 启动 frida-server
308 349 if [ -f "/data/local/tmp/frida-server" ]; then
309 350 su -c "/data/local/tmp/frida-server &"
310 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 356 sleep 5
316 357 fi
317 358  
318   -# 启动 hermes (lemonPCRM) — 在 proot 内
  359 +# 启动 hermes-agent (lemonPCRM) — 在 proot 内
319 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 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 371 termux-notification \
331 372 --title "Hermes WeChat" \
332   - --content "守护进程已启动" \
  373 + --content "自动回复已启动" \
333 374 --priority low
334 375 ```
335 376  
... ... @@ -346,38 +387,50 @@ chmod +x ~/.termux/boot/start-hermes.sh
346 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 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 435 ```json
383 436 {
... ... @@ -390,9 +443,7 @@ chmod +x ~/.termux/boot/start-hermes.sh
390 443 "max_msgs_per_poll": 50,
391 444 "hermes": {
392 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 448 "auto_reply": {
398 449 "enabled": false,
... ... @@ -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 477 pkg install python sqlcipher termux-api
427 478  
428 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 485 ```bash
434 486 # 在 proot Ubuntu 中
435   -cd ~/aipcrm
  487 +cd ~/hermes-wechat
436 488 npm install
437 489 # 配置 .env 中的 Claude API key
438 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 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 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 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 530 ### Step 6: 设置开机自启
... ... @@ -484,29 +538,34 @@ chmod +x ~/.termux/boot/start-hermes.sh
484 538  
485 539 ## 11. 故障排查
486 540  
487   -### Q: DB 解密失败
  541 +### Q: DB 轮询报错 "database is locked"
  542 +**A**: 微信正在写入 DB 时 sqlcipher 尝试打开。tracker 已对此静默处理,会在下次轮询重试。如频繁出现,可增大轮询间隔。
  543 +
  544 +### Q: sqlcipher 解密失败
488 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 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 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 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 570 ### Q: termux-notification-list 返回空
512 571 **A**:
... ... @@ -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 590 1. **微信封号风险**: 自动化操作存在被检测风险,建议使用测试小号
522 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 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 25 │ handleMessage() │
32 26 │ - 判断单聊/群聊@/引用 │
33 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 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 72 ### 备用流程:Python Daemon
80 73  
... ... @@ -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 119 | `hermes_wechat_daemon.py` | **备用 daemon**:DB 轮询 → hermes/LLM API → sender | Termux / proot |
125 120 | `config.json` | 运行时配置 | Termux / proot |
126 121 | `keepalive.sh` | **保活脚本**:wake-lock + 进程守护 | Termux |
... ... @@ -156,9 +151,9 @@ cp -r /sdcard/hermes/local-testkit ~/local-testkit
156 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 158 ```bash
164 159 proot-distro login ubuntu --user ubuntu -- bash -c \
... ... @@ -172,19 +167,36 @@ nohup proot-distro login ubuntu --user ubuntu -- bash -c \
172 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 189 ```bash
178 190 cd ~/hermes-wechat/server/services/wechat
179 191 export MY_WXID=wxid_xxxxxx
180 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 196 nohup node auto-reply.js > auto-reply.log 2>&1 &
185 197 ```
186 198  
187   -### 5. 启动保活脚本(推荐)
  199 +### 6. 启动保活脚本(推荐)
188 200  
189 201 ```bash
190 202 cd ~/local-testkit
... ... @@ -196,7 +208,7 @@ chmod +x keepalive.sh
196 208 nohup ./keepalive.sh > keepalive.log 2>&1 &
197 209 ```
198 210  
199   -### 6. 备用:启动 Python daemon
  211 +### 7. 备用:启动 Python daemon
200 212  
201 213 ```bash
202 214 cd ~/local-testkit
... ... @@ -213,9 +225,9 @@ python3 hermes_wechat_daemon.py --daemon
213 225 |------|--------|------|
214 226 | `MY_WXID` | — | 你的微信 wxid(必填) |
215 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 232 ### Python daemon config.json
221 233  
... ... @@ -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 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 284 └── plan.md
272 285  
273 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 292 ├── index.js # 统一入口
278 293 └── README.md # plugin 文档
279 294 ```
... ...