Commit 120da9ef4c0abdd1ae69a505085e7897182d2726

Authored by yibo
1 parent b56db049

docs: 加 HANDOFF.md — 给 claude-run / 后续开发者的 Pixel 部署上下文

包含:
- 运行中进程清单(auto-reply / hermes gateway / A11y service)
- phone 路径 ↔ Mac repo 路径对照表
- 启停 / A11y 校验 / 日志查看 / 手动测试发送命令速查
- Clash mixed proxy 网络拓扑陷阱(Termux UID 被 VPN 排除,必须走 7890)
- 当前 hermes 后端配置(Moonshot CN + kimi-k2.6)
- 两种接手开发流程(phone 上 clone repo / 手动 scp)
- 已知待办(群昵称查询、引用@回复、wake-lock、清理)
- 避雷清单(前人踩过的所有坑)

同步到 phone: /data/data/com.termux/files/home/hermes-wechat/HANDOFF.md

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
server/services/wechat/HANDOFF.md 0 → 100644
  1 +# HANDOFF — Pixel 微信自动回复部署地图
  2 +
  3 +> **谁会读这份文档**:刚接手这台 Pixel 部署的 claude-run / 开发者,没有上下文。
  4 +> **目标**:5 分钟搞清楚正在跑什么、文件在哪、怎么改、怎么验证。
  5 +>
  6 +> **生成时间**:2026-05-24,由 Mac 端 Claude 部署后写入。请定期核对实际状态,
  7 +> 因为本文件不会随代码自动更新。
  8 +
  9 +---
  10 +
  11 +## TL;DR — 这是个什么系统
  12 +
  13 +Pixel 手机本地的微信自动回复机器人。当好友(如 `yibooo`)给本机微信账号(小火子 / `wxid_51ap3m6p9xs722`)发消息,系统会:
  14 +
  15 +1. **轮询微信加密 DB**(sqlcipher)拿到新消息
  16 +2. **拉最近 N 条上下文**喂给 hermes-agent (`POST 127.0.0.1:8642/v1/chat/completions`)
  17 +3. hermes 后端走 **Moonshot CN**(kimi-k2.6)生成回复(通过 Clash mixed port 出网)
  18 +4. **`am broadcast`** 触发 LSPosed 模块里的 `HermesAccessibilityService`
  19 +5. A11y service **UI 自动化点击微信**(搜联系人 → 进聊天 → 写文本 → 点发送)
  20 +6. 息屏时由 `termux-notification-list` 兜底,先 `input keyevent 224` 点亮 + `wm dismiss-keyguard` 解锁 + `am start` 拉起微信
  21 +
  22 +**整个链路唯一能真把消息送出去的发送方式 = A11y UI 自动化**。
  23 +Frida daemon 队列 / LSPosed 反射 NetSceneSendMsg 都是死路(详见 docs)。
  24 +
  25 +---
  26 +
  27 +## 一、运行中进程清单
  28 +
  29 +```bash
  30 +# 三个核心进程
  31 +adb shell "su -c 'pgrep -af auto-reply.js'" # node 微信回复主程序
  32 +adb shell "su -c 'pgrep -af gateway.run'" # hermes-agent OpenAI-compat 网关 :8642
  33 +adb shell "su -c 'pgrep -x com.tencent.mm'" # 微信主进程
  34 +```
  35 +
  36 +| 进程 | 启动方 | 作用 |
  37 +|------|--------|------|
  38 +| `node auto-reply.js` | `~/hermes-wechat/start-ar.sh` 通过 `su -c` 起,**以 root 运行** | 微信回复主程序 |
  39 +| `python -m gateway.run` | `~/.hermes/scripts/start-gateway.sh`,**以 termux 用户 u0_a327 运行** | hermes 网关,监听 `127.0.0.1:8642` |
  40 +| `com.tencent.mm` | 系统 | 微信前台 UI,A11y 操作目标 |
  41 +| `HermesAccessibilityService` | Android AccessibilityManager 系统拉起 | **链路核心**:执行 UI 自动化 |
  42 +| `frida-server` | (历史 autostart) | **已废弃,可杀**。A11y 路径不依赖 |
  43 +
  44 +---
  45 +
  46 +## 二、文件位置 / 与 Mac repo 对应
  47 +
  48 +> Mac repo: `git@gitlab-wuwei.hexpm.com:yibo/pixel-wechat.git`(remote 名 `pixel-wechat`)
  49 +> 注意:手机上 **没有 git 仓库**,是 `adb push` 单文件部署的。
  50 +
  51 +### 2.1 微信回复主程序
  52 +
  53 +| Phone 路径 | Mac repo 路径 |
  54 +|------------|---------------|
  55 +| `~/hermes-wechat/auto-reply.js` | `server/services/wechat/auto-reply.js` |
  56 +| `~/hermes-wechat/android-wechat-sender.js` | `server/services/wechat/android-wechat-sender.js` |
  57 +| `~/hermes-wechat/android-wechat-tracker-sqlcipher.js` | `server/services/wechat/android-wechat-tracker-sqlcipher.js` |
  58 +| `~/hermes-wechat/start-ar.sh` | **未入库**(含 MY_WXID / MY_NICKNAME) |
  59 +| `~/hermes-wechat/auto-reply.log` | 运行时日志,不入库 |
  60 +
  61 +> **目录里还有但已废弃**(不要再用 / 不要再改):
  62 +> - `android-wechat-sender-frida.js` — Frida daemon 队列方案,Frida 17 注入会让微信崩
  63 +> - `android-wechat-tracker-sqlite.js` / `android-wechat-tracker.js` — 老的 tail 日志方案
  64 +> - `test-hermes*.js`、`test-phone.js` — 一次性调试脚本
  65 +
  66 +### 2.2 hermes-agent
  67 +
  68 +| Phone 路径 | 说明 |
  69 +|------------|------|
  70 +| `~/hermes-agent/` | hermes 源码(pip 安装,**不是 git 仓库**) |
  71 +| `~/.hermes/config.yaml` | **主配置**(当前指向 Moonshot CN + kimi-k2.6) |
  72 +| `~/.hermes/.env` | env 变量(`KIMI_CN_API_KEY` + `HTTPS_PROXY=http://127.0.0.1:7890`) |
  73 +| `~/.hermes/auth.json` | 凭据池,自动从 env 同步(旧 sk-kimi 还在但已标 exhausted) |
  74 +| `~/.hermes/scripts/start-gateway.sh` | 启 gateway |
  75 +| `~/.hermes/scripts/stop-gateway.sh` | 停 gateway |
  76 +| `~/.hermes/scripts/check-gateway.sh` | 检查状态 |
  77 +| `~/.hermes/scripts/auto-start.sh` | termux:boot 自启动 |
  78 +| `~/.hermes/logs/{gateway,agent,boot,errors}.log` | hermes 各类日志 |
  79 +| `~/.hermes/skills/` | **用户的 skills**(修改 prompt 行为靠这里) |
  80 +| `~/.hermes/memories/` | **用户的 memories** |
  81 +| `~/.hermes/sessions/` | 历史会话存档 |
  82 +| 配置备份 | `~/.hermes/{config.yaml,.env,auth.json}.bak.20260524_011444` |
  83 +
  84 +### 2.3 LSPosed 模块 APK(含 A11y service)
  85 +
  86 +| 位置 | 说明 |
  87 +|------|------|
  88 +| `/data/app/.../com.hermes.wechat.xposed-*/base.apk` | Android 系统安装位置(不能直接编辑) |
  89 +| Mac repo `wechat_plain/scripts/lsposed-wechat-sendmsg/` | 源码 |
  90 +| 主入口 | `app/src/main/java/com/hermes/wechat/xposed/accessibility/HermesAccessibilityService.java` |
  91 +| 重编译 | Mac 上 `./gradlew assembleRelease` + 签名 + `adb install` |
  92 +
  93 +### 2.4 微信数据库
  94 +
  95 +| 路径 | 说明 |
  96 +|------|------|
  97 +| `/data/data/com.tencent.mm/MicroMsg/653cdc2320abb089e149a3f7a7683fcf/EnMicroMsg.db` | 加密 SQLite(SQLCipher),key=`e03ce81` |
  98 +| `~/EnMicroMsg*.db*` | 历史调试 dump,**可清理** |
  99 +| `~/wechat-importer.js`、`~/wechat-reader.js` | 导入工具 |
  100 +
  101 +---
  102 +
  103 +## 三、关键命令速查
  104 +
  105 +### 启停
  106 +
  107 +```bash
  108 +# auto-reply 主程序
  109 +adb shell "su -c 'pgrep -af auto-reply.js'"
  110 +adb shell "su -c 'pkill -f auto-reply.js'"
  111 +adb shell "su -c 'nohup /data/data/com.termux/files/home/hermes-wechat/start-ar.sh > /dev/null 2>&1 &'"
  112 +
  113 +# hermes gateway
  114 +adb shell "su u0_a327 -c 'export PATH=/data/data/com.termux/files/usr/bin:\$PATH; export HOME=/data/data/com.termux/files/home; bash /data/data/com.termux/files/home/.hermes/scripts/start-gateway.sh'"
  115 +adb shell "su -c 'kill \$(cat /data/data/com.termux/files/home/.hermes/gateway.pid) 2>/dev/null; rm -f /data/data/com.termux/files/home/.hermes/gateway.pid'"
  116 +```
  117 +
  118 +### A11y service 状态(最常踩的坑)
  119 +
  120 +```bash
  121 +# 检查
  122 +adb shell "settings get secure enabled_accessibility_services"
  123 +
  124 +# 重新启用(重启 / 升级后可能被关)
  125 +adb shell "su -c 'settings put secure enabled_accessibility_services com.hermes.wechat.xposed/com.hermes.wechat.xposed.accessibility.HermesAccessibilityService; settings put secure accessibility_enabled 1'"
  126 +
  127 +# dump 当前 bound 服务
  128 +adb shell "dumpsys accessibility | grep -A3 Hermes"
  129 +```
  130 +
  131 +### 日志
  132 +
  133 +```bash
  134 +# 主程序日志(消息检测 + hermes 调用 + 发送结果)
  135 +adb shell "su -c 'tail -f /data/data/com.termux/files/home/hermes-wechat/auto-reply.log'"
  136 +
  137 +# A11y service 实际 UI 操作日志
  138 +adb shell "logcat -s HermesA11y:*"
  139 +
  140 +# hermes gateway 日志(LLM 调用 + 错误)
  141 +adb shell "su -c 'tail -f /data/data/com.termux/files/home/.hermes/logs/gateway-start.log'"
  142 +```
  143 +
  144 +### 手动测试发送
  145 +
  146 +```bash
  147 +# 私聊 yibooo(火星人)
  148 +adb shell "am broadcast -a com.hermes.wechat.SEND_MSG --es toUser yibooo --es toUserDisplayName 火星人 --es content 'test'"
  149 +
  150 +# 文件传输助手
  151 +adb shell "am broadcast -a com.hermes.wechat.SEND_MSG --es toUser filehelper --es toUserDisplayName 文件传输助手 --es content 'test'"
  152 +```
  153 +
  154 +发送验证应**看 DB**而不是看 LSPosed `Send result: true`(后者是已知误报):
  155 +
  156 +```bash
  157 +# 解密 DB 看最新 isSend=1 行
  158 +adb shell "su -c 'cp /data/data/com.tencent.mm/MicroMsg/.../EnMicroMsg.db /sdcard/check.db'"
  159 +adb pull /sdcard/check.db /tmp/check.db
  160 +sqlcipher /tmp/check.db <<EOF
  161 +PRAGMA key="e03ce81";
  162 +PRAGMA cipher_compatibility=1;
  163 +SELECT msgId, datetime(createTime/1000,'unixepoch','localtime') as t,
  164 + isSend, talker, substr(content,1,50) as c
  165 +FROM message WHERE isSend=1 ORDER BY msgId DESC LIMIT 3;
  166 +EOF
  167 +```
  168 +
  169 +### 验证 hermes API 本身
  170 +
  171 +```bash
  172 +# 简单 chat 完整性测
  173 +adb shell "su u0_a327 -c 'export PATH=/data/data/com.termux/files/usr/bin:\$PATH; curl -s --max-time 60 -X POST http://127.0.0.1:8642/v1/chat/completions -H \"Content-Type: application/json\" -d \"{\\\"model\\\":\\\"hermes-agent\\\",\\\"messages\\\":[{\\\"role\\\":\\\"user\\\",\\\"content\\\":\\\"hi\\\"}]}\"'"
  174 +```
  175 +
  176 +---
  177 +
  178 +## 四、网络拓扑陷阱(重要)
  179 +
  180 +Pixel 上跑着 **ClashMetaForAndroid**(com.github.metacubex.clash.meta,UID `u0_a330`)。
  181 +
  182 +- Clash **排除了 Termux 用户(u0_a327)出 VPN**,所以 termux 用户**没 DNS / 没默认出网**
  183 +- 但 root 调用可以走 Clash 的 fake-ip
  184 +- hermes-agent 跑在 termux 用户 → 必须 **显式走 Clash mixed port `127.0.0.1:7890`** 才能联网
  185 +
  186 +`~/.hermes/.env` 里已经配好:
  187 +
  188 +```bash
  189 +HTTPS_PROXY=http://127.0.0.1:7890
  190 +HTTP_PROXY=http://127.0.0.1:7890
  191 +NO_PROXY=127.0.0.1,localhost
  192 +```
  193 +
  194 +**症状识别**:如果看到 hermes 日志里大量 `APIConnectionError` / `Failed to resolve host` → 99% 是 Clash 没在跑,或代理 port 变了。先:
  195 +
  196 +```bash
  197 +adb shell "su -c 'netstat -tlnp 2>/dev/null | grep 7890'"
  198 +```
  199 +
  200 +---
  201 +
  202 +## 五、当前 hermes 后端配置(截至 2026-05-24)
  203 +
  204 +```yaml
  205 +# ~/.hermes/config.yaml 摘要
  206 +model:
  207 + default: kimi-k2.6
  208 + provider: kimi-coding-cn
  209 + base_url: https://api.moonshot.cn/v1
  210 +providers:
  211 + kimi-coding-cn:
  212 + name: kimi-coding-cn
  213 + base_url: https://api.moonshot.cn/v1
  214 + api_key: <Moonshot CN key in .env>
  215 + api_mode: chat_completions
  216 +```
  217 +
  218 +```bash
  219 +# ~/.hermes/.env 摘要
  220 +KIMI_CN_API_KEY=sk-JX3...
  221 +HTTPS_PROXY=http://127.0.0.1:7890
  222 +HTTP_PROXY=http://127.0.0.1:7890
  223 +NO_PROXY=127.0.0.1,localhost
  224 +```
  225 +
  226 +**注意**:hermes 内置支持的 provider 列表见
  227 +`~/hermes-agent/hermes_cli/auth.py`(`PROVIDER_CONFIGS` 字典)。切换其他 LLM 改这里 + env。
  228 +
  229 +---
  230 +
  231 +## 六、给 claude-run 接手的两种开发流程
  232 +
  233 +### 方式 1(推荐):在 phone 上 clone 同一个 gitlab 仓库到独立目录
  234 +
  235 +```bash
  236 +# 在 phone termux shell 里(不要污染现有 ~/hermes-wechat/)
  237 +cd ~
  238 +git clone git@gitlab-wuwei.hexpm.com:yibo/pixel-wechat.git pixel-wechat-dev
  239 +# 或 HTTPS(需要 PAT):
  240 +# git clone https://gitlab-wuwei.hexpm.com/yibo/pixel-wechat.git pixel-wechat-dev
  241 +
  242 +# 编辑 ~/pixel-wechat-dev/server/services/wechat/*.js
  243 +# commit + push
  244 +git add -A && git commit -m "..." && git push pixel-wechat main
  245 +
  246 +# 部署到运行目录
  247 +cp ~/pixel-wechat-dev/server/services/wechat/{auto-reply,android-wechat-sender,android-wechat-tracker-sqlcipher}.js ~/hermes-wechat/
  248 +
  249 +# 重启
  250 +pkill -f auto-reply.js
  251 +~/hermes-wechat/start-ar.sh &
  252 +```
  253 +
  254 +> 第一次 push 需要在 phone 上配 git 用户 + SSH key 推到 gitlab,或者用 HTTPS PAT。
  255 +
  256 +### 方式 2:直接改 `~/hermes-wechat/` 下文件 + 手动 scp 回 Mac 提交
  257 +
  258 +简单但容易和 Mac 端开发冲突,**不推荐多人协作**。
  259 +
  260 +---
  261 +
  262 +## 七、已知待办(任选)
  263 +
  264 +按优先级:
  265 +
  266 +1. **群聊昵称查询**:`android-wechat-tracker-sqlcipher.js` 的 `getContactNickname` 对 `@chatroom` 后缀直接 return wxid,导致 A11y 搜不到群名。
  267 + - 改法:删掉 `if (wxid.endsWith('@chatroom')) return wxid;` 分支,让它走通用 rcontact 查询
  268 +2. **群聊引用 + @ 回复**:扩 `HermesAccessibilityService.java`,加长按消息 → 选"引用" → 输入 `@` → 选成员的 UI 自动化
  269 +3. **`termux-wake-lock` 启用**:保证息屏深 Doze 时 `termux-notification-list` 仍响应。
  270 + - `am startservice -a com.termux.service_wake_lock com.termux/.app.TermuxService`
  271 + - 写进 `keepalive.sh`
  272 +4. **清理 auth.json 老凭据**:删 `credential_pool["kimi-coding"]` 里 last_status=exhausted 的项
  273 +5. **杀 Frida 残留**:`pkill frida-server` + `rm -rf ~/.frida_queue/`
  274 +6. **重命名/移除已废弃文件**:`android-wechat-sender-frida.js` 等已加废弃注释,可改名为 `*.deprecated.js` 或直接删
  275 +
  276 +---
  277 +
  278 +## 八、避雷清单(前人踩过的坑)
  279 +
  280 +| 现象 | 真实原因 | 修法 |
  281 +|------|---------|------|
  282 +| 接口返回 `ok:true` 但 yibooo 没收到 | A11y service 没启用 | `settings put secure enabled_accessibility_services ...` |
  283 +| HermesA11y 日志 `Root package=com.termux` 后退出 | broadcast 触发时微信不在前台 | broadcast 前先 `am start -n com.tencent.mm/.ui.LauncherUI` |
  284 +| `Search button not found` | `toUserDisplayName` 错 / 当前页不是 LauncherUI | 用 WeChat **nickname**,不是 wxid |
  285 +| `[HermesWX] Send result: true` 但 DB 没新消息 | LSPosed 反射路径的误报(已知 bug) | 忽略 HermesWX 日志,看 HermesA11y |
  286 +| hermes gateway 大量 `Connection error` | Clash 没在跑 / 7890 端口变了 | 检查 `netstat -tlnp | grep 7890` |
  287 +| `Gateway failed to start! setsid: exec python: No such file or directory` | `su -c` PATH 缺 termux bin | 用 `su u0_a327 -c 'export PATH=/data/data/com.termux/files/usr/bin:$PATH; bash start-gateway.sh'` |
  288 +| `pgrep -f com.tencent.mm` 找不到微信 | Android 把 `cmdline` 设成 `zygote64`,`-f` 不匹配 | 用 `pgrep -x com.tencent.mm`(匹配 `/proc/PID/comm`) |
  289 +| 频繁 `403 quota exhausted` | LLM 后端配额耗尽 | 切换 provider,见第 5 节 |
  290 +
  291 +---
  292 +
  293 +## 九、Owner / Identity
  294 +
  295 +- **本机微信账号**:`小火子` (`wxid_51ap3m6p9xs722`)
  296 +- **测试好友**:`yibooo` (nickname `火星人`)
  297 +- **A11y 内部"系统"昵称**:搜索框输的是 nickname,不是 wxid
  298 +
  299 +start-ar.sh 当前 env:
  300 +
  301 +```bash
  302 +MY_WXID=wxid_51ap3m6p9xs722 # 本机 wxid,用于 ignoreSelf
  303 +MY_NICKNAME=小火子 # 本机 nickname,用于 isAtMe 检测
  304 +HISTORY_CONTEXT_SIZE=8 # 喂给 hermes 的历史消息条数
  305 +```
  306 +
  307 +---
  308 +
  309 +## 十、看哪些日志能判断流程跑到哪一步
  310 +
  311 +按时间顺序 grep `auto-reply.log`:
  312 +
  313 +| 日志关键字 | 含义 |
  314 +|----------|------|
  315 +| `[TRACKER] DB polling started` | sqlcipher 轮询启动 |
  316 +| `[A11Y] HermesAccessibilityService is enabled` | A11y 启动校验通过 |
  317 +| `[NOTIF] Notification watcher started` | 息屏通知轮询启动 |
  318 +| `[NOTIF] WeChat notification: ...` | 检测到微信通知 |
  319 +| `[PRIVATE] yibooo: ...` / `[GROUP-AT] xxx@chatroom` | tracker 捕获新消息 |
  320 +| `[HERMES] Calling API for ... turns=N` | 调 hermes,N 是上下文条数 |
  321 +| `[HERMES] Reply: ...` | hermes 生成回复 |
  322 +| `[SENT] yibooo (as "火星人") ...` | A11y 发送 broadcast |
  323 +| `[SEND FAILED]` | 发送链路出错 |
  324 +| `[NO REPLY] Generator returned null` | hermes 返回空 → 看 hermes gateway 日志 |
  325 +
  326 +A11y 那一侧看 `logcat -s HermesA11y:*`,正常应输出:
  327 +
  328 +```
  329 +Received send request: to=yibooo searchName=火星人 content=...
  330 +Root package=com.tencent.mm
  331 +Already in correct chat, typing directly # 或 Not in chat, starting search
  332 +Setting chat text: ...
  333 +Clicking send button (by ID)
  334 +State reset
  335 +```
  336 +
  337 +---
  338 +
  339 +**END OF HANDOFF** — 改动这份文档时记得更新顶部生成时间。
... ...