- openai_compat: 扫描所有 assistant 消息剥离 orphan tool_calls(原仅查末条) - queue 加 conversationId 字段,按会话精准 drain - regenerate/editMessage 只清本会话排队消息 - newConversation 保留旧会话排队消息 - AiError 只清出错会话的队列项
9.8 KiB
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 已决策,不会超时自动拒绝」与:118APPROVAL_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:139tailSeq 局部失效 —— 功能没坏(v-html 值变化兜底更新),仅注释「末块 key 每次不同」与实现(恒为tail-1)相反,误导维护者。ApprovalOverlay.vue:61Date.now()在 computed 内,60s 冷却期不响应式过期。aiShared.ts:288-311setConvStreaming / setConvCurrentText 收敛不对称,Map 项可能残留。ConversationSidebar.vue:255标题 flash watch 死代码(if(oldTitle) return覆盖前一行)。useAiWindow.ts:309maximized 分支targetHeight三元死代码(高度 vs 宽度比较)。useToolApproval.ts:29emit: any类型擦除。useAiContext.ts:29isCompressing模块单例,多窗口共享。
❌代理误报已否决(独立核验)
[会话 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) |
建议修复优先级
- P0-1(流式误杀,最高频可复现)—— legacy
clear()改delete(activeConversationId)+ 恢复 running 工具检测 - P0-2(审批误拒 + 错气泡)—— switch / delete 清 timer + 修正注释
- P1-1 / P1-3 / P1-4(单例债三连,可攒批修)
- 其余 P1(渲染性能/死代码/防重入)按维度推进
- 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)