CLAUDE.md 18 KB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

项目概述

本仓库(aipcrm/)现在托管 3 个互相关联但独立的子项目:

子项目 路径 形态 状态
lemonPCRM server/ + frontend/ + packages/ Node.js + Express 后端 + 静态前端 主线
wechat_plain wechat_plain/ Android Xposed/A11y 模块 + Pixel 部署脚本 主线
lifelog-recorder lifelog-recorder/ Pixel 原生 Android 应用(Kotlin + Compose) v0.6 (2026-06)

lemonPCRM — lemonAI 出品的 Local-First AI 个人人脉管理系统(Personal CRM)。以人为中心、以对话为链接,通过知识图谱管理微信、LinkedIn、X 等多平台社交关系,将每个联系人与其社媒动态、会议纪要、文件、照片全面关联。

AI 对话与编排层基于 ACP 协议驱动本地 Claude CLI 进程,提供单 Agent 执行和 Master → Sub-Agent 多轮编排能力。Node.js + Express,无数据库依赖(使用 Node 22.5+ 内置 node:sqlite),数据完全本地化。

wechat_plain — Pixel 上微信 bot 的 Xposed/A11y 落地(A11y UI 自动化是当前唯一能真把消息推进 message 表的发送链路;详见下文 §微信集成架构)。

lifelog-recorder — Pixel 8 Pro 上的 24/7 录音 + 本地中文 ASR + 时间轴 lifelog 应用(2026-06 新增,详见下文 §lifelog-recorder)。

常用命令

npm run setup        # 首次初始化:cp .env.example .env + pnpm install
npm start            # 生产模式(node server/index.js)
npm run dev          # 开发模式(node --watch server/index.js,热重载)
./dev.sh             # 杀掉端口占用进程 + npm run dev(推荐开发用)

无测试框架,无 lint 脚本。使用 pnpm workspaces(packageManager: pnpm@10.28.1)。

架构

技术栈

  • 后端:Node.js (≥18) + Express,pnpm workspace monorepo
  • 数据库:Node 22.5+ 内置 node:sqlite(DatabaseSync),位于 server/db/lemon_crm.db
  • AI:Claude CLI(本地子进程,--dangerously-skip-permissions 自动批准)
  • 文件解析:mammoth(.docx)、pdf-parse(.pdf)

目录速查

目录 说明
packages/acp-core/ ACP 协议层:Protocol / Transport / Launcher 三层抽象 + ClaudeCliProtocol / AcpProtocol
packages/agent-core/ Agent 运行时(ESM):目录驱动的 Agent、AgentBus、AgentRegistry、AgentScaffold
server/index.js Express 入口(路由 + 静态前端)
server/src/core/ 编排引擎:orchestrator、master-session、agent-manager、session-pool、stream-executor、prompt-builder、parser、runtime-resolver
server/src/engine/ 进程层:run-registry、command-queue、process-guard、idle-timer、failover-error
server/src/tools/ Master 工具:call-agent-tool、tool-registry、graph/(图谱 MCP 工具集)
server/src/standalone-entry.js 编程 API:runMasterSession / runOrchestration / runSingleAgent
server/routes/ API 路由:master-chat、config、graph、wechat、import、analysis、photo
server/services/ conversation-logger、file-parser、memory-service、claude-chat-service、wechat/、linkedin/、meeting/、photo/、x/
server/db/GraphStore.js 本地知识图谱存储层(SQLite):Person / Organization / Project / Event / Asset 节点 + KNOWS / BELONGS_TO / PARTICIPATED 等边
frontend/ 静态前端(单 HTML 文件,由 server/index.js 托管)
server/config/backends.json ACP Launcher / Client 定义
server/config/agents.json 静态 Agent 注册表(可选,也可由 system/agents/ 目录发现)

API 路由

路径 说明
POST /api/master/chat Master Agent 多轮对话(SSE)
POST /api/master/upload 文件上传注入对话
GET /api/master/sessions 列出活跃 session
DELETE /api/master/sessions/:id 关闭 session
GET/PUT /api/config/items/:key 读写配置文件
GET /api/health 健康检查
GET /api/graph/nodes 列出图谱节点(?type=Person&limit=100)
GET /api/graph/nodes/:id 查询单个节点(含边)
POST /api/graph/nodes 创建/更新节点
GET /api/graph/search?q=关键词 全文搜索
POST /api/graph/edges 创建关系边
GET /api/graph/path?from=id&to=id 查询关系路径
POST /api/wechat/import-contacts 导入微信联系人到图谱
POST /api/wechat/import-chat 导入微信聊天记录
GET /api/wechat/contacts?q=搜索 查询微信联系人
GET /api/wechat/sessions 最近会话列表
POST /api/import/... 多平台数据导入
POST /api/analysis/... 图谱分析(关系链路、聚类等)
POST /api/photo/... 照片服务(人脸聚类、向量检索)

