-
- fix(auto-reply): 群引用回复改用 rawContent 调 isQuoteMe。normalize 已剥掉 <refermsg> XML,而 isQuoteMe 需原始 XML 判断引用的是不是机器人,否则永远 false → 只引用不 @ 的回复被静默忽略([NORMALIZE] 之后无下文) - deploy-to-phone.sh: 修正推送清单(原漏了 auto-reply.js / sqlcipher tracker, 从未真正部署过运行时代码);改走 /sdcard dd + chown u0_a327 + md5 校验 - HANDOFF.md v4: 加 §零「claude-run 环境须知」(.claude 被 chown root 致 Bash 全挂 / 唯一 live 日志 / bot root vs agent u0_a327 须用 su);删 lemon-acp clone - AGENTS.md + CLAUDE.md: agent 一次性可读的接手入口(claude-run 改代码 + hermes 跑 su 操作 的混合分工) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
-
症状:owner 用引用回复发"...@小火子",机器人完全没反应,日志只有 [NOTIF] 没有 [GROUP-AT]。 根因: 1. 引用回复在 message 表的 type=822083633(0x31000031 appmsg), tracker fetchNew 的 `WHERE type IN (1, 10002)` 把它过滤掉,根本没 fetch 2. 即使 fetch 了,content 是 appmsg XML,"@小火子" 在 <title> 里且后面 跟 </title>(不是空格),isAtMe 的 `@昵称(\s|\n|$)` 也匹配不上 修复: - tracker: type IN (1, 10002, 822083633) - handleMessage 顶部规范化:appmsg/refermsg XML → 提取 <title> 文本 重组成 "senderPrefix:\n<title>",下游 @ 检测/quote/hermes 都用干净文本 - 保留 rawContent(含 refermsg)传给 parseApprovalCommand, 这样 owner 用引用回复发"同意"时仍能从 refermsg 判断同意哪条 pending Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
-
#1 auto-reply.js: callHermesRunStream 的 res.on('end') 还在调旧的 flush() (重构成 flushTextBuf/emit 时漏改),触发 ReferenceError: flush is not defined → 之前直接崩 node(被 keepalive 重启打断 run)。改用 flushTextBuf/flushReasoningBuf。 (上一个 commit 的 unhandledRejection 兜底捕获到了这个真凶) #2 HermesAccessibilityService @ 选人卡死: - 成员名匹配:选人面板节点文本是"昵称: ZZ"等带前缀格式,严格 equals 匹配不上 → 新增 findMentionMember 做包含匹配 + 向上找 clickable 祖先 - 找不到成员时 fallback:之前直接 typeFinalContent 但仍停在"选择提醒的人" 整页/半屏页,找不到 bkk 输入框 → 卡死。改成先 GLOBAL_ACTION_BACK 退出 选人页回到聊天页,800ms 后再 findInnerEditText + typeFinalContent 实测:宜创群回复曾卡在 HalfScreenTransparentActivity(选择提醒的人), 要 @ 的 ZZ 在面板显示为"昵称: ZZ",严格匹配失败 + 无退出逻辑 → 卡死。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> -
症状:宜创群授权后的 hermes 回复 run 跑到一半,node 进程自己退出, keepalive 检测到重启,打断了正在进行的 run,回复没发出。 根因推断:Node 26 默认遇未捕获的 Promise rejection 就退出进程。 流式 callHermesRunStream + sendChain Promise 链路某处漏接 reject。 修复:入口加 process.on('unhandledRejection') / ('uncaughtException') 兜底,捕获后记日志([UNHANDLED REJECTION] / [UNCAUGHT EXCEPTION]) 保持进程存活,不再被 keepalive 打断。下次复现能从日志看到真正的错误栈。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> -
症状:"同意 宜创AI全员群" 用引用回复发出后没被识别,pending 没清。 根因:WeChat 引用回复存成 type=57 appmsg XML,真正的回复文本在 <title> 里、被引用的原消息在 <refermsg> 里。parseApprovalCommand 拿到整段 XML 匹配不上 ^(同意|...) 正则。 修复: - parseApprovalCommand 检测 <appmsg>/<refermsg>,提取 <title> 作为 回复文本(指令动词),提取 <refermsg> 作为 quotedContext - handleApproval 目标匹配优先级:显式 target → quotedContext 里出现的 pending displayName(引用哪条授权请求就同意哪个)→ 最近一个 pending 实测:引用回复"同意"(refermsg 含"宜创")→ [AUTH] Approved 宜创AI全员群 → 加白名单 + 自动回复 pending 的 "@小火子 在吗" Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
-
需求:不再对任何人自动回复,只有白名单的群/私聊才自动回; 非白名单的发信人,先转给主人(owner)请示是否授权。 实现: - WhitelistStore(持久化 .whitelist.json):allowed Set + pending dict - CONFIG.ownerWxid / ownerDisplayName(默认 yibooo / 火星人,env 可覆盖) - owner 永远在白名单(constructor 自动加) handleMessage 门禁逻辑(replyType 判定后): - 发送者身份 senderWxid:私聊=talker,群聊=content 前缀里的 senderWxid - owner 自己发的(私聊/群@):先 parseApprovalCommand 看是不是授权指令, 否则放行 - 非 owner 且 talker 不在白名单:requestAuthorization 转给 owner,不回复 - 白名单内:generateAndSendReply(原流程,抽成独立方法) 授权流程: - requestAuthorization: 给 owner 发「【授权请求】X 发来:...,回复『同意』授权」, 记入 pending(同一 talker 不重复请示) - parseApprovalCommand: 解析「同意/授权/批准/approve」「拒绝/忽略/deny」+ 可选目标名 - handleApproval: 按 wxid/昵称/senderName 匹配 pending(没指定取最近一个), 同意→加白名单+回复 TA 刚才那条 pending 消息;拒绝→移除 pending - sendPlainTo: 给 owner 发纯文本(不走 hermes),复用 A11y broadcast 重构:把回复生成从 handleMessage 抽成 generateAndSendReply({talker,content, msgId,replyType,senderName,raw}),授权通过后可复用(回复 pending 消息时 msgId=null) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> -
代码(server/services/wechat/auto-reply.js): - 切到 /v1/runs + GET /events SSE 长连接替代 /v1/chat/completions 同步阻塞 - 监听新事件: message.complete → 主路径多步消息(每 turn 末尾的可见 content) message.delta → content tokens 兜底 reasoning.delta → 默认丢弃;STREAM_REASONING=true 时加 🧠 前缀 reasoning.available → 同上 tool.started → flush 累积;STREAM_TOOL_PROGRESS=true 时发 🔧 进度 run.completed → 最终 output utterance + resolve - 串行 sendChain:第一条带 quote+@,后续仅文本;A11y 完成前不发下一条 - 诊断日志节流(高频事件 5s 一次) - log() 去掉 fs.appendFileSync(sync I/O 在高频流式下会卡死 node) Hermes upstream 补丁(phone 上 ~/hermes-agent/gateway/platforms/api_server.py): - 加 _reasoning_cb + reasoning_callback wire-up(_create_agent 签名 / AIAgent 构造) - 加 _interim_cb + interim_assistant_callback wire-up(同上) - /v1/runs 启动 agent 时把两个新回调都传进去 - 发现:原 hermes /v1/runs 默认只挂 stream_delta_callback + tool_progress_callback, 漏了 gateway 平台用的另外几个关键回调,导致 /v1/runs 客户端只能收到最终一条回复 实测 Kimi 限制: - Kimi-k2.6 在 hermes agentic mode 下 assistant_message.content 永远空 - 所有内容在 reasoning_content → interim_assistant_callback 因 content 为空 return → 不 fire - 短期 workaround: STREAM_REASONING=true - 长期: prompt 工程 / SOUL.md 优化 HANDOFF.md: - 新章节 §十一 A11y 显式状态机(5 状态 + classifier + dispatch) - 新章节 §十二 流式多步消息(SSE 事件表 + hermes 补丁说明 + Kimi 限制 + ENV flag 表) - 已知待办从 6 条扩到 9 条(新增 Kimi content 沉默、长消息分段、群消息合流、流式重试、多模型 fallback、SOUL.md 优化等) - 避雷清单加 4 条今天新踩的坑 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
-
旧路径:/v1/chat/completions 同步阻塞拿完整 reply → 一条微信消息。 长 agent loop 用户体验差("稍等" 之后再无下文)。 新路径: 1) POST /v1/runs { input, conversation_history, session_id, instructions } 拿 run_id 立即返回 2) GET /v1/runs/{run_id}/events SSE 长连接,消费事件: - message.delta { delta } → 累积到 textBuf - tool.started → flush(false): textBuf → onUtterance - run.completed { output } → flush(true): 最后一段 - run.failed/cancelled → reject 3) onUtterance 在串行 Promise 队列(sendChain)里跑: - idx===0 的 utterance 带 quoteText + atSender(群里 @ 原消息) - 后续 idx>0 仅文本,不重复 quote/@ - 发完后 await sleep(14000 或 9000) 等 A11y 跑完整一轮 chat-list-click → quote → @ → type → send,避免下一个 broadcast 打断当前 FSM 状态 handleMessage: - 移除旧的 this.replyGenerator + sendTextMessageBroadcast 同步路径 - 改成 callHermesRunStream(messages, sessionId, onUtterance) - state.markReplied 在 run 整体完成后才标记(避免中间步骤已发但失败时重发漏掉) 诊断:增加 [STREAM EV] 日志统计事件类型 + textBuf 长度, 方便诊断"agent loop 跑很久但没 utterance"等情况(实测发现 深度探索时 hermes 几乎只调 tool 不发 text,多步分割效果依赖 LLM 行为)。 keepalive.sh: HERMES_TIMEOUT 180s → 600s,长 agentic run 经常 3-5min。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> -
#1 主进入路径改造(用户提议)— A11y 在不在目标聊天页时, 优先扫主界面 chat list 直接点击进入,而不是走"搜索 → 点结果"。 理由:搜索 "丁鹏、🍋火星人、火星人" 这种群名会被 WeChat 解读成 "创建包含这 3 人的新群"建议,clickFirstSearchResult 误点进了 MMCreateChatroomUI,然后 bkk 不出现,整条发送链路挂掉。 新流程: - HermesAccessibilityService.enterChatViaListOrSearch: 扫 root 下所有 ID_CHATLIST_NAME (kbq) 节点 文本严格 equals pendingToUserDisplayName 时 向上找 ID_CHATLIST_ROW (cj1) clickable 容器并 ACTION_CLICK 等 2.2s 后检查 bkk 出现就 performTypeAndSend - 找不到匹配(chat 不在可见列表 / 不在 LauncherUI)回退到 performSearch - 实测:群在 chat list 第一/二行可定位,整段比搜索快 ~3s #2 @ 选人面板修复 - auto-reply.js: senderName 是 wxid("yibooo"),但群 @ 面板显示 的是昵称("火星人"),传 wxid 进 picker 永远找不到。改成 getContactNickname(senderName) 翻译后再传给 broadcast 的 atSender。 - HermesAccessibilityService.pickMentionMemberAndContinue: @ 失败 fallback 时旧 edit 节点引用已过期(IPC backed,600ms postDelayed 后失效),SET_TEXT 静默 false 整条发送挂掉。 fallback 路径重新 getRootInActiveWindow + findInnerEditText 拿 fresh edit 节点再 type 内容。 实测(msgId 1556 type=57): ChatList scan: 10 kbq names → matched 丁鹏、小火子、火星人、延墨... Clicking row → Chat UI loaded Quote bubble @小火子 测试一下 → long-press → 引用 @ mention typing → Clicking @ member: 火星人 Append "延墨好!欢迎欢迎..." → Clicking send button Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> -
新增功能: - HermesAccessibilityService 支持广播 extras quoteText / atSender - quoteText: 在聊天页找文本含此 substring 的 bkl 气泡 → 长按 → 点「引用」 菜单项 → 输入回复文本 → 点发送 - atSender: 在输入框先打「@」触发选人面板 → 找匹配成员点击 → 追加回复 实测路径完整跑通(msgId 1409 验证): 1. 广播触发 A11y service 2. 进入聊天页("already in correct chat" 或经过搜索) 3. findBubbleByPartialText 在 bkl 列表里倒序匹配 4. dispatchGesture 派发 800ms 合成长按手势(ACTION_LONG_CLICK 在 WeChat GestureDetector 实现上不触发菜单,必须用 gesture 派发) 5. 等 1.2s 后用 findClickableByTextAllWindows 搜「引用」 — 关键:菜单在 popup window 不在 active window,必须遍历 getWindows() 才能找到 6. 点击引用 → 引用 chip 注入到输入框 → 写文本 → 点发送 7. DB 写入的 message 是 type=57 (微信内部引用回复消息类型) 关键依赖(Android AccessibilityService): - accessibility_service_config.xml 必须设 canPerformGestures="true" 否则 dispatchGesture 不回调 onCompleted 群聊路径: - auto-reply.js handleMessage 在 replyType=group-at/group-quote 时自动 从触发消息 content 解析 bubbleText 当 quoteText,senderName 当 atSender - android-wechat-sender.js sendTextMessageBroadcast 接受 options.quoteText / options.atSender,拼到 am broadcast --es 参数里 测试覆盖(在 Pixel 实测): - ✅ 私聊回归:plain text 路径不影响(msgId 1408) - ✅ 引用回复:私聊场景已验证(msgId 1409 type=57) - ⏸ 群聊 + @ 群成员:代码已写完,需要真实群消息触发才能测 APK 已重编 + 用 debug.keystore 签 + adb install -r 升级(A11y 权限保留)。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
-
把 6 步链路接到唯一可用的发送路径(HermesAccessibilityService): 1. termux-notification-list 监听通知(息屏兜底) 2. input keyevent 224 点亮屏幕 + wm dismiss-keyguard 解锁 3. am start LauncherUI 切微信前台 4. sqlcipher 轮询 EnMicroMsg.db 拉新消息 + 最近 N 条上下文 5. 调 hermes-agent /v1/chat/completions 生成回复 6. am broadcast com.hermes.wechat.SEND_MSG → A11y UI 自动化发送 关键改动: - android-wechat-sender.js - sendTextMessageBroadcast 增加 options.toUserDisplayName(A11y 搜索必传) - 新增 checkAccessibilityServiceEnabled / enableAccessibilityService - android-wechat-tracker-sqlcipher.js - 新增 getContactNickname(wxid):查 rcontact 拿 conRemark/nickname, 带 filehelper/weixin 等内置 helper 的硬编码 fallback - 新增 fetchHistory(talker, limit):拉最近 N 条文本消息正序返回 - auto-reply.js - 移除已废弃的 sendTextMessageFrida - 启动时校验 A11y service,未启用则尝试自动启用并打印 manual fix 命令 - handleMessage 中:先 getContactNickname 拿 displayName、再 fetchHistory 给 hermes 做多轮上下文,最后用 broadcast + displayName 发送 - buildHermesMessages 重写:自动把历史按 isSend 转 user/assistant 多轮 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
-
- auto-reply.js 接入 hermes-agent OpenAI-compatible API (:8642) - 新增 android-wechat-tracker-sqlcipher.js 替代 LSPosed 日志 tail - 新增 android-wechat-sender-frida.js 替代 am broadcast 发送 - 更新 keepalive.sh、README、HermesWeChatModule.java - 添加 .gitignore SQLite 变体规则 - 新增 docs: 息屏发送尝试记录与屏幕自动唤醒方案 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
-
- auto-reply.js: connect to hermes /api/master/chat for AI reply generation - SSE response parsing, session isolation per talker - notification watcher (termux-notification-list) for screen-off fallback - wechat foreground check + wake before send - hermes_wechat_daemon.py: prefer hermes API, fallback to direct LLM API - config.json: add hermes host/port for local API connection - keepalive.sh: termux-wake-lock + process guardian for hermes + auto-reply - Move docs into local-testkit/docs/ directory - .gitignore: exclude server/db/ Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
-
Mac side (lemonPCRM): - Express server with /api/master/chat SSE endpoint - MasterSession multi-round orchestration - AgentManager + ToolRegistry - ClaudeCliProtocol ACP implementation Pixel side (wechat_plain/scripts/): - hermes_wechat_daemon.py: standalone daemon - SQLCipher DB decryption with WAL checkpoint fix - Direct LLM API calls (Kimi/Anthropic/OpenAI) - am broadcast to trigger LSPosed send - Auto wake WeChat to foreground before poll/send - lsposed-wechat-sendmsg: LSPosed module - sendTextMessageDirect via WeChat internal API - u01.r1.a -> builder -> m1.a() full chain - Background thread NetScene submission Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>