HANDOFF.md 14.1 KB

HANDOFF — Pixel 微信自动回复部署地图

谁会读这份文档:刚接手这台 Pixel 部署的 claude-run / 开发者,没有上下文。 目标:5 分钟搞清楚正在跑什么、文件在哪、怎么改、怎么验证。

生成时间:2026-05-24,由 Mac 端 Claude 部署后写入。请定期核对实际状态, 因为本文件不会随代码自动更新。


TL;DR — 这是个什么系统

Pixel 手机本地的微信自动回复机器人。当好友(如 yibooo)给本机微信账号(小火子 / wxid_51ap3m6p9xs722)发消息,系统会:

  1. 轮询微信加密 DB(sqlcipher)拿到新消息
  2. 拉最近 N 条上下文喂给 hermes-agent (POST 127.0.0.1:8642/v1/chat/completions)
  3. hermes 后端走 Moonshot CN(kimi-k2.6)生成回复(通过 Clash mixed port 出网)
  4. am broadcast 触发 LSPosed 模块里的 HermesAccessibilityService
  5. A11y service UI 自动化点击微信(搜联系人 → 进聊天 → 写文本 → 点发送)
  6. 息屏时由 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 端开发冲突,不推荐多人协作。


七、已知待办(任选)

按优先级:

  1. 群聊昵称查询:android-wechat-tracker-sqlcipher.js 的 getContactNickname 对 @chatroom 后缀直接 return wxid,导致 A11y 搜不到群名。
    • 改法:删掉 if (wxid.endsWith('@chatroom')) return wxid; 分支,让它走通用 rcontact 查询
  2. 群聊引用 + @ 回复:扩 HermesAccessibilityService.java,加长按消息 → 选"引用" → 输入 @ → 选成员的 UI 自动化
  3. termux-wake-lock 启用:保证息屏深 Doze 时 termux-notification-list 仍响应。
    • am startservice -a com.termux.service_wake_lock com.termux/.app.TermuxService
    • 写进 keepalive.sh
  4. 清理 auth.json 老凭据:删 credential_pool["kimi-coding"] 里 last_status=exhausted 的项
  5. 杀 Frida 残留:pkill frida-server + rm -rf ~/.frida_queue/
  6. 重命名/移除已废弃文件: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 节

九、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 — 改动这份文档时记得更新顶部生成时间。