# 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）
