重构: AI聊天可靠性批次(审批累计计数/write_file diff预览/会话隔离补清/token缓存)

This commit is contained in:
2026-06-16 19:56:29 +08:00
parent ba7f35552b
commit d00b30f0ba
12 changed files with 238 additions and 45 deletions

View File

@@ -76,15 +76,15 @@
- [x] ✅(batch56·2026-06-16·workflow wsxxurgbr+主代核查) F-260616-08 [P2] — **AI 数据获取工具 offset 分页 + 探总量**。**全闭环落地**:①6 工具(list_projects/tasks/ideas/trash/read_file搜索模式/search_files)全部补 offset+limit 参数,返回 items/total/has_more 替代 truncated ②新增 `get_project_count`+`get_task_count` 探总量工具 ③knowledge_search 解钳 `min(3)``min(20)`。— src-tauri/src/commands/ai/tool_registry.rs + src-tauri/src/commands/knowledge.rs
- [ ] F-260616-09 [P1/架构] — **AiSession 单例 → 多会话并发架构(多窗口来回切 + 并行执行)**。用户需求(3 条消息强化):「应用级支持同时具备多会话」+「同时具备多会话的能力」+「多个会话窗口来回切,**并行执行**」。现状Explore 走查确认):`AiSession` **应用级单例**`state.rs:164` `ai_session: Arc<Mutex<AiSession>>`init:205messages/generating/stop_flag/pending_approvals/agent_language 全一份。设计「单例 + 软隔离」——generating 互斥(同一时刻**只一个对话能跑**) + 切换旧 loop 退出(`agentic.rs:177-190` B-260615-11) + 切换时 generating→readonly(`commands.rs:873-881`)。`llm_concurrency` per_conv 因此退化为单对话内并发(`state.rs:93-96` 注释已标注预留改 HashMap)。**memory 已标记「AiSession 单例未动」架构债**(aichat-arch-extensibility)。**残留 bug**(表象=新建会话上下文残留):前端 `newConversation`(`useAiConversations.ts:31-41`) 漏清 queue/generatingConvId/agentRound/searchQuery后端 `ai_conversation_create`(`commands.rs:784-821`) 漏清 agent_language/stop_flag(设 true 停旧 loop 没复位 false)。**用户「并行执行」明确=B 方向**(A 只解切换残留不满足并行)。**分档****A 短期隔离修复**(补漏清字段,维持单例软隔离,解残留,低风险立即做,**B 前置**) + **B 多会话并发架构**(单例 → `HashMap<conv_id, SessionState>`,每会话独立 messages/generating/stop_flag/pending_approvals + llm_concurrency per_conv 改真 per-conv HashMap + loop 内校验改 conv_id 索引各自 session + 切换不退出旧 loop 各自跑完真并发 + 事件路由核对 conversation_id 全覆盖)。**决策点(待定)**(a) A 先做解燃眉 vs 直接 B——倾向 **A 先**(残留真 bug 立即解B 架构另立项) (b) B 形态——AiSession 整体 HashMap vs 拆字段(messages/pending 已可按 conv reload只拆 generating/stop_flag per-conv)——倾向**后者侵入小** (c) B 并发上限——复用 llm_concurrency.global 限并发会话数防 token 暴增——倾向是 (d) **多窗口 UI 形态**——(d1) 单面板侧栏会话列表切换+后台并行(改动小) vs (d2) 每会话独立 Tauri 窗口(`useAiWindow.ts` detached 已有基础可扩,改动大但「窗口来回切」语义最贴)——**待用户定**,倾向先 d1 后 d2 (e) 旧 loop 切换处理——不退出各自跑完(真并发,倾向) vs 仍退出。— state.rs(AiSession 去单例化) + commands.rs(create/switch 重构 + 漏清补) + agentic.rs(:177-190 校验改 conv_id 索引) + context.rs(ContextManager per-conv) + useAiConversations.ts(漏清补) + stores/ai.ts(per-conv state) + llm_concurrency per_conv + useAiWindow.ts(若 d2 多窗口)。**B 是大改架构升级,需单独设计文档 + 立项**。**待用户定 A 先做 / B 立项 / 多窗口 d1·d2 方向**。
- [ ] F-260616-09 [P1/架构·**A路线✅(batch60·2026-06-16·workflow whae812z5+主代核查),B多会话并发待立项**] — **AiSession 单例 → 多会话并发架构(多窗口来回切 + 并行执行)**。用户需求(3 条消息强化):「应用级支持同时具备多会话」+「同时具备多会话的能力」+「多个会话窗口来回切,**并行执行**」。现状Explore 走查确认):`AiSession` **应用级单例**`state.rs:164` `ai_session: Arc<Mutex<AiSession>>`init:205messages/generating/stop_flag/pending_approvals/agent_language 全一份。设计「单例 + 软隔离」——generating 互斥(同一时刻**只一个对话能跑**) + 切换旧 loop 退出(`agentic.rs:177-190` B-260615-11) + 切换时 generating→readonly(`commands.rs:873-881`)。`llm_concurrency` per_conv 因此退化为单对话内并发(`state.rs:93-96` 注释已标注预留改 HashMap)。**memory 已标记「AiSession 单例未动」架构债**(aichat-arch-extensibility)。**残留 bug**(表象=新建会话上下文残留):前端 `newConversation`(`useAiConversations.ts:31-41`) 漏清 queue/generatingConvId/agentRound/searchQuery后端 `ai_conversation_create`(`commands.rs:784-821`) 漏清 agent_language/stop_flag(设 true 停旧 loop 没复位 false)。**用户「并行执行」明确=B 方向**(A 只解切换残留不满足并行)。**分档****A 短期隔离修复**(补漏清字段,维持单例软隔离,解残留,低风险立即做,**B 前置**) + **B 多会话并发架构**(单例 → `HashMap<conv_id, SessionState>`,每会话独立 messages/generating/stop_flag/pending_approvals + llm_concurrency per_conv 改真 per-conv HashMap + loop 内校验改 conv_id 索引各自 session + 切换不退出旧 loop 各自跑完真并发 + 事件路由核对 conversation_id 全覆盖)。**决策点(待定)**(a) A 先做解燃眉 vs 直接 B——倾向 **A 先**(残留真 bug 立即解B 架构另立项) (b) B 形态——AiSession 整体 HashMap vs 拆字段(messages/pending 已可按 conv reload只拆 generating/stop_flag per-conv)——倾向**后者侵入小** (c) B 并发上限——复用 llm_concurrency.global 限并发会话数防 token 暴增——倾向是 (d) **多窗口 UI 形态**——(d1) 单面板侧栏会话列表切换+后台并行(改动小) vs (d2) 每会话独立 Tauri 窗口(`useAiWindow.ts` detached 已有基础可扩,改动大但「窗口来回切」语义最贴)——**待用户定**,倾向先 d1 后 d2 (e) 旧 loop 切换处理——不退出各自跑完(真并发,倾向) vs 仍退出。— state.rs(AiSession 去单例化) + commands.rs(create/switch 重构 + 漏清补) + agentic.rs(:177-190 校验改 conv_id 索引) + context.rs(ContextManager per-conv) + useAiConversations.ts(漏清补) + stores/ai.ts(per-conv state) + llm_concurrency per_conv + useAiWindow.ts(若 d2 多窗口)。**B 是大改架构升级,需单独设计文档 + 立项**。**待用户定 A 先做 / B 立项 / 多窗口 d1·d2 方向**。
- [x] ✅(batch58·2026-06-16·workflow w2dw65maz+主代核查,vue-tsc 0err) F-260616-10 [P1·bug] — **provider 切换/默认值重启·刷新后失效(前端 activeProvider 不同步 DB 默认)**。用户实测Settings 设 A 为默认 → 重启 → 默认变 B(列表第一个)。**根因全在前端,后端链路正确**(独立核验不信 PROGRESS.md「已修复」声明不可信)`ai_set_provider`(`commands.rs:747-774`) 互斥落库✅ + `update_full`(`crud.rs:1127`) SQL UPDATE 含 `is_default`✅ + `get_active_provider`(`prompt.rs:9-32`) 走 `DB.is_default`✅。**根因链**:①重启后前端 `store.activeProvider = null`(`stores/ai.ts:53`,无 localStorage 持久化 + 无启动恢复) ②`loadProviders`(`useAiPanel.ts:91-93`) 只拉列表**不同步 DB 默认** → activeProvider 仍 null ③**UI 错觉**`activeProviderName`(`AiChat.vue:1169-1172`) null 时 fallback `providers[0]`(列表第一个) ≠ 用户设的 A → 「默认变另一个」 ④**真覆盖**:用户点 provider bar 切换 `cycleProvider`(`AiChat.vue:1180-1185`)`findIndex(activeProvider=null)` 返回 **-1** → `next = ps[(1+1)%len] = ps[0]` = 列表第一个 B → `setProvider(B)` → 后端互斥落库把 `DB.is_default` 从 A **真改成 B**。即使用户不点切换UI 也显示 providers[0] 而非 A。**修复方向**:①`loadProviders` 后同步 `activeProvider` = DB.is_default 的 provider——后端加 `ai_get_active_provider` IPC 返回当前默认 id`listProviders` 返回值带 `active_provider_id``cycleProvider` findIndex=-1 陷阱修正(null 时应从 DB 默认起算,非 ps[0]) ③前端 activeProvider 持久化(localStorage `df-ai-active-provider`,对齐 `df-ai-active-conv` 模式)。— useAiPanel.ts(loadProviders 同步默认) + AiChat.vue(:1169 activeProviderName fallback + :1180 cycleProvider findIndex 陷阱) + commands.rs(新 ai_get_active_provider IPC) + api/ai.ts + stores/ai.ts(activeProvider 持久化)。**确定性 bug低风险可立即修**。
- [ ] F-260616-11 [P1/设计风险] — **审批后 iteration 重置致 max_iterations/token 超配**性能分析批次发现:`try_continue_agent_loop` 重新 spawn `run_agentic_loop`**iteration 从 0 重计**`agentic.rs:418` 注释F-260616-03 续跑决策 a 的延续审批续跑复用此入口。每次审批Med/High 工具)后配额重置,多次审批=反复重跑 max_iterations 轮,实际可跑远超配额,**token 消耗不受 max_iterations 严格控制**。影响:长对话多次审批致 token 失控 + 对话变慢(每审批一次重跑 LLM 轮次)。**方向(待评估)**:①审批不重置 iteration累计计数②审批是用户决策点重置合理但需独立「审批次数」配额 ③至少 token 总量兜底。— agentic.rstry_continue iteration 计数策略)。**需决策**iteration 累计 vs 重置
- [x] ✅(batch60合批·2026-06-16·主代核查,cargo 0err) F-260616-11 [P1·决策a累计计数] — **审批后 iteration 重置致 max_iterations/token 超配**决策(2026-06-16):**a 累计计数**(try_continue 不重置 iteration,跨审批续跑延续累计)。**已实施**:`iteration_used:usize` 累计字段(`mod.rs:215/230` AiSession+new init 0)+`start_iteration:usize` 参数(`agentic.rs:114` run_agentic_loop signature +`:176` loop 边界 `start_iteration..max_iterations` +`:210` 一致性块 `iteration_used=iteration+1` +`:578` try_continue 加参 +`:675` spawn 透传)+三处 reset(`commands.rs:55/152/412` ai_chat_send/ai_regenerate/ai_edit_last 新生命周期)+ai_approve 两处累计读 iteration_used(`:260-262` 拒绝续跑 +`:313-315` 通过续跑)+`ai_continue_loop:601` reset+传0(F-03 决策a 达max重计区分)。两路径严格:审批累计(ai_approve) vs 达max重计(ai_continue_loop,F-03 a 用户授权重来) vs 新消息 reset。边界case:start≥max loop 空区间→converged=false→AiMaxRoundsReached(防无限审批烧 token,正 F-11 语义)。原:`try_continue_agent_loop` 重 spawn `run_agentic_loop` iteration 从 0 重计致多次审批反复重跑 max_iterations 轮 token 失控。— agentic.rs + commands.rs + mod.rs
- [ ] F-260616-12 [P2/依赖F-09] — **retry 持 permit 不释放(多会话隐患)**。F-260616-07 落地的流式重试循环(`agentic.rs:238-239`)重试期间持有 global+per_conv permit 不释放(注释「防新请求挤占」)。当前 AiSession 单例 + 主 loop 串行无影响,但 **F-260616-09 多会话并发后**,重试期间阻塞其他对话 LLM 调用。**方向**:多会话落地时核对——主 loop 串行下重试持 permit 防自己挤占无意义,倾向重试不持 permit 或仅持 per_conv。— agentic.rs:238 + F-260616-09 多会话架构。**依赖 F-260616-09 立项后一并处理**。
- [ ] F-260616-13 [P2/小优化] — **build_for_request 持锁重活 + system_prompt token 每轮重估**。性能分析批次发现:每轮 `stream_llm``session_arc.lock()``agentic.rs:251`)持锁期间做 `TokenEstimator::estimate_text(system_prompt)`L252+ `build_for_request`L253 history clone + 裁剪。system_prompt loop 外固定传入,**每轮重估其 token 是浪费**可缓存build_for_request 持锁做 history clone 是重活消息多时200 cap锁持有期长。**方向**①system_prompt token loop 外算一次缓存 ②build_for_request 先 clone messages 释放锁再裁剪。低收益优化。— agentic.rs:251-253。
- [x] ✅(batch60·2026-06-16·workflow whae812z5+主代核查,cargo 0err) F-260616-13 [P2] — **build_for_request 持锁重活 + system_prompt token 每轮重估**。性能分析批次发现:每轮 `stream_llm``session_arc.lock()``agentic.rs:251`)持锁期间做 `TokenEstimator::estimate_text(system_prompt)`L252+ `build_for_request`L253 history clone + 裁剪。system_prompt loop 外固定传入,**每轮重估其 token 是浪费**可缓存build_for_request 持锁做 history clone 是重活消息多时200 cap锁持有期长。**方向**①system_prompt token loop 外算一次缓存 ②build_for_request 先 clone messages 释放锁再裁剪。低收益优化。— agentic.rs:251-253。
- [x] ✅(主代核验·2026-06-16) F-260616-14 [P2/核验] — **max_tokens 8192 截断 + 前端批量审批核验**。核验结论:①`max_tokens=8192`(实际 `agentic.rs:270`行号漂移≈6-8k 字日常够用,长回复截断由 batch59 MidStream 保文兜底不丢文,**可配低优先非阻塞**(登记可选优化)②前端批量审批 **AE-2025-01 已完整闭环**ToolCardList.vue:4-11 `ai-batch-approve` 栏 + AiChat.vue:444 `@batch-approve``store.batchApprove` + useAiSend.ts:338 遍历 pendingApprovals 逐个 approveToolCall + i18n approveAll/rejectAll满足「一轮多 Med/High 全 insert pending→一次批量批全部」③audit.rs:496 `process_tool_calls` 一轮多工具 Med/High 进审批门控 insert pending + Low 并行执行链路通。无阻塞 bug销账。— agentic.rs:270 + ToolCardList.vue + audit.rs:496。
@@ -126,7 +126,7 @@
- [x] ✅(batch35·2026-06-16·workflow) **①-3** i18n 文案 — `src/i18n/{zh-CN,en}/taskDetail.ts`(taskDetail.* 命名空间,8 key 对称:workflowAdvanceTitle/Advancing/AdvanceFailed{msg}/StepRunning{node}/StepsProgress{done}{total}/CompletedHint/FailedHint)。注:todo 原路径 `locales/tasks.ts` 不准,实为 `i18n/taskDetail.ts`
- **实施顺序**:④-1→②-1→②-5→②-2→②-3→②-4→②-6→①-1/①-3。**最小里程碑**(④-1+②-1+②-2+②-3):run_workflow 单 task_advance 节点 DAG 端到端推进 todo→in_progress
- [ ] F-260616-07 阶段3 AI 执行闭环advance_task/run_workflow 工具+AiNode+自审)+ **F-03收口三件**(advance_task注册AI工具+update_task描述/schema移status+df-storage tasks白名单移status,同步落地防AI工具行为不一致)
- [ ] F-260616-08 阶段4 Git 集成kind+git闸门+worktree
- [x] ⏸️(待决策.md已决b暂缓·2026-06-16) F-260616-08 阶段4 Git 集成kind+git闸门+worktree
- [x]**CR-260616-01 代码审查完成** → 审查登记已迁 [待审查.md](./待审查.md)(职责分离:审查队列独立,不进 todo)。结论 🔴0 🟡6 ⚪4 质量优,8 维度全过。**待修项 CR-01-A~I 见下方推进区**。
> **审查发现待修项(2026-06-16 · CR-260616-01)**:
@@ -150,9 +150,9 @@
- [x] ✅(batch50·2026-06-16·workflow w6nqio4o5) **CR-15-1** [med] `crates/df-execute/src/shell.rs:60-79``.kill_on_drop(true)` → 已修复(cmd.kill_on_drop(true) 三类型覆盖 + 注释配合 tokio::time::timeout 防孤儿,文案「进程已终止」名副其实)
- [x] ✅(主代理验证:不成立·workspace cargo check exit 0) 🔴 **CR-23-1** [high·紧急·阻断构建] `crates/df-ai/src/context.rs:491` E0594 → **不成立**:审查代理跑 batch51 中间态 filter 版(EXIT 101),batch51 最终改 for 循环(EXIT 0,df-ai + workspace 双验证,代理自报属实)。context.rs:488 当前是 `for t in iter_mut()` 单层引用可写 status,无 filter 闭包。审查锚定滞后(跑 batch51 中间态)— CR-260616-23 归档
- [x] ✅(主代理修·CSS :has item 左边线) **CR-22-1** [med·UX] `AiChat.vue` 已置顶非 hover 不可见 → 已修复(`.ai-conv-item:has(.ai-conv-item-act--pinned){ box-shadow: inset 2px 0 0 accent }` 左边线强调,不破坏 hover 浮出 toggle,原方案图钉常驻与 actions display:none 矛盾,改 :has item 左线最简)— CR-260616-22 归档
- [ ] **CR-34-1** [med] useAiPanel.ts:101 loadProviders 守卫 `if(!state.activeProvider)` 只查 null 不查有效性 — localStorage savedProvider 指向已删除 provider 时 activeProvider 残留无效 id → cycleProvider:1185 findIndex=-1 跳过 ps[0] + 发消息消费方用无效 id。修法:守卫加 `|| !list.some(p=>p.id===state.activeProvider)` 残留无效时回落 DB is_default — CR-260616-34 归档
- [ ] **CR-30-1** [med] agentic.rs:298 退避 `1<<retry_attempt` 纯指数无 jitter/无总预算/一律重试(含 MidStream),三处注释(agentic.rs:37/commands.rs:1106/state.rs:185)误称「对齐 retry.rs(1s→2s→4s+jitter)」。retry.rs:67-78 实有 ±20% jitter+30s 预算+4xx 分类(:102-104 Fatal 立即放弃)。修法:agentic 加 jitter+总预算+错误分类真对齐,或删注释改「独立简化策略」— CR-260616-30 归档
- [ ] **CR-30-2** [med] AiStreamRetry 死事件 + MidStream 重试文本混乱 — 后端 emit(agentic.rs:310)+types.ts:214 variant 有,但 useAiEvents.ts switch 无 case 穿透忽略;MidStream 失败重试时已 emit AiTextDelta 累积 currentText,重试新 delta 追加→文本重复。修法:useAiEvents 补 case+重试前清 currentText;或后端 MidStream 不重试只重试 Init(对齐决策a)— CR-260616-30 归档
- [x] ✅(CR-260616-35 复审 PASS·主线程预核验) **CR-34-1** [med] useAiPanel.ts:102 loadProviders 守卫 `if(!state.activeProvider)` 只查 null 不查有效性 — localStorage savedProvider 指向已删除 provider 时 activeProvider 残留无效 id → cycleProvider:1185 findIndex=-1 跳过 ps[0] + 发消息消费方用无效 id。修法:守卫加 `|| !list.some(p=>p.id===state.activeProvider)` 残留无效时回落 DB is_default — CR-260616-34 归档
- [x] ✅(CR-260616-35 复审 PASS·agent cargo 6+20 passed) **CR-30-1** [med] agentic.rs:298 退避 `1<<retry_attempt` 纯指数无 jitter/无总预算/一律重试(含 MidStream),三处注释(agentic.rs:37/commands.rs:1106/state.rs:185)误称「对齐 retry.rs(1s→2s→4s+jitter)」。retry.rs:67-78 实有 ±20% jitter+30s 预算+4xx 分类(:102-104 Fatal 立即放弃)。修法:agentic 加 jitter+总预算+错误分类真对齐,或删注释改「独立简化策略」— CR-260616-30 归档
- [x] ✅(CR-260616-35 复审 PASS·agent cargo 6+20 passed) **CR-30-2** [med] AiStreamRetry 死事件 + MidStream 重试文本混乱 — 后端 emit(agentic.rs:310)+types.ts:214 variant 有,但 useAiEvents.ts switch 无 case 穿透忽略;MidStream 失败重试时已 emit AiTextDelta 累积 currentText,重试新 delta 追加→文本重复。修法:useAiEvents 补 case+重试前清 currentText;或后端 MidStream 不重试只重试 Init(对齐决策a)— CR-260616-30 归档
### ⏸️ 待用户授权/确认巡检悬挂项·2026-06-16
@@ -274,7 +274,7 @@
- [x] ARC-260615-02 ✅(2026-06-16,待commit) — ~~`/decisions` 路由死链~~ 删 nav 项App.vue secondaryNav+ 删 `nav.decisions` i18n keyzh/en。决策治理 = F-260614-08 长期项未实现,入口提前占位成死链;`dashboard.recentDecisions`Dashboard.vue不同命名空间保留
**短期(低成本)**
- [ ] ARC-260615-03 — **重评降级2026-06-16**df-execute **非空壳**——shell.rs `execute()` 已完整实现cmd/sh 跨平台 + kill_on_drop + timeout + env+ 被 tool_registry.rs `run_command` 复用F-260615-05。原 todo「76 行/1 函数 + TODO」基于 lib.rs 空判,过时。合并进 df-nodes 破坏职责分离(节点定义 vs 执行运行时)+ 动依赖树,**转架构维护决策,非清债**
- [x] ✅(决策a·2026-06-16·转架构维护记录无代码) ARC-260615-03 — **重评降级2026-06-16**df-execute **非空壳**——shell.rs `execute()` 已完整实现cmd/sh 跨平台 + kill_on_drop + timeout + env+ 被 tool_registry.rs `run_command` 复用F-260615-05。原 todo「76 行/1 函数 + TODO」基于 lib.rs 空判,过时。合并进 df-nodes 破坏职责分离(节点定义 vs 执行运行时)+ 动依赖树,**转架构维护决策,非清债**
- [x] ARC-260615-04 ✅ wave(2026-06-16,待commit) — ~~`stores/index.ts` 补 `export useAiStore`~~ barrel 补导出(3 处 view 直连可选迁移,非强制)
**中期(技术债)**
@@ -505,7 +505,7 @@
> 用户需求AiChat 消息气泡支持一键拷贝——**文本拷贝**(content 原文→剪贴板) + **截图拷贝**(气泡 DOM 渲染→图片→剪贴板,粘贴出图片)。**现状**:全项目零 clipboard 实现grep `navigator.clipboard`/`copy`/`clipboard`/`拷贝`/`复制` 零命中)。气泡结构:用户消息 `.ai-msg-bubble--user`(纯文本 `{{msg.content}}`, :185)AI 消息 `.ai-msg-bubble--ai.ai-md`(v-html 渲染 MD, :197)。**Tauri WebView2(Edge)**Clipboard API 完整支持(`writeText`/`write([ClipboardItem])`),无需额外 plugin做减法不用 `@tauri-apps/plugin-clipboard-manager`)。**安全约束**:必须在用户点击事件(手势)内调用剪贴板 API。
- [x] F-260615-13 [P2] — **AiChat 气泡文本拷贝**。每条消息气泡右上角加拷贝按钮(📋 icon,hover 显示)。点击 → `navigator.clipboard.writeText(msg.content)` → 短暂 toast「已复制」。AI 气泡拷原始 content(非 v-html 渲染后 HTML,防 HTML 标签污染);用户气泡同。按钮位置:`.ai-msg-bubble` 内右下角或 hover 时右上角浮现(不占空间,信息密度优先)。i18n 加 `aiChat.copied` / `aiChat.copyMsg`。— src/components/AiChat.vue(:185 用户/:197 AI) + src/i18n (第⑫批销账 06-15)
- [ ] F-260615-14 [P2] — **AiChat 气泡截图拷贝(图片)**。同位置第二按钮(🖼️ 或与文本拷贝合并为下拉)。技术链:`html2canvas(bubbleElement)` → canvas → `canvas.toBlob('image/png')``new ClipboardItem({ 'image/png': blob })``navigator.clipboard.write([item])`。需加依赖 `html2canvas`(~90KB,或轻量替代 `dom-to-image-more` ~3KB,待选型)。**注意**:html2canvas 对 CSS 支持有限(阴影/渐变/CSS 变量可能不全),但气泡样式简单(ai-md 基本色+圆框),风险低。粘贴目标:Word/微信/钉钉等富文本编辑器可接图片;纯文本框不行(正常行为)。— src/components/AiChat.vue + 新依赖(html2canvas/dom-to-image-more 待选)
- [x] ✅(待决策.md已决c不做·2026-06-16) F-260615-14 [P2] — **AiChat 气泡截图拷贝(图片)**。同位置第二按钮(🖼️ 或与文本拷贝合并为下拉)。技术链:`html2canvas(bubbleElement)` → canvas → `canvas.toBlob('image/png')``new ClipboardItem({ 'image/png': blob })``navigator.clipboard.write([item])`。需加依赖 `html2canvas`(~90KB,或轻量替代 `dom-to-image-more` ~3KB,待选型)。**注意**:html2canvas 对 CSS 支持有限(阴影/渐变/CSS 变量可能不全),但气泡样式简单(ai-md 基本色+圆框),风险低。粘贴目标:Word/微信/钉钉等富文本编辑器可接图片;纯文本框不行(正常行为)。— src/components/AiChat.vue + 新依赖(html2canvas/dom-to-image-more 待选)
### 🟡 AI Chat 交互体验改进2026-06-14 方案 → 待办化)
@@ -548,7 +548,7 @@
- [x] AE-2025-01 [P0] — **批量审批**。同一轮多 pending 时ToolCardList 顶部显示「全部批准(N) / 全部拒绝」按钮。改动:①`ToolCardList.vue` 加批量操作栏(监听 pending_approval 数量)②`useAiSend.ts` 新增 `approveAll(rejectAll)` 循环调 `ai_approve`。预估 0.5 天 — src/components/ToolCardList.vue + src/composables/ai/useAiSend.ts (第⑫批销账 06-15)
- [x] AE-2025-02 [P0] — **审批计数器 + 跳转**。输入框上方/header 显示 `⏳ N 项待审批` 徽标,点击跳转到第一个 pending 卡片。改动:①`AiChat.vue` header 区域加审批徽标 ②`ToolCardList.vue` 暴露 `scrollToFirstPending` 方法。预估 0.5 天 — src/components/AiChat.vue + src/components/ToolCardList.vue (第⑬批销账 06-15)
- [ ] AE-2025-03 [P0] — **write_file diff 预览** ⚠️**降级阻塞(2026-06-16·batch54 workflow 探索+主代核验)**:后端 write_file **未返 diff**(`tool_registry.rs:694` 结果 payload `{path,bytes_written,old_size,encoding}` 无 diff 字段,`generate_diff` 仅 patch_file 调用),前端 ToolCard 无 diff 可渲染,审批 payload `AiApprovalRequired`/`PendingApproval` 亦无 diff(只有 `args={path,content,encoding}` 纯新内容,拿不到旧文件)。需**后端先补**再追加前端渲染。**两条路径**(均 src-tauri 改动,跨 mod.rs/audit.rs/types.ts/AiChat.vue 领地):路径 B(推荐,满足审批预览诉求)审批 payload 加 `diff` 字段 + audit.rs:555 挂起审批前预读旧文件 + `generate_diff` 注入事件;路径 A 仅 write_file handler 补 diff 但结果阶段才有(审批时看不到,不满足)。前端部分待后端 diff 返回后追加 `<pre>` 红删绿增渲染。原诉求:write_file 审批时若文件已存在,展示前后对比 diff 而非裸 contentcontent 可能几百行代码截断到 300 字符)。改动:①后端 `tool_registry.rs` write_file handler 执行前读旧文件返回 diff ②`ToolCard.vue` pending_approval + name=write_file 时渲染 diff 视图(红删绿增)。关联 F-260615-10(diff 工具)可复用。预估 1 天 — src-tauri/src/commands/ai/tool_registry.rs + src/components/ToolCard.vue
- [x] ✅(batch60·2026-06-16·workflow whae812z5+主代核查·路径B,cargo+vue-tsc 0err) AE-2025-03 [P0] — **write_file diff 预览** ✅**已落地(batch60 路径B)**:后端 write_file **未返 diff**(`tool_registry.rs:694` 结果 payload `{path,bytes_written,old_size,encoding}` 无 diff 字段,`generate_diff` 仅 patch_file 调用),前端 ToolCard 无 diff 可渲染,审批 payload `AiApprovalRequired`/`PendingApproval` 亦无 diff(只有 `args={path,content,encoding}` 纯新内容,拿不到旧文件)。需**后端先补**再追加前端渲染。**两条路径**(均 src-tauri 改动,跨 mod.rs/audit.rs/types.ts/AiChat.vue 领地):路径 B(推荐,满足审批预览诉求)审批 payload 加 `diff` 字段 + audit.rs:555 挂起审批前预读旧文件 + `generate_diff` 注入事件;路径 A 仅 write_file handler 补 diff 但结果阶段才有(审批时看不到,不满足)。前端部分待后端 diff 返回后追加 `<pre>` 红删绿增渲染。原诉求:write_file 审批时若文件已存在,展示前后对比 diff 而非裸 contentcontent 可能几百行代码截断到 300 字符)。改动:①后端 `tool_registry.rs` write_file handler 执行前读旧文件返回 diff ②`ToolCard.vue` pending_approval + name=write_file 时渲染 diff 视图(红删绿增)。关联 F-260615-10(diff 工具)可复用。预估 1 天 — src-tauri/src/commands/ai/tool_registry.rs + src/components/ToolCard.vue
**P1 — 增强控制力**
@@ -570,7 +570,7 @@
- [ ] B-260614-05 — **[P2→降级]** 分离窗口detached跨窗口状态 — **核对修正**reattachPanel 已接线(不再死代码)+ `tauri://destroyed` 监听已复位状态,"detached 永真卡死"已修复;剩余 localStorage `df-ai-gen`/`df-ai-text` 是 Sprint 19 **有意保留**(流式临时快照高频写),非 bug。仅在出现新场景失效时再评估改全局 emit/listen — **去重**:与 AR-M5跨窗口 state 完全隔离同类aichat 审查描述更深 — source:代码审查 + Sprint 19 (06-14)
- [x] T-260614-03 — ~~工具层白名单与 crud 白名单双份去重~~ ✅ WF-B ⑤ 已解决tool_registry update_project 复用 is_allowed_column与 CRUD 白名单同源,删硬编码 match(06-14)
- [ ] F-260614-02 — 技能联想「使用」 — 首批 3 类联想已做,联想后实际触发/执行技能未实现 (06-14)
- [ ] F-260614-02 — 技能联想「使用」·**决策ai调用已定待设计**(2026-06-16) — 首批 3 类联想已做,「使用」=**AI 调用技能**(联想选中→技能内容/指令注入 AI context→AI 按指令执行,非执行本机 claude 技能)。实施机制待设计(技能→AI context 注入 vs 技能注册 AI 工具,依赖 F-07 trait 下沉)
- [ ] F-260614-03 — **[需设计]** 灵感对抗评估接 LLM — 核对 adversarial.rs`evaluate()` 纯启发式,接 LLM 需改签名注入 provider。3 种注入方式待定A.trait 解耦df-ideas 定义 IdeaAnalyzer trait推荐/ B.df-ideas 直接依赖 df-ai / C.closure。属架构决策非直接做 (06-14)
- [x] T-260614-04 — ~~路径校验根治~~ ✅ 已完成resolve_workspace_path 加 canonicalize 防 symlink 逃逸 + 词法 starts_with 兜底仅校验、返回词法路径保持前端友好cargo check 0 err / 22 test pass(06-14)
- [ ] F-260614-04 — 多 Provider 负载均衡池 — 备用模型/多账号聚合,全局容量=min(各 provider 上限之和, global_cap) (06-14)
@@ -579,10 +579,10 @@
- [x] T-260614-09 — ~~idea.rs 物理删不级联~~ ✅ WF-E 完成idea.rs:149 delete→purge_with_descendants1 行,签名兼容)(06-14, commit 89da9fa)
- [x] T-260614-10 — ~~findBinding canonicalize~~ ✅ WF-E 判定已解决normalize_path 已含 canonicalize 优先 + 词法回退find_binding_conflict 两端对称已用,无需重复加;零改动)(06-14, commit 89da9fa)
- [ ] F-260614-07 — **[架构前置]** df-ai-core trait 下沉拆 crate — F-03 注入方式前置(决策记录已收敛:原 F-03 的 A/B/C 选型作废,统一为全局 AI trait 下沉独立 cratedf-ideas/df-nodes 依赖 trait 而非 df-ai 具体 impl。解锁 F-03 — source:功能决策记录 (06-14)
- [ ] F-260614-08 — 决策治理产品化 — 活契约/规格契约自检机制产品化为可操作功能(当前散落文档机制) — source:功能决策记录缺口
- [ ] F-260614-09 — 项目 status 字段治理 — status 状态机规范化planning/active/archived 等枚举约束) — source:功能决策记录缺口
- [ ] F-260614-10 — 知识库 MCP Server + Tier 2/3 — 对外 MCP 暴露 + 分层存储(当前仅 Tier 1 全栈) — source:PROGRESS Sprint15
- [ ] T-260614-11 — 全局#6 条件表达式引擎升级 — df-workflow conditions 仅支持 true/false 字面量,升级为真表达式求值 — source:PROGRESS 全局问题
- [x] ⏸️(待决策.md已决c暂缓·2026-06-16) F-260614-08 — 决策治理产品化 — 活契约/规格契约自检机制产品化为可操作功能(当前散落文档机制) — source:功能决策记录缺口
- [x] ⏸️(待决策.md已决c暂缓·2026-06-16) F-260614-09 — 项目 status 字段治理 — status 状态机规范化planning/active/archived 等枚举约束) — source:功能决策记录缺口
- [x] ⏸️(待决策.md已决c暂缓·2026-06-16) F-260614-10 — 知识库 MCP Server + Tier 2/3 — 对外 MCP 暴露 + 分层存储(当前仅 Tier 1 全栈) — source:PROGRESS Sprint15
- [x] ⏸️(待决策.md已决暂缓·2026-06-16) T-260614-11 — 全局#6 条件表达式引擎升级 — df-workflow conditions 仅支持 true/false 字面量,升级为真表达式求值 — source:PROGRESS 全局问题
- [x] T-260614-12 — ~~df-ideas 死代码~~ ✅ WF-E 部分完成capture.rs 删 CaptureInput/IdeaCapture 死码,保留 Idea/IdeaScores 共享实体promotion/scoring/adversarial 内"两套 Recommendation/PromotionPolicy 死枚举"嫌疑 agent 未确认存在/保留为对外契约,本次未动,待复查)(06-14, commit 89da9fa)
- [x] F-260615-01 ✅(2026-06-16,待commit a3cccb070fe9c8821) — **[P1 功能增强]** HumanNode 审批节点支持自定义选项 + 单选/多选类型。现状config `options: Vec<String>` 已支持任意数量2/3/…数量扩展已通),但 `decision` 是单 `String` 仅单选语义,`options` 空=自由文本。增强目标:不止「同意/拒绝」二选一,可配置 N 个候选项 + 单选(single)/多选(multiple)两种类型。**改动面**:①`df-core/events.rs` `WorkflowEvent::HumanApprovalRequest``select_type``HumanApprovalResponse` decision 单值→多值(`decisions: Vec<String>` 或保留 decision 兼容 + 加 decisions`human_node.rs` config 解析 `select_type` + 校验(多选时每项 ∈ options可加 min/max 选中数约束③IPC `approve_human_approval` 签名 ④前端 `stores/project.ts` approve + `api/types.ts` 事件类型 + 审批弹窗 UI单选 radio / 多选 checkbox⑤单测改断言 + 新增多选/超限测。**注意**:向后兼容现 single 调用方,`select_type` 缺省 = single — source:用户需求(06-15)crates/df-nodes/src/human_node.rs + src-tauri/src/commands/workflow.rs + src/stores/project.ts — **✅已实施(a3cccb070fe9c8821,2026-06-16,待commit)**:6 文件契约向后兼容(events.rs SelectType 枚举 Single/Multiple 缺省 Single+HumanApprovalResponse decision+decisions 双字段/human_node.rs 校验 single len==1·multiple len≥1·∈options/workflow.rs IPC 加 decisions+select_type Option 缺省兼容/types.ts/project.ts approve/ProjectDetail.vue checkbox 多选 UI)。cargo check 0 err/df-nodes 21 test(含 4 新增多选)/vue-tsc 0 err。主代理核查契约向后兼容 + 6 文件边界
- [x] F-260615-02 ✅(2026-06-16,待commit) — **[P1 功能]** task 详情查看。✅已实施:get_task_by_id IPC(复用 TaskRepo::get_by_id ok_or_else 转 Result)+lib.rs 注册+taskApi.get+/tasks/:id 路由+TaskDetail.vue(11 字段:标题/描述/状态/优先级/关联项目 router-link 解析名/分支标签/负责人/基础分支/工作流定义/创建更新时间;复用 constants/project 标签+formatDate+watch route.params.id 重载)+Tasks.vue 列表项 @click router.push;TaskRecord TS 类型已存在无需新增;主代理核查 cargo check 0 err/vue-tsc 0 err/git diff 6 文件边界干净。现状:`src/views/Tasks.vue` 仅列表,无独立 TaskDetail 视图/路由grep 仅 `ProjectDetail.vue` 嵌套任务命中,无独立详情页)。需求:点击 task 查看详情。**数据模型已就绪**`df-storage/src/models.rs:53` `TaskRecord` 12 字段id / project_id / title / description / status / priority / branch_name / assignee / workflow_def_id / base_branch / created_at / updated_at。**改动面**:①新建 `src/views/TaskDetail.vue` 视图 + 路由(`router/index.ts` `/tasks/:id`②task 详情 IPC`get_task_by_id`,核对 `commands/task.rs` 现有 IPC 是否已有,无则补)③`Tasks.vue` 列表项点击 → 跳详情 ④详情页字段展示title/description 渲染、status/priority 标签、关联项目名解析 project_id→name、branch/assignee 信息、时间戳)⑤可选:详情页内编辑(`update_task` IPC 已存在FR-D6。— source:用户需求(06-15)src/views/Tasks.vue + src/router/index.ts + src-tauri/src/commands/task.rs
@@ -693,7 +693,7 @@
- [x] ✅(batch34·2026-06-16·workflow wvr7mdo0p) B-260615-42 [P1] — ~~错误信息不一致~~ 同类错误在不同场景下返回的信息格式不统一。根因:错误处理分散在不同模块,缺乏统一规范。影响:用户和调试者难以理解错误含义。修法:制定错误码规范 + 统一错误格式 + 前端错误码映射 — src-tauri/src/commands/ai/mod.rs + src/api/types.ts
**P2 — 中等问题**
- [ ] B-260615-43 [P2] — ~~无撤销重做功能~~ AI 操作(如删除项目、修改任务)无撤销功能。根因:缺乏操作历史记录和回滚机制。影响:误操作无法恢复。修法:实现操作历史栈 + 撤销/重做 UI + 关键操作二次确认 — src/stores/project.ts + src/views/ProjectDetail.vue
- [x] ⏸️(决策c暂缓·2026-06-16) B-260615-43 [P2] — ~~无撤销重做功能~~ AI 操作(如删除项目、修改任务)无撤销功能。根因:缺乏操作历史记录和回滚机制。影响:误操作无法恢复。修法:实现操作历史栈 + 撤销/重做 UI + 关键操作二次确认 — src/stores/project.ts + src/views/ProjectDetail.vue
- [x] B-260615-44 [P2] — ~~工具响应时间无反馈~~ 工具执行(如 read_file、write_file长时间无响应时用户不知道卡在哪里。根因缺乏工具执行进度反馈。修法添加工具执行进度提示 + 超时警告 + 取消按钮 — src/composables/ai/useAiStream.ts
- [x] ✅(batch41·2026-06-16·workflow w9vtei15z) B-260615-45 [P2] — ~~内存泄漏隐患~~ 已核查覆盖(useAiEvents stopListener 三 listener+计时器全清 + AiChat onBeforeUnmount + MESSAGE_CAP=200 滚动淘汰),无泄漏不强改 长时间使用后内存占用持续增长。根因:消息缓存、事件监听器未及时清理。修法:优化缓存策略 + 定期清理机制 + 内存使用监控 — src/stores/ai.ts + src/composables/ai/useAiEvents.ts

View File

@@ -118,6 +118,17 @@
- **关联**:todo F-260615-14
- **状态**:✅ 已决(2026-06-16)— **决策:c 不做**。文本拷贝+代码块复制已覆盖核心需求,等用户真实反馈再定。
#### F-260616-11 审批续跑 iteration 计数策略
- **背景**:`try_continue_agent_loop` 审批后续跑 iteration 从 0 重计(agentic.rs:418 注释,F-260616-03 续跑决策 a 延伸),多次审批(Med/High 工具)=反复重跑 max_iterations 轮,token 消耗不受 max_iterations 严格控制。
- **决策点**:iteration 累计 vs 重置?
- **选项**:
- a: 累计计数(try_continue 不重置 iteration,跨审批续跑延续累计)
- b: 重置 + 独立「审批次数」配额
- c: token 总量兜底(不碰 iteration,加 token 硬上限)
- **推荐**:**a + c**(累计防超配 + token 兜底)
- **关联**:todo F-260616-11 / F-260616-03
- **状态**:✅ 已决(2026-06-16)— **决策:a**。累计计数,try_continue 不重置 iteration 跨审批累计,防多次审批 token 失控。token 兜底 c 暂不加(避免过度设计,iteration 累计已控)。实施待 batch60 完成(agentic.rs 锁释放)。
---
### ④类 — 设计方向(方案未定,需人定 A/B/C 选型)
@@ -233,7 +244,7 @@
- **决策点**:「使用」语义?(执行本机 claude 技能?AI 调用?需明确)
- **推荐**:需先澄清语义(关联 memory aichat-skill-slash-autocomplete)
- **关联**:todo F-260614-02
- **状态**:🟡 待澄清语义
- **状态**:✅ 已决(2026-06-16)— **决策:ai调用**(联想选中技能→AI 调用执行,非执行本机 claude 技能。实施机制待设计:技能内容注入 AI context 或技能注册为 AI 工具,依赖 F-07 trait 下沉)。
#### F-260614-04 多 Provider 负载均衡池
- **背景**:备用模型/多账号聚合,全局容量=min(各 provider 上限之和, global_cap)。
@@ -282,7 +293,7 @@
- **选项**: a:维持独立(职责清晰:节点定义vs执行运行时) / b:合并df-nodes(减少crate数)
- **推荐**:**a**(维持独立,转架构维护记录非清债)
- **关联**:todo ARC-260615-03
- **状态**:🟡 待决
- **状态**:✅ 已决(2026-06-16)— **决策:a**(维持独立,df-execute 非空壳 shell.rs execute() 已完整实现 + 被 run_command 复用,转架构维护记录非清债)。
#### ARC-260615-07 中期技术债7件
- **背景**:df-core改名df-types / IPC层抽取df-app / AI loop下沉df-ai / 3 view绕store调api / 类型契约ts-rs代码生成 / AiSession多会话 / AppState分组 / IPC命名统一。
@@ -306,7 +317,7 @@
- **选项**: a:仅AI工具操作(delete_task/delete_project/restore/run_command等) / b:全局(含项目/任务CRUD) / c:暂缓(二次确认已部分覆盖)
- **推荐**:**c 暂缓**(High-risk工具已有二次确认AE-05,撤销复杂度高)
- **关联**:todo B-260615-43
- **状态**:🟡 待决
- **状态**:✅ 已决(2026-06-16)— **决策:c 暂缓**(High-risk 已有二次确认 AE-05,撤销复杂度高)。
### C类 — 用户实测/澄清(需用户操作给反馈,主会话无法代办)

View File

@@ -23,11 +23,35 @@
## 当前队列
### CR-260616-35 batch59 CR-34-1 守卫 + CR-30 流式重试对齐决策a1 + UX-2025-04 断线保文 — 待审
### CR-260616-36 batch60 F-09A 隔离补清 + F-13 性能优化 + AE-03 write_file diff 预览(路径B) — ⏳ 待审
- **范围**: 3 agent 合并批(workflow whae812z5,主代独立核查全过 + cargo check --workspace EXIT 0 + vue-tsc EXIT 0)。①**F-09 A 路线**(单例软隔离补漏清字段,不动 B 多会话架构):前端 `useAiConversations.ts:42-45` newConversation 补清 `queue=[]/generatingConvId=null/agentRound=0/searchQuery=''`(防旧会话排队消息带进新会话 + 后台事件错路由 + 侧栏旧搜索过滤);后端 `commands.rs:842-843` ai_conversation_create 补清 `stop_flag.store(false,SeqCst)`(关键:上方生成中分支置 true 停旧 loop 不复位则新会话 loop 启动即见 stop 退出) + `agent_language=None`(防沿用旧语言设置)。②**F-13 性能**:`agentic.rs:173` system_prompt token loop 外算一次缓存为 sys_tokens(整个 loop 不变参数,原每轮+每次重试重算);`:219-225` 外层 messages 构建用 sys_tokens;`:262-270` 重试循环改 `messages: messages.clone()` 复用外层 Vec(删每次重试的 session_arc.lock + build_for_request 全量 clone + estimate_text)。安全前提核验:stream_llm 不接 session_arc + 重试块到 process_tool_calls 间无 push,重试内 session.messages 与外层一致,重建等价复用。③**AE-03 路径 B**(审批 payload 加 diff,非路径 A):`tool_registry.rs:32` generate_diff fn→pub(crate)(复用 F-260615-10 LCS);`audit.rs:508-520` 新增 build_write_file_diff(从 args 取 path+content → 预读旧文件 tokio::fs::read_to_string → 无变化/旧文件缺失/读失败均 None → generate_diff);`audit.rs:588-609` 仅 write_file draft 挂起审批前预计算 approval_diff 注入 PendingApproval.diff(:600 clone)+AiApprovalRequired.diff(:609 move);恢复路径 diff=None(文件可能已变);`mod.rs:101/269` AiApprovalRequired+PendingApproval 加 `diff: Option<String>`;`types.ts:198` AiApprovalRequired 加 `diff?: string`;`useAiEvents.ts` event.diff 传 toolCall 信息;`ToolCard.vue:40-41` 模板 `v-if="tc.diff"` diff 块 + :394 diffLines computed(按 +/-/space 前缀拆分)+ :677-705 样式(add 绿 del 红 ctx 灰 + token 复用)。
- **维度**: ①F-09 单例字段补清完整性(stop_flag 复位是否覆盖所有生成中→新建路径/agent_language 影响面) + B 路线边界(确认未误动 AiSession 单例) ②F-13 行为不变性核验(sys_tokens 缓存是否真不变/messages.clone() 复用是否等价重建/stream_llm 不持 session_arc 前提是否成立) + 锁持有期缩短收益确认 ③AE-03 路径 B 安全性(预读旧文件只读不改/审批拒绝不执行 handler/路径不校验风险——恶意路径读失败仅 None) + diff 注入完整性(实时挂起+恢复路径双覆盖/前端消费链 event→toolCall→模板) + generate_diff pub(crate) 暴露面。
- **commit**: 待提交(累积 ~12 文件攒批 + 4 决策回填)。
### CR-260616-37 F-11 审批续跑 iteration 累计计数(跨审批不重置) — ⏳ 待审
- **范围**: F-260616-11 决策a 落地(与 batch60 三项合批 commit)。`mod.rs:215/230` AiSession 加 `iteration_used:usize` 字段+new() init 0;`agentic.rs:114` run_agentic_loop 加 `start_iteration:usize` 参数 +`:176` loop 边界改 `start_iteration..max_iterations` +`:210` 一致性校验块更新 `session.iteration_used=iteration+1` +`:578` try_continue_agent_loop 加 start_iteration 参数 +`:675` spawn 透传 +`:499-501` 注释区分两路径;`commands.rs:55/152/412` ai_chat_send/ai_regenerate/ai_edit_last reset iteration_used=0(新生命周期)+`:260-262` ai_approve 拒绝续跑读 session.iteration_used 累计传 +`:313-315` ai_approve 通过续跑累计传 +`:601` ai_continue_loop reset iteration_used=0 传 0(F-03 决策a 达max重计区分)。两路径严格区分:审批续跑(ai_approve 累计透传 iteration_used) vs 达max续跑(ai_continue_loop reset+传0,F-03 决策a 用户授权重来) vs 新消息(reset 0)。
- **维度**: ①iteration_used 字段生命周期完备性(所有会话生命周期入口 reset 覆盖——create/send/regenerate/edit_last/continue_loop 是否齐全无遗漏路径) ②start_iteration 透传链完整(agentic run_agentic_loop←try_continue_agent_loop←ai_approve 两个调用点,无断链) ③两路径区分正确性(ai_approve 累计 vs ai_continue_loop 重计,F-11/F-03 决策 a 分别归属无混淆) ④边界 case(start≥max loop 空区间→converged=false→AiMaxRoundsReached,防无限审批烧 token,符合 F-11 语义) ⑤并发安全(iteration_used 在一致性 lock 块更新,与现有 lock 模式一致)。
- **commit**: 待提交(同 CR-36 合批)。
- **主代独立核查**: ✅ 全过(useAiConversations:42-45 补清 / commands:842-843 stop_flag.store(false)+agent_language=None / agentic:173 sys_tokens loop 外缓存 + :219-225 外层用 + :262-270 重试 messages.clone() 复用 + 保文退避逻辑未动(Partial 不重试/retry_deadline 30s) / tool_registry:32 generate_diff pub(crate) / audit:508-520 build_write_file_diff 预读+无变化/缺失 None+复用 generate_diff / audit:588-609 仅 write_file 生成 diff 注入 PendingApproval(clone)+AiApprovalRequired(move) / mod:101+269 diff 字段 / types:198 diff? / ToolCard:40-41 模板 + :394 diffLines + :677-705 样式 / cargo+vue-tsc 双 EXIT 0)。
- **审查 agent 待复审重点**: ①F-13 重试 messages.clone() 复用等价性——核验 stream_llm 签名(stream_recv.rs:137 不接 session_arc) + 重试块到 process_tool_calls 间确实无 session.messages.push(若遗漏则重试复用旧 messages 致 tool_calls 丢失) ②F-13 sys_tokens 缓存——核验 system_prompt 确为 run_agentic_loop 不变参数(整个 loop 期间无 mutate) ③AE-03 build_write_file_diff 安全——预读旧文件 `tokio::fs::read_to_string(path)` 路径未校验(恶意 path 读失败仅 None 不产生危害,但核验无 symlink 逃逸读敏感文件风险——审批只读,write_file handler 自身 validate_path 执行时兜底) ④AE-03 diff 注入双路径——实时挂起(audit:588-609 有 diff)+恢复路径(PendingApproval diff 字段是否持久化,若仅内存则重启恢复审批无 diff) ⑤文件锁独立性——3 agent 改动文件无重叠(F-09: useAiConversations/commands / F-13: agentic / AE-03: mod/tool_registry/audit/types/useAiEvents/ToolCard),useAiEvents.ts Agent C 独占(A/B 未碰)确认无冲突。
---
### CR-260616-35 batch59 CR-34-1 守卫 + CR-30 流式重试对齐决策a1 + UX-2025-04 断线保文 — ✅ 已审(PASS)
- **范围**: 3 agent 合并批(workflow wdlt0xfl6,主代独立核查全过)。①**CR-34-1** useAiPanel.ts:102 守卫加有效性检查 `|| !list.some(p=>p.id===activeProvider)`(deleted provider 残留回落 DB is_default) ②**CR-30-1/CR-30-2 + UX-2025-04 合并**:stream_recv.rs `StreamResult` enum(Complete/Partial/InitFailed 三分支) + `classify_status_or_class`(4xx Fatal/5xx+429+timeout Retryable 镜像 retry::is_status_retryable) + agentic.rs 重试对齐决策 a1(Init 失败重试复用 retry::backoff_delay 1s→2s→4s±20%jitter + 30s 总预算 retry_deadline + Fatal 分类 4xx 立即放弃; MidStream Partial 不重试保文→入库 + AiCompleted(incomplete=true) + 系统提示「响应因网络中断不完整」) + 删 3 处误导注释(agentic.rs:12/commands.rs/state.rs) + retry.rs `pub mod` + `backoff_delay`/`is_status_retryable``pub` 暴露供 src-tauri 复用 + useAiEvents.ts:177 补 `case 'AiStreamRetry'`(消除死事件穿透) + AiCompleted case 处理 incomplete + types.ts:204/mod.rs `incomplete?:boolean` 字段(所有 emit 点补 None 向后兼容) + i18n aiStreamRetry/responseIncomplete 双语。
- **维度**: 流式核心中风险(重试循环正确性/保文语义/跨 crate pub 暴露影响面) + 前端事件消费完整性 + 决策 a1 对齐度(Init 可重试 ≤max_retries+jitter+30s+Fatal, MidStream 不重试) + B-260615-05 保留(空文本无 finished 仍 InitFailed 交重试)。
- **commit**: 待提交(累积 ~30 文件攒批)。
- **复审结论(2026-06-16·CR-30 agent 亲跑 cargo + 主线程 CR-34-1 预核验)**: ✅ **PASS** — 🔴0 🟡0 ⚪0
- **验证**: `cargo check --workspace` EXIT 0(5 warning 全 pre-existing dead_code)/ `cargo test -p df-ai retry` **6 passed**(backoff_is_monotonic_within_jitter/status_classification/fatal_aborts_immediately/first_attempt_ok/retry_once_then_ok/all_retryable_exhausts)/ `cargo test stream_recv` **20 passed**(classify_429_retryable/classify_4xx_fatal 等)。
- **CR-30-1(7 项全 PASS)**:agentic.rs:320 真用 `retry::backoff_delay(retry_attempt+1)`(非自写,旧 `1<<retry_attempt` 仅留注释)/ backoff 含 ±20% jitter(retry.rs:74-85 SystemTime nanos)/ 30s 总预算(agentic.rs:242 retry_deadline + :290 budget_exhausted + :321 min 截断)/ 4xx Fatal 立即放弃(agentic.rs:293 `!retryable` return + stream_recv.rs:411-434 classify_status_or_class 镜像 is_status_retryable)/ 三处注释修正(commands.rs:1114 / state.rs:184 / agentic.rs:38)/ DRY 无重写(retry_with_backoff 仍 pub(crate) 流式不走)/ AttemptOutcome+is_reqwest_error_retryable 暴露合理。
- **CR-30-2(7 项全 PASS)**:useAiEvents.ts:177-186 `case 'AiStreamRetry'` 完整(取最后 isError 消息 + i18n t 更新「重试 n/m」,break 非穿透)/ 看门狗 reset 正确(AiStreamRetry 不在 NO_RESET_WATCHDOG Set)/ MidStream 决策 agentic.rs:274-285 Partial→incomplete=true + break 不重试,:286-342 InitFailed retryable 分支重试 / currentText 清空(useAiEvents.ts:316 AiError case `state.currentText=''` 再 AiStreamRetry 更新气泡,无累积重复)/ AiCompleted incomplete 系统提示(后端 agentic.rs:384 push session.messages 落库 + 前端 useAiEvents.ts:278-285 气泡)/ types.ts:215-217 variant + :202-204 incomplete?:boolean / i18n aiStreamRetry+responseIncomplete 双语齐。
- **回归(4 项无回归)**:permit RAII 释放(agentic.rs:405-406 显式 drop + loop 作用域 Drop)/ guard.reset 耗尽复位(8 exit 点全覆盖 :137/:300/:314/:352/:393/:457/:480/:543)/ request 每轮重建(:251-266 messages 从 session lock 重构)/ 无新死代码(AttemptOutcome/is_reqwest_error_retryable/is_status_retryable/backoff_delay/AiStreamRetry/classify_status_or_class 全有消费)。
- **CR-34-1(主线程预核验 PASS)**:useAiPanel.ts:102 守卫 `|| !list.some(p=>p.id===activeProvider)` 残留无效回落 DB is_default,cycleProvider 不受影响(findIndex 不再 -1 跳过 ps[0])。
- **UX-2025-04 断线保文**:核心 ✅(MidStream Partial 保文入库 + incomplete 系统提示);「继续生成」按钮未做(可复用 UX-2025-03 重试能力按需补,非阻断)。
- **待修项回流 todo**: **无**(CR-34-1/30-1/30-2 三条确认修复,todo 勾完成)。
- **主代独立核查**: ✅ 全过(useAiPanel:102 守卫 / stream_recv:97 enum 三分支 + 注释 / agentic:242 retry_deadline + :274 Partial 不重试 + :286 InitFailed 重试 + :320 backoff_delay 复用 + :360 if incomplete 保文入库 + :400 emit incomplete:Some(true) / useAiEvents:177 AiStreamRetry case / types:204 incomplete / retry.rs:62+74 pub + agentic:14 `use df_ai::retry` 跨 crate 通)。
- **审查 agent 待复审重点**: ①stream_recv StreamResult 三分支边界(Partial vs InitFailed 空文本路径,B-260615-05 保留点) ②agentic 重试循环 permit RAII 释放/边界(max_retries=0/guard.reset) ③跨 crate retry pub 暴露是否过度(retry_with_backoff 仍 pub(crate)) ④AiCompleted incomplete 前端消费完整性(系统提示气泡镜像后端) ⑤UX-2025-04「继续生成」按钮**未做**(保文核心 ✅,按钮可复用 UX-2025-03 重试能力或按需补)。