# aichat 对话功能深度走查报告(2026-07-17) > 触发:用户反馈「对话功能现在存在很多问题,深入检查」。 > 方法:5 个 general-purpose 代理并行走查 5 维度(发送/流式/渲染/会话侧栏顶栏/审批上下文)→ 主代理独立 grep/read 核验所有 P0 + 关键 P1(不信代理结论,每条标注核验状态)。 > 性质:**走查任务,未改任何代码**。本报告为问题清单 + 修复方向,待用户确认后实施。 ## 健康度总评:B- 最近一批发改动(BUG-2026-07-17:状态机收敛方向正确)引入 **2 个 P0 回归**;其余 P1 多源自 **F-09 per-conv 改造未完成**的单例债(queue / legacy watchdog / _approvalTimers / modelOverride / _lastDelta 仍是全局单例,多会话并发下串话)。 --- ## P0(功能 bug,建议立即修) ### P0-1 流式 watchdog legacy 路径全局误杀并发会话 + 删 running 保护致长工具误杀 ✅核验成立 **位置**:`useAiStream.ts:57-110, 73-78` / `useAiSend.ts:156,226`(doSend reset 同款)/ `audit/mod.rs:125-128` **双重回归**(本次改动引入): ① **legacy 路径全局 clear**:doSend / regenerate(`useAiSend.ts:156`) / editMessage(`:226`) 调 `resetStreamWatchdog()` **无 convId** → 走 `_legacyWatchdog` fallback(`useAiStream.ts:128-132`)。45s 到期 `onStreamTimeout()` 无参 → `convStates.clear()`(`:77`)清掉**所有并发会话**的生成态。其他会话停止按钮失效、UI 与后端长期不一致。 ② **删 running 保护**:`onStreamTimeout`(`:69`「不管 stillGenerating 检测」)去掉了原 stillGenerating + running 工具检测。长工具执行(cargo build / 测试套件 / 大文件 patch >45s)必然触发。后端心跳首 tick **30s**(`audit/mod.rs:126` 弃首 tick),AiHeartbeat 到达时前端 `resetStreamWatchdog(convId)` 只动 per-conv Map、**不清 legacy**,保护不了 doSend 挂的 legacy timer。 **证据**:`useAiStream.ts:69`「不管 stillGenerating 检测(即使已经 false 也再清理一次)」+ `:77` `convStates.clear()` + `audit/mod.rs:125-128` 心跳 30s。 **影响**:多会话并发下其他会话被误杀 + 任何 >45s 的工具执行被前端误判断流(回到 BUG-260624-03 修复前的用户报障状态)。 **修复方向**: - legacy 路径 `convStates.clear()` → `convStates.delete(state.activeConversationId)`(对齐 legacy 单 timer 只跟踪当前会话语义) - 恢复 `onStreamTimeout` 的 running 工具检测(工具执行中跳过 + reset 续等),或 `STREAM_TIMEOUT_MS` 回调 ≥75s - 根治:doSend / regenerate / editMessage 的 `resetStreamWatchdog` 传 convId 走 per-conv(F-09 迁移) ### P0-2 审批计时器切/删会话不清 + 注释与代码矛盾 ✅核验成立 **位置**:`useAiConversations.ts:92-257`(switch 无 clearApprovalTimer)/ `:260-272`(delete 无清)/ `aiShared.ts:118`(默认 15min)vs `useAiConversations.ts:244-246`(注释「Infinity 已决策不超时」) **证据**: - switchConversation 全程未调 clearApprovalTimer / clearAllApprovalTimers(已核 90-257) - deleteConversation 仅 `clearConvStreamState(id)`,不清 `_approvalTimers` 中属于该会话的 timer - `startApprovalTimer` 到点回调(`aiShared.ts:147-163`)调 `aiApi.approve(id,false)` + push 错误气泡到 `getMessages()`(**当前 active 视图**) - `:244-246` 注释「APPROVAL_TIMEOUT_MS=Infinity 已决策,不会超时自动拒绝」与 `:118` `APPROVAL_TIMEOUT_DEFAULT_MS=900_000`(15min)**直接矛盾** **影响**:① 切走会话后 15min 到点 → 误拒后台 pending 工具 + 错误气泡 push 到当前(错误)会话视图;② 用户重启恢复审批后离开,回来发现审批已被无声自动拒绝。 **修复方向**:switchConversation 开头 `clearAllApprovalTimers()`(对齐 pendingApprovals 单例语义);deleteConversation 清该 conv 的 timer;修正 `:244` 注释。 --- ## P1(明显缺陷) ### P1-1 [发送] queue=[] 跨会话丢消息 ✅核验成立 `useAiSend.ts:155,225` `state.queue = []`,queue 是 store 全局单例。B 会话 regenerate/editMessage 清掉 A 排队的用户消息,静默永久丢失。 ### P1-2 [发送] drainQueue 无并发互斥 ✅核验成立 `useAiSend.ts:257-281` fire-and-forget 无锁,AiCompleted 连发 / stop+Completed 竞态致并发 doSend,前端 user/ai 占位双 push(后端 generating 守卫挡大部分但视图已污染)。 ### P1-3 [会话] TopBar watch(activeConversationId) 无条件重置 modelOverride ✅核验成立 `TopBar.vue:325-327` 切会话无条件 `modelOverride = enabledModels[0]`,切会话往返丢失用户手选模型;与 `:335` `watch(enabledModels,{immediate})` 叠加语义脆弱。modelOverride 是模块级 ref,不持久化、不 per-conv。 ### P1-4 [发送] tryForceSend 跨会话错位 + 队列续发 spans 丢失 ✅核验成立 `useAiSend.ts:323-328` queue item 无 convId / spans 字段,切会话后 forceSend 发到当前 active 而非原会话;mention chip 续发降级为纯文本。 ### P1-5 [流式] _lastDelta 单值跨会话/跨轮误丢合法 delta ⚠️部分成立 `useAiEvents.ts:46,292` 单值重复检测,跨会话首 delta 撞同 / 同轮合法重复(Markdown 表格分隔符等)被误丢。难复现。 ### P1-6 [流式] state.currentText += delta 无 streaming 守卫 ⚠️部分成立 `useAiEvents.ts:300`,P0-1 触发后超时收尾仍到达的 delta 污染 currentText。修 P0-1 即间接修复。 ### P1-7 [渲染] MessageItem.vue system/assistant/error 死代码 ✅核验成立 `MessageItem.vue:94-170`,MessageList 仅 `role==='user'` 引用 MessageItem(`:663-673`),AI 渲染全在 MessageList 内联。MessageItem 内 `.ai-cursor-blink` 是孤岛 class(全仓库无其他定义)。维护陷阱。 ### P1-8 [渲染] _blockCache 无失效机制 ✅核验成立 `useStreamRenderer.ts:117` 模块级 Map 无会话切换 / 配置变更失效,长会话膨胀 + Markdown 主题热改无效 + 瞬态错误固化。 ### P1-9 [渲染] messages deep watch + JSON.stringify 每帧全量遍历 ✅核验成立 `MessageList.vue:244` deep watch 每帧 O(N×content) 遍历,长会话流式掉帧。 ### P1-10 [渲染] useToolApproval onApprove confirmDialog 无防重入 ✅核验成立 `useToolApproval.ts:98-113` High 风险工具 confirmDialog await 期间 approving 未置 true,双击重复审批。 ### P1-11 [会话] 删当前会话 active=null 无 fallback ✅核验成立 `useAiConversations.ts:260-272` 删除当前会话后空白无引导,应回落相邻会话。 ### P1-12 [审批] 审批卡无 conversationId,多会话路由靠 activeConversationId 推断 ⚠️部分成立 `useAiApproval.ts:65`,F-09 多会话并发下 A 后台审批卡渲染到 B 视图时点击路由不准(依赖多会话并发 + 后台审批叠加才触发)。 --- ## P2(健壮性/可读性) - `useStreamRenderer.ts:139` tailSeq 局部失效 —— **功能没坏**(v-html 值变化兜底更新),仅注释「末块 key 每次不同」与实现(恒为 `tail-1`)相反,误导维护者。 - `ApprovalOverlay.vue:61` `Date.now()` 在 computed 内,60s 冷却期不响应式过期。 - `aiShared.ts:288-311` setConvStreaming / setConvCurrentText 收敛不对称,Map 项可能残留。 - `ConversationSidebar.vue:255` 标题 flash watch 死代码(`if(oldTitle) return` 覆盖前一行)。 - `useAiWindow.ts:309` maximized 分支 `targetHeight` 三元死代码(高度 vs 宽度比较)。 - `useToolApproval.ts:29` `emit: any` 类型擦除。 - `useAiContext.ts:29` `isCompressing` 模块单例,多窗口共享。 --- ## ❌代理误报已否决(独立核验) - **~~[会话 P0] switchConversation currentText accessor 重定向~~**:代理4 称 `:206 state.currentText=''` 在两次 await 间被 accessor 重定向。**否决**——`:113` 设 `activeConversationId=B` 后到 `:206` 是同步连续代码(无 await),`:206` 之后才 `:212 await`。accessor 不会中途重定向,写的是正确的目标会话。代理4 时序分析错误。 --- ## 根源分析 所有 P0/P1 的共同根因:**F-09 per-conv 改造未完成**。 | 状态源 | per-conv 化状态 | 串话表现 | |---|---|---| | streaming / currentText | ✅ 已 per-conv(DEC-07a accessor) | — | | convStates(conv_state 真相源) | ✅ 已 per-conv(Map) | 但 watchdog legacy fallback 路径 `clear()` 全局误杀(P0-1) | | queue | ❌ 全局单例 | 跨会话丢消息(P1-1)、强发错位(P1-4) | | watchdog | ⚠️ 双轨(per-conv Map + legacy 单 timer) | legacy 路径不清 by 心跳、全局 clear(P0-1) | | _approvalTimers | ❌ 全局 Map | 切/删会话不清(P0-2) | | modelOverride | ❌ 模块级 ref | 切会话丢失手选(P1-3) | | _lastDelta | ❌ 模块级单值 | 跨会话误丢 delta(P1-5) | --- ## 建议修复优先级 1. **P0-1**(流式误杀,最高频可复现)—— legacy `clear()` 改 `delete(activeConversationId)` + 恢复 running 工具检测 2. **P0-2**(审批误拒 + 错气泡)—— switch / delete 清 timer + 修正注释 3. **P1-1 / P1-3 / P1-4**(单例债三连,可攒批修) 4. 其余 P1(渲染性能/死代码/防重入)按维度推进 5. P2 择机清理 ## 相关文件 - `src/composables/ai/useAiStream.ts`(P0-1 核心) - `src/composables/ai/useAiSend.ts`(P0-1 reset / P1-1,2,4) - `src/composables/ai/streamingGuard.ts`(P0-1 forceReset 同款 clear) - `src-tauri/src/commands/ai/audit/mod.rs`(P0-1 后端心跳 30s) - `src/composables/ai/useAiConversations.ts`(P0-2 / P1-11) - `src/composables/ai/aiShared.ts`(P0-2 _approvalTimers / P2 收敛) - `src/components/ai/TopBar.vue`(P1-3 双 watch) - `src/components/ai/MessageList.vue`(P1-7,9) - `src/components/ai/MessageItem.vue`(P1-7 死代码) - `src/composables/ai/useStreamRenderer.ts`(P1-8 / P2 tailSeq) - `src/composables/ai/useToolApproval.ts`(P1-10)