结论速览
四个讨论点,我的建议先放前面,理由在后面各章。
① 产品形态
一个跨端本地 APP(Win / macOS / Linux / iOS / Android)+ 一个自托管的轻服务端 + 一个 Agent hook 脚本。APP 常驻托盘/后台,Agent 完成任务即“来电”;通话中来的新汇报自动排队,挂断后下一个响铃。
② 架构方向
“Hook 接入 → 服务端精炼成结构化简报 → 推送来电 → 客户端播报/打断 → 生成提示词 → 回原会话”。服务端只做无状态的单轮 LLM 调用(简报生成、问答、提示词);TTS / STT 与对话状态都在客户端,免费语音通道的限流按用户分摊,服务端只兜底;hook 带回 sessionId + cwd,是后续闭环的关键。
③ 技术选型
客户端 Tauri 2 + React 19 + TS(一套代码五端);服务端 Node 22 + Hono + zod,存储 MySQL 8;推送 SSE + 轮询兜底;LLM 调用用 Vercel AI SDK(generateObject / streamText)接现有 CLIProxy,不引入 Agent 框架;配音 Azure 路径 B 在客户端直连(复用 youtube-dubbing-extension 实现,已在纯网页里实测通过);语音识别首期同样走 Azure 路径 B(客户端直连,与 TTS 共用 token),本地 SenseVoice 作为二期提速选项。
④ 首期需求
桌面端先跑通完整闭环:Claude Code / Codex 接入 → 简报生成(口播稿 + 卡片 + 决策点)→ 来电/排队 → 客户端 Azure 语音逐句播报 → 点“打字”或按住说话即暂停、问答后续播 → 挂断生成提示词 + “回原会话”命令一键复制。移动端、自动续跑、多用户放二期。
1产品形态
核心不是“把报告念出来”,而是“替你把报告消化成可决策的信息”。
📞 来电,而不是通知
完成事件以全屏来电/置前窗口出现,显示 Agent、项目、一句话结论和“需要你决策 N 项”。可接听、拒绝(稍后在列表回看)、静音排队。
🗣️ 真人助理口吻
先结论后细节:“老板,登录重构做完了,测试全过;有一件事要你拍板……”。原文事实被抽成事实清单,口播稿每段标注覆盖了哪些事实,服务端校验关键事实(风险、决策、数字、未完成项)零遗漏。
🖼️ 卡片配图
每段口播对应一张卡片:总结 / 改动清单 / 风险 / 决策点(带选项与推荐)/ 下一步。卡片随语音翻页,手动翻页也会跳到对应语音段。
✋ 随时打断
按住说话或直接打字即暂停播报;助理基于原始报告 + 已播内容回答或记下你的意见,然后问“要我继续吗?”,从断点继续。决策点可直接点选项回答。
📋 挂断即出提示词
通话结束,把“原报告 + 你的所有意见/决策”合成一段可直接粘贴给 Agent 的提示词,同时给出回到原会话的命令(如 claude --resume <id>),可复制、可编辑。
🧵 排队与回看
通话中来的新事件自动进队列,角标显示“还有 N 个汇报在排队”;挂断后自动响下一个。所有汇报、对话记录、提示词可在历史里回看重播。
闭环方案:怎么“回到之前的 session”?
你提到的难点:对话完自动执行只能开新 CLI,回不到原会话;另一个想法是干脆做一个类 Multica 的完整工具。
好消息是主流 Agent CLI 都支持按会话 ID 恢复并追加指令,而 hook 恰好能拿到这个 ID,所以不需要先重造一个 Multica:
| Agent | hook 给到的标识 | 交互式回到原会话 | 无人值守续跑(二期) |
|---|---|---|---|
| Claude Code | Stop hook stdin:session_id、cwd、last_assistant_message | cd <cwd> && claude --resume <session_id> | claude -p --resume <session_id> "<prompt>" |
| Codex | notify 参数 JSON:thread-id、cwd、last-assistant-message | codex resume <thread-id> | codex exec resume <thread-id> "<prompt>" |
| 其他 / 自研 | POST /v1/events(generic) | 展示提示词,人工粘贴 | 回调 URL(由接入方实现) |
分三级演进,由浅入深
L1 复制即用
挂断页给出:① 提示词 ② “回原会话”命令(已拼好 cwd 与会话 ID)。你粘贴即继续,零风险,所有 Agent 通用。
L2 一键续跑
桌面端增加本地 Runner:点“让它继续”,在原 cwd 执行 claude -p --resume / codex exec resume。续跑结束又触发 hook → 再次来电,形成真正闭环。需要白名单目录与确认弹窗。
L3 任务编排
当你需要同时管多个项目的队列、看板、多 Agent 分工时,再在 Runner 之上加任务层——即你说的“类 Multica”。先有 L1/L2 验证价值,再决定是否值得做,或直接对接 Multica。
建议:首期做 L1,架构上为 L2 预留(事件已存 source/sessionId/cwd,骨架里已实现)。不建议一上来做完整编排工具——工作量大一个数量级,而核心痛点(读报告、做决策)在 L1 就已被解决。
2系统架构
三个部件:Agent 侧 hook、自托管服务端、跨端客户端。实线 = 首期,虚线 = 二期。
服务端 / 客户端怎么分工
- 服务端:简报生成(事件到达时你可能不在线)、LLM 凭据与限额;不保存会话、不跑循环
- 客户端:TTS / STT、对话上下文、打断与续播——免费语音通道的限流按用户 IP 分摊
- 服务端每次调用都是无状态单轮;Node 异步 I/O 下等模型返回不占线程
为什么 SSE + 轮询兜底
- 你提的“轮询”完全可用,但 SSE 秒级到达且更省
- 事件带单调
seq,断线用Last-Event-ID续传,不丢不重 - 后台/休眠的手机靠 APNs/FCM 唤醒(二期)
为什么先单用户自托管
- 报告含代码与业务信息,默认私有部署
- MySQL 8:本机 docker 已有实例,与现有项目运维方式一致;表结构走版本化迁移(
db/migrations),访问集中在EventStore - 多租户/账号体系放到有外部用户时再做
核心流程
一次汇报从 Agent 结束到你拿到提示词的完整路径。
- Agent 完成:Claude Code
Stop/ Codexnotify触发outbrief-hook,带上最终报告、sessionId、cwd。 - 入库:
POST /v1/events→ 分配seq,状态received;异步进入简报生成。 - 精炼:Brief Engine 先抽事实清单(数字/风险/决策/未完成项标为关键),再写口播段 + 卡片,校验“关键事实全部被覆盖”,未覆盖则重写一次或以“补充说明”卡片兜底。整个过程是一次结构化 LLM 调用 + 代码校验,不是 Agent。
- 配音:客户端收到简报后直连 Azure 按句合成并本地缓存;首句就绪即响铃,后续边播边合成;客户端失败才请求服务端兜底。
- 来电:SSE 推送到所有在线客户端;空闲则响铃,通话中则排队。
- 播报 + 打断:逐句播放、卡片同步;点“打字”或按住说话立即暂停,Azure STT 识别语音后把问题连同上下文发给 LLM Gateway,回答播完后“继续”从当前句开头接着讲。
- 挂断:生成“下一步提示词”+ 回原会话命令;状态写回
completed,队列里的下一个响铃。
简报结构(服务端产出,客户端渲染)
{
"title": "登录模块重构汇报",
"verdict": "partial", // done | partial | blocked
"facts": [{ "id": "f-redis", "text": "Session 迁到 Redis,需新增 REDIS_URL",
"importance": "critical" }],
"segments": [{
"id": "seg-1",
"speech": "老板,登录重构做完了,42 个测试全过。有两件事要你拍板……",
"card": { "kind": "summary", "title": "总体进展",
"bullets": ["JWT 校验换成 jose", "42/42 测试通过"] },
"coveredFactIds": ["f-jwt", "f-tests"]
}],
"decisions": [{ "id": "d-legacy", "question": "旧接口立即下线还是保留一个版本?",
"options": ["保留到 v2.3", "立即下线"], "recommendation": "保留到 v2.3" }]
}
已用真实报告在 CLIProxy 上跑通该结构(gemini-3.6-flash-high 约 5.6 s,事实全覆盖、未覆盖列表为空),口播稿长度约为原文 1/3~3/5。
UI 交互设计
视觉语言对齐微信视频来电:深色全屏、绿色接听、红色挂断;通话中是“语音为主、卡片为辅”。
1. 旧接口保留到 v2.3,加 Deprecation 头;
2. 在 docs/deploy.md 补 REDIS_URL 配置说明,生产 Redis 由运维 Terraform 开通;
3. 完成后跑全量测试并汇报。
桌面端:托盘常驻 + 来电浮窗 + 队列
空闲时来电:窗口置前 + 系统级“请求注意”(Dock 跳动 / 任务栏闪烁);移动端二期用 CallKit / 全屏通知。
3技术选型
原则:一套 TS 类型贯穿三端;能复用现有资产就复用;首期零运维。
| 层 | 选择 | 理由 | 备选 / 放弃原因 |
|---|---|---|---|
| 客户端框架 | Tauri 2 + React 19 + TypeScript + Vite | 一套代码覆盖 Win/macOS/Linux/iOS/Android;包体小(~10 MB 级);原生能力用 Rust 或 Swift/Kotlin 插件补 | Electron 无移动端;Flutter 与现有 TS 资产割裂;RN 桌面(尤其 Linux)弱 |
| 客户端状态 | 纯函数 callQueue 状态机 + React state | 来电/排队/挂断逻辑可单测,与 UI 解耦,移动端直接复用 | 首期不引入 Redux/Zustand |
| 音频 | WebView HTMLAudioElement / Web Audio;MediaRecorder 录音 | 按句 MP3 顺序播放、随时暂停;录音转 16 kHz WAV 交给 Azure STT | 移动端后台播放二期用原生插件 |
| 服务端 | Node 22 + Hono + zod | 协议以服务端 src/protocol.ts(zod)为准,客户端 / hook 各自镜像类型;Hono 自带 SSE;Node 22 原生跑 TS,无构建 | Spring Boot 同现有 proxy 栈,但无法共享类型、无法直接复用 TS 版 TTS |
| 存储 | MySQL 8(mysql2)+ 版本化 SQL 迁移 | 本机 docker 已有 MySQL,与现有项目一致;启动时自动迁移;访问集中在 EventStore | SQLite:部署到服务器后要单独管文件与备份;Redis 首期不需要 |
| 下发 | SSE(fetch 流,带 Authorization)+ Last-Event-ID + 轮询兜底 | 秒级到达、服务端实现简单、天然续传 | WebSocket 双向能力首期用不上;APNs/FCM 二期 |
| LLM | 现有 CLIProxy(OpenAI 兼容),默认 gemini-3.6-flash-high,可配置 | 已有账号与额度;JSON 结构化输出实测可用;模型可随时切 | 直连各家 SDK:多一套 key 管理 |
| LLM 调用 | Vercel AI SDK:ai + @ai-sdk/openai-compatible;简报 generateObject(zod schema),问答 / 提示词 streamText | 三类调用都是单轮,不需要 Agent 框架;schema 放服务端,客户端镜像类型;同一套 API 在 Node 和 WebView 都能跑 | LangChain / LangGraph / Mastra:解决的是多步工具循环,这里用不上,徒增抽象 |
| TTS | Azure 路径 B,客户端直连(同 youtube-dubbing-extension:前端做、后端兜底) | 零成本、音色丰富;限流按用户 IP 分摊;已在纯网页实测(CORS 放行 *) | 服务端官方 Speech Key 作兜底 |
| STT | 首期 Azure 路径 B STT,客户端直连(与 TTS 共用签名 / token) | 免费、零安装、实现与 TTS 同一套;实测 11.3 s 音频往返 4.5 s | 本地 SenseVoice(0.18 s,但要带 240 MB 模型和 sherpa-onnx 原生依赖)放二期;Web Speech API 在 WebView2 / WebKitGTK 上不可用 |
| Hook | 零依赖 Node 脚本 outbrief-hook | 装在任何开发机都能跑;超时短、失败 exit 0 永不阻塞 Agent | Rust 单文件二进制:二期做分发时再考虑 |
| 工程 | 三个独立仓库(server / app / hook)· pnpm · Biome · Vitest · GitHub Actions | 每个仓库一条命令 lint/typecheck/test;server CI 带 MySQL 服务;app CI 覆盖三平台 Tauri 构建 | ESLint+Prettier:配置更重 |
语音链路(参考 youtube-dubbing-extension)
扩展里的 src/background/api/edge-tts/* 已是纯 TS + WebCrypto 实现,移植到客户端 outbrief-app/src/voice/(WebView 直接 fetch);服务端兜底改用官方 Speech Key 的标准 REST,不需要共享代码。
协议(路径 B:cognitiveservices/v1)
- HMAC-SHA256 签名(
MSTranslatorAndroidApp)→X-MT-Signature POST dev.microsofttranslator.com/apps/endpoint换取 JWT + region(时钟偏差时用服务器时间重签)- TTS:
POST https://{region}.tts.speech.microsoft.com/cognitiveservices/v1,SSML(voice/prosody/lang),输出audio-24khz-48kbitrate-mono-mp3 - STT:同一 JWT 调
{region}.stt.speech.microsoft.com/…/conversation/cognitiveservices/v1,16 kHz WAV - 错误策略沿用扩展:400 不重试、401 刷新 token、429 立即降级、408/5xx 指数退避;并发 ≤ 3
- WebView 里必须加
referrerPolicy: "no-referrer":换 token 的接口带任何 Referer 都会 401(错误码 401001),浏览器默认会带;扩展的后台 Service Worker 不带所以没遇到
实测(spike)
| 场景 | 结果 |
|---|---|
| Node 22:签名换 JWT | 200,region eastasia,~0.2 s |
| Node 22:TTS(中文约 11 s 语音) | 200,audio/mpeg 68 KB,~2.6 s |
| 纯网页(Chromium,无扩展、无代理):签名 → JWT → TTS → STT | 全部 200;TTS 5.6 s 语音耗时 1.9 s;STT 11.3 s 音频往返 4.5 s |
CORS 预检(tauri://localhost、http://tauri.localhost) | 三个接口都返回 Access-Control-Allow-Origin: * 并放行所需请求头 |
| 本地 SenseVoice-Small int8(M4 Max CPU,2 线程) | 模型 240 MB,加载 0.43 s;11.3 s 音频识别 0.18 s(RTF 0.016) |
含义:TTS 放客户端没有技术障碍,WebView 里直接 fetch 即可,不需要 Rust 转发;STT 同理,客户端直连即可。
风险提示:路径 B 是非官方通道,可能被限流或封禁。客户端直连把限流分摊到每个用户;服务端配置 AZURE_SPEECH_KEY 后作为兜底走官方 Speech 服务(同一 SSML、同一端点形态),上层无感。
设计细化(讨论补充)
针对五个追问:要不要 Agent、TTS 放哪、Agent 循环放哪、怎么打断、语音识别用什么。
① 把汇报转成图文简报,要不要 Agent?——不要,一次结构化调用就够
结论
这是一个输入固定、输出结构固定的变换,没有“先看结果再决定下一步调什么工具”的环节,所以不需要 Agent,直接调大模型 API:
- 一次
generateObject:输入原始报告,输出 zod 定义的简报(事实清单 / 口播段 / 卡片 / 决策点) - 代码校验:zod 结构 + “
critical事实全部被某段覆盖” - 不合格才补一次调用(把缺失事实回传让它改);仍不合格就追加“补充说明”卡片
实测一次调用约 5.6 s,事实全覆盖。简报在服务端生成:事件到达时你可能不在线,接起电话时应该已经准备好。
技术栈
| 用途 | 选择 |
|---|---|
| 模型调用 | ai(Vercel AI SDK Core)+ @ai-sdk/openai-compatible 指向 CLIProxy |
| 结构化输出 | generateObject({ schema }),schema 放服务端 src/protocol.ts,客户端镜像类型 |
| 问答 / 提示词 | streamText,流式返回,首句到了就先合成先播 |
| 提示词管理 | 模板文件 + 版本号,放服务端;用固定样本做回归评测 |
不用 LangChain / LangGraph / Mastra:它们解决多步工具循环、状态持久化,这里没有这些需求。
② TTS 能不能放客户端?——能,已验证,采用“前端做、后端兜底”
验证结果
- 在普通网页(非扩展)里用 WebCrypto 签名 → 换 JWT → TTS → STT,全部 200,MP3 能直接播放
- 三个接口的 CORS 对 Tauri 的两种 origin 都返回
Access-Control-Allow-Origin: *,WebView 直接fetch即可,不需要 Rust 转发 - 唯一的坑:换 token 的接口只要带 Referer 就 401,浏览器默认会带 → 请求加
referrerPolicy: "no-referrer"
落地方式
- 把扩展的
edge-tts/*移植到outbrief-app/src/voice/(签名、token 缓存、SSML、重试、合成、识别) - 客户端:按句合成,按“文本 + 音色 + 语速”哈希缓存在本地,重听不再请求
- 服务端:
POST /v1/tts只在客户端连续失败时调用,用官方AZURE_SPEECH_KEY,按用户限额 - 效果:免费通道的限流落在每个用户自己的 IP 上,服务器不成瓶颈
③ 多轮交互时 Agent 放哪?——循环放客户端,服务端只做无状态单轮
业界做法
- Vercel AI SDK 客户端工具:服务端每次请求只跑一步
streamText;没有execute的工具调用下发到前端onToolCall执行,addToolOutput写回结果,sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls自动发起下一轮——循环由前端驱动,服务端不保存状态 - OpenAI Agents SDK(语音 Agent):
RealtimeAgent/RealtimeSession直接跑在浏览器里,服务端只签发临时 key(ek_…) - Claude Code / Codex / Cursor:Agent 循环都在用户本机,云端只是模型 API
OutBrief 的具体做法
| 交互 | 谁发起 | 服务端做什么 |
|---|---|---|
| 生成简报 | 事件到达 | 1 次 generateObject |
| 打断问答(每轮) | 客户端 | 1 次 streamText,客户端带上报告 + 简报 + 对话历史 |
| 挂断生成提示词 | 客户端 | 1 次 streamText |
| 翻卡片 / 记录决策 / 续播 | 客户端 | 不参与(客户端工具) |
服务端接口 POST /v1/llm/:task,task 只能是 qa / next-prompt,系统提示词由服务端按 task 选,客户端不能自带,防止网关被当成通用代理。
关于线程:Node 是异步 I/O,等模型返回时不占线程,一个进程可以同时挂数千个流式请求;真正的成本是模型 token 和每个会话的内存。“服务端只做单轮 + 不存会话”把后者也去掉了,扩容就是加无状态实例。
④ 怎么打断?——按你说的:点“打字”或按住说话,立即暂停
规则
- 触发暂停:按住说话(按钮或空格)、点“打字”、在输入框敲下第一个字、点决策选项
- 暂停时记住“第几段、第几句”;口播按句合成,所以续播从当前句开头重新讲,不会从半个词开始
- 回答播完后等你:点“继续”、按回车、或说“继续”(识别结果就是这个词时直接续播,不调 LLM)
- 回答过程中再次打断:取消还没返回的 LLM 请求和还没播的回答音频
- 首期不做“你一开口就自动打断”:扬声器外放时麦克风会收进自己的声音,需要回声消除 + VAD,放二期
状态机
playing ─按住说话/打字→ paused
paused ─松开/发送────→ answering
answering ─再次打断─────→ paused
answering ─回答播完─────→ waiting
waiting ─继续─────────→ playing
任意状态 ─挂断─────────→ summarizing
answering:Azure STT → LLM 流式 → 按句 TTS;再次打断会 abort 进行中的请求;playing 从当前句开头续播;summarizing 生成下一步提示词。
⑤ 语音识别用什么?——首期 Azure 免费通道,本地 SenseVoice 放二期
| 方案 | 费用 | 实测 / 资料 | 结论 |
|---|---|---|---|
| SenseVoice-Small int8(本地,sherpa-onnx) | 免费开源 | M4 Max CPU 2 线程:11.3 s 音频 0.18 s 识别完,模型 240 MB,加载 0.43 s;官方资料称中文准确率优于 Whisper-Large、速度快约 15 倍 | 二期sherpa-onnx 有官方 Rust crate,可放进 Tauri command;打断延迟成为痛点时再上 |
| Azure 路径 B STT(客户端直连) | 免费(非官方) | 网页里实测 200,11.3 s 音频往返 4.5 s;单次音频上限 60 s | 首期与 TTS 共用 token,零安装;超过 60 s 的语音按段切分 |
| Whisper large-v3-turbo(whisper.cpp) | 免费开源 | 自回归解码,比 SenseVoice 慢;中文不如 SenseVoice,英文更好 | 以英文为主的用户可以作为可选项 |
| 系统语音识别(iOS Speech / Android SpeechRecognizer) | 免费 | 移动端原生,可离线 | 二期移动端备选 |
| Web Speech API | — | WebView2(Windows)、WebKitGTK(Linux)不支持 | 不用 |
两种方案在同一段测试音频上都把“旧接口”识别成了“就接口”(TTS 读音本身有歧义)。所以打断问答时会把识别出的文字显示给你,可以改一下再发。录音用 AudioWorklet 直接采 16 kHz 单声道 PCM,二期换本地模型时录音链路不用改。
4首期需求(Phase 1 · 桌面端闭环 MVP)
目标:你在一台 Mac/Windows/Linux 上,同时跑 3 个 Agent 项目,全部靠“接电话”处理完汇报,并通过复制提示词继续推进。
| # | 需求 | 验收标准 | 骨架状态 |
|---|---|---|---|
| P1-1 | Agent 接入:Claude Code Stop hook、Codex notify、generic HTTP | 三种来源事件入库,保留 sessionId/cwd;服务端不可达时 Agent 不受影响 | 已完成 |
| P1-2 | 事件下发与排队:SSE + 续传 + 轮询兜底;通话中新事件排队,挂断后自动响下一个 | 断网重连不丢不重;3 个并发事件依次响铃 | 已完成 |
| P1-3 | 来电 UI:来电 / 通话中 / 挂断三态,窗口置前 + 请求注意,接听/拒绝回写状态 | 桌面三平台可构建;拒绝的事件进入历史可重听 | 来电/排队已完成,历史待做 |
| P1-4 | Brief Engine(服务端):AI SDK generateObject 单次调用产出事实 + 口播段 + 卡片 + 决策点,代码校验关键事实覆盖 | zod 校验通过;critical 事实 100% 覆盖;口播稿 ≤ 原文 60% | 待做 |
| P1-5 | Voice(客户端优先):outbrief-app/src/voice/ 移植路径 B(TTS + STT),客户端按句合成 + 本地缓存;服务端 /v1/tts 官方 Key 兜底 | 首句音频 ≤ 3 s 可播;401/429/5xx 按策略处理;客户端失败自动走兜底 | 待做 |
| P1-6 | 通话播放器:逐句播报、卡片同步翻页、暂停/继续(从当前句开头)、跳段 | 翻卡片与语音段双向同步 | 待做 |
| P1-7 | 打断问答:点“打字”/按住说话即暂停 → Azure 路径 B STT 识别(识别文字可改后再发)→ 客户端带上下文调无状态 LLM Gateway → 回答播完“继续”;决策点可点选 | 回答不编造原文外事实;意见与决策全部记入会话 | 待做 |
| P1-8 | 挂断总结:生成下一步提示词 + 回原会话命令,可编辑、一键复制 | 提示词包含全部决策与意见;Claude Code / Codex 命令可直接运行 | 待做 |
| P1-9 | 设置:服务器地址/token、音色、语速、LLM 模型;托盘常驻、开机自启 | 设置持久化,重启后自动连接 | 连接设置已完成 |
首期明确不做
移动端打包与推送唤醒、Local Runner 自动续跑、多用户/账号体系、流式 STT、历史全文检索。
路线图
桌面闭环 MVP
- P1-1 ~ P1-9
- 自托管部署文档
- 你自己日常使用,校准口吻与卡片
移动端 + 一键续跑
- iOS / Android 打包,CallKit / 全屏来电
- APNs / FCM 推送唤醒
- Local Runner:
claude -p --resume/codex exec resume - 本地 SenseVoice STT,打断后识别从 4.5 s 降到 0.2 s
多项目编排(按需)
- 项目维度看板、优先级、勿扰时段
- 多用户、团队共享汇报
- 视情况对接 Multica 或自建任务层
风险与对策
| 风险 | 影响 | 对策 |
|---|---|---|
| 精炼丢关键信息 | 你基于错误信息做决策 | 先抽事实再写稿;critical 事实强制覆盖校验;卡片“原文”入口随时可看原报告 |
| Azure 路径 B 被限流/封禁 | 没有声音 | 客户端直连分摊限流;服务端官方 Key 兜底;都失败时降级为“卡片 + 文字”通话 |
| 移动端后台限制 | 来电不响 | 首期桌面优先;二期走 APNs/FCM + CallKit/全屏通知,不依赖长连接 |
| 报告含敏感代码/密钥 | 数据外泄 | 默认自托管;token 鉴权;LLM 走自有 CLIProxy;后续可加脱敏 |
| 打断延迟偏高 | 对话不自然 | 二期换本地 SenseVoice(实测 0.2 s);回复流式输出,首句先合成先播 |
| 路径 B 密钥随客户端分发 | 被更多人滥用 | 该密钥已公开(read-frog、youtube-dubbing-extension 同款),不增加暴露面;官方 Key 只在服务端 |
| Azure STT 往返约 4.5 s | 打断后要等 | 识别期间显示“正在听写”;二期接本地 SenseVoice,Azure 退为兜底 |
项目骨架(已上传 GitHub)
三个私有仓库,本地都在 ~/work/code/outbrief/ 下。骨架不是空壳:事件接入 → MySQL → 排队 → 来电 UI 已端到端跑通。
~/work/code/outbrief/
├── outbrief-server/ asasas234/outbrief-server · Hono + MySQL
│ ├── src/ /v1/events · /v1/stream(SSE) · 状态回写 · protocol.ts(zod,协议唯一来源)
│ ├── db/ bootstrap.sql(建库/账号)· migrations/(建表,启动时自动执行)
│ └── docs/ proposal.html 本方案 · adr/ 0001–0003
├── outbrief-app/ asasas234/outbrief-app · Tauri 2 + React 19
│ ├── src/ callQueue 状态机 · SSE 客户端 · 来电/通话 UI · protocol.ts(镜像)
│ │ voice/(首期新增:Azure 路径 B TTS + STT)
│ └── src-tauri/ Rust 壳(窗口置前、请求注意)
└── outbrief-hook/ asasas234/outbrief-hook · 零依赖 Node 脚本
└── src/ Claude Code Stop / Codex notify 归一化 · protocol.ts(镜像)
已验证
- 三个仓库各自
pnpm lint/typecheck/test全绿:server 8(真实 MySQLoutbrief_test库)· app 6 · hook 7 - 本机 docker
some-mysql已建outbrief/outbrief_test库和outbrief账号,pnpm db:migrate建好agent_events表 - 真实启动服务 → 用 hook CLI 发 Claude Code / Codex 事件 → SSE 实时收到;无关事件类型被忽略;服务端宕机时 hook 仍
exit 0 - 浏览器驱动 UI:来电 → 接听 → 通话中再来事件 → 显示“还有 1 个汇报在排队” → 挂断后自动响下一个;服务端状态正确回写
cargo build(Tauri 2.11,Rust stable)通过