# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## 项目概述 本仓库(`aipcrm/`)的**主线 = `lifelog-recorder/`** —— Pixel 10 Pro Fold 上一个**完全端上、零云端**的 Local-First 个人记忆 + 人脉管理(Personal CRM)原生应用:24/7 录音 + 本地中文 ASR + 声纹/人脸识别 + 端上 LLM 纪要/对话 + 以「人」为中心的联系人档案 + 全相册语义索引。所有能力跑在设备上,**不依赖任何 Mac/PC 服务**。 | 子项目 | 路径 | 形态 | 状态 | |---|---|---|---| | **lifelog-recorder** | `lifelog-recorder/` | Pixel 原生 Android 应用(Kotlin + Compose) | **主线 v0.8** | | **phoneuse** | `phoneuse/` | Android MCP 工具层的设计文档 + Mac 侧 frida_bridge(实现代码已迁到 `device/`);现由 **dsh** 消费 | 并行实验,多数工具未验证 | | **device** | `device/` | **Pixel 上运行时代码的快照**(微信自动回复、手机 MCP、cron、日报链、wechat-viewer、dsh 配置) | 手机是主版本,见 §device/ | | **wechat_plain** | `wechat_plain/` + `xhs_lsposed_a16_fix/` | Pixel 上微信 bot:编排/审批走 `wechat_plain/`,实际发送 hook 源码在 `xhs_lsposed_a16_fix/wechathook_src/`(LSPosed 内部 API hook,非 A11y) | 辅助 | | **lemonPCRM** | `server/` + `frontend/` + `packages/` | Node.js + Express 后端 + 静态前端 | **已废弃** | > [!IMPORTANT] 方向已转向纯端上 App > 早期主线是 **lemonPCRM**(Mac 上 Node/Express + 知识图谱 + ACP 驱动 Claude CLI 的服务端 CRM)。 > 现已**废弃**:其 Personal CRM 能力已在 `lifelog-recorder` 里**原生端上重实现**(联系人档案、人脸/声纹识别、 > AI 印象、RAG 对话、照片语义检索),不再依赖 Mac。`server/` `frontend/` `packages/` 代码仅作历史留仓, > **新功能不要往里加,也不要把 app 回链到它**。App 与旧 lemonPCRM 的能力对比 / 仍存差距见下文 > §lifelog-recorder「App 即 Personal CRM:与旧 lemonPCRM 的能力对比」。 ### wechat_plain(端上微信 bot,辅助) > [!IMPORTANT] 发送链路已迁移,本节的 A11y 方案是历史记录 > **当前实际生效的发送链路已不是 A11y**,而是 `xhs_lsposed_a16_fix/`(源码 `wechathook_src/WeChatHook.java`,安装包名 `com.example.wechathook`)里的 LSPosed 内部 API hook:注入 `com.tencent.mm` 进程 → 广播 `com.hermes.wechat.SEND_MSG_INTERNAL` → hook 内部发送 API 入队 → 一次 HOME→前台切换(onResume)flush 送达确认。`device/pixelphone/wechat_reply/wechat_reply_orchestrator.py` / `device/pixelphone/event_bus/wechat_hook_send.py` 都走这条路,细节见 memory `微信 API 发送 onResume 派发`。下面的 `HermesAccessibilityService`(`lsposed-wechat-sendmsg` 模块,包名 `com.hermes.wechat.xposed`)**现在手机上已不装**,仅留作当年绕开反射/hook 失败时的方案记录,别照着这段去启用它。 Pixel 上微信发送/跟踪的落地,纯设备内运行(不依赖 Mac): - **~~发送消息(唯一可用链路)~~(历史,已被上面的内部 API hook 取代)**:`am broadcast com.hermes.wechat.SEND_MSG` → **`HermesAccessibilityService`**(lsposed-wechat-sendmsg 模块内)→ 启动微信 LauncherUI → 点搜索按钮 → 搜索 nickname(如"火星人")→ 点结果 → `bkk` 容器内 EditText 输入文本 → 点 `bql` 发送按钮。纯 A11y UI 自动化,绕开所有反射 / hook 失败。 - **已废弃的两条发送路径(命名误导,勿用)**:LSPosed 反射 API(`HermesWeChatModule.sendTextMessageDirect`,`Send result: true` 是误报,消息不进 `message` 表);Frida daemon(Frida 17 + 微信 8.0.72 注入大 bundle 触发反调试崩溃)。 > [!NOTE] A11y service 启用方式(历史记录,当前链路不需要) > `enabled_accessibility_services` 系统设置易被关掉,重启: > ```bash > 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'" > ``` 详见 `wechat_plain/` 内文档。编排脚本(orchestrator / watcher / policy)的当前版本在 `device/pixelphone/wechat_reply/`,`wechat_plain/scripts/auto-reply/` 只留文档。 ## device/(手机运行时快照;dsh 取代 Hermes,2026-10-17) > 手机是**主版本**,`device/` 只是快照:先在手机上改,再 `device/sync-device.sh` 拉回来 `git diff` / commit。不拉 `.db`/日志/`.env`/`.hermes_env.sh`,脚本末尾扫密钥,命中即中止。 **Hermes 已被 dsh(DeepSeek Harness,`@deepseek-ai/dsh`)取代,gateway 已下线**(`hermes` CLI 不可用,别再写调 `hermes chat/-z` 的新代码)。手机上的新布局: | 手机路径 | 内容 | |---|---| | `~/pixelphone/{event_bus,wechat_reply,scripts,digest,cron-logs}` | 手机 MCP(`android_mcp.py`)、微信自动回复(watcher→orchestrator→reply-sender)、cron 脚本、日报链(`digest/wechat-group-digest`、`digest/gpt-image-gen`) | | `~/dsh/` | dsh 安装(5 个 bionic 补丁 `patches/apply.cjs`)、`run-headless.sh`、`run-web.sh`(带 `no-phone.yml` 补丁)、`phone_lazy_mcp.py`(把 ~65 个手机 MCP 工具收成 `phone_list/describe/call` 三个元工具) | | `~/.dsh/skills/` | 保留的 ~47 个 skill(**必须平铺**,dsh 不递归嵌套目录);原 597 个 Hermes skill 仍在 `~/.hermes/skills/`,只待归档 | | `~/.dsh/profiles/{headless,web}/cordis.patch.yml` | 默认模型 `zai-coding/glm-5.3-flash`、persona(原 SOUL.md)、手机 MCP 接入 | | `/data/adb/service.d/{crond,dsh-web,wechat-reply-watcher}.sh` | Magisk 开机自启(Hermes gateway 相关脚本已挪到 `/data/adb/retired/`) | | Termux `crond`(`crontab -l`,见 `device/crontab.txt`) | 取代 Hermes cron:reply-sender 5min、viewer 看门狗 10min、学习管线、BOSS 简历 6h、gate-audit、**TGO 日报 01/02/03 点链**(`daily-digest.sh gen|img|send`)+ 每小时 TGO 预摘要 | **微信自动回复引擎**:member(闲聊)与 admin(任务)两个分支都走 `dsh headless`(`WECHAT_REPLY_ENGINE`,默认 `dsh`)。admin 读 `~/.dsh/skills/reply-task-executor`,手机 MCP 已放开;但 `WECHAT_REPLY_NO_DIRECT_SEND=1` 闸门仍会让 `wechat_hook_send.send_*` 直发被拒,**发送只能走 orchestrator**。不可信内容的任务(viewer 文章摘要)用 `--patch ~/dsh/patches/no-phone.yml` 关掉手机 MCP。 **别踩**: - 不要启动 runit 的 `crond` 服务(已 `down`),否则与 service.d 拉起的 crond 双跑,`reply-sender` 会重复发送。 - 看门狗清 `orch.lock` 的阈值必须 > `GEN_TIMEOUT`(600s),否则 admin 长任务会被第二个 drain 重复处理(已改为 900s)。 - 手机 Termux 对 `/sdcard` 无权限,涉及 `/sdcard` 的脚本要 `su -c`(BOSS 管线即如此)。 - 回滚点:手机 `~/premigrate-backup.tgz`(迁移前的 event_bus/wechat_reply/scripts 代码)。 ## phoneuse(Android MCP 工具层,并行实验;消费方已从 Hermes 换成 dsh) > 完整架构见 `phoneuse/ARCHITECTURE.md`,Sprint 任务 + 真机审计记录见 `phoneuse/TASKS.md`,产品 vision 原始稿见 `phoneuse/phoneuse.md`。本节只给定位 + 别踩的坑,细节进文件看。 ### 定位 把 Pixel 上**所有 App** 虚拟化成 **LLM agent runtime(原 Hermes Agent,现为 dsh,跑在设备 Termux 里)**可调用的数据源/能力源:App Runtime Access → Semantic Extraction → Memory Graph → Agent Planning,目标是替代"截图 + OCR + 点击"那种第一代 phone use。跟 lifelog-recorder 是**不同的项目脉络**,2026-06-23 才拍平合并进本仓库,目前两边没有打通。 > [!IMPORTANT] 不是零 Mac > lifelog-recorder 的"完全本地零云端"约束**不适用于 phoneuse 的 Frida 层**:`frida_bridge.py` 跑在 Mac 上,设备端 MCP 工具通过 `adb reverse tcp:8767 tcp:8767` 调用 Mac 上的 frida Python 库去 attach 设备进程。判断这部分能力时不要套用 lifelog-recorder 那套"零网络"假设。 ### 架构(两套并行实现,未合并) - **实现 A(Python/Termux,主链路已跑通)**:`notification_monitor.py`(root dumpsys 轮询) → `event_server.py`(FastAPI+SQLite 队列,:8765) → `android_mcp.py`(FastMCP stdio,Hermes 直连),独立工具模块各自 `register_xxx_tools(mcp)`:`ui_control_tools`/`device_tools`/`telephony_tools`/`ocr_tools`/`frida_inspect_tools`。 - **实现 B(Kotlin APK,大量 stub)**:`AgentAccessibilityService`/`AgentNotificationListenerService` 实时事件 → Unix socket → MCP HTTP/SSE(:8080);`frida-bridge` 模块是 Frida REST client 骨架。 ### MCP 工具现状:不要假设"写了就能用" `android_mcp.py` 注册 ~60 个工具。`TASKS.md` 2026-06-16 真机逐个审计结论:**真可用 11 / 降级 1 / 失效 23**,且明确写了"之前版本所有任务全标 `[x]` 是错的"。典型坑:`EVENT_BUS_DIR` 未定义连带 connector/persona/cloud 11 个工具 `NameError`;`_run_sqlcipher` 不支持参数化导致部分微信查询工具直接 `datatype mismatch`。改这块前先读 `TASKS.md` 的审计记录,别被勾选状态骗。 ### Frida 深度运行时层(Layer 5,"特种部队",只打高价值 App) 通用能力(**不需要逆向任何具体 App**): - `frida_call(package, script)` — 注入任意调用方自写的 JS。 - `inspect_foreground_intent(package)` — 枚举目标进程里所有存活 Activity,按 `hasWindowFocus()` 识别前台的那个,dump 它的 Intent(action/data/component/extras)。 - `dump_live_bitmaps(package, max_export)` — 枚举目标进程堆里所有存活 Bitmap,导出最大的 N 张为 base64 PNG。 字符串解密 / `sendMessage()` 之类具体方法 hook 是**per-app 逆向**,不通用——微信 8.x 重度混淆已经卡死过一次(`TASKS.md` S3-05/S3-06),明确排除在这层通用能力之外。 > [!IMPORTANT] `frida_bridge.py` 版本锁定 + 已知匹配 bug > Mac 端跑 `frida_bridge.py` 必须用 **`frida==17.9.10` + `frida-tools==14.8.0`**(`phoneuse/.venv`,`start_frida_bridge.sh` 一键起 `adb reverse` + 起桥)——更新版本的 `frida-tools` 对 `frida` 有严格版本 range 依赖,配不上设备上跑的 `frida-server 17.9.10` 会直接报错。 > 现有进程匹配(`req.package in p.name`)按子串匹配 frida 报告的人类可读进程标签,不是包名——微信主进程标签是 `"WeChat"` 不含包名子串,会回退匹配到无 UI 的 `:push` 子进程,导致**静默空结果**(不报错,看起来像目标 App 没有 Activity/Bitmap)。`inspect_foreground_intent`/`dump_live_bitmaps` 已对空结果加诊断提示,根因(`frida_bridge.py` 本身)未修——影响面会牵连 `frida_call` 其它调用方,留作后续单独修。 ### 已知阻塞 - 真机连续性差:开发时常掉 USB,涉及真机的验证要先确认 `adb devices` 能看到设备。 - 微信相关的 Frida/A11y 经验跟 `wechat_plain` 是分开积累的——`wechat_plain` 已验证过 Frida daemon 注入微信大 bundle 会触发反调试崩溃(见上文 §wechat_plain);`phoneuse` 走的是 USB attach 而非常驻 daemon,崩溃模式不一定一样,但动手前先看那段记录,别重新踩同一个坑。 ### Hermes Agent 运行时现状(**历史记录**,据 2026-06-24 一次性设备备份;Hermes 已于 2026-10-17 被 dsh 取代,gateway 下线) > 备份本身**不进 git**(含真实 API key + 真实微信消息原文,已加进根 `.gitignore` 的 `/hermes_backup_*/`),看完建议删掉或挪出仓库。这里只摘录"对以后开发有用"的事实,原始内容不再保留引用路径。 `android_mcp.py`(现位于 `device/pixelphone/event_bus/`)当时的真实消费方是**第三方框架 Hermes Agent**(Nous Research 出品,非本项目自研),跑在手机 Termux 里,靠 `~/.hermes/config.yaml` 的 `mcp_servers.android-phone` 一条配置连过来;驱动模型是 Kimi(`model.default: kimi-for-coding`,`base_url: api.kimi.com/coding`)。Hermes 自己又在 phoneuse 之外长出了一套 skills/plugins/cron,**会直接调用 phoneuse 的 MCP 工具**,值得记下来: - **`memory_graph.db` 确认在线增长**:备份快照里 1020 person / 1255 message / 518 vector,对比 `TASKS.md` 2026-06-16 审计时的 522/1205/510+——证明 `ingest_wechat_to_memory`/`embed_memory_messages` 这条链路在真实持续跑,不是一次性验证。 - **`wechat-image-sender` skill(2026-06-23 验证通过)补上了 phoneuse 自己没做的能力**:`send_wechat_message`/`mcp_android_phone_send_wechat_message` 只能发文字,发图片靠这个 skill 的 `am start -a android.intent.action.SEND -t image/png` 唤出微信"选择聊天"→OCR 找联系人(拼音兜底)→固定坐标 `(899, 2627)` 点发送,针对 1344×2992 屏幕全程验证可用。**以后要给 `wechat_send.py` 加发图能力,这条路子是现成验证过的,不用重新摸索**。该 skill 更早的笔记(`image_sending_limitations.md`)里提过想用 Frida bridge 做但因为"不在 Termux 里跑"放弃——这正是这次新增的 `frida_inspect_tools.py`/`start_frida_bridge.sh` 解决的那条链路,以后两边可以接上看看。 - **`wechat-group-digest` skill**:查 `EnMicroMsg.db` 群消息→提取公众号文章/讨论→Kimi 摘要→Pillow/GPT-Image-2 生成报纸风格信息图→调 `mcp_android_phone_send_wechat_message` 发文字摘要。绑了两个 cron job(每天 00:00 / 12:00),**截至备份时两个都报错失败**:`cannot import name 'env_float' from 'utils'`——hermes-agent 自身 Python 包版本问题,不是 phoneuse 代码的锅,升级 hermes-agent 应该能解,但别假设这两个 cron 在正常工作。 - **`feishu-lark` skill + `hermes-feishu` 插件**:飞书机器人通道(身份 "pixel phone agent"),跟微信是两条独立的消息平台,互不影响。 - **`VisualAgent` cron daemon**(每 5 分钟轮询 `/sdcard/VisualAgent/commands/` 执行 screenshot/tap/swipe/input/start_app):跟 phoneuse 自己的 `ui_control_tools.py` 是两套独立的 UI 自动化通道,目前没有合并,谁先谁后没有约定。 > [!NOTE] 备份不完整 > 备份自带的 `manifest.json` 列了 5 个 skill(含 `screen-wake`、`android-visual-automation`),但备份目录里实际只有 3 个(`wechat-group-digest`/`wechat-image-sender`/`feishu-lark`)——别假设这份备份是手机上 `~/.hermes/` 的完整镜像。 ## lifelog-recorder(Pixel 10 Pro Fold 24/7 录音 + 本地中文 ASR + 本地 LLM,v0.8) > 完整接手地图、踩坑清单、命令速查见 `lifelog-recorder/HANDOFF.md`。本节只勒住整个项目的**架构骨架 + 关键决策来源**,不重复细节。 ### 项目目标 把 Pixel 10 Pro Fold 当成**永远开机的麦克风**:7×24 后台录音 → VAD 切段 → 中文 ASR 转文字 → 入库 → 按时间轴显示和搜索 → LLM 总结成纪要。最终目的是把所有「我说过 / 别人对我说过」的内容沉淀成可搜索、可回放的个人记忆库,并在 **app 内自成端上 Personal CRM**(photolog):某段语音 → 声纹 → 联系人档案,照片 → 人脸 → 同一联系人。**整套 CRM 跑在设备上,已取代旧的 Mac 端 lemonPCRM**。 约束:**完全本地 / 零云端依赖 / 月成本 0 元 / 隐私走 0 网络**。 ### 架构骨架 **block-based diarization-first(2026-06-07 起,commit 07f3d22)**:弃掉「录一句·转一句·实时聚类」(短句 embedding 噪声大、实时滑动聚类污染 anchor → 声纹串人)。改为攒块 → 整块 diarize → 逐 turn 转写,从源头产出单说话人干净 embedding。**没有实时字幕**,文字在块关闭后出。 ``` Foreground Service (foregroundServiceType=microphone, WAKE_LOCK partial) └ AudioRecord 16k mono PCM16 (VOICE_RECOGNITION source, no AGC/NS) └ Silero VAD ONNX (500ms 静音切段 / 30s 强切) ── 段不再直接入库 └ BlockBuffer:speech-only PCM 攒「块」(filesDir/blocks//audio.pcm + manifest) 关块:静音≥5min 或 块≥10min ──Channel──> BlockProcessor (与采集解耦) └ DiarizationEngine: pyannote 分割 + campplus + 聚类 → turns[start,end,speaker] └ 每 turn: paraformer 转写 + campplus 干净嵌入 + SpeakerClusterer.assign 全局认人 └ Room SQLite (turn = 时间轴一行: Segment/Transcript/Summary/SpeakerProfile/Chat*) └ Compose 底部 4-tab (人物 / 录音 / 对话 / 设置);「录音」内含 4 顶部子 tab (录音 / 时间轴 / 声纹 / 纪要) ├ SummaryWorker:按时间间隔切「会话」→ 本地 llama-swap(8080) Qwen3.5-2B 出纪要 ├ ChatScreen:本地 2B/4B(llama-swap 8080) + Hermes(8642→Kimi) 多端点对话 └ photolog/CRM 层 (PhotologScreen/PersonProfileScreen/PhotologViewModel + vision/) 人物 tab = 「我管理的人物」;通讯录(微信+手机两源合并)是池子(勾选纳入);相册人脸标注/绑声纹自动入库 相册 tab = 全相册自动索引(MediaStore 全量):Tier-0 人脸/时间秒级铺 + Tier-1 VL 场景描述充电补 + 搜图(关键词/语义) ``` **声纹纠偏 L1+L2**(`SpeakerClusterer`):L1 = reclusterAll 和 assign 都**冻结人工命名 anchor 不漂移**(止住「秦子琛吸宜博→质心漂→吞更多」雪球);L2 = 重命名/合并时 `rebuildAnchor` 用该人**全部段均值**重建质心(多 anchor,取代单段注册)。用户流程:听一段 → 命名/合并 → 「重新聚类」。阈值默认 0.45(设置页 0.35–0.65)。CAMPPlus 在短中文段上区分度有天花板,详见 memory `lifelog-block-diarization-pipeline.md`。 > ⚠️ 旧的周期 `DiarizationWorker`/`DiarizationScheduler` 已删除(diarize 现在在块关闭时当场做)。WorkManager 每 worker 有 **10min 硬上限**,是把 diarize 放进常驻前台服务而非 WM worker 的原因。 **本地 LLM 栈(v0.6,零云端)**:Termux 自编 llama.cpp HEAD + llama-swap v223 监听 `127.0.0.1:8080`,按 model 名(`2b`/`4b`)热切换 Qwen3.5-{2B,4B}-Q4_K_M。纪要 worker + App 对话都打 8080;Magisk `service.d` 开机自启。完整部署/坑见 memory `pixel-local-llm-stack.md` + HANDOFF.md。 **photolog / 人脉 CRM 层(v0.7 新增)**:在录音之外把 lifelog 长成端上 mini-CRM。`PhotologScreen`(人物 tab + 相册 tab)、`PersonProfileScreen`(联系人档案:tab 式 + 记录搜索 + 微信聊天/朋友圈/AI 印象 + 「🎤 录一段绑定 TA」当场录声纹)、`PhotologViewModel` 统管。**两个人物池分离**:「我管理的人物」(CRM 关心的人,人物 tab 显示)vs「通讯录全部联系人」(池子,选人页里勾选纳入)。池子有**两个来源**:微信(Mac 推的 enroll 数据)+ 手机系统通讯录(`READ_CONTACTS` → `ContactsContract`,contactRef `phone:`,按手机号/姓名跨源自动合并);`Person` 还存手填 `realName`(真名)/`resume`(简历),档案页可编辑。`vision/`(`FaceEngine`/`FaceMatcher`/`PhotoProcessor`/`PhotoScheduler`/`PhotoFaceWorker`/`PhotoVlWorker`/`PhotoVlState`/`GalleryScanner`/`VisionClient`/`EmbedClient`/`WeChatEnrollWorker`)做相册扫描 + 人脸聚类 + 微信头像锚点入库;微信头像配方与「锚点 embedding 必须端上算」见 memory `wechat-avatar-face-enrollment.md`。声纹绑人 ↔ 人脸标注 ↔ 微信联系人三者打通到同一 `Person`。 **全相册自动索引 / 分层管线(v0.8 新增)**:把手机**整个相册**自动跑成可搜索内容。核心矛盾:本地 VL(Qwen2.5-VL-3B)真机 **~30-40s/张**,几千张全量 VL 按天计 → **分层**。`GalleryScanner` 游标遍历 MediaStore 全量,**只存 `content://` uri 不拷文件**(拷一份撑爆私有目录),`mediaId` 去重增量;`photos` 加 `source`/`mediaId`/`scanState`/`captionEmbedding`(Room **v14**)。**Tier-0 `PhotoFaceWorker`**(15min,battery-not-low):扫描 + ML Kit 人脸 + 512d emb + GPS,秒级全量铺好,立刻按人/时间搜;**Tier-1 `PhotoVlWorker`**(15min,**+charging**):Qwen-VL 场景描述 + caption 语义向量,优先级队列(有人脸>近期),慢慢补。`scanState`:`0 待索引→1 人脸已铺→2 已出描述`;imported 源(手动导入)走同管线,只是 `decode` 走 file path。**搜图**:关键词(caption+人名 LIKE,中文子串比 FTS4 靠谱)+ 语义(`EmbedClient` 打 llama-swap `embed` 模型出 query 向量 → 全库 caption 向量余弦 top-K;没配 embed 模型则优雅降级,只用关键词)。**VL 控制 `PhotoVlState`**(singleton,paused 持久化):相册页运行中显「暂停/停止」、空闲显「立即理解/继续」,正在 VL 的格子转圈,格子角标 待索引/待理解/已描述;暂停连充电时的周期 worker 也尊重。详见 `HANDOFF.md` §四 photolog 段。 技术栈:Kotlin 2.1 + Compose BOM 2024.12.01 + Room 2.6.1 + WorkManager 2.10 + OkHttp 4.12,AGP 8.7.3,minSdk 30 / targetSdk 35(Android 14 强制 `foregroundServiceType` 起步),arm64-v8a 单 ABI。 ### 接口 **内部组件接口**(pipeline,详见 `lifelog-recorder/HANDOFF.md` §四): | 上游 | → | 下游 | 数据形式 | |---|---|---|---| | `AudioCapture` | SharedFlow | VAD | `FloatArray` 30ms 帧 (512 samples) | | `Vad.acceptWaveform()` | poll | `Vad.front()` / `pop()` | `SpeechSegment(start, samples: FloatArray)` | | `BlockBuffer.append()` | sync | filesystem | `filesDir/blocks//{audio.pcm, manifest.json}`(speech-only PCM + run 偏移) | | `BlockBuffer.close()` | Channel | `BlockProcessor.process()` | 块目录(关块条件:静音≥5min 或 块≥10min) | | `DiarizationEngine.diarize()` | sync | — | `List`(pyannote 分轮) | | `ParaformerEngine.transcribeFloat()` | sync | — | `OfflineRecognizerResult(text, tokens, timestamps, lang, emotion, event)`(逐 turn) | | `SegmentWriter` | sync | filesystem | `filesDir/segments/YYYY-MM-DD/HH-MM-SS-MS.opus`(turn 切片) | | `Repository.segments/transcripts` | suspend | Room | DAOs in `data/Daos.kt`(turn = 一行) | **外部接口**: - `ModelDownloader` → `https://hf-mirror.com/csukuangfj/sherpa-onnx-paraformer-zh-2023-09-14/`(断点续传,fallback huggingface.co) - `XfyunIatClient` → `wss://iat.xf-yun.com/v1`(讯飞大模型,**仅作云复核 fallback**) - 未来 Summary Worker → `http://127.0.0.1:8642/v1/chat/completions`(hermes gateway 转发到 Kimi) **对外暴露的 IPC**:`RecorderService` 仅做自启/暂停(`startForegroundService` / `stopService`),状态走 `RecorderState` (StateFlow) 推给 UI;没有 binder/广播接口。 ### 历史沿革 / PoC 失败与成功记录 > 这一节是**为什么选了 paraformer 而不是 whisper / 云 ASR** 的完整溯源。后面 session 想换技术栈前必读。 **Round 1 — 全云 ASR 路线(弃)**:最初想用「VAD 段 → 上传讯飞 / 百度 / Groq Whisper」流式 ASR。讯飞「中文识别大模型」WebSocket 客户端已经写完(`asr/XfyunIatClient.kt`,鉴权 + 三段帧协议 + 增量结果解析),但发现按次计费下 24/7 用量会冲到月 5000+ 元。「讯飞流式听写区间包 80 元/月 unlimited」是**消费端「讯飞听见」App 的卡,不是开发者 API**——这是用户和我都踩过的坑。 **Round 2 — whisper.cpp on-device(弃)**:在 Pixel 8 Pro Termux 编了 whisper.cpp + NEON 优化,跑 95s 中文 TTS sample: | 模型 | 线程 | RTF | 速度 | 中文质量 | |---|---|---|---|---| | ggml-tiny.bin | 4 | 0.26 | **3.8× realtime** | ❌ 输出乱码「战亡未来一年」,中文不可用 | | ggml-small-q5_1.bin (181 MB) | **8 (最快)** | 1.66 | **0.60× realtime** | ✓ 转写正确 | | ggml-small.bin (fp16, 465 MB) | 4 | 1.76 | 0.57× | 同 q5_1(量化省的全被 ARM dequant 吃回) | **结论**:whisper-small **任何配置都 < 1× realtime**,连续录会越积越多。tiny 够快但中文崩。**on-device whisper 路线直接砍掉**。 **Round 3 — sherpa-onnx + 中文专门优化模型(采用)**:阿里 DAMO 的 paraformer-zh 跟 SenseVoice 都是 ONNX 部署、Android 友好。同样 95s sample: | 模型 | 测试环境 | RTF | 速度 | 备注 | |---|---|---|---|---| | paraformer-zh int8 (232 MB) t=4 | proot Ubuntu | 0.068 | **14.7× realtime** | 选定 | | paraformer-zh int8 t=8 | proot | 0.099 | 10.1× | 8 线程反而慢,X3+A715 协调开销 | | SenseVoice int8 (228 MB) t=4 | proot | 0.092 | 10.9× | 带情绪/事件 metadata,但慢 | | **paraformer-zh int8 t=4** | **真机 Android app** | **0.048** | **20.94× realtime** | proot 摘掉后再快 43% | | **paraformer-zh int8 t=4** | **真实活语音(用户对手机说)** | — | **20-32×** | 1-10s 段,~50-300ms 转写 | **结论**:on-device paraformer 完胜。月成本 0 元,延迟 <300ms,隐私零网络。讯飞客户端代码留作云复核 fallback。 ### 关键坑(接手必读) 1. **不要把 .onnx 当 asset mmap**:sherpa-onnx v1.13.2 native code 对 asset 压缩兼容差,silero VAD 直接 SIGSEGV。已加 `androidResources.noCompress.add("onnx")` 但保险起见在 `RecorderService.runPipeline` 里把 VAD 模型从 assets 拷到 `filesDir` 后再用 file path 加载。 2. **silero_vad.onnx 别从 snakers4 的 GitHub raw 拉**:会被 LFS 重定向成 HTML。用 `https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/silero_vad.onnx`。 3. **Tensor G3 paraformer 线程甜点 = 4,不是 8**。 4. **AID_INET (3003) gotcha**:跨 root + adb 调试 Termux 用户态时必须 `su -G 3003 10288 -c "..."`,否则 DNS 全断。详见 `~/.claude/projects/-Users-yibo-Documents-aipcrm/memory/magisk-su-android-inet-group.md`。 5. **`keys.properties` gitignored**:讯飞凭证从这里读到 `BuildConfig.XFYUN_*`。模板 `keys.properties.example`。 6. **232 MB paraformer 模型不进仓库**:首次启动从 hf-mirror.com 下到 `app.filesDir/models/paraformer-zh/`。dev 加速可以预先 `adb push` 进去(HANDOFF.md §三)。 ### 未来计划(按优先级) 完整待办、估时、技术路线见 `lifelog-recorder/HANDOFF.md` §五。 ✅ **v0.2–v0.9 已完成**:Opus 编码 / BOOT 自启 + 电池白名单 / FTS4 中文搜索 / 段播放 / **声纹在线聚类** / **会话级纪要(按时间间隔切会话)** / **底部 4-tab + 子 tab UI** / **App 内对话(本地 2B/4B + Hermes)** / **本地 LLM 栈(llama-swap)** / **Room 迁移(去 destructive)** / **block-based diarization-first 录音管线(v0.7,攒块→整块分轮→逐 turn 转写)** / **声纹纠偏 L1 冻结 anchor + L2 多 anchor 重建** / **设置页(`SettingsScreen`:纪要模型后端 / 聚类阈值 / llama-swap 端点 / 电池豁免)** / **photolog 人脉 CRM 层(人物档案 + 相册人脸 + 微信通讯录/朋友圈/AI 印象)** / **存储 TTL(超期删 .opus,文字永留)** / **全相册自动索引(v0.8:MediaStore 全量扫描 + Tier-0 人脸/Tier-1 VL 分层管线 + 关键词/语义搜图 + VL 暂停/停止控制,Room v14)** / **微信抽取完全端上化(v0.8 P2 #6:SQLCipher key 端上推导 + 联系人/头像/聊天/朋友圈全不依赖 Mac)** / **Transcript→Person 提及关系(v0.9 P0 #5:词典反查 + MentionScanner 实时扫 + mentions 表 + 「被提到」tab)** / **人↔人共现边(v0.9 P1 #1:RelationBuilder + relations 表 + 「相关」tab + CommunitiesScreen)** / **待跟进 & 话题实体(FollowupExtractor + TopicExtractor,4B LLM 抽取)**。 剩余摘要: - ⭐ **语义搜图落地**(代码已完成:`EmbedClient` + `SettingsScreen` embed 字段;**需在 Pixel llama-swap config.yaml 加 embed 模型**才生效,中文 GGUF 如 bge-small-zh,参见 HANDOFF.md §七 调试陷阱) - ✅ **内部 Transcript 实体 → Person 表**(已完成:`crm/MentionExtractor.kt` + `MentionScanner` 词典反查 + `BlockProcessor` 实时扫 + `PersonProfileScreen` 「被提到」tab + `RelationBuilder` 共现边) - ✅ **微信抽取端上化**(已完成 2026-06-10:SQLCipher 端上解密 + 联系人/头像/朋友圈全端上,不依赖 Mac) ### App 即 Personal CRM:与旧 lemonPCRM 的能力对比 App(photolog)已覆盖、并在「联系人」维度超过旧 lemonPCRM 的核心: | 能力 | 旧 lemonPCRM(Mac/server) | 现 app(端上) | |---|---|---| | 联系人实体 | `Person` 图节点 | `Person`(融合 **声纹/人脸/微信/手机** 四模态,更丰富) | | 多源导入 | 微信 / LinkedIn / X / 会议 / 照片 | 微信 + 手机通讯录 + 手动 + **24/7 录音(独有)** | | 每人聚合视图 | 社媒动态 / 纪要 / 文件 / 照片 | 聊天 / 朋友圈 / 照片 / **录音** / AI 印象 / 简历(`PersonProfileScreen` 四 tab) | | AI | Master Agent over 图谱(工具编排) | RAG 对话 over 转写/纪要(`LifelogRetriever`)+ AI 印象 | | 照片 | 人脸聚类 + CLIP 检索 | 人脸聚类 + 语义检索(端上 ML Kit + ArcFace + Qwen-VL + embed) | | 部署 | Mac Node 服务(依赖 Claude CLI) | **纯端上**(更隐私 / 零成本 / 零网络) | **仍存差距**(旧 lemonPCRM 有、app 还没有): 1. **人↔人关系图**:app 只有 *人↔模态*(声纹/人脸/微信/手机)链接,**没有 person↔person 边**(KNOWS / 同事 / 同项目)。app 现在是「带富资料的扁平联系人表」,不是知识图谱。 2. **Organization / Project / Event 实体**:app 只有 `Person`/`Photo`/`Face`,没有组织/项目/事件节点。 3. **关系分析**:旧有 关系路径(`path`) / 社群发现(`communities`) / 待跟进(`followups`);app 三者皆无。 4. **AI agent / 工具编排**:app 是**纯 RAG 对话**,AI 不能对数据做动作(建人 / 连边);旧有 Master→Sub-Agent + 图谱工具(`create_person`/`link_persons` 等)。 5. **Transcript 内容 → 实体**:app 只按**声纹**把录音连到 `Person`(谁说的),**没有从转写文本抽人名/项目**做关联(旧 lemonPCRM 的 `MENTIONED_IN`)——这正是下一步要补的。 6. **微信抽取端上化**:微信联系人/头像/聊天/朋友圈仍由 **Mac 一次性抽取**(解密 `EnMicroMsg.db` + 抠头像)推到 `filesDir/wx_enroll/`,再由 `WeChatEnrollWorker` 端上入库。手机系统通讯录已完全端上;微信抽取是「纯 App」最后一块拼图。 ### 下一步:Transcript 实体 → 内部 Person 表(取代旧「回链 lemonPCRM」) **不再回链外部 server**,改在 **app 内部**打通:从 `Transcript.text` / `Summary` 抽实体(人名/项目/地点,端上 LLM 或规则 NER)→ 与本地 `Person` 表匹配 → 建一张「提及」关系(新表 `mentions(segmentId, personId, ...)` 或 Person↔Segment 边)。让 `PersonProfileScreen` 除了现有「TA 自己说的」(声纹匹配段)之外,还能显示「**TA 被哪些录音提到、说了什么**」。这直接补差距 #5;同段被同时提到的人 → 共现边,又为差距 #1(人↔人关系图)打基础。 > 上面 6 项差距的**可执行 TODO 清单(带优先级 P0–P3 + 做法)**见 `lifelog-recorder/HANDOFF.md` §「端上 CRM 补齐 — TODO」。路线:P0 #5 → P1 #1 → P2 #6。 ## 注意事项 - **构建**:`cd lifelog-recorder && JAVA_HOME=/opt/homebrew/opt/openjdk@17 ./gradlew :app:assembleDebug`,`adb install -r ...`。无测试框架。改 Room schema **必须写 Migration**(`AppDatabase` 已去 destructive 改 fail-loud;24/7 录音库静默清掉是大事故,见 memory `lifelog-room-migration-wipe.md`)。 - **改完即真机验证**:app 是 24/7 录音的命脉,改 `RecorderService` / 管线后务必装机看 logcat(`reconcile →` / `pipeline running` / `BlockProcessor`)确认录音没断。 - **`keys.properties` / `local.properties` gitignored**:讯飞凭证 + Hermes 端点 → `BuildConfig`;`sdk.dir` 换机要改。模板见 `keys.properties.example`。 - **`server/` `frontend/` `packages/`(旧 lemonPCRM)已废弃**:不再维护、不要往里加功能、不要让 app 依赖它。 > [!NOTE] 旧 lemonPCRM 的服务端架构(ACP 四层 / Master→Sub-Agent / GraphStore / 微信 server 链路)已从本文件移除。 > 如确需考古,查 git 历史里本文件的早期版本。