Files
DevFlow/docs/05-代码审查/aichat-对话功能走查-2026-07-17.md
绝尘 e9e3578d26 修复: DeepSeek 400 全量扫描 + 队列 per-conv 隔离
- openai_compat: 扫描所有 assistant 消息剥离 orphan tool_calls(原仅查末条)
- queue 加 conversationId 字段,按会话精准 drain
- regenerate/editMessage 只清本会话排队消息
- newConversation 保留旧会话排队消息
- AiError 只清出错会话的队列项
2026-07-20 00:19:50 +08:00

145 lines
9.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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` 弃首 tickAiHeartbeat 到达时前端 `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-convF-09 迁移)
### P0-2 审批计时器切/删会话不清 + 注释与代码矛盾 ✅核验成立
**位置**`useAiConversations.ts:92-257`switch 无 clearApprovalTimer/ `:260-272`delete 无清)/ `aiShared.ts:118`(默认 15minvs `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-convDEC-07a accessor | — |
| convStatesconv_state 真相源) | ✅ 已 per-convMap | 但 watchdog legacy fallback 路径 `clear()` 全局误杀P0-1 |
| queue | ❌ 全局单例 | 跨会话丢消息P1-1、强发错位P1-4 |
| watchdog | ⚠️ 双轨per-conv Map + legacy 单 timer | legacy 路径不清 by 心跳、全局 clearP0-1 |
| _approvalTimers | ❌ 全局 Map | 切/删会话不清P0-2 |
| modelOverride | ❌ 模块级 ref | 切会话丢失手选P1-3 |
| _lastDelta | ❌ 模块级单值 | 跨会话误丢 deltaP1-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