Commit 1dfe5938647d3071afc137d95246d1bb6cff37a2
1 parent
d38d9a22
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>
Showing
5 changed files
with
415 additions
and
304 deletions
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 "am broadcast -a com.hermes.wechat.SEND_MSG --es toUser 'filehelper' - |
| 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 "am broadcast -a com.hermes.wechat.SEND_MSG --es toUser 'filehelper' - |
| 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: 'pending' } | { status: 'true' } | { status: '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 "pgrep -x com.tencent.mm" > /dev/null 2>&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 | ``` | ... | ... |