配置文件

文件 说明
.env PORT(默认 3010)、HOST、CORS_ORIGINS 等
server/config.js 端口、CORS、目录路径的运行时配置对象
server/config/backends.json ACP Launcher 定义(command、args、protocol)
server/config/agents.json 静态 Agent 注册(可选)

关键架构模式

ACP 协议四层结构

Application (AgentManager)
    ↓
Protocol  (ClaudeCliProtocol 直接 spawn / AcpProtocol JSON-RPC)
    ↓
Transport (ProcessTransport stdio / PipeTransport / WebSocketTransport)
    ↓
Launcher  (ProcessLauncher spawn / NoneLauncher 无操作)

runtime-resolver.js 根据 backends.json + agents.json 配置自动推断使用哪一层。

ClaudeCliProtocol 调用方式:

  • 生成命令:claude --input-format stream-json --output-format stream-json --verbose --include-partial-messages --dangerously-skip-permissions
  • 输入格式:{"type":"user","message":{"role":"user","content":[{"type":"text","text":"..."}]}}
  • 输出:NDJSON 事件流
  • 单进程跨多轮复用(session reuse)

Mock 模式:设置 LEMONBOSS_CLAUDE_MODE=mock 可读取 fixture 文件而不启动真实进程,用于调试。

Master → Sub-Agent 调度流程

  1. 用户消息 → MasterSession.handleMessage()
  2. PromptBuilder 组装系统提示(含工具描述 + 可用 Agent 列表)
  3. Claude 响应中的工具调用用 [TOOL_CALL: tool_name] {...json...} [/TOOL_CALL] 格式标记
  4. ToolRegistry.parseToolCalls() 解析后交给 AgentManager.callAgent()
  5. SessionPool 按 agentName:client:deploy:workDir[:scope] 为 key 复用已有 session
  6. 工具结果注回对话,最多循环 10 轮(SAFETY_MAX_LOOPS=15)

Orchestrator 额外提供格式自动修正(MAX_REPAIRS=5)与结果验证(ResultTracker)。

Agent 发现与记忆

  • Agent 优先从 system/agents/ 目录发现(目录驱动),agents.json 作为静态补充
  • 每个 Agent 的记忆存储在其目录下的 memory/ 子目录,纯文件系统
  • AgentScaffold 用于创建新 Agent 目录结构

知识图谱(GraphStore)

server/db/GraphStore.js 使用 node:sqlite(Node 22.5+ DatabaseSync),无需额外依赖。

  • 节点类型:Person、Organization、Project、Event、Asset
  • 边类型:KNOWS、BELONGS_TO、PARTICIPATED、CONTAINS、MENTIONED_IN、ASSOCIATED
  • 分析状态:analysis_status 字段跟踪节点是否已被 AI 分析(none / pending / done)
  • Master 工具:server/src/tools/graph/ 提供 create_person、get_person、link_persons、search_graph、get_relationship_path 等工具供 AI 调用

微信集成架构

server/services/wechat/ 提供从 Mac 和 Pixel 双端点与微信交互的能力:

  • 发送消息(实际工作链路):am broadcast com.hermes.wechat.SEND_MSG → HermesAccessibilityService(lsposed-wechat-sendmsg 模块内)→ 启动微信 LauncherUI → 点搜索按钮 → 搜索 toUserDisplayName(必须传 nickname,如"火星人")→ 点搜索结果 → 找 bkk 容器内的 EditText 输入文本 → 点 bql 发送按钮。纯 UI 自动化,绕开所有反射 / hook 失败。
  • 消息跟踪:android-wechat-tracker-sqlcipher.js 直接轮询解密后的 EnMicroMsg.db(SQLCipher)
  • 自动回复:auto-reply.js 结合 tracker + broadcast,在 Pixel 上独立运行
  • 数据导入:wechat-importer.js / wechat-reader.js 将联系人和聊天记录导入知识图谱

[!WARNING] 关于其他两条已废弃的发送路径

  • LSPosed 反射 API(HermesWeChatModule.sendTextMessageDirect):监听同一广播但走 u01.r1.a → m1.a()。Send result: true 是误报——builder.a() 后主线程异步提交触发 null 异常被 swallow,消息不进 message 表。
  • Frida daemon 队列(android-wechat-sender-frida.js + wechat_plain/scripts/frida-daemon/frida_daemon.js):Frida 17 + 微信 8.0.72 注入大体积 bundle 触发反调试,微信进程崩溃,已废弃。 这两条路径的命名让人误解;真正能把消息推进 message 表的只有 A11y service。

