新增: 并行多轮设计文档+审查回填+待决策更新
Plan-driven子流并行Phase1-3/CR-260620-01~03审查回填/④必须做+②已完成
This commit is contained in:
133
docs/02-架构设计/单对话并行多轮-设计-2026-06-20.md
Normal file
133
docs/02-架构设计/单对话并行多轮-设计-2026-06-20.md
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
# 单对话并行多轮推理 — 设计文档
|
||||||
|
|
||||||
|
> 2026-06-20 · aichat B 路线(决策能力)· 用户决策:场景常见,**必须做**
|
||||||
|
|
||||||
|
## 一、目标
|
||||||
|
|
||||||
|
单对话内 AI 把复杂任务拆**子流并行跑**,加速(多文件/多方案/多模块对比)。
|
||||||
|
|
||||||
|
## 二、当前限制
|
||||||
|
|
||||||
|
- `run_agentic_loop`(`src-tauri/src/commands/ai/agentic/mod.rs:331-1206`)单链 ReAct:一轮一轮串行,每轮依赖前轮
|
||||||
|
- 已有并行:工具级 `join_all`(Low risk)+ 主辅 2 路并发(`per_conv permits=2`,state.rs:121)
|
||||||
|
- 缺:**主推理链分叉子流**(一轮一轮无法拆并行多轮)
|
||||||
|
- `coordinator.rs`(crates/df-ai/,30 行空壳)多 Agent 协作占位
|
||||||
|
|
||||||
|
## 三、架构:Plan-driven 子流并行
|
||||||
|
|
||||||
|
延伸 intent 三层链路:
|
||||||
|
|
||||||
|
```
|
||||||
|
intent(识别,纯函数✅)
|
||||||
|
→ plan_hint(轻规划,纯函数:工具编排/可并行组/顺序)
|
||||||
|
→ planning(重规划,LLM:任务分解为 Plan DAG)
|
||||||
|
→ execution(调度:可并行组 spawn 子 ReAct loop → join → 汇总)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Plan 数据结构
|
||||||
|
|
||||||
|
```rust
|
||||||
|
struct Plan {
|
||||||
|
subtasks: Vec<SubTask>, // 子任务列表
|
||||||
|
parallel_groups: Vec<Vec<Id>>, // 可并行组(DAG 同层)
|
||||||
|
}
|
||||||
|
struct SubTask {
|
||||||
|
id: String,
|
||||||
|
goal: String, // 子任务目标(LLM 规划产出)
|
||||||
|
tools: Vec<String>, // 子任务可用工具(intent/plan_hint 提示)
|
||||||
|
deps: Vec<String>, // 依赖子任务(DAG 前驱)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 四、关键设计决策
|
||||||
|
|
||||||
|
| 维度 | 决策 | 理由 |
|
||||||
|
|---|---|---|
|
||||||
|
| 触发 | LLM 规划为主 + plan_hint 启发式辅助 | 灵活 + 可控 |
|
||||||
|
| 粒度 | 子任务级(独立 ReAct 子链) | 每子流独立工具序列 |
|
||||||
|
| 状态隔离 | PerSubFlowState(扩展 F-09 PerConvState) | 子流独立 messages/工具上下文,共享 conv 元数据 |
|
||||||
|
| 资源 | sub_permit ≤3(默认)+ token 限额 + 用户可见 cost | 防 cost 爆炸 |
|
||||||
|
| 失败 | 部分子流失败 → 降级(汇总成功 + 标注失败) | 不全挂 |
|
||||||
|
| UX | 子流折叠/进度,事件按 sub_flow_id 路由(扩展 conv_id) | 多流不混乱 |
|
||||||
|
| 与 F-09 协调 | 两层并发:conv 级(permits)+ sub_flow 级(sub_permit),全局限额协调 | 分层资源池 |
|
||||||
|
|
||||||
|
## 五、渐进实施路径
|
||||||
|
|
||||||
|
### Phase 1:plan_hint 轻规划(低风险,独立有价值)
|
||||||
|
|
||||||
|
- **改**:`crates/df-ai/src/intent.rs` 扩 `tool_subset_for` → 输出 `plan_hint`(工具编排:可并行组/顺序依赖)
|
||||||
|
- **接入**:主 loop 据 plan_hint 编排工具调用顺序/并行(跑题 P0 filter_tool_defs 延伸)
|
||||||
|
- **纯函数零延迟**(intent 已纯函数,plan_hint 同),单测覆盖
|
||||||
|
- **收益**:工具调用更智能(顺序/并行组),**但不真正多轮并行**
|
||||||
|
- **风险**:低(纯函数,可回退,常量开关)
|
||||||
|
- **工作量**:小(intent 扩展 + 单测)
|
||||||
|
|
||||||
|
### Phase 2:planning 层 + Plan 结构(中)
|
||||||
|
|
||||||
|
- **改**:`coordinator.rs` 空壳 → 消费 intent + plan_hint + 用户请求 → LLM 规划 → Plan DAG
|
||||||
|
- Plan 数据结构 + planning LLM prompt(分解任务为子任务 + 依赖)
|
||||||
|
- **execution 仍串行跑 Plan**(按 DAG 顺序,不并行)— 先验证规划质量
|
||||||
|
- **收益**:任务分解能力(Phase 3 前验证 LLM 规划可靠性)
|
||||||
|
- **风险**:中(LLM 规划质量:拆错/漏依赖)
|
||||||
|
- **工作量**:中
|
||||||
|
|
||||||
|
### Phase 3:并行 execution(大)
|
||||||
|
|
||||||
|
- **改**:execution 按 Plan `parallel_groups` spawn 子 ReAct loop(tokio task)→ join → 汇总 LLM
|
||||||
|
- PerSubFlowState 状态隔离 + sub_flow_id 事件路由
|
||||||
|
- sub_permit 资源控制 + 失败降级 + 前端多流 UX
|
||||||
|
- **收益**:真正并行加速
|
||||||
|
- **风险**:**高**(并发状态/资源/UX/错误边界多)
|
||||||
|
- **工作量**:大
|
||||||
|
|
||||||
|
## 六、风险与回退
|
||||||
|
|
||||||
|
- 每 Phase 独立可回退(常量开关:plan_hint_enabled / planning_enabled / parallel_execution_enabled)
|
||||||
|
- Phase 1 纯函数,零回归风险
|
||||||
|
- Phase 2/3 验证质量/ROI 再推进(Phase 2 验证规划质量 → 决定 Phase 3)
|
||||||
|
|
||||||
|
## 七、关联
|
||||||
|
|
||||||
|
- intent.rs(跑题 P0 已接入,CR-08)/ coordinator.rs 空壳(SMELL-P2-4)/ conditions(T-260614-11)
|
||||||
|
- F-09 真并发(PerConvState,CR-07)/ per_conv permits=2(state.rs:121)
|
||||||
|
- memory [[aichat-arch-extensibility]] / [[aichat-roadmap-ab-split]](B 路线)
|
||||||
|
|
||||||
|
## 八、决策记录
|
||||||
|
|
||||||
|
- **2026-06-20 用户**:场景常见,**必须做**。B 路线启动。
|
||||||
|
- 实施:Phase 1(plan_hint,低风险独立)→ Phase 2(planning,验证质量)→ Phase 3(并行 execution)。不跳 Phase。
|
||||||
|
|
||||||
|
## 九、核心功能定位(用户 2026-06-20 提升)
|
||||||
|
|
||||||
|
并行多轮是 DevFlow **核心功能**(未来核心卖点),非边缘增强。**战略级投入**,按核心功能标准设计/打磨/测试,非凑合。
|
||||||
|
|
||||||
|
## 十、全局平衡设计原则(并行多轮不孤立设计)
|
||||||
|
|
||||||
|
核心功能须与 DevFlow 全局协调:
|
||||||
|
|
||||||
|
| 全局维度 | 协调点 |
|
||||||
|
|---|---|
|
||||||
|
| **F-09 真并发** | 两层并发:conv 级(permits)+ sub_flow 级(sub_permit),共享全局限额池,防资源争抢 |
|
||||||
|
| **A 路线(跑题改进)** | plan_hint 延伸 intent(已接入),不冲突;跑题收敛工具 + 并行编排互补 |
|
||||||
|
| **资源全局** | cost/并发/性能:sub_permit≤3 + token 限额 + 用户可见 cost;与 llm_concurrency 全局协调 |
|
||||||
|
| **工具/授权/审批** | 子流工具调用经同样授权白名单(本轮权限批)+ 审批(High risk);子流不绕过 |
|
||||||
|
| **前端 UX 全局** | 多流展示与现有 conv/消息 UI 协调(子流折叠/进度,不破坏现有消息流) |
|
||||||
|
| **架构演进** | coordinator/intent/per_conv/conditions 全局一致(Plan 数据结构贯穿,非各层各搞) |
|
||||||
|
|
||||||
|
## 十一、打磨标准(三原则,核心功能级)
|
||||||
|
|
||||||
|
对齐 memory [[ai-improvement-principles]]:
|
||||||
|
|
||||||
|
- **优雅**:机制 > prompt 说教(Plan-driven 显式数据结构调度,非 LLM 硬控并行;Plan 可审查/可中断/可恢复)
|
||||||
|
- **可靠**:每改进配兜底(规划质量校验 / 失败降级 / 资源限额 / sub_flow 超时)
|
||||||
|
- **易迭代**:分阶段常量开关(`plan_hint_enabled` / `planning_enabled` / `parallel_execution_enabled`),每 Phase 可独立回退
|
||||||
|
|
||||||
|
## 十二、规划调整(核心功能级 → 全局设计优先)
|
||||||
|
|
||||||
|
核心功能 + 打磨 + 全局平衡 → **不草率 Phase 1**。调整:
|
||||||
|
|
||||||
|
1. **全局架构设计优先**(下一步):多维度调研并行多轮与全局各层(intent/agentic/per_conv/coordinator/前端/资源/授权)的协调点 → 全局设计方案(数据流/状态机/资源池/UX 统一)
|
||||||
|
2. **Phase 1 实施等全局设计成熟**(Plan 数据结构贯穿全局后,plan_hint 才有根基)
|
||||||
|
3. **每 Phase 核心功能级测试**(单测 + 集成 + 边界 + 并发竞态),非跑通即止
|
||||||
|
|
||||||
|
> 全局设计可能需多代理并行调研各维度(架构/资源/UX/并发/授权),汇总全局方案。
|
||||||
99
docs/todo.md
99
docs/todo.md
@@ -55,14 +55,14 @@
|
|||||||
**现象**:重启 devflow 后,DB 里 status=pending 的待审批工具(tool_call)在前端不显示 toolCard,用户无法审批;即使触发审批报「未找到挂起的审批」。restore 功能被 batch8 clear 抵消。
|
**现象**:重启 devflow 后,DB 里 status=pending 的待审批工具(tool_call)在前端不显示 toolCard,用户无法审批;即使触发审批报「未找到挂起的审批」。restore 功能被 batch8 clear 抵消。
|
||||||
|
|
||||||
**根因(跨函数时序冲突 + 设计契约违反)**:
|
**根因(跨函数时序冲突 + 设计契约违反)**:
|
||||||
1. **restore 填充(冷启动同步)**:`AppState::init`(`state.rs:336` setup:25 block_on)→ `restore_pending_approvals`(`audit.rs:291`)从 DB `list_pending` 重建 pending 到 `session.pending_approvals` 内存。
|
1. **restore 填充(冷启动同步)**:`AppState::init`(`state.rs:500` setup block_on)→ `restore_pending_approvals`(`audit/restore.rs:26`)从 DB `list_pending`(`:27`)重建 pending 到 `session.pending_approvals` 内存。
|
||||||
2. **clear 必触发(冷启动必达)**:`AiChat.vue:2229 onMounted → store.startListener()` → `useAiEvents.ts:386 emit('ai-client-ready')` → L0 回调(`lib.rs:38`)→ **`lib.rs:62 session.pending_approvals.clear()` 无条件清空全部**(含 restore 重建)。
|
2. **clear 必触发(冷启动必达)**:`AiChat.vue onMounted → store.startListener()` → `useAiEvents.ts emit('ai-client-ready')` → L0 回调(`lib.rs:38`)→ **`lib.rs:62` 原为 `session.pending_approvals.clear()` 无条件清空全部**(含 restore 重建)。
|
||||||
3. **前端不显示**:`ai_pending_tool_calls`(`commands.rs:508-517` 数据源 = `session.pending_approvals.values()` 内存,非 DB)→ clear 后返空。
|
3. **前端不显示**:`ai_pending_tool_calls`(`commands/chat.rs:692` 数据源 = `session.pending_approvals.values()` 内存,非 DB)→ clear 后返空。
|
||||||
4. **审批报错**:`ai_approve`(`commands.rs:328 remove` 内存)→ None → DB `find_by_tool_call_id` 查到 status=pending 但不在「已处理」白名单(`:336 executed/rejected/failed`)→ `:346 return Err「未找到挂起的审批」`。
|
4. **审批报错**:`ai_approve`(`commands/chat.rs:344`)→ DB `find_by_tool_call_id`(`:358` match)查到 status=pending 但不在「已处理」白名单(`:360 executed/rejected/failed`)→ `:370 return Err「未找到挂起的审批」`。
|
||||||
|
|
||||||
**batch8 回归证据**:`git show 6ad4ec2 -- lib.rs` 确认 `session.pending_approvals.clear()`(`:62`)是 batch8 **新增**;批2 版本 L0 只 reset generating,不 clear pending。
|
**batch8 回归证据**:`git show 6ad4ec2 -- lib.rs` 确认 `session.pending_approvals.clear()`(`:62`)是 batch8 **新增**;批2 版本 L0 只 reset generating,不 clear pending。
|
||||||
|
|
||||||
**设计契约直接违反**:`commands.rs:1672-1675` switchConversation 用 `retain(... != Some(&conversation_id))` 精确保留 restore 重建的 pending,注释明说「防 init 重建的内存 HashMap 被清空,重启恢复链路:restore → switchConversation → ai_pending_tool_calls → ai_approve」。L0 `:62 clear()` 无条件清空全部 → 断该链路。
|
**设计契约直接违反**:`commands/conversation.rs:312-315` switchConversation 用 `retain(... != Some(&conversation_id))`(`:315`)精确保留 restore 重建的 pending,注释明说「防 init 重建的内存 HashMap 被清空,重启恢复链路:restore → switchConversation → ai_pending_tool_calls → ai_approve」。L0 `:62 clear()` 无条件清空全部 → 断该链路。
|
||||||
|
|
||||||
**修复方案**:
|
**修复方案**:
|
||||||
- **方案 A(推荐)**:`lib.rs:62` `clear()` → `retain(|_, a| !a.recovered)`。仅清非 recovered(本次会话/HMR 死 pending),保留 restore 重建(`PendingApproval.recovered=true` `audit.rs:331`)。对齐 switchConversation 保护意图,两全。
|
- **方案 A(推荐)**:`lib.rs:62` `clear()` → `retain(|_, a| !a.recovered)`。仅清非 recovered(本次会话/HMR 死 pending),保留 restore 重建(`PendingApproval.recovered=true` `audit.rs:331`)。对齐 switchConversation 保护意图,两全。
|
||||||
@@ -138,7 +138,7 @@
|
|||||||
|
|
||||||
> 功能需求,已走查定位链路 + 方案记录,待用户确认后实施。
|
> 功能需求,已走查定位链路 + 方案记录,待用户确认后实施。
|
||||||
|
|
||||||
- [ ] F-260616-09 [P1/架构·**A路线✅(batch60 + 06-19核验补漏已落地:commands.rs:1404/useAiConversations.ts:80 → 阶段1跳过),B 设计完成 2026-06-19([F-09设计文档](./02-架构设计/已编号方案/F-09-多会话并发架构设计-2026-06-19.md))·阶段2 批1-8 待拍板 b-1(messages per-conv)启动(见待决策.md)**] — **AiSession 单例 → 多会话并发架构(多窗口来回切 + 并行执行)**。用户需求(3 条消息强化):「应用级支持同时具备多会话」+「同时具备多会话的能力」+「多个会话窗口来回切,**并行执行**」。现状(Explore 走查确认):`AiSession` **应用级单例**(`state.rs:164` `ai_session: Arc<Mutex<AiSession>>`,init:205),messages/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 是大改架构升级,需单独设计文档 + 立项**。**✅ 已决(2026-06-16·待决策归档):a — A 隔离修复 + B 立项(🔥 B 阶段高优先级尽快实施),UI d1+d2 都做(侧栏切换 + 独立 Tauri 窗口)**。
|
- [ ] F-260616-09 [P1/架构·**A路线✅(batch60 + 06-19核验补漏已落地:commands.rs:1404/useAiConversations.ts:80 → 阶段1跳过),B 设计完成 2026-06-19([F-09设计文档](./02-架构设计/已编号方案/F-09-多会话并发架构设计-2026-06-19.md))·阶段2 批1-8 待拍板 b-1(messages per-conv)启动(见待决策.md)·**messages per-conv 已落地(2026-06-20 核验):mod.rs:326 `pub per_conv: HashMap<String, PerConvState>` + :384 `conv(&mut self, conv_id)` 访问器 + :394 `conv_read` + `tests_f09_per_conv` 测试模块 + 决策e 真并发已上线(见文件末 F-09 决策e 段,CR-260620-02 审 PASS)****] — **AiSession 单例 → 多会话并发架构(多窗口来回切 + 并行执行)**。用户需求(3 条消息强化):「应用级支持同时具备多会话」+「同时具备多会话的能力」+「多个会话窗口来回切,**并行执行**」。现状(Explore 走查确认):`AiSession` **应用级单例**(`state.rs:164` `ai_session: Arc<Mutex<AiSession>>`,init:205),messages/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 是大改架构升级,需单独设计文档 + 立项**。**✅ 已决(2026-06-16·待决策归档):a — A 隔离修复 + B 立项(🔥 B 阶段高优先级尽快实施),UI d1+d2 都做(侧栏切换 + 独立 Tauri 窗口)**。
|
||||||
|
|
||||||
- [ ] 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-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 立项后一并处理**。
|
||||||
|
|
||||||
@@ -188,7 +188,7 @@
|
|||||||
**🔴 P0 必须修复 (4项)**:
|
**🔴 P0 必须修复 (4项)**:
|
||||||
|
|
||||||
- [x] ✅(workflow w5siwnipj 核验+主代实施·agentic:348/knowledge_inject:130,139,312/idea:292-296 加 tracing::warn 降级不改返回值·audit:344 误报(CR-11 已修三路 match)·commands:290 BUG-11 已修·cargo check EXIT 0 + vue-tsc EXIT 0) **SMELL-P0-1** — **[P0🔴]** `unwrap_or_default` 吞错 **5 高危闭环**。agentic.rs:348(provider 池空走原空池兜底)/knowledge_inject.rs:130,139,312(检索/解析失败走原降级不注入/提炼跳过)/idea.rs:296(tags 坏降级空继续评估)本批 match+warn(空 Vec 零行为变更);audit.rs:344 误报(audit_finalize CR-11 已修三路 match,line 漂移+旧版本);commands.rs:290 BUG-11 已修。**58 处全量排查降 P2 非紧急**(高危已清,余为 Option 取默认/JSON 序列化等低危)。
|
- [x] ✅(workflow w5siwnipj 核验+主代实施·agentic:348/knowledge_inject:130,139,312/idea:292-296 加 tracing::warn 降级不改返回值·audit:344 误报(CR-11 已修三路 match)·commands:290 BUG-11 已修·cargo check EXIT 0 + vue-tsc EXIT 0) **SMELL-P0-1** — **[P0🔴]** `unwrap_or_default` 吞错 **5 高危闭环**。agentic.rs:348(provider 池空走原空池兜底)/knowledge_inject.rs:130,139,312(检索/解析失败走原降级不注入/提炼跳过)/idea.rs:296(tags 坏降级空继续评估)本批 match+warn(空 Vec 零行为变更);audit.rs:344 误报(audit_finalize CR-11 已修三路 match,line 漂移+旧版本);commands.rs:290 BUG-11 已修。**58 处全量排查降 P2 非紧急**(高危已清,余为 Option 取默认/JSON 序列化等低危)。
|
||||||
- [x] ✅(workflow w2xkw4ybh 抽 register_data_tools(18 持 db 工具:list_projects/list_tasks/list_ideas/update_project/create_project/bind_directory/create_task/update_task/advance_task/run_workflow/delete_task/create_idea/delete_project/restore_project/purge_project/list_trash/get_project_count/get_task_count)+ 主代抽 register_file_tools(10 文件工具:run_command/read_file/list_directory/write_file/patch_file/file_info/append_file/delete_file/rename_file/search_files)·build_ai_tool_registry 1091→7 行·加基线测试 test_build_ai_tool_registry_baseline_tool_count(Database::open_in_memory 断言 len()==28 + tool_names() 集合锁定)·主代核查 cargo check --workspace EXIT 0 + cargo test df-ai 119 passed + devflow 基线 1 passed + vue-tsc EXIT 0) **SMELL-P0-2** — **[P0🔴]** `tool_registry.rs:363 build_ai_tool_registry` **1091 行单函数** — 拆分为按功能分组注册函数(register_data_tools 18 db 工具 / register_file_tools 10 文件工具),build_ai_tool_registry 收敛到 7 行编排 + 基线测试锁定 28 工具数防回归。
|
- [x] ✅(workflow w2xkw4ybh 抽 register_data_tools(18 持 db 工具:list_projects/list_tasks/list_ideas/update_project/create_project/bind_directory/create_task/update_task/advance_task/run_workflow/delete_task/create_idea/delete_project/restore_project/purge_project/list_trash/get_project_count/get_task_count)+ 主代抽 register_file_tools(10 文件工具:run_command/read_file/list_directory/write_file/patch_file/file_info/append_file/delete_file/rename_file/search_files)·build_ai_tool_registry 1091→7 行·加基线测试 test_build_ai_tool_registry_baseline_tool_count(Database::open_in_memory 断言 len()==28 + tool_names() 集合锁定)·主代核查 cargo check --workspace EXIT 0 + cargo test df-ai 119 passed + devflow 基线 1 passed + vue-tsc EXIT 0) **SMELL-P0-2** — **[P0🔴]** `tool_registry.rs:363 build_ai_tool_registry` **1091 行单函数** — 拆分为按功能分组注册函数(register_data_tools 18 db 工具 / register_file_tools 10 文件工具),build_ai_tool_registry 收敛到 7 行编排 + 基线测试锁定工具数防回归。**注(2026-06-20 核验):基线已升至 29(tool_registry.rs:1841 `test_build_ai_tool_registry_baseline_tool_count` 断言 `len()==29`,18 data + 10 file + 1 http;F-260619-03 Phase A 后 http_request 注册致 +1)。**
|
||||||
- [ ] **SMELL-P0-3** — **[P0🔴]** `AiChat.vue` **4026 行 God 组件** — 拆分: ConversationSidebar(侧栏+搜索) / MessageList(消息列表+流式) / ChatInput(输入框+附件) / ApprovalPanel(审批卡片)。目标单组件 <500 行。
|
- [ ] **SMELL-P0-3** — **[P0🔴]** `AiChat.vue` **4026 行 God 组件** — 拆分: ConversationSidebar(侧栏+搜索) / MessageList(消息列表+流式) / ChatInput(输入框+附件) / ApprovalPanel(审批卡片)。目标单组件 <500 行。
|
||||||
- [x] ✅(workflow wowdnw4ba·tests/shell.rs 5 #[tokio::test] 覆盖成功/非零/超时/env/working_dir + shell.rs:55 过时TODO注释清理·execute逻辑零改动·主代 cargo test -p df-execute 5 passed + cargo check --workspace EXIT 0) **SMELL-P0-4** — **[P0🔴]** `df-execute` crate **零测试**(shell.rs 120行已集成工作流) — 补 shell 命令执行+超时+输出截断基础测试。
|
- [x] ✅(workflow wowdnw4ba·tests/shell.rs 5 #[tokio::test] 覆盖成功/非零/超时/env/working_dir + shell.rs:55 过时TODO注释清理·execute逻辑零改动·主代 cargo test -p df-execute 5 passed + cargo check --workspace EXIT 0) **SMELL-P0-4** — **[P0🔴]** `df-execute` crate **零测试**(shell.rs 120行已集成工作流) — 补 shell 命令执行+超时+输出截断基础测试。
|
||||||
|
|
||||||
@@ -369,7 +369,7 @@
|
|||||||
|
|
||||||
- [x] ✅(workflow wwchro468 general-purpose agent 实施 6 文件 + 主代兜底核验·前端 ToolCard.vue advance_task 渲染取 r.title||r.id(:474/:891)+TASK_ID_TOOL_ARG 映射(:799)+displayArgValue task_id→title(:834)+i18n zh/en aiTool.ts;后端 audit.rs resolve_task_label(查 tasks 表 :151 三臂对齐 resolve_project_label)+build_approval_reason advance_task 分支(:173)+tool_registry.rs 删 tool_display_hint advance_task 项(:1575 让 :1549 模板生效)·主代 cargo check -p devflow EXIT 0 + vue-tsc EXIT 0 + grep 印证) **UX-260618-14 [P1]** — **advance_task 审批卡/结果渲染缺任务名 + 显 UUID/空白**。①审批卡 `ToolCard.vue:800` `PROJECT_ID_TOOL_ARG` 未登记 advance_task → id 裸显 UUID;②审批 reason `audit.rs:163` tool_display_hint 短路致 `tool_registry.rs:1546` 模板死代码 + `:172` 任务 id 误查 projects 表;③结果 `ToolCard.vue:477` 模板取 r.id(UUID)非 r.title(TaskRecord 有 title `models.rs:57`),"空白"=UUID 不可读非字段缺失。**修法 B(最快)**:ToolCard:477 取 r.title fallback r.id + i18n 改 `任务「{title}」已推进至 {status}`;**A1** 登记 TASK_ID_TOOL_ARG(advance_task/run_workflow)id→title 回显;**C** 删 `tool_registry.rs:1572` tool_display_hint 让 :1546 模板生效 + audit.rs 加 resolve_task_label。附带:`:1546/:1547` 模板均死代码 + audit.rs:172 任务 id 误查隐患。— `src/components/ToolCard.vue`(:474-478,800-825) + `src-tauri/src/commands/ai/{tool_registry.rs:1546/1572,audit.rs:163/172}` + `src/i18n/{zh-CN,en}/aiTool.ts`
|
- [x] ✅(workflow wwchro468 general-purpose agent 实施 6 文件 + 主代兜底核验·前端 ToolCard.vue advance_task 渲染取 r.title||r.id(:474/:891)+TASK_ID_TOOL_ARG 映射(:799)+displayArgValue task_id→title(:834)+i18n zh/en aiTool.ts;后端 audit.rs resolve_task_label(查 tasks 表 :151 三臂对齐 resolve_project_label)+build_approval_reason advance_task 分支(:173)+tool_registry.rs 删 tool_display_hint advance_task 项(:1575 让 :1549 模板生效)·主代 cargo check -p devflow EXIT 0 + vue-tsc EXIT 0 + grep 印证) **UX-260618-14 [P1]** — **advance_task 审批卡/结果渲染缺任务名 + 显 UUID/空白**。①审批卡 `ToolCard.vue:800` `PROJECT_ID_TOOL_ARG` 未登记 advance_task → id 裸显 UUID;②审批 reason `audit.rs:163` tool_display_hint 短路致 `tool_registry.rs:1546` 模板死代码 + `:172` 任务 id 误查 projects 表;③结果 `ToolCard.vue:477` 模板取 r.id(UUID)非 r.title(TaskRecord 有 title `models.rs:57`),"空白"=UUID 不可读非字段缺失。**修法 B(最快)**:ToolCard:477 取 r.title fallback r.id + i18n 改 `任务「{title}」已推进至 {status}`;**A1** 登记 TASK_ID_TOOL_ARG(advance_task/run_workflow)id→title 回显;**C** 删 `tool_registry.rs:1572` tool_display_hint 让 :1546 模板生效 + audit.rs 加 resolve_task_label。附带:`:1546/:1547` 模板均死代码 + audit.rs:172 任务 id 误查隐患。— `src/components/ToolCard.vue`(:474-478,800-825) + `src-tauri/src/commands/ai/{tool_registry.rs:1546/1572,audit.rs:163/172}` + `src/i18n/{zh-CN,en}/aiTool.ts`
|
||||||
|
|
||||||
- [x] ✅(agent ux15-batch1 方案A实施 3 文件 + 主代兜底核验·stream_recv 5处重试路径移除AiError emit + agentic Fatal/耗尽单emit AiError + useAiEvents AiStreamRetry首次创建+后续更新·主代 cargo check EXIT 0 + cargo test df-ai 119 passed + vue-tsc EXIT 0 + grep核验5处emit判定 + 修正注释残留 :251/:299) **UX-260618-15 [P1] 第一批(方案A根治N+1)** ✅ — **流式失败重试(1/4 2/4 3/4)每轮独立气泡**。N+1 降为「1 重试气泡 + 1 最终错误气泡」。**第二批(方案C 聚合气泡显示错误原因)可选增强待定**。后端每轮重试 emit AiError(`stream_recv.rs:200/258/298/358/398` 5 处 InitFailed 必 emit),前端 `useAiEvents.ts:326` AiError case 每次 push 新气泡无去重;`AiStreamRetry`(:177)本意更新末条合并但被新 AiError 破坏 → N+1 独立气泡。`agentic.rs:262` 注释误以为 stream_llm 只 emit 一次(实多次)。**用户诉求升级**:不只是修 bug,要**聚合显示设计**(重试 1→4 轮过程聚合单气泡 + 最终结果,勿一排错误吓人)。详见 review 会话重试聚合设计方案(3 角度论证:可行性/前后端一致性/架构整合)。**修法 A(推荐)**:`stream_recv.rs` 5 处 InitFailed 移除 AiError emit(改纯返 retryable,emit 权交调用方)+ `agentic.rs` 重试 loop 只 emit AiStreamRetry、耗尽/Fatal 才 emit 最终 AiError。影响仅流前失败重试路径(Fatal/MidStream 保文/正常错误/压缩失败不受影响)。— `src-tauri/src/commands/ai/{stream_recv.rs:200/258/298/358/398,agentic.rs:207-300}` + `src/composables/ai/useAiEvents.ts:177/326`
|
- [x] ✅(agent ux15-batch1 方案A实施 3 文件 + 主代兜底核验·stream_recv 5处重试路径移除AiError emit + agentic Fatal/耗尽单emit AiError + useAiEvents AiStreamRetry首次创建+后续更新·主代 cargo check EXIT 0 + cargo test df-ai 119 passed + vue-tsc EXIT 0 + grep核验5处emit判定 + 修正注释残留 :251/:299) **UX-260618-15 [P1] 第一批(方案A根治N+1)** ✅ — **流式失败重试(1/4 2/4 3/4)每轮独立气泡**。N+1 降为「1 重试气泡 + 1 最终错误气泡」。**第二批(方案C 聚合气泡显示错误原因)可选增强待定**。后端每轮重试 emit AiError(`stream_recv.rs:206/265/306/364/407` 5 处 InitFailed 返回点·**注(2026-06-20 核验):UX-15 已实施,5 处均改为「不 emit AiError,返回 InitFailed{retryable,error} 交调用方」,注释明示「UX-260618-15: 不再 emit AiError」,见 stream_recv.rs:120/203/262/303),前端 `useAiEvents.ts:326` AiError case 每次 push 新气泡无去重;`AiStreamRetry`(:177)本意更新末条合并但被新 AiError 破坏 → N+1 独立气泡。`agentic.rs:262` 注释误以为 stream_llm 只 emit 一次(实多次)。**用户诉求升级**:不只是修 bug,要**聚合显示设计**(重试 1→4 轮过程聚合单气泡 + 最终结果,勿一排错误吓人)。详见 review 会话重试聚合设计方案(3 角度论证:可行性/前后端一致性/架构整合)。**修法 A(推荐)**:`stream_recv.rs` 5 处 InitFailed 移除 AiError emit(改纯返 retryable,emit 权交调用方)+ `agentic.rs` 重试 loop 只 emit AiStreamRetry、耗尽/Fatal 才 emit 最终 AiError。影响仅流前失败重试路径(Fatal/MidStream 保文/正常错误/压缩失败不受影响)。— `src-tauri/src/commands/ai/{stream_recv.rs:200/258/298/358/398,agentic.rs:207-300}` + `src/composables/ai/useAiEvents.ts:177/326`
|
||||||
|
|
||||||
- [x] ✅(主代串行·doc-discovery 源码核验 `time.ts:44` 已 `(i18n as any).global.locale.value === 'en'`·CR-260618-24 修复闭环 + CR-260618-25 A-time 复审 ✅ 双印证·漏销账补登) **UX-260618-16 [P1🔴]** — **`time.ts:44` formatDate 漏 `.value` 致 en locale i18n 失效**(CR-260618-24 批2 审查发现·Agent C 铁证)。`legacy:false` composition 模式 `i18n.global.locale` 是 ref,`(i18n as any).global.locale === 'en'` 比对象===字符串**恒 false** → en locale 下 formatDate 永走 zh-CN 分支,i18n 化失效(对齐 CR-260615-08 未完成回归)。铁证:`App.vue:227`/`GeneralPanel.vue:259` 均用 `i18n.global.locale.value`,唯 time.ts:44 漏。**修法**:补 `.value` → `(i18n as any).global.locale.value === 'en'`(locale 取值 'en' 非 'en-US',App.vue:227 印证)。— `src/utils/time.ts:44`
|
- [x] ✅(主代串行·doc-discovery 源码核验 `time.ts:44` 已 `(i18n as any).global.locale.value === 'en'`·CR-260618-24 修复闭环 + CR-260618-25 A-time 复审 ✅ 双印证·漏销账补登) **UX-260618-16 [P1🔴]** — **`time.ts:44` formatDate 漏 `.value` 致 en locale i18n 失效**(CR-260618-24 批2 审查发现·Agent C 铁证)。`legacy:false` composition 模式 `i18n.global.locale` 是 ref,`(i18n as any).global.locale === 'en'` 比对象===字符串**恒 false** → en locale 下 formatDate 永走 zh-CN 分支,i18n 化失效(对齐 CR-260615-08 未完成回归)。铁证:`App.vue:227`/`GeneralPanel.vue:259` 均用 `i18n.global.locale.value`,唯 time.ts:44 漏。**修法**:补 `.value` → `(i18n as any).global.locale.value === 'en'`(locale 取值 'en' 非 'en-US',App.vue:227 印证)。— `src/utils/time.ts:44`
|
||||||
|
|
||||||
@@ -381,16 +381,16 @@
|
|||||||
|
|
||||||
**P0(超红线/最脏,本周期优先)**
|
**P0(超红线/最脏,本周期优先)**
|
||||||
|
|
||||||
- [ ] **REFACTOR-260619-01 [P0]** — **commands.rs(ai,1923 行)拆 5 模块**。5 组职责正交:B 发送审批控制(1140,13 IPC)/C 提供商(305)/D 会话CRUD(360)/E 杂项(63)+A helper。**先拆 C `provider_cfg.rs`(零风险试水:仅依赖 ai_providers 无 session 锁/per_conv/spawn)→ D `conversation_crud.rs`(`conversation.rs` 已存在,命名避冲突)→ E `misc_cfg.rs`→ B `chat_control.rs`(最复杂,等 F-09 B 批4 更稳)**。关键:`pub use self::commands::*;` glob 保留(mod.rs:58)否则 invoke_handler 注册断。— `src-tauri/src/commands/ai/commands.rs` + `mod.rs:58`
|
- [x] ✅(2026-06-20 核验已拆分·`commands.rs` 已删除→`src-tauri/src/commands/ai/commands/` 目录 5 模块:chat.rs(56888 字节·发送审批控制 B 域)/config.rs(5496·杂项 E)/conversation.rs(38573·会话CRUD D)/mod.rs(2058·re-export only,`pub use self::{chat,conversation,provider,config}::*` 透传)/provider.rs(15262·提供商 C)·glob 链保留:commands/mod.rs:13 注释明 `pub use self::{chat,conversation,provider,config}::*` → ai/mod.rs `pub use self::commands::*` 透传 commands::ai::*) **REFACTOR-260619-01 [P0]** — **commands.rs(ai,1923 行)拆 5 模块**。5 组职责正交:B 发送审批控制(1140,13 IPC)/C 提供商(305)/D 会话CRUD(360)/E 杂项(63)+A helper。**先拆 C `provider_cfg.rs`(零风险试水:仅依赖 ai_providers 无 session 锁/per_conv/spawn)→ D `conversation_crud.rs`(`conversation.rs` 已存在,命名避冲突)→ E `misc_cfg.rs`→ B `chat_control.rs`(最复杂,等 F-09 B 批4 更稳)**。关键:`pub use self::commands::*;` glob 保留(mod.rs:58)否则 invoke_handler 注册断。— `src-tauri/src/commands/ai/commands.rs` + `mod.rs:58`
|
||||||
- [ ] **REFACTOR-260619-02 [P0]** — **anthropic_compat.rs(1025 行)拆模块**。含本会话加的 precheck/summarize(诊断)+ convert_request(159)+ SSE 解析(109)。**保守拆 3 模块**:`types.rs`(请求响应结构体 ~90)+ `sse.rs`(apply_anthropic_event ~160+测试)+ 残留 provider.rs(convert/complete/stream/precheck 留 impl 块不拆,因 Rust impl 不跨文件)。激进拆 4(加 convert.rs)需把 convert_request 等 4 关联 fn 改自由函数(动 6 处 Self:: 调用点),风险高不推荐。— `crates/df-ai/src/anthropic_compat.rs`
|
- [ ] **REFACTOR-260619-02 [P0]** — **anthropic_compat.rs(1025 行)拆模块**。含本会话加的 precheck/summarize(诊断)+ convert_request(159)+ SSE 解析(109)。**保守拆 3 模块**:`types.rs`(请求响应结构体 ~90)+ `sse.rs`(apply_anthropic_event ~160+测试)+ 残留 provider.rs(convert/complete/stream/precheck 留 impl 块不拆,因 Rust impl 不跨文件)。激进拆 4(加 convert.rs)需把 convert_request 等 4 关联 fn 改自由函数(动 6 处 Self:: 调用点),风险高不推荐。— `crates/df-ai/src/anthropic_compat.rs`
|
||||||
- [ ] **REFACTOR-260619-03 [P0]** — **audit.rs(959 行)拆 5 模块**。`audit/{mod,list,reason,finalize,dedup,process}.rs`。`process_tool_calls`(250+行热路径)**整块搬 process.rs 不拆函数体**(锁内 await 边界不动,CR-260618-11#5 性能注记)。11+ 私有 fn 升 pub(super):audit_tool_call/audit_finalize/find_cached_high_risk_result/build_write_file_diff/build_approval_reason 等。`PENDING_APPROVAL_PLACEHOLDER`/`risk_str` 提 mod.rs 共享。— `src-tauri/src/commands/ai/audit.rs`
|
- [x] ✅(2026-06-20 核验已拆分·`audit.rs` 已删除→`src-tauri/src/commands/ai/audit/` 目录:mod.rs(31813 字节·process_tool_calls 热路径 + 编排)/reason.rs(7796·build_approval_reason)/finalize.rs(3874·audit_tool_call+audit_finalize,mod.rs:115 `pub(crate) use finalize::{audit_finalize, audit_tool_call}`)/cache.rs(7685·find_cached_high_risk_result)/diff.rs(1686·build_write_file_diff)/restore.rs(10033·restore_pending_approvals)/idea_source.rs(13607·任务→灵感溯源)/data_change.rs(2432)/utils.rs(1486·risk_str/常量)) **REFACTOR-260619-03 [P0]** — **audit.rs(959 行)拆 5 模块**。`audit/{mod,list,reason,finalize,dedup,process}.rs`。`process_tool_calls`(250+行热路径)**整块搬 process.rs 不拆函数体**(锁内 await 边界不动,CR-260618-11#5 性能注记)。11+ 私有 fn 升 pub(super):audit_tool_call/audit_finalize/find_cached_high_risk_result/build_write_file_diff/build_approval_reason 等。`PENDING_APPROVAL_PLACEHOLDER`/`risk_str` 提 mod.rs 共享。— `src-tauri/src/commands/ai/audit.rs`
|
||||||
|
|
||||||
**P1**
|
**P1**
|
||||||
|
|
||||||
- [ ] **REFACTOR-260619-04 [P1]** — **ToolCard.vue(1527 行)拆 5 子组件+composable+util**。先 `useToolFormat.ts`(纯函数 ~280,零风险)+ `ToolResultBody.vue`(420 最大块);再 ToolCardHeader/ToolApproval/useToolApproval。风险:折叠态 shouldKeepOpen 三处共享 / 审批状态机断链(B-260616-08 回归)/ ToolCardList 批量审批联动。— `src/components/ToolCard.vue` + `ToolCardList.vue`
|
- [ ] **REFACTOR-260619-04 [P1]** — **ToolCard.vue(1527 行)拆 5 子组件+composable+util**。先 `useToolFormat.ts`(纯函数 ~280,零风险)+ `ToolResultBody.vue`(420 最大块);再 ToolCardHeader/ToolApproval/useToolApproval。风险:折叠态 shouldKeepOpen 三处共享 / 审批状态机断链(B-260616-08 回归)/ ToolCardList 批量审批联动。— `src/components/ToolCard.vue` + `ToolCardList.vue`
|
||||||
- [ ] **REFACTOR-260619-05 [P1]** — **agentic.rs(1231 行)抽 agentic_runtime.rs + agentic_stream.rs**。主 loop 是**单函数 720 行不可按函数拆**(工具执行/审批是 loop 内 if 分支;process_tool_calls 在 audit.rs)。先抽 A+E+F `agentic_runtime.rs`(GeneratingGuard+try_continue_agent_loop+ContinueSnapshot,~195,最高收益最低风险)+ B+C `agentic_stream.rs`(StreamOutcome+stream_one_provider,~165)。**D run_agentic_loop 等 F-09 B 批4 落地再评估**(避免 per_conv 双线作战)。— `src-tauri/src/commands/ai/agentic.rs`
|
- [x] ✅(2026-06-20 核验已拆分·`agentic.rs` 已删除→`src-tauri/src/commands/ai/agentic/` 目录:mod.rs(83390 字节·run_agentic_loop 主 loop)/guard.rs(2894·GeneratingGuard+ContinueSnapshot 抽出对齐 A+E+F `agentic_runtime`)·stream_recv.rs(独立文件,StreamOutcome+stream_one_provider 对齐 B+C `agentic_stream`)) **REFACTOR-260619-05 [P1]** — **agentic.rs(1231 行)抽 agentic_runtime.rs + agentic_stream.rs**。主 loop 是**单函数 720 行不可按函数拆**(工具执行/审批是 loop 内 if 分支;process_tool_calls 在 audit.rs)。先抽 A+E+F `agentic_runtime.rs`(GeneratingGuard+try_continue_agent_loop+ContinueSnapshot,~195,最高收益最低风险)+ B+C `agentic_stream.rs`(StreamOutcome+stream_one_provider,~165)。**D run_agentic_loop 等 F-09 B 批4 落地再评估**(避免 per_conv 双线作战)。— `src-tauri/src/commands/ai/agentic.rs`
|
||||||
- [ ] **REFACTOR-260619-06 [P1]** — **ai_node.rs(1107 行)拆 3 模块**。`ai/{mod,params,ai_node,self_review}.rs`。params.rs(provider 解析 helper ~270)+ ai_node.rs(~130)+ self_review.rs(~300)。测试分块清晰(752/939/1069 三段),低风险。fixture provider_stub/config_with 留 params.rs `pub(super)`。— `crates/df-nodes/src/ai_node.rs`
|
- [ ] **REFACTOR-260619-06 [P1]** — **ai_node.rs(1107 行)拆 3 模块**。`ai/{mod,params,ai_node,self_review}.rs`。params.rs(provider 解析 helper ~270)+ ai_node.rs(~130)+ self_review.rs(~300)。测试分块清晰(752/939/1069 三段),低风险。fixture provider_stub/config_with 留 params.rs `pub(super)`。— `crates/df-nodes/src/ai_node.rs`
|
||||||
- [ ] **REFACTOR-260619-07 [P1]** — **(已有 SMELL-P0-3)AiChat.vue(4075)拆 ConversationSidebar/ChatHeader/MessageList/ChatInput**。方案已定,状态/composable 已外移,拆 template+局部 script。前置:先提交工作区未提交改动(本会话 shouldRenderMsg/scroll/1214 预检等)。— `src/components/AiChat.vue`
|
- [x] ✅(2026-06-20 核验已拆分·`src/components/ai/` 子目录已存在子组件:ConversationSidebar.vue + ChatInput.vue + MessageList.vue + TopBar.vue + DirAuthDialog.vue + MaxRoundsCard.vue·AiChat.vue 从 4075 行缩到 750 行·状态/composable 已外移,模板拆分完成) **REFACTOR-260619-07 [P1]** — **(已有 SMELL-P0-3)AiChat.vue(4075)拆 ConversationSidebar/ChatHeader/MessageList/ChatInput**。方案已定,状态/composable 已外移,拆 template+局部 script。前置:先提交工作区未提交改动(本会话 shouldRenderMsg/scroll/1214 预检等)。— `src/components/AiChat.vue`
|
||||||
- [ ] **REFACTOR-260619-08 [P1]** — **(已有 SMELL-P0-2)tool_registry.rs(2023)按功能分组注册函数拆**。register_crud_tools/register_file_tools(已抽)/register_workflow_tools 等,每个 <200 行。— `src-tauri/src/commands/ai/tool_registry.rs`
|
- [ ] **REFACTOR-260619-08 [P1]** — **(已有 SMELL-P0-2)tool_registry.rs(2023)按功能分组注册函数拆**。register_crud_tools/register_file_tools(已抽)/register_workflow_tools 等,每个 <200 行。— `src-tauri/src/commands/ai/tool_registry.rs`
|
||||||
|
|
||||||
**P2 暂缓(窗口未到/收益低)**
|
**P2 暂缓(窗口未到/收益低)**
|
||||||
@@ -411,6 +411,8 @@
|
|||||||
|
|
||||||
- [ ] **B-260619-02 [P1]** — **GLM 1214 messages 非法(数据驱动定位:单条 tool_result 过大)**。DB 直查报错对话 f64dee94:全量 546 条(多数 compressed),**active(发往GLM)仅 7 条结构合法**(system/assistant/user/assistant/user 交替,tool_use↔tool_result 配对 orphan=0,无连续 role,57K 字节 ~19K tokens **未超 GLM 128K**)。**真凶**:单条 tool_result 巨大(read_file 整文件 #2=21843B/#3=16511B/#6=11482B),T-05 截断阈值 50KB 未触发(21KB<50KB),但 **GLM anthropic 端点单条 tool_result content 限制更严(~10-20KB 即拒 1214)**。次要:#4 assistant content 空(len=0 只有 tool_use);"回复一半消失"=1214 在 GLM 流极早(message_start 前)error→stream_recv InitFailed→emit AiError→前端清 currentText,保文逻辑未覆盖。**修法方向**:① tool_result 截断阈值收紧 50KB→8-10KB(read_file/list_directory 大文件截断+提示)② MidStream 早 1214 保文(apply_anthropic_event error 时若已 message_start 不清流式)③ 空 assistant convert 兜底。**已加诊断(待重编译验证)**:anthropic_compat precheck(首条/连续/input/空content/orphan 5 类 Init bail)+ MidStream error 附 messages 摘要(SSE error chunk 塞摘要到前端 raw)+ HTTP/1.1 治 GLM HTTP/2 RST + 错误源链进 anyhow 文案。— `crates/df-ai/src/anthropic_compat.rs`(precheck/summarize/MidStream 摘要/version HTTP_1_1) + tool_result 截断(T-05 `tool_registry.rs`/`audit.rs` 阈值 50K→8-10K) + MidStream 保文(`stream_recv.rs`)
|
- [ ] **B-260619-02 [P1]** — **GLM 1214 messages 非法(数据驱动定位:单条 tool_result 过大)**。DB 直查报错对话 f64dee94:全量 546 条(多数 compressed),**active(发往GLM)仅 7 条结构合法**(system/assistant/user/assistant/user 交替,tool_use↔tool_result 配对 orphan=0,无连续 role,57K 字节 ~19K tokens **未超 GLM 128K**)。**真凶**:单条 tool_result 巨大(read_file 整文件 #2=21843B/#3=16511B/#6=11482B),T-05 截断阈值 50KB 未触发(21KB<50KB),但 **GLM anthropic 端点单条 tool_result content 限制更严(~10-20KB 即拒 1214)**。次要:#4 assistant content 空(len=0 只有 tool_use);"回复一半消失"=1214 在 GLM 流极早(message_start 前)error→stream_recv InitFailed→emit AiError→前端清 currentText,保文逻辑未覆盖。**修法方向**:① tool_result 截断阈值收紧 50KB→8-10KB(read_file/list_directory 大文件截断+提示)② MidStream 早 1214 保文(apply_anthropic_event error 时若已 message_start 不清流式)③ 空 assistant convert 兜底。**已加诊断(待重编译验证)**:anthropic_compat precheck(首条/连续/input/空content/orphan 5 类 Init bail)+ MidStream error 附 messages 摘要(SSE error chunk 塞摘要到前端 raw)+ HTTP/1.1 治 GLM HTTP/2 RST + 错误源链进 anyhow 文案。— `crates/df-ai/src/anthropic_compat.rs`(precheck/summarize/MidStream 摘要/version HTTP_1_1) + tool_result 截断(T-05 `tool_registry.rs`/`audit.rs` 阈值 50K→8-10K) + MidStream 保文(`stream_recv.rs`)
|
||||||
|
|
||||||
|
- [x] ✅(2026-06-20·cargo df-ai+devflow EXIT 0 零 warning + vue-tsc EXIT 0·主 worktree 改,workflow worktree baseRef 故障绕过) **CR-260620-04 [P0]** — **1214/400 messages 非法·配对错根因(compress 绕过 sanitize)+ 停用模型路由穿透**。用户贴 deepseek 400「tool_calls must be followed by tool messages」/「tool must be response to tool_calls」确认 **tool_use/tool_result 配对错**(与 B-260619-02 tool_result 大小根因**互补,1214 多根因**)。**根因**:`sanitize_messages`(context.rs:212 畸形配对自愈,三档+孤儿)只在 `build_for_request` 调(主对话),**`compress_via_llm`(compress.rs:46)构造 prompt 不经它**(:70 extend active_msgs 直进)→ 压缩带毒 → 400「自动上下文压缩失败」。**修法**:① `context.rs:212 fn sanitize_messages`→`pub fn`(跨 crate);② `compress.rs` 空检查后 `let active_msgs = df_ai::context::ContextManager::sanitize_messages(active_msgs)` 治毒 + `:90` 错误带 `active_count` 诊断;③ 停用模型 `agentic/mod.rs:188,450` model_override `.any(|m|m.model_id==id)`→`==id && m.enabled`(会话内 stale override 不穿透 disabled)。**关联**:B-260619-02(tool_result 大小根因 GLM 1214,截断 50K→8-10K **仍待做** `:412`)/ F-09 newConversation(CR-260620-02 ✅)/ 用户实测 deepseek 400。**未做**:tool_result 截断(B-260619-02 大小根因,GLM 若还犯需做)+ stream_recv SSE 错误 summarize(诊断仅 compress 层,stream_recv 待补)。— `crates/df-ai/src/context.rs` + `src-tauri/src/commands/ai/compress.rs` + `src-tauri/src/commands/ai/agentic/mod.rs`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 🔧 2026-06-19 ToolCard 同批/跨轮 UI 合并(子代理方案·其他会话实施·P1)
|
### 🔧 2026-06-19 ToolCard 同批/跨轮 UI 合并(子代理方案·其他会话实施·P1)
|
||||||
@@ -435,7 +437,7 @@
|
|||||||
|
|
||||||
> 用户需求:推进任务时能即时、方便地关联到灵感及灵感的对抗式评估等相关信息。当前关联链路是 Idea → promote → Project → Tasks,任务和灵感只能通过项目间接关联,无法直接追溯。
|
> 用户需求:推进任务时能即时、方便地关联到灵感及灵感的对抗式评估等相关信息。当前关联链路是 Idea → promote → Project → Tasks,任务和灵感只能通过项目间接关联,无法直接追溯。
|
||||||
|
|
||||||
- [ ] **F-260619-01 [P2]** — **任务关联灵感:TaskRecord 新增 idea_id 字段**。推进任务时即时查看关联灵感的描述、多维评分(scores)、对抗式评估(ai_analysis)等信息,辅助决策。
|
- [x] ✅(2026-06-20 核验已落地·migrations.rs:47 `(20, migrate_v20)` + :361 `migrate_v20` 列存在探测 ALTER + :562/:579 V9_SQL tasks 建表带 idea_id 列(新库直有,老库 V20 补)·models.rs:39 + :82 TaskRecord `pub idea_id: Option<String>` + `#[serde(default)]` 兼容旧 JSON·crud/task_repo.rs:34 `task_from_row row.get("idea_id")` + :52 INSERT + :63 UPDATE + :83/:184/:205 SELECT 列表(list_active/find_by_id/list_deleted)全含 idea_id·测试 migrations.rs:1052 `v20_legacy_db_adds_idea_id_column` + :1078 列存在跳 ALTER 验证·对齐 F-260619-05 已标✅ 同实现) **F-260619-01 [P2]** — **任务关联灵感:TaskRecord 新增 idea_id 字段**。推进任务时即时查看关联灵感的描述、多维评分(scores)、对抗式评估(ai_analysis)等信息,辅助决策。
|
||||||
|
|
||||||
**数据模型变更**:TaskRecord 新增 `pub idea_id: Option<String>`(关联灵感 ID,可空=未关联),`#[serde(default)]` 兼容旧 JSON。
|
**数据模型变更**:TaskRecord 新增 `pub idea_id: Option<String>`(关联灵感 ID,可空=未关联),`#[serde(default)]` 兼容旧 JSON。
|
||||||
|
|
||||||
@@ -466,7 +468,7 @@
|
|||||||
> 灵感来源:`9ffa5f15` DevFlow MCP Server(对抗式评估 7.2/10,推荐 with resources)。
|
> 灵感来源:`9ffa5f15` DevFlow MCP Server(对抗式评估 7.2/10,推荐 with resources)。
|
||||||
> 评估关键结论:技术切入点和研发性价比极佳(ToolDefinition ↔ MCP schema 几乎一致,转换成本极低),但反方强度 0.78——暴露 delete_project 等破坏性操作缺乏鉴权/确认/回滚,外部 AI 幻觉可能导致不可逆误删。**必须在协议接入的同时做工具集安全降级**。
|
> 评估关键结论:技术切入点和研发性价比极佳(ToolDefinition ↔ MCP schema 几乎一致,转换成本极低),但反方强度 0.78——暴露 delete_project 等破坏性操作缺乏鉴权/确认/回滚,外部 AI 幻觉可能导致不可逆误删。**必须在协议接入的同时做工具集安全降级**。
|
||||||
|
|
||||||
- [ ] **F-260619-02 [P2]** — **DevFlow MCP Server:对外暴露任务/项目/灵感管理能力**。以 MCP(Model Context Protocol)server 形式对外暴露 DevFlow 数据层工具,让外部 AI 客户端(Claude Code / Cursor / Cline)直接读写 DevFlow 的项目、任务、灵感数据。
|
- [x] ✅(2026-06-20 核验已落地·crates/df-mcp 存在:lib.rs+protocol.rs+server.rs+tools.rs,`pub use server::run_server` 导出 ·src-tauri/src/main.rs:16 `args.iter().nth(1) == Some("mcp-server")` 子命令分支 + :28 `run_mcp_server` + :99 `df_mcp::run_server(&db_path, read_only)` 启动 ·src-tauri/Cargo.toml:36 `df-mcp = { path = "../crates/df-mcp" }` 依赖已接 ·lib.rs 注释含安全降级(High 默认拒绝/Medium 日志/--read-only/不暴露文件工具)对齐设计) **F-260619-02 [P2]** — **DevFlow MCP Server:对外暴露任务/项目/灵感管理能力**。以 MCP(Model Context Protocol)server 形式对外暴露 DevFlow 数据层工具,让外部 AI 客户端(Claude Code / Cursor / Cline)直接读写 DevFlow 的项目、任务、灵感数据。
|
||||||
|
|
||||||
**形态**:独立 stdio MCP server 子命令(方案 A)。`devflow mcp-server` 启动,应用未运行时也能被 Claude Code 拉起。
|
**形态**:独立 stdio MCP server 子命令(方案 A)。`devflow mcp-server` 启动,应用未运行时也能被 Claude Code 拉起。
|
||||||
|
|
||||||
@@ -654,3 +656,72 @@
|
|||||||
|
|
||||||
**状态**:✅ P0-P2 全完成(合并 de04970 + a2db5c7 + 38 苛刻测 + 试验记录文档)。待重启 dev 实跑验证 LLM 实际响应效果(机制测过 ≠ LLM 行为改善)。
|
**状态**:✅ P0-P2 全完成(合并 de04970 + a2db5c7 + 38 苛刻测 + 试验记录文档)。待重启 dev 实跑验证 LLM 实际响应效果(机制测过 ≠ LLM 行为改善)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 单对话内并行多轮推理(主循环 ReAct 串行 → 规划式并行)
|
||||||
|
|
||||||
|
**背景**:单对话内并行现状评估——工具级并行(Low risk `join_all`,`audit/mod.rs:448/499`)✅ + 主/辅助 LLM 2 路并发(`per_conv permits=2`,`state.rs:121`)✅,但**主循环单链 ReAct 不支持并行多轮推理**(`run_agentic_loop` `agentic.rs:331-1206` 一轮一轮串行,无法拆子流并行)。这是单对话内并行的真正天花板,`permits=2` 是配套限流(非瓶颈)。
|
||||||
|
|
||||||
|
**根因**:ReAct 每轮依赖前轮结果,不产生并行多轮需求。要并行多轮需**规划式分解**,`coordinator.rs`/`conditions` 空壳未落地(SMELL-P2-4 B 路线占位 / memory [[aichat-arch-extensibility]])。
|
||||||
|
|
||||||
|
**方向(架构·规划放哪层——分轻/重)**:
|
||||||
|
- **轻规划融入意图层**:扩展 `intent.rs` `tool_subset_for` → `plan_hint`(工具编排/可并行组/顺序)+ `suggested_model_tier` 模态。**保持纯函数零延迟**(intent 已接入 loop,跑题 P0 `filter_tool_defs` 收敛工具 29→5-10 + 三重 fallback,102d398)。轻规划是自然延伸(意图→编排提示)。
|
||||||
|
- **重规划独立 planning 层**:`coordinator.rs` 空壳 → 演进 planning,消费 intent + plan_hint,做任务分解/子流并行调度/条件分支(conditions)。接入 loop,有状态(intent.rs 是无状态纯函数,重规划违背其定位,不塞进去)。
|
||||||
|
- **三层链路**:`intent`(识别,纯函数)→ `plan_hint`(意图层轻规划)→ `planning`(独立重规划/调度)→ `execution`(loop 按 plan 并行跑)
|
||||||
|
|
||||||
|
**决策点(待定)**:
|
||||||
|
- (a) 轻规划先扩(`intent → plan_hint`,低风险,跑题 P0 基础延伸)vs 直接重规划 planning(大改,coordinator 落地)
|
||||||
|
- (b) planning 形态:`coordinator.rs` 空壳演进 vs 新建 `plan.rs`
|
||||||
|
- (c) 并行多轮语义:用户是否真需单对话内并行探索多方案(当前 ReAct 单链足够大多数场景,可能非刚需)
|
||||||
|
|
||||||
|
**关联**:`intent.rs`(CR-08)/ 跑题 P0 intent 接入(102d398)/ `coordinator.rs` 空壳(SMELL-P2-4)/ conditions(T-260614-11)/ F-09 B 路线 / memory [[aichat-arch-extensibility]] / [[aichat-roadmap-ab-split]](B 路线)
|
||||||
|
|
||||||
|
**状态**:📋 待决策(是否推进单对话内并行多轮?轻规划先 / 重规划独立?)。属 aichat B 路线(决策能力)范畴,非刚需(P0-P2 跑题已大幅改善,ReAct 单链覆盖大多数场景)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🟡 F-09 决策e 前端 newConversation 未跟进(中断弹窗遗留·用户实测)
|
||||||
|
|
||||||
|
**背景**:用户实测——生成中点新建对话,弹「当前正在生成,确定中断并新建?」。F-09 决策e 真并发已落地(切会话/新建不该打断旧 loop,各 conv 并行),该弹窗与并行矛盾。
|
||||||
|
|
||||||
|
**根因**:前端 `newConversation` 是 **A 路线单 loop 软隔离遗留**(B-260615-10 + F-260616-09 A 路线),没随决策e B 路线升级:
|
||||||
|
- `AiChat.vue:246-251 confirmNewConversation`:生成中弹「中断并新建」确认(B-260615-10 软复位,单 loop「中断当前+新建」逻辑)
|
||||||
|
- `useAiConversations.ts:78 state.generatingConvs.clear()`:A 路线 F-260616-09 清字段,清空**所有**后台生成跟踪(后台 loop 后端还在跑,前端丢失跟踪 → 侧栏不显旧 conv 生成态 / 事件路由错乱)
|
||||||
|
- 对比 `switchConversation:89-91`:决策e **已落地**(「允许生成中切换:后台继续生成,事件按 conversation_id 路由」)。**`newConversation` 漏升级**。
|
||||||
|
|
||||||
|
**决策e 应有行为**:新建对话 = createConversation + 切 active,**旧 conv 后台 loop 继续**(后端 loop 按 conv 存在性退出,新建不删旧 conv 不打断)。不弹中断 + 不 clear generatingConvs(后台生成态保留,侧栏显双会话生成)。
|
||||||
|
|
||||||
|
**修法**:
|
||||||
|
- `confirmNewConversation` 去弹窗,直接 `store.newConversation`(决策e 新建=切到新 conv,旧后台继续,非中断)
|
||||||
|
- `newConversation` 去 `generatingConvs.clear()`(后台生成继续不清);`messages/queue/pendingApprovals/currentText` 是新 conv 本地视图复位,保留
|
||||||
|
- 核对:新建后旧 conv 后台事件路由正常(`generatingConvs` 含旧 conv,`isGenerating(旧)` 仍 true,侧栏双生成) + `AiCompleted`/`AiError` 按 conversation_id 正确收尾旧 conv
|
||||||
|
|
||||||
|
**优先级**:🟡 P1(决策e 体验破坏:用户期望并行被中断提示打断,且 clear 致后台生成前端丢失跟踪)
|
||||||
|
**关联**:F-09 决策e(CR-07 已审 PASS 后端真并发)/ B-260615-10 软复位(待废弃)/ F-260616-09 A→B 路线 / memory [[devflow-aichat-review-pending]]
|
||||||
|
**状态**:✅ 已实施(CR-260620-02 审查 PASS,confirmNewConversation 去弹窗 + newConversation 去 clear,2026-06-20)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🟡 MED-1 bind/create/update_project(Medium)AI 工具路径审批执行后 reload 未兑现(CR-260620-01 审查 MED)
|
||||||
|
|
||||||
|
**背景**:CR-260620-01 审查发现 — bind_directory/create_project/update_project 是 RiskLevel::Medium(tool_registry.rs:483/506/536),走 pending 审批,执行在 chat.rs `ai_approve`(非 process_tool_calls 内联)。bind_dir_to_project / repo.update_field 直写 DB 绕过 project.rs IPC,批3 三处 reload(create_with_binding/update_project/relocate)覆盖不到 AI 工具绑定路径。tool_registry.rs:545-549 注释承诺"reload 兜底"但代码空头。
|
||||||
|
|
||||||
|
**后果**:AI 用 bind_directory/create_project/update_project 工具绑定目录后,新目录未进白名单 → 后续文件工具仍误弹窗,需手动 Reload/重启。(用户手动绑定走 IPC project.rs 已修批3;AI 工具绑定路径漏)
|
||||||
|
|
||||||
|
**修法**(单点):chat.rs `ai_approve` 执行 Medium 工具成功后,若 tool_name ∈ {bind_directory, create_project, update_project},调 `state.reload_allowed_dirs()`。覆盖所有 Medium path 绑定工具审批执行路径。
|
||||||
|
|
||||||
|
**优先级**:🟡 MED(非安全,用户可手动 Reload;AI 主动 bind 场景,用户主诉 IPC 路径已解决)
|
||||||
|
**关联**:CR-260620-01 审查 MED-1 / 批3 project.rs reload(IPC 路径已修)/ agent3 问题5
|
||||||
|
**状态**:✅ 已实施(chat.rs ai_approve 两执行点 path 工具后 reload_allowed_dirs,cargo check EXIT 0,2026-06-20)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ⚪ LOW F-09 stale 注释 + 死 i18n key 清理(CR-260620-02 审查 LOW)
|
||||||
|
|
||||||
|
**背景**:CR-260620-02 审查 — F-09 去中断弹窗后,3 处子组件 stale doc 注释仍写"带二次确认"(TopBar.vue:163 / ConversationSidebar.vue:226/242)+ i18n key `confirmNewConvWhileGenerating`(zh-CN settings:120 / en:119)成死 key(0 消费方)。
|
||||||
|
|
||||||
|
**修法**:删死 i18n key + 改 stale 注释为"决策e 直接新建"。
|
||||||
|
**优先级**:⚪ LOW(质量清理,可选)
|
||||||
|
**关联**:CR-260620-02 审查 LOW-1 / F-09 决策e
|
||||||
|
**状态**:✅ 已实施(删 aiChat.ts confirmNewConvWhileGenerating key zh/en + 改 TopBar/Sidebar 3 处 stale 注释,vue-tsc EXIT 0,2026-06-20)
|
||||||
|
|
||||||
|
|||||||
30
docs/待决策.md
30
docs/待决策.md
@@ -161,7 +161,7 @@
|
|||||||
- **方向**:单向(任务→灵感)vs 双向(灵感侧反向显示关联任务列表)
|
- **方向**:单向(任务→灵感)vs 双向(灵感侧反向显示关联任务列表)
|
||||||
- **推荐**:**tasks 加 `idea_id REFERENCES ideas(id)`**(复用 projects 模式,1对1 起步,单向,后续按需扩展)。低侵入(单列迁移 + Repo 白名单 + 工具 idea_id 参数 + 前端展示)。
|
- **推荐**:**tasks 加 `idea_id REFERENCES ideas(id)`**(复用 projects 模式,1对1 起步,单向,后续按需扩展)。低侵入(单列迁移 + Repo 白名单 + 工具 idea_id 参数 + 前端展示)。
|
||||||
- **关联**:todo.md F-260619-05(设计点详情)
|
- **关联**:todo.md F-260619-05(设计点详情)
|
||||||
- **状态**:🟡 待用户拍板(产品粒度/方向)。主代推荐 tasks.idea_id 1对1 单向,确认后 spawn 实施。
|
- **状态**:✅ **已实施**(2026-06-20 调研确认)— tasks.idea_id 1对1 单向已落地:TaskRepo `idea_id` 字段(task_repo.rs:34 row.get / :52 INSERT / :63 UPDATE / :83 SELECT 全含)+ create_task 工具 idea_id 可选参数(tool_registry.rs:637「可选传 idea_id 关联灵感(1对1 单向)」)。复用 projects.idea_id 模式,粒度 1对1/单向/单列,符合推荐方向。前端任务卡片展示灵感来源(可选增强)待补。
|
||||||
|
|
||||||
#### 消息级溯源 P2 切读策略(技术决策·大改需知情)
|
#### 消息级溯源 P2 切读策略(技术决策·大改需知情)
|
||||||
- **背景**:消息级溯源 P0(地基 ai_messages 表 ✅ CR-10)+ P1(溯源字段 ✅ CR-12)完成。P2 切读是拆表实际启用(读写路径从 `ai_conversations.messages` JSON 切到 `ai_messages` 表)。
|
- **背景**:消息级溯源 P0(地基 ai_messages 表 ✅ CR-10)+ P1(溯源字段 ✅ CR-12)完成。P2 切读是拆表实际启用(读写路径从 `ai_conversations.messages` JSON 切到 `ai_messages` 表)。
|
||||||
@@ -174,6 +174,34 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## workspace_root 分发适配(编译期 CARGO_MANIFEST_DIR 写死,跨机器/跨平台失效)
|
||||||
|
|
||||||
|
**背景**:`workspace_root_path()`(state.rs)/ `workspace_root()`(tool_registry.rs)/ `workspace_root_str()`(mod.rs)三处同源用 `env!("CARGO_MANIFEST_DIR")`(编译期写死编译机源码路径)。DevFlow 打包分发后:
|
||||||
|
- 用户机器无编译机路径(如 `E:\wk-lab\devflow`)→ workspace_root 失效
|
||||||
|
- 跨平台(Windows/Linux 各发行版)各自编译机路径写死,分发到不同机器/平台均失效
|
||||||
|
- 影响:相对路径锚点(check_path_authorization / resolve_workspace_path_impl)+ 默认白名单(default_with_root)+ run_command 默认 working_dir 均依赖此值,分发后指向不存在路径 → AI 文件工具全部失效
|
||||||
|
|
||||||
|
**当前(开发机自用)**:CARGO_MANIFEST_DIR = 开发机 DevFlow 源码,AI 访问源码合理。仅开发期有效。
|
||||||
|
|
||||||
|
**根因**:workspace_root 语义错位 — 当前 = DevFlow 自身源码目录(开发 DevFlow 的语义);但 DevFlow 分发后是"用户用它开发自己项目的工具",workspace_root 应 = 用户项目(动态,非编译期)。
|
||||||
|
|
||||||
|
**方向(运行期动态 workspace_root)**:
|
||||||
|
- **a current_exe 安装目录**:`std::env::current_exe().parent()`。分发后 = 安装目录。但安装目录非用户工作空间(AI 应访问用户项目,非 DevFlow 安装目录)。语义仍错。
|
||||||
|
- **b 用户项目绑定(推荐方向)**:workspace_root 概念淡化,默认白名单 = 用户绑定项目(projects.path,reload_allowed_dirs 已读),相对路径锚 = 当前活跃项目目录。无固定编译期 root。最贴合"用户用 DevFlow 开发自己项目"语义。需改 check_path_authorization / resolve 锚活跃项目(当前锚固定 workspace_root)。
|
||||||
|
- **c 用户配置工作空间**:KV workspace_path,用户首启配置。灵活但需 UI + 首启引导。
|
||||||
|
- **d 混合**:debug 开发期 CARGO_MANIFEST_DIR(源码)/ release 分发期 current_exe 或用户项目(`cfg!(debug_assertions)` 区分)。过渡方案。
|
||||||
|
|
||||||
|
**决策点**:
|
||||||
|
- workspace_root 语义(DevFlow 安装目录 vs 用户项目 vs 用户配置工作空间)
|
||||||
|
- 相对路径锚点(固定 root vs 活跃项目)
|
||||||
|
- 是否区分开发期/分发期(cfg!(debug_assertions))
|
||||||
|
- docs/kms 开发机暴露(随 b/c 自然解决:无固定 root 则 docs/kms 不在默认白名单)
|
||||||
|
|
||||||
|
**关联**:F-260619-03 去固定根(已删 is_authorized 硬编码放行)/ projects.path 自动授权(reload_allowed_dirs)/ memory [[devflow-project-path-binding]]
|
||||||
|
**状态**:🟡 待决策(workspace_root 运行期动态方案 a/b/c/d)。**分发前必须解决**(否则分发后 AI 文件工具全部失效)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 已决归档
|
## 已决归档
|
||||||
|
|
||||||
按月归档(随时间增长追加月份文件,防主文件膨胀):
|
按月归档(随时间增长追加月份文件,防主文件膨胀):
|
||||||
|
|||||||
161
docs/待审查.md
161
docs/待审查.md
@@ -23,6 +23,123 @@
|
|||||||
|
|
||||||
## 当前队列
|
## 当前队列
|
||||||
|
|
||||||
|
### CR-260620-01 AI 授权目录改进批(working tree 未提交:strip_verbatim比对侧收口+黑名单统一+search_files symlink+bind reload+toast接线+单测·state.rs/tool_registry.rs/mod.rs/project.rs/chat.rs/AllowedDirsPanel.vue/Settings.vue/i18n×2) — ✅ 已审(ISSUES·🟡1⚪2)
|
||||||
|
|
||||||
|
- **范围**:三方审查(安全/UX/跨端)交叉印证改进。误弹窗核心根因(strip_verbatim 仅写入侧,比对侧遗漏)收口到 `is_authorized` 单点(candidate strip)。黑名单两套不一致(validate_path contains vs is_in_system_blacklist 分段)统一单一来源 + 补 windows 根/programdata/.ssh。search_files symlink 逃逸(file_type 不跟随)。bind 路径 reload(project.rs create_with_binding/update_project/relocate 三处)。AllowedDirsPanel toast 接线 + pickDir 即加。P0 workspace_root 收紧已**回退**(编译期写死不适合分发,转待决策)。
|
||||||
|
- **维度**:安全(授权绕过/黑名单/symlink 逃逸)+ 一致性(读写形态/黑名单统一)+ 回归(现有测试 + 新增单测)
|
||||||
|
- **核验方式**:`git diff` working tree 逐文件核验 + cargo test state:: 18 passed + vue-tsc EXIT 0
|
||||||
|
- **待审点**:is_authorized strip 收口是否覆盖所有调用方(resolve :334 canon_resolved / check_path :448)/ validate_path 统一后 .ssh 等是否仍挡 / 黑名单 windows 段命中即拒是否误伤合法路径 / bind reload 是否遗漏 path 写入点
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**复审结论(2026-06-20·主代独立 grep/read 核验源码形态 + cargo test state:: 18 passed)**: ✅ **ISSUES** — 🔴0 🟡1 ⚪2
|
||||||
|
|
||||||
|
**范围边界声明**:`git diff HEAD --stat` 显示本批实际改动 13 文件,其中 `AiChat.vue`(confirmNewConversation 去中断弹窗 + 删 isViewingGenerating dead code)+ `useAiConversations.ts`(newConversation 去 generatingConvs.clear)属 **F-09 决策e 真并发**改动,**非授权目录范围**,已由独立 **CR-260620-02** 登记(已审 PASS·⚪2),本审查不覆盖(避免审查污染/越界)。`Cargo.toml` 仅 LF/CRLF 行尾(warning 无实质改动)。本审查严格聚焦授权目录 8 文件。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**① is_authorized strip 收口覆盖性 PASS(核验 4 调用方)**:
|
||||||
|
- `state.rs:356-365 is_authorized` 比对侧 `strip_verbatim(candidate.to_path_buf())` 单点收口 ✅
|
||||||
|
- `tool_registry.rs:326 is_authorized(&resolved)` — resolved 词法锚定(`PathBuf::from(path)` 或 `root.join(path)`),**无 verbatim 前缀**,strip 无害(原样返回),逻辑等价 ✅
|
||||||
|
- `tool_registry.rs:334 is_authorized(&canon_resolved)` — canon_resolved 经 `std::fs::canonicalize` 返回带 `\\?\` 前缀,**strip 后命中** persistent(写入侧也 strip),形态一致 ✅(误弹窗核心根因修复点)
|
||||||
|
- `state.rs:460 check_path_authorization is_authorized(&resolved)` — resolved 词法层无前缀,strip 无害等价 ✅
|
||||||
|
- `audit/mod.rs:187 check_path_authorization` 转调,同源 strip 生效 ✅
|
||||||
|
- **覆盖完整**:4 个 is_authorized 调用点 + 1 个 check_path_authorization 全部经比对侧 strip,无遗漏调用方。
|
||||||
|
|
||||||
|
**② validate_path 凭据防护 PASS(统一后仍挡)**:
|
||||||
|
- `tool_registry.rs:136-138` `.ssh/.aws/.gnupg + Windows/ProgramFiles/ProgramData` 统一走 `crate::state::is_in_system_blacklist`(分段匹配,消除 contains 子串误伤如 `my\windows\backup`) ✅
|
||||||
|
- `tool_registry.rs:140-142` AppData 保留 `lower.contains("\\appdata\\")` 单独挡(用户级数据,分段匹配会误伤 `D:\backup\appdata` 合法目录名 — 决策合理,已注释说明) ✅
|
||||||
|
- `.ssh/.aws/.gnupg` 迁移后 `is_in_system_blacklist:422` 任意路径段命中即拒,validate_path 经 `PathBuf::from(&normalized)` 传入分段匹配,等价且更精准 ✅
|
||||||
|
- **纵深防御完整**:validate_path(词法前置)+ is_authorized 内黑名单(运行时双保险)+ set_allowed_dirs:652 预校验(防持久化系统目录),三层防线齐全。
|
||||||
|
|
||||||
|
**③ 黑名单 windows 段命中即拒误伤面 PASS(分段匹配无子串误伤)**:
|
||||||
|
- `state.rs:408 if *seg == "windows" { return true }` — seg 是按 `/`/`\` 分隔的**完整路径段**,`E:\my-windows\backup` 分段为 ["e:","my-windows","backup"],`"my-windows" != "windows"` 不误伤 ✅
|
||||||
|
- `test_blacklist_no_false_positive:892` 印证(`E:/my program files backup/x` 不拒)✅
|
||||||
|
- `test_blacklist_windows_root_programdata_creds:983` 印证 win.ini/explorer.exe/ProgramData/.ssh/.aws/.gnupg 全拒 + devflow 自身 + `D:\backup\appdata` 不误伤 ✅
|
||||||
|
- Windows 根段命中即拒(含根本身,不再限定 system32 子目录):防 win.ini/hosts 等敏感文件,合理增强 ✅
|
||||||
|
- **`.ssh/.aws/.gnupg` 段在 `cfg!(windows)` 块外**(跨平台):设计正确,Unix 凭据目录同样需挡 ✅
|
||||||
|
|
||||||
|
**④ search_files_recursive symlink 逃逸 PASS**:
|
||||||
|
- `tool_registry.rs:1797-1804` 改 `entry.file_type().await?`(不跟随 symlink)+ symlink 一律 `continue`(不匹配/不递归),消除 workspace 内 symlink → 外部目录的递归逃逸 ✅
|
||||||
|
- metadata 延迟到命中后才 `entry.metadata().await?`(避免对每个条目都跟随 symlink 读元数据,性能 + 安全双收益) ✅
|
||||||
|
- 与 `list_dir_recursive` 行为对齐(注释 :1798 标注)✅
|
||||||
|
|
||||||
|
**⑤ P0 回退完整性 PASS(workspace_root 三处同源)**:
|
||||||
|
- `state.rs:490` / `tool_registry.rs:275` / `mod.rs:282` 三处均用 `env!("CARGO_MANIFEST_DIR").parent().parent()`,**无残留 project_root_path** ✅
|
||||||
|
- `state.rs:485-489` 加分发适配警告注释(编译期写死不适合分发,转待决策),诚实标注已知限制 ✅
|
||||||
|
- `mod.rs:281 workspace_root_str` 额外 canonicalize(trust key 归一化用,与 run_command 默认 working_dir 对齐),用途独立于 is_authorized 比对,无需 strip ✅
|
||||||
|
- **default_with_root:340/reload:619 空白名单时保留 workspace_root** 兜底,向后兼容(开发机自用有效)✅
|
||||||
|
|
||||||
|
**⑥ set_allowed_dirs/get_allowed_dirs 一致性 PASS**:
|
||||||
|
- `state.rs:664-665 set_allowed_dirs` 返回时过滤 workspace_root(与 `get_allowed_dirs:680` 一致),内部根不暴露前端 ✅
|
||||||
|
- AllowedDirsPanel.vue:74 删 `isWorkspaceRoot` 前端宽松匹配(原 `endsWith('/src-tauri')` 在分发场景失效),改由后端统一过滤,前后端读写形态一致 ✅
|
||||||
|
|
||||||
|
**⑦ AllowedDirsPanel 即时持久化 + toast PASS**:
|
||||||
|
- emit toast 通道(Settings.vue `@toast="showToast"`),替代 console.error 用户零反馈 ✅
|
||||||
|
- pickDir 选完即加(消除二次点"添加"摩擦)+ 单按钮双态(newDir 空→选目录/有值→添加),UX 改进 ✅
|
||||||
|
- dirs 由 `setAllowedDirs` 返回值回填(canonicalize + strip 后真实路径),读写不漂移 ✅
|
||||||
|
- i18n zh-CN/en settings.ts 4 toast key + allowedDirsSelectDir + desc 对齐(workspace_root→devflow 自身)中英对称 ✅
|
||||||
|
|
||||||
|
**⑧ chat.rs:552 docstring 修正 PASS**:PerConvState 已删,改 AppState.session 进程级会话临时授权语义,文档与代码一致 ✅
|
||||||
|
|
||||||
|
**⑨ 回归 PASS**:cargo test state:: **18 passed 0 failed**(含 3 新增 + 15 原有)。test_blacklist_windows_system32:878 在新"windows 段即拒"逻辑下仍 pass(更早 return true)。test_blacklist_no_false_positive 在 `.ssh/.aws/.gnupg` 增强后仍 pass(测试用例合法路径不含这些段)✅
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**🟡 MED-1:bind_directory / create_project(path) AI 工具路径 reload 残留(注释承诺未兑现)**
|
||||||
|
|
||||||
|
| file:line | 问题 | 建议 |
|
||||||
|
|-----------|------|------|
|
||||||
|
| `tool_registry.rs:545-549`(bind_directory handler 注释)+ `:523-525`(create_project 带 path 路径) | **注释承诺"通过 app_handle emit 事件让 state reload,或下次工具调用前 reload 兜底",但代码未实际触发 reload**。bind_dir_to_project:375 经 `repo.update_field(id, "path", path)` 直接写 DB,**绕过 project.rs IPC**(create_with_binding/update_project/relocate 三处 reload 覆盖不到)。register_project_tools 闭包无 AppState 参数,确实无法直接 reload。reload_allowed_dirs 仅在启动(state.rs:553 init)+ project.rs 三 IPC + set_allowed_dirs(:661)触发,**"下次工具调用前 reload 兜底"是空头承诺** — 无该机制(主代 grep reload_allowed_dirs 全 src 印证,无工具调用前置 reload 路径)。后果:AI 调 bind_directory 绑定新目录后,该目录未进内存白名单,后续文件工具仍误弹窗,直到用户手动点 Reload 或重启。 | 二选一:(a) register_project_tools 闭包捕获 AppState 句柄(若可获取),bind_dir_to_project 后调 `state.reload_allowed_dirs().await`;(b) process_tool_calls 前置轻量校验:每次工具调用前比对 projects.bind_directory 与内存白名单差集,差集非空触发 reload(兑现"下次工具调用前 reload 兜底"注释)。当前不影响安全(只是 UX 误弹窗,用户可手动 Reload),降 MED。 |
|
||||||
|
|
||||||
|
**⚪ LOW-1:strip_verbatim 单前缀 strip + 大小写敏感(理论边界,实际安全)**
|
||||||
|
|
||||||
|
`state.rs:471-478 strip_verbatim` 用 `s.strip_prefix(prefix)`(prefix 为 `\\?\`/`\\.\`/`\??\` ASCII 小写),Windows OS 生成的 verbatim 前缀**始终固定小写**(Rust std canonicalize 保证),大小写敏感 strip 不会漏。仅 strip 一个前缀(无嵌套场景)。理论边界:若 LLM 传入大写 `\\?\`(非 canonicalize 来源)则 strip 失败 — 但 LLM 传入路径走 validate_path + check_path_authorization 词法层,不经 canonicalize,不会带 verbatim 前缀。实际安全,降 LOW 标注。
|
||||||
|
|
||||||
|
**⚪ LOW-2:reload_allowed_dirs 项目绑定目录读 list_all(无 active 过滤)**
|
||||||
|
|
||||||
|
`state.rs:601-606 reload_allowed_dirs` 读 `repo.list_all()` 全量项目(含回收站软删项目?)的 bind_directory 合并入 persistent。若软删项目 path 仍被读入,会残留无效授权目录(指向已"删除"项目路径)。建议核验 ProjectRepo::list_all 是否过滤 `status != 'deleted'`,或 reload 时 filter active 项目。当前影响:无害(目录仍存在,授权不影响安全,仅白名单略冗余),降 LOW。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
- **🟡 MED-1**: bind_directory / create_project(path) AI 工具路径 reload 残留(注释承诺未兑现,UX 误弹窗,非安全)
|
||||||
|
- **⚪ LOW-1**: strip_verbatim 大小写敏感 strip 理论边界(实际 OS 固定小写,安全)
|
||||||
|
- **⚪ LOW-2**: reload_allowed_dirs 读 list_all 可能含软删项目 path(白名单略冗余,无害)
|
||||||
|
- **待修项回流 todo**: 🟡 **1 项**(tool_registry.rs:545-549 bind_directory/create_project AI 工具路径 reload 兑现 — 二选一:闭包捕获 AppState / process_tool_calls 前置差集 reload)
|
||||||
|
|
||||||
|
### CR-260620-02 F-09 决策e newConversation 跟进(working tree 未提交:AiChat.vue confirmNewConversation 去中断弹窗+删 isViewingGenerating dead code / useAiConversations.ts newConversation 去 generatingConvs.clear()) — ✅ 已审(PASS·⚪2)
|
||||||
|
|
||||||
|
- **复审结论(2026-06-20·审查 agent 独立 git diff + grep/read 核验源码形态)**: ✅ **PASS** — 🔴0 🟡0 ⚪2
|
||||||
|
- **验证**: `git diff` AiChat.vue/useAiConversations.ts 逐行核验 + vue-tsc --noEmit EXIT 0 + 独立 grep 全 src/ 无残留。
|
||||||
|
|
||||||
|
**① confirmNewConversation 调用路径 PASS**: 唯二调用方均正确直达 `store.newConversation()`:
|
||||||
|
- AiChat.vue:7 `@confirm-new-conversation="confirmNewConversation"`(ConversationSidebar emit)
|
||||||
|
- AiChat.vue:18 `@new-conversation="confirmNewConversation"`(TopBar emit)
|
||||||
|
- 去弹窗后 handler 体仅剩 `await store.newConversation()`(:248-250),无残留 isViewingGenerating/confirmDialog 中断逻辑 ✅
|
||||||
|
|
||||||
|
**② isViewingGenerating 删除完整性 PASS**: grep 全 src/ 命中 AiChat.vue 已无 isViewingGenerating。其余 3 处命中(MessageList.vue:196 / MaxRoundsCard.vue:42 / DirAuthDialog.vue:50)均为各组件**本地独立 computed**(`store.state.streaming && store.isGenerating(state.activeConversationId)`),非 import AiChat 的——AiChat 删除不影响它们。vue-i18n/tsconfig noUnusedLocals 编译通过(vue-tsc EXIT 0)✅
|
||||||
|
|
||||||
|
**③ newConversation 去 clear 后旧 conv 收尾 PASS**: 事件路由按 conversation_id 正确分发,旧 conv 后台 loop 完成时收尾无依赖 generatingConvs 全清:
|
||||||
|
- useAiEvents.ts:461-469 `handleEvent` 外围 — 非当前会话事件(`!isCurrent`)仅 AiCompleted/AiError 执行 `state.generatingConvs.delete(convId)` + `loadConversations()`,不污染当前视图 ✅
|
||||||
|
- useAiEvents.ts:362(AiCompleted)/ :415(AiError)— `state.generatingConvs.delete(event.conversation_id || '')` 按 conv_id 删除(per-conv),非全清 ✅
|
||||||
|
- 去 clear 后旧 conv 后台完成时按 conv_id 正确从 Set 移除,侧栏态正确刷新(侧栏 `isGenerating(id)` 读 Set,双会话生成可见)✅
|
||||||
|
|
||||||
|
**④ switchConversation 对齐 PASS**: newConversation 真对齐 switchConversation 并行语义。两者均:不中断后台 loop / 不清 generatingConvs(per-conv 跟踪保留) / 事件按 conv_id 路由。switchConversation:91-205 grep 核验无 generatingConvs.clear() / 无 loop 杀逻辑 ✅
|
||||||
|
|
||||||
|
**⑤ queue 清空边界 PASS(含 WATCH)**: `state.queue`(ai.ts:90)是**单一全局数组**非 per-conv。drainQueue(useAiSend.ts:246-267)调 sendMessage 续发,路由到 `state.activeConversationId`(非队列起源 conv)。故 queue 语义是"active-conv 视图的待发意图"非跨 conv 跟踪。newConversation 清 `queue = []`(:80)实际**正确且更安全**:防 conv A 排队消息在 conv A 的 AiCompleted 触发 ai-drain-queue 后误发到新 conv B(注释"防旧会话排队消息带进新会话 drain"准确)。**🟡→⚪ WATCH-1**:switchConversation(:91-205)**不清 queue**,真并发下若 conv A 排队消息 + 切到 conv B,A 的 AiCompleted 会把队列项发到 B(误路由,既有逻辑非本 CR 引入,登记待评估)。
|
||||||
|
|
||||||
|
**⑥ 后端依赖 PASS**: 独立 Read conversation.rs:154-201 `ai_conversation_create` 确认决策e 后端已落地:
|
||||||
|
- :163-166 注释明确「新建会话不杀旧 loop」(B-260615-10 单 loop 软复位废弃)
|
||||||
|
- :188 仅切 `active_conversation_id = Some(id.clone())`
|
||||||
|
- :196-198 注释「旧 conv 的 per_conv 不清除(决策e:后台 loop 继续跑)」+ :198 `session.conv(&id)` 为新会话建独立 per_conv
|
||||||
|
- 前端去弹窗/去 clear 跟进无后端依赖,后端先行已就绪 ✅
|
||||||
|
|
||||||
|
**⑦ confirmDialog 基础设施不受影响 PASS**: grep AiChat.vue confirmDialog 仍有 5 活跃消费方(:230 confirmDeleteConversation / :236 confirmClearChat / :376 tryForceSend / :545 handleClearContext 等),useConfirm 模块仍 import(:136)。仅 confirmNewConversation 退出消费,无回归 ✅
|
||||||
|
|
||||||
|
- **⚪ WATCH-1**: queue 全局数组非 per-conv。switchConversation 不清 queue,真并发下「conv A 排队 + 切 conv B」时 A 的 AiCompleted→ai-drain-queue 会把队列项误发到 B。newConversation 清 queue 是正确收口但两路径行为不一致。建议统一(switchConversation 也清 queue,或 queue 改 per-conv)。既有逻辑非本 CR 引入。
|
||||||
|
- **⚪ LOW-1**: stale doc 注释。去弹窗后三处子组件注释仍描述旧行为(「带二次确认」/「生成中走确认弹层」/「生成中需二次确认」):TopBar.vue:163 / ConversationSidebar.vue:226 / ConversationSidebar.vue:242。纯文档不影响行为,建议顺手同步。另 i18n key `confirmNewConvWhileGenerating`(zh-CN:120 / en:119)已成死 key(0 消费方,仅 2 文件定义残留),可选清理。
|
||||||
|
- **待修项回流 todo**: **无** 🔴/🟡 项
|
||||||
|
|
||||||
### CR-260618-01 batch68 未提交改动批量(41文件+754/-2434:虚拟滚动删除+timestamp打戳+audit吞错修复+fetch_models合并+分组折叠+todo精简) — ✅ 已审(PASS·🟡1 WATCH)
|
### CR-260618-01 batch68 未提交改动批量(41文件+754/-2434:虚拟滚动删除+timestamp打戳+audit吞错修复+fetch_models合并+分组折叠+todo精简) — ✅ 已审(PASS·🟡1 WATCH)
|
||||||
|
|
||||||
- **复审结论(2026-06-18·主代独立 grep/read 核验 diff)**: ✅ **PASS** — 🔴0 🟡1 ⚪2
|
- **复审结论(2026-06-18·主代独立 grep/read 核验 diff)**: ✅ **PASS** — 🔴0 🟡1 ⚪2
|
||||||
@@ -1426,6 +1543,50 @@
|
|||||||
- **🟡 WATCH-1**: **workspace_root 去固定根「动态白名单完整语义」声明部分兑现** — `state.rs:362-365` 删除固定放行成立(diff 核验),但 `reload_allowed_dirs`(:577)仍无条件 `set.insert(workspace_root_path())`(pre-existing 非 e4c2e31 引入)。即用户从 Settings KV 删除工程根后,下次 reload 会强制把工程根塞回 persistent → `is_authorized` 经 persistent 命中照样放行 → 「用户删白名单后需授权」的预期无法兑现。本次只删了 is_authorized 的硬编码,未触及 reload 兜底注入。**非破坏性**(默认行为不变向后兼容),但 commit message「动态白名单完整语义」与实现存在落差。无对应「删除白名单后需授权」的对抗测印证(test_allowed_dirs_workspace_root_authorized:741 用 default_with_root 经 persistent 命中,不覆盖删除场景)。
|
- **🟡 WATCH-1**: **workspace_root 去固定根「动态白名单完整语义」声明部分兑现** — `state.rs:362-365` 删除固定放行成立(diff 核验),但 `reload_allowed_dirs`(:577)仍无条件 `set.insert(workspace_root_path())`(pre-existing 非 e4c2e31 引入)。即用户从 Settings KV 删除工程根后,下次 reload 会强制把工程根塞回 persistent → `is_authorized` 经 persistent 命中照样放行 → 「用户删白名单后需授权」的预期无法兑现。本次只删了 is_authorized 的硬编码,未触及 reload 兜底注入。**非破坏性**(默认行为不变向后兼容),但 commit message「动态白名单完整语义」与实现存在落差。无对应「删除白名单后需授权」的对抗测印证(test_allowed_dirs_workspace_root_authorized:741 用 default_with_root 经 persistent 命中,不覆盖删除场景)。
|
||||||
- **⚪ LOW-1**: DEBUG_RULES 5 关键词权重(0.6-0.7)为经验值,无量化校准依据(db 分析 591ff4a0 仅统计偏好率未回归权重调优)。后续可据真实意图误判数据校准。
|
- **⚪ LOW-1**: DEBUG_RULES 5 关键词权重(0.6-0.7)为经验值,无量化校准依据(db 分析 591ff4a0 仅统计偏好率未回归权重调优)。后续可据真实意图误判数据校准。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### CR-260620-03 MED-1 修复(path 绑定 Medium 工具审批后 reload 白名单)+ LOW 清理(死 i18n key + stale 注释)(working tree 未提交:chat.rs ai_approve/ai_authorize_dir 两处 reload + i18n×2 删 confirmNewConvWhileGenerating + TopBar.vue/Sidebar.vue 注释同步·commit-policy-deferred) — ✅ 已审(PASS·⚪1 WATCH)
|
||||||
|
|
||||||
|
- **结论(2026-06-20·审查 agent 独立 git diff + grep/read 核验源码形态 + vue-tsc EXIT 0)**: ✅ **PASS** — 🔴0 🟡0 ⚪1
|
||||||
|
- **验证**: `git diff HEAD` 5 文件逐行核验 + `npx vue-tsc --noEmit` EXIT 0 + 独立 grep 全 src/ 无残留。
|
||||||
|
|
||||||
|
**① MED-1 path 工具集完整性 PASS(无遗漏)**:
|
||||||
|
- `grep registry.register` tool_registry.rs 全 AI 工具清单核验:**写 `projects.path` 的 AI 工具恰为三个**:
|
||||||
|
- `update_project`(tool_registry.rs:481,Medium)— `repo.update_field(id, field, value)`(:495),白名单含 path 字段(is_allowed_column 放行)
|
||||||
|
- `create_project`(tool_registry.rs:501,Medium)— 可选 path 走 `bind_dir_to_project`(:525)
|
||||||
|
- `bind_directory`(tool_registry.rs:534,Medium)— 走 `bind_dir_to_project`(:544)
|
||||||
|
- `matches!(approval.tool_name.as_str(), "bind_directory" | "create_project" | "update_project")`(chat.rs:484/:649)三工具全覆盖 ✅
|
||||||
|
- 其他 Medium 工具(create_task/update_task/advance_task/create_idea/append_file/rename_file)**不写 projects.path**,无需 reload ✅
|
||||||
|
- `import_project`/`relocate_project_path`/`import_projects_batch` 是 `commands/project.rs` IPC(用户前端操作,**非 AI 工具,不经 ai_approve**),由各自 reload 路径覆盖(CR-260620-01 已审),**AI 工具集无遗漏** ✅
|
||||||
|
|
||||||
|
**② MED-1 reload 时机正确性 PASS(两处执行点核验)**:
|
||||||
|
- `ai_approve`(chat.rs:479-487):reload 在 `if exec_result.is_ok()` 内(成功才 reload,失败不污染白名单)+ `emit_data_changed` 后(顺序无依赖,emit 仅 app.emit 给前端)+ `matches!` 限定 path 工具 ✅
|
||||||
|
- `ai_authorize_dir`(chat.rs:644-652):同结构,正确 ✅(注:此处的 matches! 永不命中,见 ⑤)
|
||||||
|
- `state.reload_allowed_dirs()`(state.rs:586-640)副作用核验:读 Settings KV(:589)+ 读 `repo.list_all()` DB(:600)+ `std::fs::canonicalize` IO(:629)+ 写 `allowed_dirs.write()` RwLock 短临界区(:637-639)+ 保留 session 临时授权(:638-639 std::mem::take)。性能可接受(单用户桌面,项目数量级低,非热路径),无副作用风险 ✅
|
||||||
|
|
||||||
|
**③ MED-1 执行分支正确性 PASS(Medium 走 ai_approve 审批非内联)**:
|
||||||
|
- 三 path 工具 risk 注册仍是 `RiskLevel::Medium`(tool_registry.rs:483/506/536 独立 grep 印证)✅
|
||||||
|
- `ai_approve`(chat.rs:352 消费 pending + :379 拒绝 path_auth 挂起 + :463-467 执行)是 Medium/High 审批执行入口 ✅
|
||||||
|
- chat.rs:510 注释明确「create_idea 是 Medium risk → 经审批执行(本路径),非 process_tool_calls 内联执行」语义对齐 ✅
|
||||||
|
|
||||||
|
**④ MED-1 无回归 PASS(仅 path 工具 reload)**:
|
||||||
|
- `matches!` 精确限定三个工具名,其他 Medium 工具审批执行(create_idea/create_task/update_task/advance_task/append_file/rename_file)不触发 reload,行为不变 ✅
|
||||||
|
- reload 在 `exec_result.is_ok()` 内,失败路径不 reload(避免白名单错乱)✅
|
||||||
|
|
||||||
|
**⑤ LOW 死 key 确认 PASS(0 消费方)**:
|
||||||
|
- `grep confirmNewConvWhileGenerating` 全 src/ **No matches found** — 中英两 i18n 文件定义处已删,0 消费方 ✅
|
||||||
|
- vue-tsc --noEmit EXIT 0 印证无类型回退 ✅
|
||||||
|
|
||||||
|
**⑥ LOW 注释准确 PASS(3 处同步 F-09 决策e)**:
|
||||||
|
- TopBar.vue:163 `/** 新建会话(父 confirmNewConversation,F-09 决策e 直接新建无中断确认) */` — 准确对齐 CR-260620-02 F-09 决策e(去弹窗直接新建,旧 conv 后台 loop 继续不中断)✅
|
||||||
|
- ConversationSidebar.vue:226 `* - confirm-new-conversation:新建对话(F-09 决策e 直接新建,旧后台继续不中断),交父 confirmNewConversation` ✅
|
||||||
|
- ConversationSidebar.vue:242 `/** 新建对话(F-09 决策e 直接新建,旧后台继续不中断),父调 confirmNewConversation() */` ✅
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
- **⚪ WATCH-1(ai_authorize_dir 第二处 reload 永不命中)**: chat.rs:644-652(ai_authorize_dir 内)的 `matches!(approval.tool_name.as_str(), "bind_directory" | "create_project" | "update_project")` reload 是**冗余防御性代码,运行时永不触发**。核验:ai_authorize_dir 触发条件是 `process_tool_calls 预校验文件工具路径未命中白名单`(chat.rs:554 docstring + :579-581 强制 `approval.path_auth.is_some()`,否则报错「非路径授权挂起」),而 path_auth 挂起的 pending 只会是 read_file/write_file/list_directory 等**文件工具**(预校验路径未命中白名单触发),**绝不会是 bind_directory/create_project/update_project**(这三个是 Medium 风险审批,走 ai_approve 消费 `path_auth=None` 的 pending,chat.rs:379 防御性拒绝 path_auth 挂起进 ai_approve)。即 ai_authorize_dir 的 approval.tool_name 永远不会命中三个 path 工具 → :649 reload 死分支。**无害**(防御性兜底,若未来架构调整把 path 工具也走路径授权流程则自动生效),但当前为冗余。建议保留(对称防御,防未来重构遗漏)或加注释标注「当前 ai_authorize_dir 不消费 path 工具 pending,此 reload 为对称防御兜底」。不阻塞,降 WATCH。
|
||||||
|
- **待修项回流 todo**: **无** 🔴/🟡 项(⚪ WATCH-1 冗余防御,保留即可)
|
||||||
|
|
||||||
- **待修项回流 todo**: **无** 🔴/🟡 项(🟡 WATCH-1 已修:reload_allowed_dirs 删无条件 insert workspace_root,KV 有配尊重用户 persistent,兑现"用户删白名单后工程根需授权"动态白名单完整语义)
|
- **待修项回流 todo**: **无** 🔴/🟡 项(🟡 WATCH-1 已修:reload_allowed_dirs 删无条件 insert workspace_root,KV 有配尊重用户 persistent,兑现"用户删白名单后工程根需授权"动态白名单完整语义)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
Reference in New Issue
Block a user