修复: DeepSeek 400 全量扫描 + 队列 per-conv 隔离

- openai_compat: 扫描所有 assistant 消息剥离 orphan tool_calls(原仅查末条)
- queue 加 conversationId 字段,按会话精准 drain
- regenerate/editMessage 只清本会话排队消息
- newConversation 保留旧会话排队消息
- AiError 只清出错会话的队列项
This commit is contained in:
2026-07-20 00:18:30 +08:00
parent 42efb31bbf
commit e9e3578d26
59 changed files with 2875 additions and 1330 deletions

View File

@@ -0,0 +1,144 @@
# 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