[!IMPORTANT] A11y service 启用方式 enabled_accessibility_services 系统设置易被关闭。检查与重新启用:

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'"

详见 server/services/wechat/README.md。

lifelog-recorder(Pixel 8 Pro 24/7 录音 + 本地中文 ASR + 本地 LLM,v0.6)

完整接手地图、踩坑清单、命令速查见 lifelog-recorder/HANDOFF.md。本节只勒住整个项目的架构骨架 + 关键决策来源,不重复细节。

项目目标

把 Pixel 8 Pro 当成永远开机的麦克风:7×24 后台录音 → VAD 切段 → 中文 ASR 转文字 → 入库 → 按时间轴显示和搜索 → LLM 总结成纪要。最终目的是把所有「我说过 / 别人对我说过」的内容沉淀成可搜索、可回放的个人记忆库,下接 lemonPCRM 的人脉图谱(某段语音 → 某联系人 → 某项目)。

约束:完全本地 / 零云端依赖 / 月成本 0 元 / 隐私走 0 网络。

架构骨架

Foreground Service (foregroundServiceType=microphone, WAKE_LOCK partial)
  └ AudioRecord 16k mono PCM16  (VOICE_RECOGNITION source, no AGC/NS)
     └ Silero VAD ONNX (sherpa-onnx 包装,500ms 静音切 / 30s 强切)
        ├ paraformer-zh int8 ONNX  (sherpa-onnx JNI, 4 threads, ~20× realtime)
        └ 3dspeaker campplus 192-d 嵌入 → SpeakerClusterer 在线聚类(自动「说话人N」,方案B)
           └ Room SQLite v3 (Segment/Transcript/Summary/SpeakerProfile/Chat*; addMigrations, 已去 destructive)
              └ Compose 5-tab UI (录音 / 对话 / 时间轴 / 声纹 / 纪要)
                 ├ SummaryWorker:按时间间隔切「会话」→ 本地 llama-swap(8080) Qwen3.5-2B 出纪要
                 └ ChatScreen:本地 2B/4B(llama-swap 8080) + Hermes(8642→Kimi) 多端点对话

本地 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。

技术栈: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)
ParaformerEngine.transcribeFloat() sync — OfflineRecognizerResult(text, tokens, timestamps, lang, emotion, event)
SegmentWriter sync filesystem filesDir/segments/YYYY-MM-DD/HH-MM-SS-MS.wav
Repository.segments/transcripts suspend Room DAOs in data/Daos.kt

外部接口:

  • 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 任何配置都 ,连续录会越积越多。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 元,延迟

关键坑(接手必读)

  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.6 已完成:Opus 编码 / BOOT 自启 + 电池白名单 / FTS4 中文搜索 / 段播放 / 声纹在线聚类(方案B 自动 diarization) / 会话级纪要(按时间间隔切会话) / 5-tab UI / App 内对话(本地 2B/4B + Hermes) / 本地 LLM 栈(llama-swap) / Room 迁移(去 destructive)。

剩余摘要:

  • ⭐ 存储 TTL(删超 N 天 .opus,文字永留)
  • ⭐ 设置页(存储用量 / 模型切换 / 暂停时段)
  • ⭐ 多 anchor 平均(连选多段标同一人提高匹配率)
  • ⭐ 回链 lemonPCRM(Transcript 实体 → GraphStore MENTIONED_IN,v2)

与 lemonPCRM 的关系

目前完全独立——lifelog-recorder 的 Room DB 跟 lemonPCRM 的 GraphStore 没打通。未来计划:把 Transcript / Summary 表里的实体(人名 / 地名 / 项目)抽到 GraphStore 当 MENTIONED_IN 边,让你的「某次会议说了什么」可以从图谱里反查。这是 v2 范畴。

注意事项

  • server/src/standalone-entry.js 的 loadConfig() 优先读 backends.json + agents.json,无需数据库。
  • prompt-builder.js 的 getSystemStateXML() 只返回时间戳(公司/OKR 层已移除)。
  • Agent 目录内放 SKILL.md 文件会绕过协议封装,直接作为系统提示使用。
  • server/index.js 包含控制字符净化中间件:Agent 通过 curl 发送的 JSON body 可能含原始换行符,若解析失败会自动清理 \x00-\x1F 控制字符后重试。

!WARNING Master Agent 必读:在 Bash tool 中使用含多重双引号的复杂命令(如 curl -d '{"id":"lemonv"}')时,JSON 转义容易崩溃并陷入死循环。 解决方案:任何需要提交 JSON Payload 的操作,先写入 /tmp/*.json 文件,再通过 curl -d @/tmp/payload.json 发送。