HANDOFF — Pixel 微信自动回复部署地图
谁会读这份文档:刚接手这台 Pixel 部署的 claude-run / 开发者,没有上下文。 目标:5 分钟搞清楚正在跑什么、文件在哪、怎么改、怎么验证。
生成时间:2026-05-24,由 Mac 端 Claude 部署后写入。请定期核对实际状态, 因为本文件不会随代码自动更新。
TL;DR — 这是个什么系统
Pixel 手机本地的微信自动回复机器人。当好友(如 yibooo)给本机微信账号(小火子 / wxid_51ap3m6p9xs722)发消息,系统会:
- 轮询微信加密 DB(sqlcipher)拿到新消息
- 拉最近 N 条上下文喂给 hermes-agent (
POST 127.0.0.1:8642/v1/chat/completions) - hermes 后端走 Moonshot CN(kimi-k2.6)生成回复(通过 Clash mixed port 出网)
am broadcast触发 LSPosed 模块里的HermesAccessibilityService- A11y service UI 自动化点击微信(搜联系人 → 进聊天 → 写文本 → 点发送)
- 息屏时由
termux-notification-list兜底,先input keyevent 224点亮 +wm dismiss-keyguard解锁 +am start拉起微信
整个链路唯一能真把消息送出去的发送方式 = A11y UI 自动化。 Frida daemon 队列 / LSPosed 反射 NetSceneSendMsg 都是死路(详见 docs)。
一、运行中进程清单
# 三个核心进程
adb shell "su -c 'pgrep -af auto-reply.js'" # node 微信回复主程序
adb shell "su -c 'pgrep -af gateway.run'" # hermes-agent OpenAI-compat 网关 :8642
adb shell "su -c 'pgrep -x com.tencent.mm'" # 微信主进程
| 进程 | 启动方 | 作用 |
|---|---|---|
node auto-reply.js |
~/hermes-wechat/start-ar.sh 通过 su -c 起,以 root 运行 |
微信回复主程序 |
python -m gateway.run |
~/.hermes/scripts/start-gateway.sh,以 termux 用户 u0_a327 运行 |
hermes 网关,监听 127.0.0.1:8642 |
com.tencent.mm |
系统 | 微信前台 UI,A11y 操作目标 |
HermesAccessibilityService |
Android AccessibilityManager 系统拉起 | 链路核心:执行 UI 自动化 |
frida-server |
(历史 autostart) | 已废弃,可杀。A11y 路径不依赖 |
二、文件位置 / 与 Mac repo 对应
Mac repo:
git@gitlab-wuwei.hexpm.com:yibo/pixel-wechat.git(remote 名pixel-wechat) 注意:手机上 没有 git 仓库,是adb push单文件部署的。
2.1 微信回复主程序
| Phone 路径 | Mac repo 路径 |
|---|---|
~/hermes-wechat/auto-reply.js |
server/services/wechat/auto-reply.js |
~/hermes-wechat/android-wechat-sender.js |
server/services/wechat/android-wechat-sender.js |
~/hermes-wechat/android-wechat-tracker-sqlcipher.js |
server/services/wechat/android-wechat-tracker-sqlcipher.js |
~/hermes-wechat/start-ar.sh |
未入库(含 MY_WXID / MY_NICKNAME) |
~/hermes-wechat/auto-reply.log |
运行时日志,不入库 |
目录里还有但已废弃(不要再用 / 不要再改):
android-wechat-sender-frida.js— Frida daemon 队列方案,Frida 17 注入会让微信崩android-wechat-tracker-sqlite.js/android-wechat-tracker.js— 老的 tail 日志方案test-hermes*.js、test-phone.js— 一次性调试脚本
2.2 hermes-agent
| Phone 路径 | 说明 |
|---|---|
~/hermes-agent/ |
hermes 源码(pip 安装,不是 git 仓库) |
~/.hermes/config.yaml |
主配置(当前指向 Moonshot CN + kimi-k2.6) |
~/.hermes/.env |
env 变量(KIMI_CN_API_KEY + HTTPS_PROXY=http://127.0.0.1:7890) |
~/.hermes/auth.json |
凭据池,自动从 env 同步(旧 sk-kimi 还在但已标 exhausted) |
~/.hermes/scripts/start-gateway.sh |
启 gateway |
~/.hermes/scripts/stop-gateway.sh |
停 gateway |
~/.hermes/scripts/check-gateway.sh |
检查状态 |
~/.hermes/scripts/auto-start.sh |
termux:boot 自启动 |
~/.hermes/logs/{gateway,agent,boot,errors}.log |
hermes 各类日志 |
~/.hermes/skills/ |
用户的 skills(修改 prompt 行为靠这里) |
~/.hermes/memories/ |
用户的 memories |
~/.hermes/sessions/ |
历史会话存档 |
| 配置备份 | ~/.hermes/{config.yaml,.env,auth.json}.bak.20260524_011444 |
2.3 LSPosed 模块 APK(含 A11y service)
| 位置 | 说明 |
|---|---|
/data/app/.../com.hermes.wechat.xposed-*/base.apk |
Android 系统安装位置(不能直接编辑) |
Mac repo wechat_plain/scripts/lsposed-wechat-sendmsg/ |
源码 |
| 主入口 | app/src/main/java/com/hermes/wechat/xposed/accessibility/HermesAccessibilityService.java |
| 重编译 | Mac 上 ./gradlew assembleRelease + 签名 + adb install |
2.4 微信数据库
| 路径 | 说明 |
|---|---|
/data/data/com.tencent.mm/MicroMsg/653cdc2320abb089e149a3f7a7683fcf/EnMicroMsg.db |
加密 SQLite(SQLCipher),key=e03ce81 |
~/EnMicroMsg*.db* |
历史调试 dump,可清理 |
~/wechat-importer.js、~/wechat-reader.js |
导入工具 |
三、关键命令速查
启停
# auto-reply 主程序
adb shell "su -c 'pgrep -af auto-reply.js'"
adb shell "su -c 'pkill -f auto-reply.js'"
adb shell "su -c 'nohup /data/data/com.termux/files/home/hermes-wechat/start-ar.sh > /dev/null 2>&1 &'"
# hermes gateway
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'"
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'"
A11y service 状态(最常踩的坑)
# 检查
adb shell "settings get secure enabled_accessibility_services"
# 重新启用(重启 / 升级后可能被关)
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'"
# dump 当前 bound 服务
adb shell "dumpsys accessibility | grep -A3 Hermes"
日志
# 主程序日志(消息检测 + hermes 调用 + 发送结果)
adb shell "su -c 'tail -f /data/data/com.termux/files/home/hermes-wechat/auto-reply.log'"
# A11y service 实际 UI 操作日志
adb shell "logcat -s HermesA11y:*"
# hermes gateway 日志(LLM 调用 + 错误)
adb shell "su -c 'tail -f /data/data/com.termux/files/home/.hermes/logs/gateway-start.log'"
手动测试发送
# 私聊 yibooo(火星人)
adb shell "am broadcast -a com.hermes.wechat.SEND_MSG --es toUser yibooo --es toUserDisplayName 火星人 --es content 'test'"
# 文件传输助手
adb shell "am broadcast -a com.hermes.wechat.SEND_MSG --es toUser filehelper --es toUserDisplayName 文件传输助手 --es content 'test'"
发送验证应看 DB而不是看 LSPosed Send result: true(后者是已知误报):
# 解密 DB 看最新 isSend=1 行
adb shell "su -c 'cp /data/data/com.tencent.mm/MicroMsg/.../EnMicroMsg.db /sdcard/check.db'"
adb pull /sdcard/check.db /tmp/check.db
sqlcipher /tmp/check.db <<EOF
PRAGMA key="e03ce81";
PRAGMA cipher_compatibility=1;
SELECT msgId, datetime(createTime/1000,'unixepoch','localtime') as t,
isSend, talker, substr(content,1,50) as c
FROM message WHERE isSend=1 ORDER BY msgId DESC LIMIT 3;
EOF
验证 hermes API 本身
# 简单 chat 完整性测
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\\\"}]}\"'"
四、网络拓扑陷阱(重要)
Pixel 上跑着 ClashMetaForAndroid(com.github.metacubex.clash.meta,UID u0_a330)。
- Clash 排除了 Termux 用户(u0_a327)出 VPN,所以 termux 用户没 DNS / 没默认出网
- 但 root 调用可以走 Clash 的 fake-ip
- hermes-agent 跑在 termux 用户 → 必须 显式走 Clash mixed port
127.0.0.1:7890才能联网
~/.hermes/.env 里已经配好:
HTTPS_PROXY=http://127.0.0.1:7890
HTTP_PROXY=http://127.0.0.1:7890
NO_PROXY=127.0.0.1,localhost
症状识别:如果看到 hermes 日志里大量 APIConnectionError / Failed to resolve host → 99% 是 Clash 没在跑,或代理 port 变了。先:
adb shell "su -c 'netstat -tlnp 2>/dev/null | grep 7890'"
五、当前 hermes 后端配置(截至 2026-05-24)
# ~/.hermes/config.yaml 摘要
model:
default: kimi-k2.6
provider: kimi-coding-cn
base_url: https://api.moonshot.cn/v1
providers:
kimi-coding-cn:
name: kimi-coding-cn
base_url: https://api.moonshot.cn/v1
api_key: <Moonshot CN key in .env>
api_mode: chat_completions
# ~/.hermes/.env 摘要
KIMI_CN_API_KEY=sk-JX3...
HTTPS_PROXY=http://127.0.0.1:7890
HTTP_PROXY=http://127.0.0.1:7890
NO_PROXY=127.0.0.1,localhost
注意:hermes 内置支持的 provider 列表见
~/hermes-agent/hermes_cli/auth.py(PROVIDER_CONFIGS 字典)。切换其他 LLM 改这里 + env。
六、给 claude-run 接手的两种开发流程
方式 1(推荐):在 phone 上 clone 同一个 gitlab 仓库到独立目录
# 在 phone termux shell 里(不要污染现有 ~/hermes-wechat/)
cd ~
git clone git@gitlab-wuwei.hexpm.com:yibo/pixel-wechat.git pixel-wechat-dev
# 或 HTTPS(需要 PAT):
# git clone https://gitlab-wuwei.hexpm.com/yibo/pixel-wechat.git pixel-wechat-dev
# 编辑 ~/pixel-wechat-dev/server/services/wechat/*.js
# commit + push
git add -A && git commit -m "..." && git push pixel-wechat main
# 部署到运行目录
cp ~/pixel-wechat-dev/server/services/wechat/{auto-reply,android-wechat-sender,android-wechat-tracker-sqlcipher}.js ~/hermes-wechat/
# 重启
pkill -f auto-reply.js
~/hermes-wechat/start-ar.sh &
第一次 push 需要在 phone 上配 git 用户 + SSH key 推到 gitlab,或者用 HTTPS PAT。
方式 2:直接改 ~/hermes-wechat/ 下文件 + 手动 scp 回 Mac 提交
简单但容易和 Mac 端开发冲突,不推荐多人协作。
七、已知待办(任选)
按优先级:
- 群聊昵称查询:
android-wechat-tracker-sqlcipher.js的getContactNickname对@chatroom后缀直接 return wxid,导致 A11y 搜不到群名。- 改法:删掉
if (wxid.endsWith('@chatroom')) return wxid;分支,让它走通用 rcontact 查询
- 改法:删掉
- 群聊引用 + @ 回复:扩
HermesAccessibilityService.java,加长按消息 → 选"引用" → 输入@→ 选成员的 UI 自动化 termux-wake-lock启用:保证息屏深 Doze 时termux-notification-list仍响应。am startservice -a com.termux.service_wake_lock com.termux/.app.TermuxService- 写进
keepalive.sh
- 清理 auth.json 老凭据:删
credential_pool["kimi-coding"]里 last_status=exhausted 的项 - 杀 Frida 残留:
pkill frida-server+rm -rf ~/.frida_queue/ - 重命名/移除已废弃文件:
android-wechat-sender-frida.js等已加废弃注释,可改名为*.deprecated.js或直接删
八、避雷清单(前人踩过的坑)
| 现象 | 真实原因 | 修法 |
|---|---|---|
接口返回 ok:true 但 yibooo 没收到 |
A11y service 没启用 | settings put secure enabled_accessibility_services ... |
HermesA11y 日志 Root package=com.termux 后退出 |
broadcast 触发时微信不在前台 | broadcast 前先 am start -n com.tencent.mm/.ui.LauncherUI |
Search button not found |
toUserDisplayName 错 / 当前页不是 LauncherUI |
用 WeChat nickname,不是 wxid |
[HermesWX] Send result: true 但 DB 没新消息 |
LSPosed 反射路径的误报(已知 bug) | 忽略 HermesWX 日志,看 HermesA11y |
hermes gateway 大量 Connection error |
Clash 没在跑 / 7890 端口变了 | 检查 `netstat -tlnp |
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' |
pgrep -f com.tencent.mm 找不到微信 |
Android 把 cmdline 设成 zygote64,-f 不匹配 |
用 pgrep -x com.tencent.mm(匹配 /proc/PID/comm) |
频繁 403 quota exhausted |
LLM 后端配额耗尽 | 切换 provider,见第 5 节 |
守护脚本里 pgrep -f node.*auto-reply.js 永远返 true(导致永远不重启 / 0 次启动)或永远返 false(导致 spawn 风暴) |
pgrep -f 在 su -c "..." 包装下会自匹配自己 shell 的 cmdline;如果改用 PID 文件,$! 抓到的是 sh -c wrapper PID(comm=sh)而不是 node 子进程 |
用 `su -c "ps -ef \ |
部署 JS 后 node 启动报 SyntaxError: Invalid regular expression flags 指向不存在的行号(文件 wc 看是干净的) |
phone 上某 claude-run/hermes 会话的 shell snapshot hook 在 dash 启动时尝试写 /data/local/tmp/hermes-snap-*.sh 失败,错误以追加方式注入到 hermes-wechat/*.js 末尾 |
部署用 dd if=src of=dst 而不是 cp;启动 node 时直接 su -c 'cd X && env... nohup node ...',不经 start-ar.sh / dash wrapper |
[HERMES] API timeout / socket hang up 在 turns=9 多上下文场景下偶发 |
Moonshot kimi-k2.6 长 context LLM call 可达 60s+,旧的 HERMES_TIMEOUT=120000 偶发不够 |
启动 env 设 HERMES_TIMEOUT=180000(3 分钟),keepalive.sh 已内联 |
九、Owner / Identity
- 本机微信账号:
小火子(wxid_51ap3m6p9xs722) - 测试好友:
yibooo(nickname火星人) - A11y 内部"系统"昵称:搜索框输的是 nickname,不是 wxid
start-ar.sh 当前 env:
MY_WXID=wxid_51ap3m6p9xs722 # 本机 wxid,用于 ignoreSelf
MY_NICKNAME=小火子 # 本机 nickname,用于 isAtMe 检测
HISTORY_CONTEXT_SIZE=8 # 喂给 hermes 的历史消息条数
十、看哪些日志能判断流程跑到哪一步
按时间顺序 grep auto-reply.log:
| 日志关键字 | 含义 |
|---|---|
[TRACKER] DB polling started |
sqlcipher 轮询启动 |
[A11Y] HermesAccessibilityService is enabled |
A11y 启动校验通过 |
[NOTIF] Notification watcher started |
息屏通知轮询启动 |
[NOTIF] WeChat notification: ... |
检测到微信通知 |
[PRIVATE] yibooo: ... / [GROUP-AT] xxx@chatroom |
tracker 捕获新消息 |
[HERMES] Calling API for ... turns=N |
调 hermes,N 是上下文条数 |
[HERMES] Reply: ... |
hermes 生成回复 |
[SENT] yibooo (as "火星人") ... |
A11y 发送 broadcast |
[SEND FAILED] |
发送链路出错 |
[NO REPLY] Generator returned null |
hermes 返回空 → 看 hermes gateway 日志 |
A11y 那一侧看 logcat -s HermesA11y:*,正常应输出:
Received send request: to=yibooo searchName=火星人 content=...
Root package=com.tencent.mm
Already in correct chat, typing directly # 或 Not in chat, starting search
Setting chat text: ...
Clicking send button (by ID)
State reset
END OF HANDOFF — 改动这份文档时记得更新顶部生成时间。