重构: aichat 双轨状态机收口 + AiCompressed 事件拆分

- generating bool + CONV_STATE_ENABLED 开关双轨退役,ConvState enum 单一真相源
- can_accept_request 接入 chat 域入口(覆盖 Stopping 竞态,严谨于 is_active)
- AiCompressed 拆 AiManualCompressed/AiAutoCompressed(治自动压缩误触桌面toast+刷新)
- convStates/getConvState 下沉 aiShared.ts 破循环依赖
This commit is contained in:
2026-06-25 03:20:42 +08:00
parent fc705443bd
commit c011f864fd
22 changed files with 526 additions and 428 deletions

View File

@@ -277,8 +277,40 @@ if has_tool_calls { /* push */ }
---
## 收口记录2026-06-25
> 双轨状态机退役enum 单一真相源。本文档 §3.2「轻量状态机,不引入框架」的轻量路线在批3 完成**升级**原方案是「bool 保留 + enum 视图只读」,收口后 enum 成为写侧真相源,bool 与开关双轨全部删除。
### 收口了什么
1. **双轨退役(写侧)**
- `generating: bool` 字段(顶层单例 + `PerConvState` 内)删除。
- `CONV_STATE_ENABLED` 灰度开关删除批2 常量删,批3 全部门控分支删)。
- `ConvState` enum`conv_state.rs`5 态 Idle/Generating/Stopping/Error/Compressed成为**唯一真相源**,所有状态写入经 `transition_to` 守卫(非法转换拒绝 `Err(InvalidTransition)`)。
- `GeneratingGuard`guard.rs的 new/reset/drop 改为无条件迁移 `ConvState` + emit `AiConvStateChanged`(原 `if CONV_STATE_ENABLED` 门控删)。
2. **AiCompressed 拆 Manual/Auto治 BUG-260624-05**
-`AiCompressed` 单事件被自动压缩路径误用 → 桌面端每次发送误弹 toast + 误刷整会话。
-`AiManualCompressed`(手动 IPC,3 处 emit 点,前端弹 toast+ `AiAutoCompressed`loop 自动,桌面静默仅复位 `isCompressing` 防按钮卡死,miniapp 仍插摘要气泡)。
3. **can_accept_request 接入(读侧)**
- `ConvState::can_accept_request()`Idle/Error 可接)接入 chat 域 4 处入口拦截:`ai_chat_send` / `ai_regenerate` / `ai_chat_edit` / `ai_is_generating`
- 比旧 `is_active()` 更严谨:覆盖 Stopping 态(漏拦停止中接新请求的竞态)。
### 为什么收口
- **双轨隐患**bool 字段与 enum 开关共存期间,任一 guard 入口漏加门控即绕过 enum 守卫回退旧 bool 写,状态机形同虚设。开关是渐进过渡的临时灰度机制,收口即删才是终态,长期保留等于把「过渡态」固化成「架构债」。
- **AiCompressed 合流**:手动(用户主动压缩,要反馈与自动loop 内压缩,静默)是两种 UX 语义,共用事件必致前端按「最严」语义(弹 toast兜底,自动路径每次都误触发。拆分是按语义而非按字段。
### 残留过渡分支(非回归,待清理)
代码内保留若干 `CONV_STATE_ENABLED off 回退分支` 注释agentic/mod.rs:1608/1722、chat.rs:1533、remote_bridge.rs:762、前端 types.ts/ChatInput.vue/MaxRoundsCard.vue/useAiEvents.ts均标注「enum 单路径」,用于兼容老后端/前端未追踪态的兜底。开关常量已删,这些注释分支实际不可触发,属说明性遗留,后续清理时一并删。
---
## 相关文档
- [df-ai AI 集成模块](../../03-模块文档/df-ai-AI集成模块-2026-06-12.md)
- [aichat 审查报告-2026-06-14](../构想审查/aichat审查报告-2026-06-14.md)
- [流式 Markdown 渲染调研-2026-06-15](../构想审查/aichat流式Markdown渲染调研-2026-06-15.md)
- `src-tauri/src/commands/ai/agentic/conv_state.rs``ConvState` enum 实现源)

View File

@@ -1002,3 +1002,10 @@
> - [x] ✅(2026-06-24 核验伪问题销账) CR-260622-01-P2-1 — **ChatMessage.timestamp 打戳"致时序错乱"核验为伪**。grep 全 src `.sort(` 零消息按 timestamp 排序:ai.ts:188 会话列表 updated_at / ConversationSidebar pinned / TopBar weight / MessageList:231 mention span start。**消息渲染顺序由 renderItems(store.state.messages 数组顺序)= DB list_by_conversation ORDER BY seq,不依赖 ChatMessage.timestamp**。timestamp 仅 formatRelative 展示(system 摘要显示插入时刻=压缩时刻,语义合理)。agent A 假设"前端按 timestamp 排序"不成立,非真实缺陷。
> - [ ] CR-260622-01-P2-2 [P2低优·评估降级] — **HTML/markdown/JSON 等非代码文件无 session 级缓存**。**2026-06-24 评估**:已有 `TOOL_RESULT_COMPRESS_ENABLED`(mod.rs:98/1029-1078 view-only 摘要,>2KB tool_result 压缩,LLM 视图摘要非全文回灌 prompt)部分缓解;read_symbol 治代码文件(主场景,降 24.4x)。非代码文件完整 session 缓存(path→hash+content+patch失效)设计复杂 + LLM patch 后重读确认行为不确定(缓存命中提示可能不够 LLM 仍重读),归 B 路线 prompt 策略(约束 patch 后不重读)更合适,工具侧完整缓存低优暂缓。
---
### ✅ 2026-06-25 generating 状态机双轨收口 + 自动压缩路径残留修复
- [x] ✅(2026-06-25) **Task#1 自动压缩成功路径残留**(BUG-260624-05 衍生)— **AiCompressed 单事件被自动压缩路径误用 → 桌面端每次发送误弹 toast + 误刷整会话**。治法:`AiCompressed``AiManualCompressed`(手动 IPC,3 处 emit 点,前端弹 toast)+ `AiAutoCompressed`(loop 自动,桌面静默仅复位 isCompressing 防按钮卡死,miniapp 仍插摘要气泡)。按语义拆事件而非按字段。— `src-tauri/src/commands/ai/mod.rs:207/212`
- [x] ✅(2026-06-25) **Task#2 双轨状态机收口**`generating: bool` 字段(顶层单例 + PerConvState)与 `CONV_STATE_ENABLED` 灰度开关双轨全部删除,`ConvState` enum(`conv_state.rs`,5 态)成唯一真相源:写侧经 `transition_to` 守卫(非法转换拒绝),`GeneratingGuard` new/reset/drop 无条件迁移 + emit;读侧 `can_accept_request()` 接入 chat 域 4 入口(ai_chat_send/ai_regenerate/ai_chat_edit/ai_is_generating),比 `is_active()` 更严谨(覆盖 Stopping 态竞态)。开关常量已删,代码内 `CONV_STATE_ENABLED off 回退分支` 注释为说明性遗留(实际不可触发,待清理)。详案见 [generating状态机加固-2026-06-15.md §收口记录](./02-架构设计/专项设计/generating状态机加固-2026-06-15.md)。— `src-tauri/src/commands/ai/agentic/{conv_state.rs,guard.rs}` + `commands/ai/{mod.rs,commands/chat.rs}`