-
- HermesClient: 加 chat_template_kwargs.enable_thinking=false —— 纪要走本地 Qwen3.5,不需要 <think>,关掉省 token/发热(Kimi 等不认会忽略)。 - SpeakerClusterer: MATCH_THRESHOLD 0.5 → 0.55 —— 0.5 偏松过度合并 (4B 测试「说话人4」吃了 23 段),收紧分得更细。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
-
- CLAUDE.md:版本表 v0.1→v0.6;架构骨架补 SpeakerClusterer 在线聚类、 5-tab UI、会话级 SummaryWorker、ChatScreen 多端点;新增「本地 LLM 栈」段 (llama.cpp HEAD + llama-swap 8080 跑 Qwen3.5 2b/4b);未来计划标注已完成项。 - HANDOFF.md:§一 现状改写;§二 文件清单补 chat/ summary/ SpeakerClusterer/ HermesClient,AppDatabase 改 v3+迁移;§五 加 v0.6 已完成项; 新增「§六 本地 LLM 栈」接手要点(部署/接线/thinking 坑/G3 实测速度)。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
-
- ChatClient: 本地 Qwen 目标加 chat_template_kwargs.enable_thinking=false (Hermes/Kimi 不认会忽略,故只对 LOCAL 加);修 parseDelta —— delta.content 为 JSON null(思考阶段/role-only 首块)时跳过,不再因 org.json.optString 返回字面 "null" 污染回复开头。 - SummaryWorker: 纪要后端解耦到本地 llama-swap(LLM_BASE_URL=8080 / LLM_MODEL="2b"), 独立于 keys.properties——那个留给聊天的 Hermes 选项(8642 网关 → Kimi)。 - AppDatabase: 删掉 fallbackToDestructiveMigration 改 fail-loud(已有 MIGRATION_2_3; 以后改 schema 不写迁移直接抛异常,绝不再静默清掉真实录音)。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
-
- SummaryScheduler.runNow 改 enqueueUniqueWork(KEEP):在跑/排队时重复触发直接忽略, 避免并发跑出重复纪要 - SummaryWorker:单篇喂 LLM 的转写条数上限 150,超长会话均匀抽样(覆盖整段、时间戳 保真),防止 prompt 撑爆上下文 / 端侧 prefill 过慢 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
-
- 首页 tab 改名「录音」,新增「对话」tab(ChatGPT 式:抽屉历史 / 气泡 / 流式 / 输入框) - chat/ChatConfig:聊天对象 = OpenAI 兼容端点 + model;本地 2B/4B 共用一个 llama-server 端口按 model 热切换,Hermes 走 gateway 8642;本地 baseUrl/model 名 app 内 ⚙ 可改 - chat/ChatClient:SSE 流式逐 token,端点不支持流式时自动降级整段解析 - 持久化到 Room:chat_conversations / chat_messages 两表,切换对象记到会话 targetId - data:AppDatabase 加 MIGRATION_2_3(纯增量加表),修复 fallbackToDestructiveMigration 在 24/7 RecorderService 重装时静默清库的风险 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
-
记录 2026-06-04 打通的 lifelog 纪要端到端链路与两项优化: - HERMES_AGENT_PIXEL_SETUP.md 新增 §3「作为 lifelog 纪要后端」: 开 api_server(API_SERVER_* env,key 与 lifelog 一致)、精简 token (platform_toolsets.api_server=[] + skills.platform_disabled → 12,500→399)、 Magisk service.d 自启(su 在 /system_ext/bin/su 的坑、动态 uid)、端到端验证 - HANDOFF.md §五:第5项 gateway 自启标记完成(service.d,非 Termux:Boot), 补 token 精简必做项、Kimi provider 配置交叉引用、api_server 出口被掐排错 API key 用占位符,未写真实凭证。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
-
- 声纹方案 B:新增 SpeakerClusterer 在线增量聚类,按余弦相似度自动发现 「说话人N」,capped 滑动均值精修质心并写穿 speaker_profiles; reclusterAll() 一键对历史段重聚类。不改 schema。 - UI 重构为 4 个底部 Tab:首页 / 时间轴 / 声纹 / 纪要。 - 时间轴:FTS 中文搜索 + 按说话人/时间范围/未匹配低置信筛选 + 排序切换, 全 CRUD(删段 / 改派说话人 / 订正转写 / 记为新说话人),批量选择删除 / 导出。 - 声纹 Tab:说话人列表(归属段数)、重命名 / 合并 / 删除、点击筛选时间轴。 - 真暂停:RecorderService 加 PAUSE/RESUME,停采集但 paraformer/VAD/聚类器常驻, 秒级恢复;RecorderState 加 isPaused。 - 纪要会话化:SummaryWorker 改为按时间间隔(UI 可调,默认 10min)自动切会话, 每场连续录音单独成篇,只总结已结束会话,时间范围重叠判重幂等。 - 纪要列表折叠显示「从 xx 到 xx · N 段 · 说话人名单 · 时长」,点开看 markdown。 - 纪要要点带 [HH
ss] 来源时间戳,点击跳回时间轴对应段并高亮(含跨午夜处理)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
-
新增 HERMES_AGENT_PIXEL_SETUP.md,记录从 Mac 经 adb 把 NousResearch hermes-agent 装到 Pixel Termux 的完整流程及两个坑(bootstrap 过期导致 node openssl 符号缺失需先 apt upgrade;wrapper /usr/bin/env shebang 在 su -c 下失效需改绝对路径),以及 Kimi Coding Plan 的原生配法(kimi-coding provider + api.kimi.com/coding,运行时自动切 anthropic 协议 + claude-code UA)。 - README.md 目录清单登记新文档 + 补登 PIXEL_CLAUDE_CODE_OAUTH_SETUP.md - API key 用占位符,未写真实凭证 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
-
把顶层平铺的脚本按功能归入子目录,并清理可重新生成的产物: - db-decrypt/: Frida 抓 key + 解密 EnMicroMsg.db(attach↔hunter 同目录依赖保持) - search/: Web 搜索服务 + 聊天统计生成 - docs/: HANDOFF.md / README_Android.md + notes/(原 md/) - 删除 *.tar.gz / *.zip 打包产物与 GITEOF,新增 .gitignore - 新增 README.md 目录索引,同步文档内路径引用 lsposed-wechat-sendmsg / local-testkit / platform-tools / frida-daemon / archive 保持不动。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
-
设备侧 hermes api_server platform 配置(HANDOFF §五新增详细步骤): - ~/.hermes/.env 加 API_SERVER_ENABLED + HOST + PORT + KEY + MODEL_NAME - pip install anthropic(kimi-for-coding 走 Anthropic-compat 协议层) - setsid + disown 启动避免 adb shell 退出时 SIGTERM 杀掉 gateway App 侧: - BuildConfig 增加 HERMES_API_URL / HERMES_API_KEY / HERMES_MODEL - keys.properties.example 同步样本 - HermesClient.kt: minimal OkHttp 客户端,POST /v1/chat/completions + Bearer auth;JSONObject 拼请求 / 解 choices[0].message.content - SummaryWorker.kt (CoroutineWorker): - 滑动 1h 时间窗;speech < 60s 直接 skip - 拉 Transcript + speaker name 拼用户提示 - system prompt 要求 markdown 出「主题概括 / 关键要点 / 人物 / 地点 / 待办」 - 写 Summary 表(windowStartTs, windowEndTs, markdown, model) - SummaryScheduler.kt: enqueueUniquePeriodicWork 每小时(KEEP)+ OneTimeWorkRequest(按钮触发) - LifelogApp.onCreate 启动时 schedulePeriodic - MainActivity: 新「纪要」section + 「现在跑一次」按钮 + SummaryCard 列表 - AndroidManifest android:networkSecurityConfig 开 127.0.0.1 + localhost cleartext 豁免(OkHttp 默认禁 HTTP,loopback 也禁) 实测:60min 窗 / 172 段 / 709s 语音 / Kimi 14.3k prompt token / 649 completion → 一份 markdown 纪要正确分辨出「电影解说 + 修仙网文」两段 内容,按要求列出主题/要点/人物/地点/待办 5 个 section。 HANDOFF.md §五 LLM 纪要 worker 移到「已完成」;下一档是存储 TTL / 设置页 / 多 anchor 平均 / lemonPCRM 回链 / hermes 自启。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
-
闭环 v0.3 的两个自然延伸: - SegmentPlayer.kt: MediaPlayer 包装,Opus 段原音播放 Android API 21+ MediaPlayer 原生解码 Ogg-Opus,验证 logcat 见 「MetaDataBase: OpusHead.. mime: audio/opus brte: 16000 #chn: 1」 音频属性设 USAGE_MEDIA + CONTENT_TYPE_SPEECH,单实例复用 - SpeakerMatcher.kt: backfillAll() 遍历所有有 embedding 的段,重算 最近邻 profile + cosine 距离;threshold 0.5 复用 RecorderService 里的逻辑;只更新变化的行(避免 Room 全表写) enroll 落库后 MainActivity 自动 launch 调用 - MainActivity: 时间轴 tap row 不再直接弹 enroll dialog,改为 ModalBottomSheet 出「▶ 播放原音 / 记为说话人」二选一 TimelineRow 加 isPlaying 状态指示,正在播放的段前缀 "▶ " DisposableEffect 在 onDispose 调 player.stop() 防泄漏 HANDOFF.md §五 更新(v0.4 移到「已完成」),加 SegmentPlayer.kt + SpeakerMatcher.kt 到文件清单。 实测(Pixel 8 Pro,电影 audio 输入): - 段 #11-14 显示 "未匹配 (0.09-0.13)" — 电影角色声纹与 Me anchor cosine 远低于 0.5 → 正确拒绝匹配 - 段 #13 19s opus 文件 MediaPlayer 立刻播放,文件 ~38 KB Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
-
声纹(用户语音里提了 3 次的 feature): - SpeakerEmbedder.kt: sherpa-onnx SpeakerEmbeddingExtractor 包装 3dspeaker CAMPPlus 16k zh-cn 模型(28 MB int8 ONNX),192 维 embedding - ModelDownloader.ensureSpeakerModel(): 首次启动从 hf-mirror.com 自动下 - Segment 实体加 embedding/speakerId/speakerSim 三列 - SpeakerProfile 实体(id/name/embedding/sampleCount/createdAt)+ SpeakerDao - RecorderService.handleSegment 每段算 embedding + 与已注册 profiles 算 cosine,threshold ≥0.5 才 assign - RecorderService 通过 speakers.observeAll() 实时 reload profile 缓存 - MainActivity 点时间轴段 → AlertDialog 输入名字 → 创建 SpeakerProfile - TimelineRow 显示 "name (sim=0.xx)" 或 "—" / "未匹配 (0.xx)" FTS4 全文搜索: - TranscriptFts 实体用 @Fts4(contentEntity=Transcript, tokenizer="unicode61") Room 自动建虚表 + 同步触发器,unicode61 把中文字当 token - searchTimeline DAO 查询 JOIN transcript_fts MATCH :query - MainActivity 顶部 OutlinedTextField 实时切换 timelineFlow - sanitizeFtsQuery 给每 token 加 * 做前缀匹配,剥掉 FTS metachars Schema: - AppDatabase v1 → v2 + fallbackToDestructiveMigration(v0.3 dev 数据可丢) - 新增 SpeakerProfile + TranscriptFts 虚表 + Segment 三列 HANDOFF.md §五 更新:声纹 + 搜索移到「已完成」,下一档变成 LLM 纪要 worker / 存储 TTL / 段播放 / 多 anchor 平均 / lemonPCRM 回链。 实测(Pixel 8 Pro Tensor G3,Android 16): - speaker model load: 441 ms - embedding 计算开销 ~10-30 ms/段,跟 paraformer 同期完成 - enroll "Me" anchor → 后续段在 logcat 见 "speaker=Me sim=0.xx" Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
-
让 v1 真能脱离开发环境 24/7 跑的最后三公里: - SegmentWriter 改 Opus(MediaCodec audio/opus encoder + MediaMuxer OGG) ~17× 压缩,~2 KB/s 实测(vs WAV 32 KB/s) 存储预算:8h 语音/天 → ~57 MB/天,~1.7 GB/月(vs WAV 51 GB/月) ffprobe 验证 codec_name=opus, channels=1, OggS magic OK - BootReceiver 监听 BOOT_COMPLETED / LOCKED_BOOT_COMPLETED / QUICKBOOT_POWERON,try-catch 兜底 Android 14+ FG service 限制 - MainActivity 加电池豁免行:dumpsys deviceidle whitelist 实时检测, 未豁免时给「申请豁免」按钮,触发系统 RequestIgnoreBatteryOptimizations 对话框;ON_RESUME 生命周期观察让从设置页回来 UI 自动刷新 - AndroidManifest: + REQUEST_IGNORE_BATTERY_OPTIMIZATIONS 权限 + BootReceiver 注册(含 3 个 boot 相关 action) HANDOFF.md §五 更新待办(v0.2 三项移到「已完成」),加存储预算 + Android 14 BOOT_COMPLETED caveat 段落(Pixel 8 Pro 实测能自动续上, 前提 app 至少开过一次 + RECORD_AUDIO 已授)。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
-
顶层「项目概述」从「lemonPCRM 单项目」改成「3 个互相关联但独立的 子项目(lemonPCRM / wechat_plain / lifelog-recorder)」总览表。 新增「lifelog-recorder」整章(位于「微信集成架构」与「注意事项」 之间),覆盖: - 项目目标(Pixel 当永远开机的麦克风,0 元 0 网络,下接 lemonPCRM 人脉图谱当个人记忆库) - 架构骨架 ASCII 图 + 技术栈摘要(Kotlin 2.1 / Compose / Room / sherpa-onnx) - 组件间数据流接口表 + 外部 HTTP/WS 接口 - 历史沿革 Round 1-3:全云 ASR(讯飞 80 元包根本不存在)/ whisper.cpp on-device(0.6× 跑不动)/ sherpa-onnx paraformer (20-32× realtime 真机实测,选定),含完整 RTF/质量对比表 - 关键坑摘要 6 条(asset mmap SIGSEGV / silero LFS 拦截 / 线程甜点 4 / AID_INET / keys.properties / 232MB 模型不进仓库) - 未来计划摘要(详细见 lifelog-recorder/HANDOFF.md §五) - 与 lemonPCRM 的关系(目前独立,v2 计划打通 GraphStore MENTIONED_IN 边) 细节避雷清单、命令速查、Pipeline 详图都不在此重复,全指向 lifelog-recorder/HANDOFF.md。CLAUDE.md 只勒住架构骨架 + 决策来源 便于每次 session 加载时快速建立心智模型。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
-
§零 环境铁律:whisper 别当默认 STT、模型别提交、asset 不走 mmap、 线程甜点是 4、AID_INET 必须传、keys.properties gitignored。 §二 核心文件清单:bot 实跑只看 RecorderService.kt + ParaformerEngine.kt + MainActivity.kt,sherpa-onnx Kotlin 绑定整目录原样塞别动。 §三 开发内循环、§四 pipeline 链路图、§五§六 优先级排序的 7 项待办、 §七 踩过的坑清单(VAD 资产 mmap SIGSEGV / silero_vad.onnx LFS 拦截 / paraformer t=8 反慢 / hf-mirror 截断 / am 启动非 exported 服务 / su 切 uid 丢 AID_INET)、§九 接手 prompt。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
-
Pixel 8 Pro 上的本地 lifelog 应用,v1 核心闭环: - 前台服务 (foregroundServiceType=microphone) + AudioRecord 16k mono PCM - Silero VAD on-device 切段(500ms 静音切、300ms 最短、30s 强切) - sherpa-onnx + paraformer-zh int8 本地 ASR Tensor G3 实测 ~25× realtime,<200ms 处理 1-10s 段 whisper-small 0.6× 跑不动,paraformer 是甜点选型 - Room (Segment + Transcript + Summary) + Compose 时间轴 LazyColumn UI - 232 MB 模型首次启动从 hf-mirror.com 下载到 app filesDir - 讯飞 中文识别大模型 WebSocket 客户端备用(云复核 fallback) 依赖: - sherpa-onnx v1.13.2 Android jniLibs (arm64-v8a, ~34 MB) - Silero VAD ONNX (~629 KB),sample.wav (3 MB) 内嵌资源 - AGP 8.7.3 / Kotlin 2.1 / Compose BOM 2024.12.01 / Room 2.6.1 后续待办(按用户语音里提到的优先级):声纹识别 / Opus 编码 / FTS5 中文搜索 / hermes-Kimi 纪要 worker。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
-
合并「先读哪三份 / 闭环四步 / 关键事实 / 先汇报再动手」为一条自包含 prompt。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
-
两个角色各一段可复用 prompt:claude-run(改代码,靠 .restart-request flag 自助重启)+ hermes(跑需要 root 的设备操作)。纳入 deploy-to-phone.sh DOCS 一起部署到 ~/hermes-wechat/。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
-
claude-run 跑在 proot、拿不到 Magisk root,杀不了 root 的 auto-reply 进程。 keepalive.sh 主循环新增:检测到 ~/hermes-wechat/.restart-request flag 就以 root pkill auto-reply 并删 flag,随后既有的 is_auto_reply_running 检测用新代码重启。 只暴露「请求重启」一个动作,不是任意命令执行。已端到端验证:u0_a327 touch flag → keepalive ~15s 内重启 bot。 - keepalive.sh: 纳入版本控制 + 加 flag 桥 - deploy-to-phone.sh: 收 keepalive.sh(.sh 给 755)+ 末尾加重启 keepalive 提示 - AGENTS.md / HANDOFF.md §零: 文档化 flag 重启途径 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
-
- 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>
-
问题:目标会话被大量群消息挤出可见 chat list → FSM CHAT_LIST 状态 findChatListRow 找不到 → 直接退搜索 → 搜索 UI 时序脆弱经常卡死。 方案 A(主):CHAT_LIST 找不到目标时,dispatchGesture 上滑列表 (540,1500→540,600)逐屏 scan,会话一定在列表里只是被挤下去。 - chatListScrolls 计数,上限 MAX_CHATLIST_SCROLLS=6 - 每条消息在 onReceive 重置 - 滚动不计入 transitionCount(用自己的上限防死循环) 方案 C(兜底):滚到上限还找不到才 performSearch 退搜索。 新增 scrollChatListDown(onCompleted) — 合成上滑手势。 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> -
- 角色(机器人本体 / owner)、门禁判定逻辑、授权交互(同意/拒绝) - .whitelist.json 格式 + 手动加白名单方法 - 引用回复授权的坑(type 57 appmsg,title + refermsg 提取) - 相关方法表 - TL;DR 加 step 0 白名单门禁;避雷清单加引用回复授权条目 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> -
- 群聊页 obn 标题带 (成员数) 后缀,equals 比对永远 false → strip 后再比 - LauncherUI fragment 容器 + CLEAR_TASK 不重置内部 fragment → 状态机 NO_CHAT_VIEW 优先点底部"微信"tab fallback BACK Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
-
症状:群里 @ 后,FSM 循环 CHAT_LIST→click→WRONG_CHAT→BACK 死磕, 最终撞 MAX_STATE_TRANSITIONS=6 cap 放弃。 根因:进群聊页后 obn (id=/obn) 文本是 "宜创AI全员群(60)"(带成员数后缀), 而 broadcast 传的 toUserDisplayName 是 "宜创AI全员群"(从 chatroom.displayname 查的,不带后缀),严格 equals 永远 false → 永远判定 WRONG_CHAT。 修法:classifyState 拿到 obn.text 后用正则剥掉末尾 (数字),再与目标做 equals 比对。两种格式都接受(带 / 不带后缀都能匹配)。 实测(22:23:00 后): classifyState: obn.text=宜创AI全员群(19) normalized=宜创AI全员群 FSM[0] state=TARGET_CHAT ← 一步到位(不再 BACK 死磕) Quote bubble matched [5] Long-press → 引用 → @ 火星人 → 写文本 → send 整条 3 秒走完 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
-
旧代码的 onReceive postDelayed 块里嵌套 if + ad-hoc relaunch 逻辑, 覆盖不到几种边界情形: - 微信停在非"会话"主 tab(通讯录/发现/我),CLEAR_TASK 不重置 LauncherUI 内部 fragment,导致 chat list 扫到 0 个 kbq 后只能 fallback 搜索 - 在错误聊天页(用户上次跟别人聊到一半),CLEAR_TASK 也保留 fragment - 在子页(搜索/创建群/朋友圈等),需要 BACK 才能退出 新实现:状态机 WeChatState ∈ {TARGET_CHAT, WRONG_CHAT, CHAT_LIST, NO_CHAT_VIEW, OUTSIDE_WECHAT} classifyState(root): O(1) 检测 bkk / obn / kbq / pkg.name → 5 状态 driveStateMachine(n): switch dispatch + postDelayed 递归 - TARGET_CHAT → performTypeAndSend (终态) - WRONG_CHAT → GLOBAL_ACTION_BACK → 再 classify - CHAT_LIST → findChatListRow + click cj1 → 再 classify (→ TARGET_CHAT) - NO_CHAT_VIEW → 优先 findBottomNavTab("微信") click(Y 最大者),fallback BACK - OUTSIDE_WECHAT → startActivity LauncherUI → 再 classify MAX_STATE_TRANSITIONS = 6 防死循环 新增辅助: findChatListRow(root, name): kbq strict equals → 回溯找 cj1 ancestor findBottomNavTab(root, text): findByText + 选 Y 坐标最大的 clickable 祖先 (区分底部 nav 与标题栏 / 联系人列表里的同名 text) 删除:enterChatViaListOrSearch (被状态机吸收) 实测(从群聊页起步,FSM[0]→[3] 共 8s): FSM[0] state=WRONG_CHAT target=火星人 → BACK FSM[1] state=WRONG_CHAT target=火星人 → BACK FSM[2] state=CHAT_LIST target=火星人 → click cj1 FSM[3] state=TARGET_CHAT target=火星人 → performTypeAndSend ✅ Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> -
- pgrep -f 在 su shell 下自匹配的 spawn 风暴 / 永远不重启二选一 - start-ar.sh dash wrapper 触发 phone 上 hermes-snap hook 污染 *.js 末尾 - HERMES_TIMEOUT 120s 在 turns=9 长 context 下偶发不够 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
-
#1 HERMES_TIMEOUT 120s → 180s Moonshot kimi-k2.6 在 turns=9 长 context 下 LLM call 可达 60s+, 旧的 2 分钟整体超时偶发不够,导致私聊 / 群聊回复直接 socket hang up。 keepalive.sh 内联 HERMES_TIMEOUT=180000 启动 node。 #2 keepalive 直起 node,绕过 start-ar.sh phone 上某个 claude-run/hermes 会话有 shell snapshot hook,每次 start-ar.sh 通过 dash 启动时触发它写 /data/local/tmp/hermes-snap-*.sh, Permission denied 错误被某种机制注入到 hermes-wechat/*.js 末尾, 下一次 node 加载就 SyntaxError 起不来。 修法:keepalive 用 `su -c 'cd X && env... nohup node auto-reply.js & echo $!'` 直接起 node,不经 dash 包装层。 #3 is_auto_reply_running 的两个 bug - 第一版 pgrep -f node.*auto-reply.js: regex 在 su shell cmdline 里 自匹配('su -c "..."' 包含 pattern),永远返 true,永远不重启 - 第二版用 PID 文件 + grep /proc/PID/comm: 但 echo $! 写的是 sh wrapper PID(comm=sh),不是 node 子进程 PID,永远返 false → 暴 spawn - 终版:ps -ef + grep -E node.auto-reply.js + grep -v sh.-c 精确匹配 "node auto-reply.js" 实进程(含空格),排除 sh wrapper line。 pattern 里用 . 占位避开嵌套引号问题(双引号外/单引号内/转义都崩) 实测:keepalive 起 1 次后稳定 45s+,loop tick 不再误重启; auto-reply.js 单实例(不再 4 个并行)。 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> -
症状:群没有自定义名字时 rcontact.nickname 为空,getContactNickname fallback 返 wxid(如 "57100316212@chatroom"),A11y 拿这个去搜索框 搜不到群,发送失败。 修复:rcontact 没拿到 conRemark/nickname 时,对 @chatroom 后缀的 wxid 额外查 chatroom.displayname,这是微信前端默认拼出来的群名 (如 "丁鹏、🍋火星人、火星人"),可以被 A11y 搜索定位。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
-
症状:群里 @小火子 发消息,auto-reply 完全没响应,日志里只有 [NOTIF] 没有 [GROUP-AT],但 DB 里这条消息确实存在。 根因:sqlcipher .mode line 输出多行 TEXT(如 message.content 是 "yibooo:\n@小火子 在吗")时长这样: content = yibooo: @小火子 在吗 createTime = 1779591001000 旧 _parseRows 看到第二行 "@小火子 在吗" 没有 ": " 或 " = " 分隔符, 就直接 IGNORED,导致 content 被截断成 "yibooo:"。isAtMe("yibooo:") 返回 false,replyType=null,handleMessage 静默 return(没 log)。 修复: - 引入 KNOWN_COLS 白名单(msgId/type/isSend/talker/content/createTime), 只在 key 是已知列名时才当作新字段,避免内容里碰巧带 "xxx = yyy" 误判为新字段 - 不匹配新字段的行作为上一字段 value 的续行追加(带 \n) - 添加单元测试式的语义注释解释 .mode line 输出格式 unit test (synthetic input): input: 'content = yibooo:\n@小火子 在吗\ncreateTime = 1779...' before: { content: 'yibooo:', createTime: '1779...' } ❌ after: { content: 'yibooo:\n@小火子 在吗', createTime: '1779...' } ✅ 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>
-
#17 群昵称查询: - android-wechat-tracker-sqlcipher.js:getContactNickname 删掉 if (wxid.endsWith('@chatroom')) return wxid 的早退分支 - 群(xxxx@chatroom)和私聊都走同一 rcontact 表查询,nickname 就是群名 - A11y 现在可以用群名搜索进入群聊 - 在 Pixel 已部署并重启 auto-reply (PID 18678) #18 wake-lock + keepalive 重写: - 通过 am startservice com.termux.service_wake_lock 启用 wake-lock (确认 dumpsys power 已持有 'termux:service-wakelock') - 重写 keepalive.sh 对齐当前部署: - 守护 4 项:wake-lock / A11y service / hermes gateway / auto-reply - hermes 走 termux native gateway 而不是 proot-distro Ubuntu(已淘汰) - 健康检查端口 8642(不再是 3010) - A11y 被关掉时自动 settings put secure 重新启用 - 在 Pixel 已部署到 ~/hermes-wechat/keepalive.sh #16 残留清理(仅 phone 操作,无 git 文件): - pkill -9 frida-server + rm -rf ~/.frida_queue/ - ~/.hermes/auth.json 删掉 credential_pool['kimi-coding'] 老 sk-kimi 条目 保留 anthropic + kimi-coding-cn Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> -
.gitignore 早就有这两条规则但文件已被跟踪,对已 tracked 文件无效。 git rm --cached 把 121 个 build artifacts 从 index 移除(保留磁盘文件)。 同时 ignore: - lsposed-wechat-sendmsg/local.properties (Android SDK 本地路径,包含个人机器信息) - lsposed-wechat-sendmsg/*.keystore (签名密钥,不应入库) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>