• 以「人」为中心融合身份的相册基础(声纹那套的人脸版):
    - data:persons/photos/faces 三表 + 增量迁移 v3→v4(DDL 对过 Room,真机零数据损失)
    - vision/FaceEngine:ML Kit 检测+5点 → 相似变换对齐 → TFLite MobileFaceNet 512维
      embedding(同人 0.976/异人 0.127,embed 44ms)。**不用 onnxruntime-android**——
      它和 sherpa 自带 libonnxruntime.so(自定义符号版本)无法共存,会弄坏音频
    - vision/VisionClient:经 llama-swap 8080 的 vl 端点(Qwen2.5-VL-3B)出场景描述,只描场景不报人名
    - vision/PhotoProcessor:人脸+embedding+VL+EXIF → 更新 photos 行 + 写 faces + 匹配 Person
    - vision/PhotoWorker + PhotoScheduler:导入只瞬时登记,重活交后台 worker 定时/闲时处理
      (15min periodic + 导入触发 runNow,单轮 7min 预算,防并发),不阻塞导入
    - vision/FaceMatcher:标注一张脸 → 回填其它照片同一人(同声纹回填)
    - ImageUtil:EXIF 时间/GPS、按方向解码、降 512px 喂 VL、embedding 打包
    - 模型 w600k_mbf.tflite(6.5MB) 进 assets(实测还原 onnx cos=1.0)
    
    Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
    yibo authored
     
    Browse Code »
  • 解决 v1 的说话人爆炸(98)+ 内存崩溃:
    
    - 不再每会话贪心建全局说话人。改为:diarization 给每段存"干净"的
      dominant-turn embedding(覆盖实时的混合 embedding),run 末尾跑一次
      SpeakerClusterer.reclusterAll 做全局聚类(本就全局 + 保留人工命名锚点)。
    - 修 OutOfMemoryError:拼接会话音频原用 ArrayList<Float>(每 float 装箱 16B,
      6min 会话 ~92MB 垃圾)→ 改 List<FloatArray> 一次性 copy;AudioDecoder
      的 ArrayList<Short> → ByteArrayOutputStream。MAX_CONCAT 18→10min 留余量。
    
    注:Daos.SegmentDao.updateEmbedding 这一行(diarization 需要)未在此 commit,
    随 photolog WIP 一起;提交 photolog 时带上即可。
    
    实测:无 OOM;但说话人仍 89——因单轮 7min 预算只 diarize 了 3/14 会话,
    其余仍是脏 embedding。periodic 多跑几轮全部 diarize 后才会真正收缩。
    有声书测试数据本就对声纹是地狱级,真实多人数据 + 阈值微调是正路。
    
    Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
    yibo authored
     
    Browse Code »
  • 逐 VAD 段单 embedding 的实时方案无法处理「一段几秒内多人说话」(混合 embedding=噪声)
    + 在线贪心聚类过度合并。改为会话级批处理重跑(与纪要 worker 同构):
    
    - DiarizationEngine:sherpa-onnx OfflineSpeakerDiarization(pyannote 分割检测
      说话人变更点+重叠 → CAMPPlus embedding → FastClustering 自动聚类)。
    - AudioDecoder:.opus(Opus-in-Ogg) → 16k mono PCM(MediaCodec,含 48k→16k 重采样 + 下混)。
    - DiarizationWorker:每个已结束/未 diarize 会话 → 解码拼接 → diarize → 局部说话人
      桥接全局 SpeakerProfile(复用 SpeakerClusterer)→ 段按重叠最大 turn 重标。
      AtomicBoolean 互斥 + 7min 预算 + 进程内 idempotency(prefs)。
    - DiarizationScheduler:periodic 15min(UPDATE) + 「现在跑一次」一并触发。
    - ModelDownloader.ensurePyannoteModel:首启下 pyannote-seg(~6MB)。
    
    实测端到端通:56 段会话 → 44 turns、检测出多说话人(核心能力达成)。
    已知待调(非 bug):
    - 测试数据是有声书(一人配几十角色),对声纹是地狱级输入 → 全局说话人偏多(98)。
      需在真实多人录音上验证;threshold 走设置页可调。
    - 全局身份桥接现为贪心 match-or-create,易累积;v1.1 应改全局重聚类。
    - 段内多说话人 v1 先归主导 turn,不拆分+重 ASR(v2)。
    
    Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
    yibo authored
     
    Browse Code »

  • - HermesAccessibilityService: 折叠态封面屏(1080×2364)下 A11y 节点 bounds 全 0、
      ACTION_CLICK/SET_TEXT 失效,改用固定坐标 dispatchGesture 全流程(搜索图标 →
      设剪贴板长按粘贴 → 首条结果 → IME 剪贴板候选插入 → 绿色发送按钮),
      删 ~750 行旧反射/节点路径(-749/+74)。
    - setup_termux_boot.sh: frida-server 路径挪到 Termux 家目录(~/hermes-wechat/bin),
      弃用 /data/local/tmp(Termux 路径约定)。
    - server/services/wechat/wechat-contacts.{json,md}: 从 EnMicroMsg.db rcontact 导出
      5423 联系人(发送时确定真实 wxid 的权威来源)。
    - wechat_plain/scripts/frida-daemon/: Frida 调试脚本(已废弃发送路径,留作参考)。
    
    Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
    yibo authored
     
    Browse Code »
  • - LifelogRetriever:「lifelog 模式」开关下,发送前本地检索纪要 + 转写拼成
      system context 注入(本地 2B/4B 和 Hermes 通用,不落库)。中文用 bigram +
      重叠打分召回(FTS4 unicode61 对中文短语不命中),命中多者优先、抑制单 bigram 噪声
    - Daos:加 timelineList / searchTextLike / summaries.recentList 三个 suspend 检索方法
    - ChatViewModel:会话状态搬进 VM(随 Activity 存活)。修复切 tab 回来:
      流式请求被取消(viewModelScope 不取消) / 变成新会话 / lifelog 开关重置——
      currentConvId / selectedTargetId / lifelogMode 均持久化到 prefs,重启也记住
    - 键盘 inset:Activity setDecorFitsSystemWindows(false) + manifest adjustResize
      + Scaffold imePadding,键盘弹出时整个界面(含底部 tab)抬到键盘上方、可见
    
    Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
    yibo authored
     
    Browse Code »
  • 只有结果 list 看不出 worker 有没有正常跑(periodic 经常 0 篇——会话没结束)。
    加一条持久化执行记录,确认「几点几分跑了、几个会话→几篇」。
    
    - SummaryRunLog:singleton + lifelog_prefs(JSON) 存最近 50 次执行;
      StateFlow 供 UI 实时刷新,prefs 保证重启不丢。
    - SummaryWorker:每次 doWork 跑完(含 0 段早退)记一行 Run(ts/made/skipped/
      total/secs/backend/trigger);trigger 区分 auto(periodic)/manual。
    - SummaryScheduler.runNow:标 trigger=manual。
    - SummariesTab:折叠「执行记录(N)· 上次 HH:mm:ss → M篇」,展开看历次。
    
    验证:手动触发 → prefs 记 {made:0,skipped:9,total:9,trigger:manual} → UI 正常显示。
    
    Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
    yibo authored
     
    Browse Code »
  • 新增第 6 个 tab「设置」(SettingsScreen.kt),把散落各处的配置收进一页:
    - 纪要:会话间隔 / 自动跑周期(改完即重排 WorkManager) / 会话最短语音 / 后端模型(2B/4B/Hermes)
    - 声纹:聚类阈值(0.45–0.65)
    - 本地模型端点:baseUrl / 2B / 4B model 名(ChatConfig)
    - 录音/电池:白名单状态 + 申请
    
    配套把写死的参数改成可配:
    - SummaryWorker:KEY_PERIOD_MIN / KEY_MIN_SPEECH_SEC,min-speech 从 prefs 读
    - SummaryScheduler:周期从 prefs 读(默认 15,硬下限 15)
    - SpeakerClusterer:thresholdFromPrefs(),RecorderService 的 live 聚类器 +
      SpeakersTab 的 reclusterAll 都用 prefs 阈值
    
    Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
    yibo authored
     
    Browse Code »
  • - §六 加最易混淆点:纪要 WorkManager periodic 每 15min(Android 硬下限,
      改间隔须 UPDATE 策略)自动扫已结束会话;UI「会话间隔 10min」是切分规则,
      与跑纪要频率无关。附 502 并发坑的解法(AtomicBoolean + 7min 预算 + 不 retry)。
    - 文件清单:SummaryScheduler/Worker 描述同步(互斥/预算/15min)+ 补 SummaryProgress。
    - 纪要后端接线改为「UI 可选 2B/4B/Hermes,经 ChatConfig 解析」。
    
    Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
    yibo authored
     
    Browse Code »
  • 之前 periodic 是 1 小时一次,体感像「不自动跑」。注意 UI 里的「10 分钟」
    是会话切分间隔(多长静音算另一场),不是跑纪要频率,两者无关。
    
    - 周期 1h → 15min(WorkManager periodic 硬下限,做不到正好 10min)。
    - ExistingPeriodicWorkPolicy KEEP → UPDATE:否则旧 1h 排程不被新间隔覆盖。
    - worker 已自带并发互斥 + 7min 时间预算,15min 频繁触发不会堆积。
    
    验证:jobscheduler 显示 SummaryWorker 下次 ~14min 后触发。
    
    Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
    yibo authored
     
    Browse Code »
  • 实测:periodic + one-shot + RETRY 死循环并发打同一个 2b 上游 → llama-swap
    proxy context-canceled → 502,纪要永远生成不出来(console 卡「运行中」)。
    
    - 进程内 AtomicBoolean 互斥:同时只跑一个 worker body(enqueueUniqueWork
      只能去重同名 one-shot,挡不住 periodic)。
    - 单轮时间预算 7min:超了停、剩余会话下轮继续,避开 WorkManager ~10min
      执行上限被中途 kill(→ context canceled → 502)。
    - 失败不再 Result.retry():未处理会话靠下一轮 periodic / 手动重跑捡,
      不再 hammering。
    
    验证:单 worker 跑 56 段会话,llama-swap 返 200,本地 2B 纪要落库。
    
    Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
    yibo authored
     
    Browse Code »
  • - SummaryWorker: 后端从 SharedPreferences 读 ChatTarget.id(local-2b/4b/hermes),
      经 ChatConfig 解析端点/model(与对话共用配置),不再写死 2B。
    - SummaryProgress:新增 worker→UI 进度 singleton(同进程 StateFlow),
      worker 每步发 line(开始/会话生成中/✅完成 Ns/❌失败/跳过统计)。
    - SummariesTab:加「纪要模型」下拉(2B/4B/Hermes,存 prefs)+ 进度 console
      (滚动框,最新在上)+「现在跑一次」运行中转圈禁用。
    
    Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
    yibo authored
     
    Browse Code »
  • - 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>
    yibo authored
     
    Browse Code »
  • - 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>
    yibo authored
     
    Browse Code »
  • - 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>
    yibo authored
     
    Browse Code »
  • - SummaryScheduler.runNow 改 enqueueUniqueWork(KEEP):在跑/排队时重复触发直接忽略,
      避免并发跑出重复纪要
    - SummaryWorker:单篇喂 LLM 的转写条数上限 150,超长会话均匀抽样(覆盖整段、时间戳
      保真),防止 prompt 撑爆上下文 / 端侧 prefill 过慢
    
    Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
    yibo authored
     
    Browse Code »
  • - 首页 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>
    yibo authored
     
    Browse Code »
  • 记录 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>
    yibo authored
     
    Browse Code »
  • - 声纹方案 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:mm:ss] 来源时间戳,点击跳回时间轴对应段并高亮(含跨午夜处理)。
    
    Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
    yibo authored
     
    Browse Code »

  • 新增 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>
    yibo authored
     
    Browse Code »
  • 把顶层平铺的脚本按功能归入子目录,并清理可重新生成的产物:
    
    - 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>
    yibo authored
     
    Browse Code »
  • 设备侧 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>
    yibo authored
     
    Browse Code »
  • 闭环 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>
    yibo authored
     
    Browse Code »
  • 声纹(用户语音里提了 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>
    yibo authored
     
    Browse Code »

  • 让 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>
    yibo authored
     
    Browse Code »
  • 顶层「项目概述」从「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>
    yibo authored
     
    Browse Code »
  • §零 环境铁律: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>
    yibo authored
     
    Browse Code »
  • 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>
    yibo authored
     
    Browse Code »

  • 合并「先读哪三份 / 闭环四步 / 关键事实 / 先汇报再动手」为一条自包含 prompt。
    
    Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
    yibo authored
     
    Browse Code »
  • 两个角色各一段可复用 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>
    yibo authored
     
    Browse Code »
  • 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>
    yibo authored
     
    Browse Code »
  • - 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>
    yibo authored
     
    Browse Code »
  • 问题:目标会话被大量群消息挤出可见 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>
    yibo authored
     
    Browse Code »
  • 症状: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>
    yibo authored
     
    Browse Code »
  • #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>
    yibo authored
     
    Browse Code »
  • 症状:宜创群授权后的 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>
    yibo authored
     
    Browse Code »
  • - 角色(机器人本体 / owner)、门禁判定逻辑、授权交互(同意/拒绝)
    - .whitelist.json 格式 + 手动加白名单方法
    - 引用回复授权的坑(type 57 appmsg,title + refermsg 提取)
    - 相关方法表
    - TL;DR 加 step 0 白名单门禁;避雷清单加引用回复授权条目
    
    Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
    yibo authored
     
    Browse Code »
  • 症状:"同意 宜创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>
    yibo authored
     
    Browse Code »
  • 需求:不再对任何人自动回复,只有白名单的群/私聊才自动回;
    非白名单的发信人,先转给主人(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>
    yibo authored
     
    Browse Code »
  • 代码(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>
    yibo authored
     
    Browse Code »

  • 旧路径:/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>
    yibo authored
     
    Browse Code »