# dsh-session-assistant 设计哲学 > 这份文档回答三个问题:**为什么这个插件存在**、**它的价值是什么**、**它按什么第一性原理设计**。 > 代码回答“怎么做”,本文回答“为什么这样做、为什么不那样做”。实现与契约以源码、`spec/`、测试为准;本文是思考的锚点,不是另一份运行时配置。 ## 1. 为什么存在:第一性原理 **对话是 Agent 与人类之间最自然的接口,而打字不是。** 人在思考问题时说话比打字快约 3–5 倍;复杂任务(“把这段改得更专业”“帮我整理成给老板的汇报”)在对话中成形,而不是一次性打出完整指令。DSH 已有完整的主 Agent(执行、工具、文件系统),但它没有“耳朵”。 第一性原理链条: 1. 人类用**语音**表达意图最自然 → Agent 需要**听**的能力 2. 听不是目的,**形成可执行的东西**才是 → 语音必须落成**当前会话的草稿** 3. 执行是主 Agent 的职责,不是语音模型的 → 草稿通过**显式授权**交给主 Agent 4. 授权后用户需要**被反馈**(提交了、在处理、完成了、在问你)→ 语音助手必须**持续存在于会话中**,而不是一次性工具 所以:Session Assistant = **当前会话的语音前端**。它不执行任务、不越会话、不冒充主 Agent;它让“说 → 定稿 → 授权 → 执行 → 反馈”整个循环都发生在用户面前这一个会话里。 ## 2. 价值主张 - **低摩擦**:点一下麦克风(或说唤醒词),直接开口;转写实时可见、可改 - **会话内闭环**:讨论、草稿、提交、主 Agent 执行、提问、完成提醒——全部绑定当前 Session,切走即停 - **显式授权**:草稿只提交一次、只在你明确说“发送/提交”时;语音模型永远不能自己执行任务 - **反馈走统一事件层**:提交、失败、主 Agent 提问、计划与完成先成为带可见性和语音策略的语义事件,再由状态条与语音分别消费 ## 3. 设计原则 1. **响应是第一要务**:任何工具执行后,用户必须得到可见或可听的反馈。状态条(dock)是“必然通道”,语音确认是“尽力通道”——不依赖 Provider 的自动续话行为。 2. **只服务当前会话**:麦克风、状态、草稿、提交、提醒都以 Slot 的 `sessionId` 为界;换会话即拆解旧控制器。 3. **语音模型不执行**:语音侧只有五个产品动作(`update_working_draft` / `prepare_agent_handoff` / `submit_to_agent` / `end_voice_session` / `organize_notes`);它只能操作会话草稿、准备交接和显式提交,知识整理只委派给可选 PKB maintainer。文件、终端、网络仍是主 Agent 的领地。 4. **提交必须显式**:`submit_to_agent` 只响应明确的语音授权;空草稿拒绝(否则输入框会静默吞掉空提交,用户听到“已提交”却什么都没发生)。 5. **Provider 差异收敛在传输层**:豆包与 OpenAI 的事件结构、续话机制、转写事件各不相同,全部在 `dsh-realtime-voice` 归一化为标准事件;产品层只消费标准事件。 6. **一切反馈都在会话内**:主 Agent 的提问与完成出现在状态条——因为用户授权后关心的是“这件事怎么样了”,而这个答案只存在于当前会话。 ## 4. 能力全景 | 能力 | 状态 | 说明 | | --- | --- | --- | | 语音会话(豆包/OpenAI Realtime、浏览器识别) | ✅ | 全双工,服务当前会话 | | 实时转写显示 | ✅ | 流式 `input_audio_transcription.delta`,多行状态条 | | 草稿讨论与修改 | ✅ | `update_working_draft`,讨论不入草稿 | | Agent 任务路由 | ✅ | workspace、当前状态、工具、副作用和验证类任务先 `prepare_agent_handoff`,显示等待确认,不会误报已执行 | | 显式提交给主 Agent | ✅ | `submit_to_agent` → composer → 当前会话;空草稿拒绝 | | 工具失败非静默 | ✅ | 失败回传模型 + 状态条显示原因 | | 主 Agent 提问提示(human-in-the-loop) | ✅ | `ask_user_question` 映射为用户感知事件并在状态条显示问题与选项 | | 主 Agent 计划里程碑 | ✅ | `todo_write` 映射为摘要型事件,只播报建立、当前步骤变化与完成进展 | | 子 Agent 主动汇报边界 | ✅ | 识别 `subagent-report`,但原始汇报保持内部静默;由主 Agent 决定用户可见内容 | | 主 Agent 完成提醒 | ✅ | 提交后监听新回复并显示状态 | | 待机 + 唤醒词 | ✅ | 待机只响应唤醒词;唤醒后自动恢复语音会话 | | 语音回答回填主 Agent 问题 | ⏳ 平台依赖 | `questionRpcId` 不在会话快照中,需宿主开放回答通道 | | 浏览器/系统朗读主 Agent 回复 | ❌ | 已删除,避免和 Realtime 助手音色形成两套语音 | ## 5. 边界(不做什么) - **不执行任务**:不碰文件、终端、网络;主 Agent 的活不抢。 - **不冒充主 Agent**:不宣称“已执行完成”;提交后由主 Agent 完成并产生会话内证据。 - **不越会话**:不监听其它会话、不全局广播;`sessionId` 是全部边界。 - **不持有长期凭据**:Provider 密钥永远停在 Host;浏览器只拿 route/profile 引用。 - **不做通用语音助手**:没有全局唤醒,也不自行拥有跨会话记忆;待机唤醒只服务当前会话,可选长期知识由独立 PKB 投影和确认。 ## 6. 架构分层 ```text dsh-multi-model-provider 模型目录 / 路由 / profile 注册(凭据在 Host) │ dsh-realtime-voice 传输与媒体:豆包 Duplex / OpenAI WebRTC / 浏览器识别 │ 事件归一化、麦克风租约、错误码(mic_not_found 等) │ dsh-session-assistant(本插件) 产品层:会话内状态机 + 草稿 + 提交 + 会话观察 │ 状态条 / 转写 / 提问提示 / 完成提醒 / 待机唤醒 └──► 当前 Session(composer / 主 Agent / 会话事件) ``` 产品层不关心 Provider 差异;传输层不关心产品语义。改动语音能力时按此边界分层进行。 ## 7. 关键决策记录(为什么这样做) | 决策 | 为什么 | | --- | --- | | 提交走 composer(`inputActions.submit()`) | 提交的就是“用户当前可见的草稿”,与打字提交同一路径,天然在当前会话、可被用户编辑确认 | | 语音侧只有 5 个产品动作 | 语音模型无文件、终端或网络执行面;`prepare_agent_handoff` 只形成等待确认的请求,`organize_notes` 只把有界讨论委派给可选 PKB maintainer,工具面仍保持最小化 | | 空草稿提交必须拒绝 | DSH composer 对空草稿提交**静默 no-op**(`if (trimmed === '') return []`)——旧实现会让模型说“已提交”而实际什么都没发生 | | `tool.result` 先于 `session.update` | Doubao Duplex 在函数结果上自动续轮;先发 `session.update` 可能冲掉 pending call 导致模型永远等不到结果 | | 豆包不发送 `response.create` | 该事件是 OpenAI 方言;官方 SDK 在函数结果后自动续轮,多发的无效事件可能让模型干等 | | 支持豆包 `items` 数组格式 | 豆包把函数调用放在 `response.function_call_arguments.done` 的 `items` 数组里(OpenAI 是扁平字段);按 OpenAI 格式解析会**丢弃所有工具事件**——这是曾让“说发送没反应”的根因 | | 空草稿/失败/完成都写状态条 | “响应是第一要务”:状态条是必然通道,模型开口是尽力通道 | | 待机监听可抢占(`preemptible`) | 待机占用麦克风租约;用户主动开语音时,待机监听让位,不阻塞 | | 会话观察基于统一映射 + callId/messageId 去重 | 快照中的具体工具与 Agent 消息先映射为 `user_input_required`、`plan_updated`、`agent_report`;语音层不理解工具名,重复事件不会再次提示 | | 子 Agent 汇报不直接对用户发声 | `subagent-report` 是给主 Agent 的内部输入;只有主 Agent 形成的可见回复才进入用户语音,保留统一判断、聚合与脱敏边界 | | 工具执行下沉到语音运行时(`registerActions`) | 语音模型是“上下文 + 工具”内核、双工 + 双输出(语音流 ∥ 工具流)的工作方式;产品层只注册执行器并持有授权门,运行时统一执行/回传/异步结果,新语音产品不再复制解析循环 | | 知识整理委派给专职整理 agent(`organize_notes`) | 语音模型做不好也做不起知识归纳(上下文小、按分钟计费);整理是异步、重推理、结构化的活,交给 PKB 的文本模型 maintainer(`curate`),语音模型只负责“说开始/说完成”,双输出让两者并行 | ## 8. 已知平台依赖与下一步 - **语音回答回填**:主 Agent 的 `ask_user_question` 通过 `question/requested` 帧挂起(`questionRpcId` 不进会话快照),语音回答要回填需要宿主开放回答通道(如把 pending question 投影到会话事件,或提供可注入的 respond API)。这是平台能力,不是本插件能单方面完成的。 - **唤醒词引擎**:当前用浏览器 `SpeechRecognition` 连续听写 + 本地关键词匹配,Chrome/Edge 可用;更省电的本地唤醒(如关键词检测模型)是后续增强,不改变产品语义。