From 9d1e5c27373efb549831e17de24f9b18b48eb4c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BB=9D=E5=B0=98?= <237809796@qq.com> Date: Wed, 17 Jun 2026 04:02:04 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96:=20todo=E9=94=80=E8=B4=A6(F-?= =?UTF-8?q?05/T-06/B-05=E8=AF=AF=E5=88=A4)+=E5=A4=9C=E9=97=B46=E5=86=B3?= =?UTF-8?q?=E7=AD=96=E8=AE=B0=E5=BD=95+CR-65=E5=AE=A1=E6=9F=A5PASS?= =?UTF-8?q?=E5=9B=9E=E5=A1=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/02-架构设计/功能决策记录-2026-06-14.md | 43 ++++++++++++++++++++- docs/todo.md | 6 +-- docs/待审查.md | 36 +++++++++++------ 3 files changed, 69 insertions(+), 16 deletions(-) diff --git a/docs/02-架构设计/功能决策记录-2026-06-14.md b/docs/02-架构设计/功能决策记录-2026-06-14.md index e50edbe..a0fd6c4 100644 --- a/docs/02-架构设计/功能决策记录-2026-06-14.md +++ b/docs/02-架构设计/功能决策记录-2026-06-14.md @@ -112,6 +112,15 @@ - **原因**:交互欺骗——用户以为删除成功,DB 实际未动。需补 `ai_provider_delete` IPC(`lib.rs` 注册 + `ai.rs` 实现 + 前端真调),并加二次确认。 - **状态**:📐 待实施 → ✅ Sprint 10 已实现(`ai_delete_provider` 命令 + 删默认清 active + 自建 `confirmDialog` 二次确认) +### 多 Provider 负载均衡池:ProviderPool 归 app 层 + 模型亲和排序 + fallback 分类 [2026-06-17] +- **决策**:ProviderPool 实现(select/fallback/capacity)放在 `src-tauri/commands/ai/` 目录下(非 df-ai crate),与 `prompt.rs::get_active_provider` / `secret.rs::build_provider_for` 同属「消费 AiProviderRecord 的 app 层」。select 排序规则:模型亲和(含 model_id 的 provider 排前)> weight 降序 > is_default 兜底。fallback 策略:InitFailed(retryable)耗尽候选后切下一个 provider;Fatal(4xx 非 429)立即放弃整个 fallback 链。per-provider 并发 cap = global_cap(差异化 cap 留后续)。否决健康度路由(需持久化健康状态,过度工程;瞬态故障由 fallback 吸收)和纯轮询(加权是轮询超集)。 +- **原因/取舍**: + - **位置选 commands/ai/ 非 df-ai**:df-ai 定位是「协议适配 + Provider trait」(存储无关),引入 ProviderPool 会创建 df-ai→df-storage 依赖(读 AiProviderRecord),破坏存储无关边界。commands/ai/ 本就是 app 层装配点(get_active_provider / build_provider_for 都在此层),ProviderPool 放此处语义一致。 + - **模型亲和排第一**:F-01 智能路由已按任务需求选定模型+provider 组合,若 select 排序不含模型亲和可能换到不含该模型的 provider,导致路由结果失效。模型亲和保「路由选的模型一定在选中 provider 上可用」。 + - **否决健康度路由**:健康检查需持久化状态(上次成功时间/连续失败计数),桌面单用户场景 provider 数量少(通常 2-5 个),瞬态故障由 fallback 重试吸收即可。健康度增加的复杂度(定时探测/状态序列化/启动恢复)远大于收益。 + - **Fatal 立即放弃**:对齐 retry.rs 的 Fatal 分类(4xx 非 429 = 请求本身非法,重试无意义),避免无效重试浪费 quota 和延迟。 +- **状态**:✅ 已落地(commit 79b6a43 / b3684f4 / 80c0955) + ## 模型能力与路由(Model Capability & Auto-Routing) ### 能力声明:复用 `ai_providers.models` JSON 字段,不建新表 [2026-06-13] @@ -142,6 +151,14 @@ - **前端 TS/Vue**:`src/api/types.ts`(新增 ModelCapability / Modality / FunctionCapabilities / CostTier 类型)/ `src/api/ai.ts`(saveProvider 加 models 参数;新增 setChatModelOverride())/ `src/stores/ai.ts`(availableModels / activeModelOverride 状态 + setModelOverride action)/ `src/views/Settings.vue`(Provider 表单增加模型池编辑区) - **状态**:📐 待实施(Phase 1 全量清单) +### 多模态消息:content:String 保留 + parts 新增,务实偏离 Vec 原案 [2026-06-17] +- **决策**:Phase 2 多模态原设计 `content: Vec` 改为 **`content: String` 保留不变 + 新增 `parts: Option>`**。老 JSON 反序列化时 parts=None 零回归。后续若严格对齐 content:Vec 需先解禁 4 个 forbidden 文件的 content 消费点(改用 content_text() / flattened_parts() 辅助方法)。 +- **原因/取舍**: + - **4 个 forbidden 文件直接消费 m.content:String**——`audit.rs`(审计日志读 content)/ `title.rs:50`(结构体字面量 ChatMessage{ content: ..., role: ... })/ `commands.rs`(IPC 层序列化)/ `knowledge_inject.rs`(知识注入读取)。其中 title.rs 是结构体字面量构造,Rust 不支持字段默认值,加任何 required 字段必炸编译。 + - **不改 content 为 Vec 的代价可控**——parts 携带多模态数据,content 保留纯文本降级路径。前端/LLM 层按 parts 是否 Some 判断是否多模态,老路径不受影响。 + - **向后兼容**——serde `#[serde(default)]` 让缺失 parts 字段的老 JSON 反序列化为 None,零回归风险。 +- **状态**:✅ 已落地(commit e3cd448) + ## 灵感模块(评估闭环) ### 启发式评分维度 @@ -342,6 +359,14 @@ > 注:UI 布局 localStorage + 模块级恢复的细节见 [归档文档](./功能决策记录-归档-2026-06-14.md)。 +### 虚拟滚动:自研 IntersectionObserver + sentinel,不引 vue-virtual-scroller [2026-06-17] +- **决策**:AI Chat 消息列表虚拟滚动选**自研方案**——IntersectionObserver + sentinel div(上下各一)+ ResizeObserver 监听容器变化,仅做渲染层裁剪(不可见窗口卸载内容,sentinel 占位保持 scrollHeight 不塌)。不选 vue-virtual-scroller 的 DynamicScroller。流式末条 pinnedKeys 保活不卸载。 +- **原因/取舍**: + - **vue-virtual-scroller 破坏既有布局**——DynamicScroller 接管滚动容器内部 DOM 结构 + 重排子节点顺序,与现有 `.ai-messages` 的 `display: flex; flex-direction: column; gap: 14px` 布局冲突。同时破坏三处既有行为:① onMessagesScroll 的 wasNearBottom 边沿收起逻辑(依赖原生 scroll 事件和 scrollTop 计算);② isNearBottom / scrollToBottom / 「回到底部」按钮计算(依赖真实 scrollHeight);③ 流式生成时自动滚到底部(DynamicScroller 的滚动语义不同)。 + - **自研只做渲染裁剪**——不接管滚动容器、不改 DOM 结构、不动事件监听。sentinel div 用 `height: Npx` 占位保持 scrollHeight 准确,IO 触发时批量 mount/unmount 可见区间外消息组件。既有滚动/收起/流式/回到底部全零感知。 + - **pinnedKeys 保活**——流式生成中末条消息频繁更新,若被 IO 卸载再挂载会导致闪烁;pinnedKeys 集合内的消息跳过卸载判断。 +- **状态**:✅ 已落地(commit e38474b) + ## 应用启动 / 数据库配置 ### Dev 与 Build 拆分独立数据库 [2026-06-13] @@ -436,6 +461,14 @@ - **退路**:若不愿加新 crate,trait 可放 `df-core`(语义稍糙——LLM 非领域类型,但零新 crate,可接受)。 - **状态**:📐 设计定稿(2026-06-14,4 项决策已定:① 拆分边界=仅 trait+数据结构 ② provider 注入=构造注入 `Engine::new(Option>)` ③ LLM 失败=自动降级启发式+warn+`EvaluatedBy` 标记 ④ provider 构造=应用层 src-tauri 装配注入),未实施。详见 [F-07-df-ai-core-trait下沉设计-2026-06-14.md](./F-07-df-ai-core-trait下沉设计-2026-06-14.md)。 +### df-core → df-types 改名:类型库非核心,语义明示「类型契约层」[2026-06-17] +- **决策**:crate `df-core` 改名为 `df-types`。全 workspace 机械改名(54 处源码引用 + git mv + 9 个 Cargo.toml 依赖声明 + Cargo.lock 自动迁移)。 +- **原因/取舍**: + - **「core」名称误导**——df-core 含 0 业务逻辑、0 内部依赖、0 宏自引用,纯粹是跨 crate 共享的类型定义(ProjectRecord / TaskRecord / IdeaRecord / KnowledgeRecord / ChatMessage 等)+ re-export 聚合。叫「core」暗示它是核心业务层,实际是「类型契约层」。 + - **改名收益 > 成本**——54 处机械替换(纯字符串,无语义改动),git mv 保历史,Cargo.lock 跟随 Cargo.toml 自动更新。一次性 10 分钟操作,消除后续所有新贡献者的认知摩擦。 + - **不影响 df-ai-core**——df-ai-core 是 AI trait 层(LlmProvider / CompletionRequest),df-types 是领域模型层(ProjectRecord 等存储实体),两者职责清晰不重叠。 +- **状态**:✅ 已落地(commit 4be1591) + ## 工作流人工审批节点(B-03) ### HumanNode 审批响应机制:subscribe→send→select! 广播过滤等待 [2026-06-14 📐] @@ -473,6 +506,14 @@ - **原因/取舍**:工作流成功与任务推进是两个独立事实,解耦避免「工作流成功但任务推进失败时回滚已完成工作流」的复杂性;失败退回让审查拒绝能回流上一态(review_rounds+1)。CAS 已防回调与手动 advance 并发撞(advance_task_atomic 捕获 InvalidState 降级)。跨表事务缺失(阶段2 回调失败降级,阶段3/4 补 Database.transaction())。 - **状态**:📐 设计定稿待实施(batch32 做 ②-5 HumanNode reject 语义化,batch33 做 ②-3/②-4 回调)。 +### AiNode 自审闸门:内部 return Err 复用 executor first_err(方案 A)[2026-06-17] +- **决策**:AiNode 自审闸门选**方案 A(AiNode 内部 return Err)**——自检失败时 return Err(SelfReviewFailed) 复用 DagExecutor 已有的 first_err 收敛 + ②-4 回调错误路径。不选方案 B(DAG edges 条件 + ConditionEngine,依赖暂缓的 T-260614-11)和方案 C(DagExecutor 核心循环改闸门钩子)。gate 配置默认 false 向后兼容(阶段2 行为不变),testing 模板可设 gate:true 启用。 +- **原因/取舍**: + - **方案 A 零 executor 核心改动**——Err 沿既有 execute() → run_node() → first_err 路径自然冒泡,executor 核心循环零行变更。②-4 回调已处理 failed 分支(退回上一态),闸门失败自动走此路径无需额外代码。 + - **方案 B 依赖 ConditionEngine**——T-260614-11 条件表达式引擎尚在 📐 设计阶段,为单个闸门功能拉入未完成的依赖链路风险高。 + - **方案 C 改 executor 核心循环**——在 node 执行前后插钩子(before/after execute)是通用扩展点,但当前仅 AiNode 一个消费者,为单一场景改核心循环过度工程;且钩子语义(是否中断后续节点、是否影响 DAG 继续执行)需详细设计,复杂度远超方案 A 的 1 行 return Err。 +- **状态**:✅ 已落地(commit e16d038) + ## 需求与待办 > 汇集散落于各决策条目状态(📐/🚧)的待办 + 新增需求细节 + 需求澄清。单一清单,避免遗漏。 @@ -501,7 +542,7 @@ | 模型能力声明与自动路由系统 Phase 1:ModelCapability 数据模型 + ModelRouter 重写 + 7 调用点接入 + Settings 模型池编辑 UI + AiChat 模型下拉。核心:按任务需求(模态/功能/成本)自动匹配合适模型,不再所有场景共用 default_model | 模型能力与路由 | 2026-06-13 📐 设计完成 | P1 | | 模型能力系统 Phase 2:多模态消息支持——ChatMessage.content: String → Vec(Text/Image);前端粘贴/拖拽图片;vision 模型自动路由 | 模型能力与路由 | 2026-06-13 📐 | P2 | | 模型能力系统 Phase 3:Agent 内智能路由——Agentic Loop 每轮按子任务构造不同 TaskRequirements;成本预算控制;模型级联降级;跨 Provider 搜索 | 模型能力与路由 | 2026-06-13 📐 | P3 | -| 📋 待澄清「显示多开」:用户报"设置里勾选'显示多开'但 AiChat 未显示"。全 src grep `多开\|多窗口\|multi\|multiInstance` 零命中;Settings.vue `settings` 对象仅 8 字段无此项。AiChat 唯一相关的是常驻「分离窗口」按钮(不受设置控制)。疑似:① 用户指分离窗口按钮(本就常驻不需设置);② 看的是打包旧版本界面;③ 想新增"允许分离窗口"设置开关。待用户截图/确认位置再定 | AI Chat | 2026-06-13 需求澄清 | — | +| 📋 已澄清「显示多开」= 多会话来回切可对话(非 AI Chat 窗口多开)[2026-06-17]:用户原意是「多个会话之间切换都可继续对话」(当前切换会话后旧会话生成态丢失)。关联 F-09 多会话架构决策——A 路线(单例 AiSession + 软隔离,Sprint 8 已落地切对话不中断路由)待实测验证是否满足需求(T-260614-02);B 路线(真多会话,AiSession 单例→多实例)是备选但触及 memory 记录的「AiSession 单例未动」架构约束。→ 2026-06-17 澄清为「多会话并发」需求,推荐先实测 A 路线再定是否需 B 路线。已记 todo L652 + 待决策.md(🟡 A/B 路线决策) | AI Chat / 多会话 | 2026-06-13 待澄清 → 2026-06-17 已澄清 | 🟡 待 A/B 路线决策 | | 🔴 待审批持久化根治(重启恢复)未生效——两处逻辑断裂致恢复链路跑不通:① `ai_conversation_switch` 无条件 `pending_approvals.clear()` 清空 `restore_pending_approvals`(init)重建的内存 HashMap,而 `ai_pending_tool_calls`/`ai_approve` 均依赖内存态 → 重启后前端 `switchConversation` 触发 clear → 审批卡片查空永不显示、审批报"未找到挂起的审批";② `ai_approve` 的 recovered 守卫跳过 `save_conversation`(注释称"防空 messages 污染老对话")前提不成立——switch 时 `restore_from_messages` 已载完整历史,审批时 messages 非空 → 执行的工具结果不落库,重启后 toolCard 显示 completed 但 result 仍是占位"需要用户审批,等待确认"。修复方向:pending 恢复链路改查 DB(`ai_tool_executions` WHERE status='pending' 持久化真相源)绕过内存 clear;`ai_approve` 内存 miss 时 fallback DB 单条重建再执行;recovered 审批通过后正常 save。可顺带删 `restore_pending_approvals`(DB 即真相源)。阻断用户"功能逻辑层面解决"诉求——现"根治"实为表面修复 | AI Chat 审批持久化 | 2026-06-13 /review 审查①② | P0 | | 📋 审批可见性缺口:pendingApprovals 无兜底渲染→卡死 [2026-06-13]:AI 发起 Med/High 工具审批(AiApprovalRequired)后暂停等审批不发 delta;前端审批唯一出口是 ToolCard 的 pending_approval 内联卡片(靠 findToolCall 置 tc.status),但 state.pendingApprovals 数组有数据却零渲染(AiChat.vue 仅 @approve 转发,无 pendingApprovals 模板)。若 tc 卡片未显示审批,用户看不到审批按钮 → AI 永久等 → 文字停卡死。待修:A. AiChat.vue 加 pendingApprovals 醒目渲染(顶部条/浮层)兜底审批可见性;或 B. 运行时确认 tc 卡片是否渲染。配套:watchdog 在 AiApprovalRequired 暂停,审批没弹则 watchdog 盲点,需加"审批超时未响应"提示 | AI Chat 审批 | 2026-06-13 | 📋 A/B 待定 | | 📋 node_executions 全表 list:当前只写不读,若未来前端要看某次工作流执行的节点明细,需**新增** `list_node_executions(execution_id)` 命令 | 工作流引擎 | 2026-06-13 代码审查 | 📐 待需求驱动 | diff --git a/docs/todo.md b/docs/todo.md index 2c35842..81ef53e 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -121,7 +121,7 @@ - [x] ✅(小修批·2026-06-17·workflow wdqxhw4x1+主代修回填/文案) B-260617-02 [P2] — **drainQueue 单条失败致队列卡死 + 错误吞没**。drainQueue(:247-251) `void sendMessage(...)` fire-and-forget;doSend IPC 失败 throw(:113) 被 void 忽略 → 无 AiCompleted 触发下次 drain → 剩余队列**永久卡住** + 用户无错误反馈。**修法**:drainQueue catch 失败,emit 错误提示 + 决定续发下一条或终止。— src/composables/ai/useAiSend.ts:247-251 - [x] ✅(小修批·2026-06-17·wdqxhw4x1) B-260617-03 [P3] — **lang 解析三处重复(DRY)**。doSend(:94-97) / regenerate(:151-154) / editMessage(:224-227) 各一份相同 `df-ai-language`→auto 回落 `df-language` 解析。**修法**:提取 `resolveLang(): string` 辅助函数,三处复用。— src/composables/ai/useAiSend.ts - [x] ✅(小修批·2026-06-17·wdqxhw4x1) B-260617-04 [P2] — **tryForceSend 失败后消息丢失无提示**。tryForceSend(:301-318) force_send 失败 catch 返 false(:316),但队首已 shift(:307) → 消息**丢失**,调用方是否提示用户未核验。**修法**:失败回填输入框或 toast(对齐 doSend 失败回填:108-113 模式)。— src/composables/ai/useAiSend.ts:301-318 -- [ ] B-260617-05 [P2/已核验·确认未清·留后续批] — **modelOverride 切换对话清逻辑缺失**。**主代+agent A 双独立核验(2026-06-17)**:useAiConversations.ts switchConversation/newConversation **零 modelOverride 清逻辑**(useAiSend.ts:67 模块级私有 ref,useAiConversations 无 import/写入)→ 切对话残留 → 新对话误用旧模型确认。修法涉依赖方向(提 store 或 useAiConversations import useAiSend,后者加循环依赖风险对齐 CR-260615-06b),非小修,留后续批。注释(:50-51)明确「切换对话前端应同步清 modelOverride.value=null」,但本文件无清逻辑(切换在 useAiConversations.ts)。若未清,切对话后 modelOverride 残留 → 新对话误用旧模型。**待核验** useAiConversations.ts switchConversation/newConversation 是否清;未清则补。— src/composables/ai/useAiSend.ts:53 + src/composables/ai/useAiConversations.ts +- [x] ✅(误判销账·2026-06-17·主代独立核验推翻 agent A) B-260617-05 [P2] — **modelOverride 切换对话清逻辑缺失(误判)**。agent A 只看 useAiConversations.ts 漏 AiChat.vue watch 兜底。**主代核验**:AiChat.vue:1236-1239 watch activeConversationId 变 → store.modelOverride.value = null 响应式清;newConversation(useAiConversations:33)+ switchConversation(:60)都改 activeConversationId → watch 触发清 store.modelOverride(= useAiSend 模块 modelOverride,composable return 共享);切对话入口全在 AiChat.vue(L46/89/155/1163/2600/2607)→ watch 全覆盖。F-01 阶段6 已实施,非 bug。注释(:50-51)明确「切换对话前端应同步清 modelOverride.value=null」,但本文件无清逻辑(切换在 useAiConversations.ts)。若未清,切对话后 modelOverride 残留 → 新对话误用旧模型。**待核验** useAiConversations.ts switchConversation/newConversation 是否清;未清则补。— src/composables/ai/useAiSend.ts:53 + src/composables/ai/useAiConversations.ts > ⚪ 可选优化(未单列 todo):① cancelQueued(:372)/editQueued(:390)/sendQueuedNow(:407) 越界检查不一致——按「确定性>防御」风格 UI 调用方必合法,三处可统一(都删或都留)② doSend/regenerate/editMessage 的 streaming+watchdog+占位前置模式重复,可提取 `beginStream()`,但回滚差异大收益中等 ③ `_pendingApprovalIds`(:62) 无超时清理——approveToolCall 卡在 await 永不返回时 id 永驻 Set(边缘态,watchdog 不清此 Set)。 @@ -534,7 +534,7 @@ ### P1 — Sprint 19 遗留 - [x] T-260614-05 — ~~工具结果入库前截断 50KB~~ ✅ mission:T-260614-04 已修复(conversation.rs 加 `truncate_for_persist` 纯函数,50KB 阈值 + 头尾各 20KB + 中段标注省略字符数;仅作用于持久化视图不污染内存真相源;3 单测 pass)(06-14) -- [ ] T-260614-06 — **[P2→中等风险]** Settings.vue 拆 panel 子组件 — 当前 1042 行 god file,4 大功能域(AI 模型/Provider 表单/连接管理/通用设置)清晰可拆到 `src/components/settings/`。**评估:非低风险**,纯重构零功能价值,要新建 4 子组件 + props/emits 接线 + CSS 拆分,单独立项做更稳 — source:Sprint 19 待评估 (06-14) +- [x] ✅(波23·2026-06-17·9c60c8f,拆 4 子组件 Provider/Connection/General/Knowledge 1042 行瘦身零行为变化) T-260614-06 — **[P2→中等风险]** Settings.vue 拆 panel 子组件 — 当前 1042 行 god file,4 大功能域(AI 模型/Provider 表单/连接管理/通用设置)清晰可拆到 `src/components/settings/`。**评估:非低风险**,纯重构零功能价值,要新建 4 子组件 + props/emits 接线 + CSS 拆分,单独立项做更稳 — source:Sprint 19 待评估 (06-14) - [x] T-260614-07 — ~~诊断日志清理~~ ✅ mission:T-260614-06 已清理(useAiEvents/useAiSend 3 处调试 console.log 直接删;AiChat.vue/main.ts 3 处启动计时改 console.debug 保留诊断能力但不污染 console;vue-tsc 0 err,src/ console.log 0 残留)(06-14) ### 🔴 generating 状态机加固(2026-06-16 审查) @@ -697,7 +697,7 @@ - [x] ✅(batch62·2026-06-16·workflow wy6rjn0en+主代核查,cargo test 19 passed) F-260614-03 — 灵感对抗评估接 LLM — F-07 trait 下沉后注入方式统一(原 A/B/C 选型作废)。填充 `evaluate_with_llm`(adversarial.rs:143):build_adversarial_prompt 三角色辩论(正方/反方/分析师)+provider.complete(temperature 0.4/max_tokens 2048/model 留空回填 default)+parse_llm_eval serde_json 解析+clamp(final_score[0,10]/confidence[0,1])+枚举非法 bail 触发 HeuristicFallback 降级。4 LLM mock 单测(a8 parse success/a9 bad JSON fallback/a10 bad enum fallback/a11 value clamp)。原 7 heuristic+scoring 8 不回归。— crates/df-ideas/src/adversarial.rs - [x] T-260614-04 — ~~路径校验根治~~ ✅ 已完成(resolve_workspace_path 加 canonicalize 防 symlink 逃逸 + 词法 starts_with 兜底;仅校验、返回词法路径保持前端友好;cargo check 0 err / 22 test pass)(06-14) - [x] ✅(后端三段·79b6a43数据层+池选择器+CR-52白项 / b3684f4池enabled/weight IPC+set_caps启动接入 / 80c0955 stream_one_provider+候选链fallback+resolved_model重算;前端api/ai.ts:142 IPC封装已接) F-260614-04 — 多 Provider 负载均衡池 — 备用模型/多账号聚合,全局容量=min(各 provider 上限之和, global_cap) (06-14)。**注**:UI 配置面板(provider 池 enabled/weight 可视化设置)是否完成待核验——后端 IPC+前端 API 层已就绪 -- [ ] F-260614-05 — 模型能力系统 Phase 2 — 多模态消息支持:ChatMessage.content: String → Vec(Text/Image);前端粘贴/拖拽图片;vision 模型自动路由 (06-14) +- [x] ✅(波20-22·2026-06-17·e3cd448/e1d396d/4317d02,F-05 全链闭环·务实偏离 content:String+parts 非 Vec) F-260614-05 — 模型能力系统 Phase 2 — 多模态消息支持:ChatMessage.content: String → Vec(Text/Image);前端粘贴/拖拽图片;vision 模型自动路由 (06-14) - [x] ✅(batch61·2026-06-16·workflow wwtn2knn6+主代核查,cargo 0err+vue-tsc 0err) F-260614-06 — 导入历史项目(scan 第二步) — 6 决策全落地:①description 走 LLM(scan.rs extract_description 复用 complete)②采样扩 `images:Vec`(scan.rs:330/343)+readme 剥 frontmatter/TOC/纯徽章行截 8KB(SAMPLE_README_MAX 2000→8000:346)+徽章域黑名单 5 域(shields.io/badge.fury/badgen 等:397-401)+is_badge_image+is_pure_badge_line③image 多模态留接口待 F-260614-05④monorepo 一层(is_monorepo:112+discover_projects:156 展开 packages/*/apps/* detect_stack 空过滤)⑤批量(scan_directory_for_projects:343 纯规则发现标已绑定 + import_projects_batch:415 并发 LLM llm_concurrency permit 限流非原子逐项独立)⑥抽 create_with_binding(:59 create/import 共用 缓解 :211 TODO)。前端 Projects.vue 导入 modal+api/project.ts 两 API+i18n 双语+scan.rs 单测全。— crates/df-project/scan.rs + src-tauri/commands/project.rs + lib.rs + api/project.ts + Projects.vue + i18n。详见功能决策记录(06-14) - [x] T-260614-09 — ~~idea.rs 物理删不级联~~ ✅ WF-E 完成(idea.rs:149 delete→purge_with_descendants,1 行,签名兼容)(06-14, commit 89da9fa) - [x] T-260614-10 — ~~findBinding canonicalize~~ ✅ WF-E 判定已解决(normalize_path 已含 canonicalize 优先 + 词法回退,find_binding_conflict 两端对称已用,无需重复加;零改动)(06-14, commit 89da9fa) diff --git a/docs/待审查.md b/docs/待审查.md index d17b195..5bcc3a6 100644 --- a/docs/待审查.md +++ b/docs/待审查.md @@ -128,21 +128,33 @@ - **⑧发送失败回填 PASS**:handleSend:1869-1871 `if(parts) pendingImages.value=snapshotImgs` 回填正确,用户可重试。 - **待修项回流 todo**: **无**(8 维度全 PASS,零问题。Phase 2c IPC 接入即全链闭环)。 -### CR-260616-65 波22 F-05多模态Phase2c闭环(IPC接parts+api透传+doSend传参)(4317d02) — 🟡 待审 +### CR-260616-65 波22 F-05多模态Phase2c闭环(IPC接parts+api透传+doSend传参)(4317d02) — ✅ 已审(PASS) -- **范围**: workflow wg928m4vb + 主代补。commands.rs ai_chat_send+ai_chat_force_send 加 parts:Option> + session.messages.push(user_parts) 取代 user()(空/None 向后兼容)+ api/ai.ts sendMessage/forceSend parts 透传。**主代补 useAiSend doSend**(波22 拆波 gap,禁止碰 useAiSend):doSend L113/115 aiApi.forceSend/sendMessage 加 parts 传参(确定性一行级,Phase 2c 全链生效)。 -- **维度**: ①IPC parts 参数 serde ②user_parts push 空兼容 ③api parts 透传 ④doSend 传参(主代补 gap)⑤FR-S1(Image base64 非敏感,不入 NodeContext/IPC 返回/日志)⑥cargo 0+vue-tsc 0。 -- **commit**: 4317d02。**主代核查**: cargo check EXIT 0 + vue-tsc EXIT 0。 -- **审查 agent 待复审重点**: ①IPC parts serde 反序列化 ②user_parts 空兼容(向后兼容 user())③api parts 透传(parts&&length>0?parts:null)④doSend 传参(主代补)⑤FR-S1 Image base64 安全。 -- **待修项回流 todo**: **无**(F-05 多模态全链闭环:2a+2b+2c+doSend)。 +- **复审结论(2026-06-17·审查 agent 亲跑 cargo+vue-tsc+独立 git show/grep/read 核验 2 轮)**: ✅ **PASS** — 🔴0 🟡0 ⚪0 + - **验证**: `cargo check --workspace` **EXIT 0**(6 pre-existing dead_code warnings 无关本 CR)/ `npx vue-tsc --noEmit` **EXIT 0**。 + - **①IPC serde 反序列化 PASS**:commands.rs:8 import `ContentPart`+ :149 `parts: Option>` 参数 + :147-148 注释阐明 Tauri 2 自动 serde 反序列化(df-ai-core provider.rs:49 `#[serde(tag = "type", rename_all = "snake_case")]` 与前端 types.ts:288-298 契约严格对齐)。 + - **②user_parts 空兼容 PASS**:commands.rs:182-186 `if let Some(ps) = parts.as_ref().filter(|p| !p.is_empty())` 双重守卫(非 None 且非空)→ user_parts() + else 分支 user() 零回归。provider.rs:139-141 user_parts 实现(parts 挂 ChatMessage.parts 字段,provider.rs:144-145 has_image 判定走 vision 端点)。 + - **③api parts 透传 PASS**:ai.ts:13+24 两函数加 parts 参数 + :19/:30 `parts && parts.length > 0 ? parts : null` 空过滤(与 commands.rs:182 守卫对称)+ :5 import `ContentPart` 类型。invoke 参数对象正确传。 + - **④doSend 传参 PASS**:useAiSend.ts:113+125 force/普通双分支加 parts 传参(`aiApi.forceSend/sendMessage(..., parts)`) + :93 函数签名接 parts + :100 本地 state 挂 `parts && parts.length > 0 ? parts : undefined`。Phase 2c 全链生效(CR-64 前端已挂 parts→本 CR IPC 接入→2a 后端处理闭环)。 + - **⑤FR-S1 Image base64 安全 PASS**:commands.rs:146-148 注释阐明「ContentPart Image base64 是图片数据非 api_key,不入敏感面」。核验:grep `NodeContext.config`/`NodeOutput`/`tool_call` schema 三点零 parts 字段(conversation.rs:153 truncate_parts 仅落 session.messages+provider 转发,audit/title/tool_calls 不读 parts)。base64 仅经 ai_chat_send→session.messages→provider HTTP 出站,不进 IPC 返回/日志/错误/工作流节点上下文。 + - **⑥forceSend 透传 PASS**:commands.rs:740 ai_chat_force_send 加 parts 参数 + :763 透传给 ai_chat_send(`ai_chat_send(..., parts).await`)。注释 :739 澄明「强制发送同款多模态支持」。 + - **⑦truncate_parts 防撑爆 PASS**:conversation.rs:87-127 truncate_parts_for_persist(Image base64 替换占位+Text 头尾截断)+ :153 落库前调用(git show 4317d02 前已有此 Phase 2a 代码,本 CR 零改动但依赖存在)。DB 撑爆防护已就绪。 + - **⑧类型契约对齐 PASS**:types.ts:288-298 ContentPart 联合类型(`type:'text'|'image'` discriminator)与 df-ai-core provider.rs:49-65 serde tag 严格对齐(snake_case ↔ 字面量 'text'/'image')。前端 4 处 `parts&&parts.length>0` 守卫(ai.ts:19/:30/useAiSend.ts:100/:331/useAiConversations.ts:90)保证空/undefined 不传。 + - **待修项回流 todo**: **无**(8 维度全 PASS,零问题。F-05 多模态全链闭环:2a 后端 ContentPart+parts+truncate+ 2b 前端粘贴/拖拽/渲染+ 2c IPC 透传+ doSend 传参,完整落地)。 + - **第 2 轮独立核验补充(2026-06-17·git show 4317d02 快照态)**: ①三文件 diff 全吻合登记声明。②行号微偏(useAiSend.ts 签名实为 :80 非 :93/state 挂 parts 实为 :87 非 :100/forceSend 实为 :114 非 :113/sendMessage 实为 :116 非 :125),代码逻辑无误,仅引用行号需校正。③FR-S1 深度核验追加:ContentPart/base64 grep 仅命中 df-ai + df-ai-core 两 crate,**df-nodes/df-workflow 零命中**(NodeContext.config 是 serde_json::Value 节点配置非消息,无 parts 字段);`all_messages_clone()` 两 IPC 调用点(commands.rs:84/672)均仅读 `.content` 做知识检索,不读 `.parts`;**持久化路径 conversation.rs:153 `save_conversation` 对每条 msg 调 truncate_parts_for_persist(Image base64→占位 Text)→ DB 落盘的 parts 已是占位**;**commands.rs:1444 ai_conversation_export "json" 格式 `serde_json::to_string_pretty(&messages)` 读的是 DB 持久化副本(record.messages),非内存全量**,故导出 JSON 不含原始 base64。FR-S1 全链零泄漏(不进 IPC 返回/日志/NodeContext/tool_call/导出)。④cargo check EXIT 0(6 pre-existing dead_code 全无关)/ vue-tsc EXIT 0,亲跑复现。 -### CR-260616-67 波24 B-03b-R10③工作流事件补execution_id治本并发串扰(bac3c76) — 🟡 待审 +### CR-260616-67 波24 B-03b-R10③工作流事件补execution_id治本并发串扰(bac3c76) — ✅ 已审(PASS) -- **范围**: workflow wznh9b2yz。df-types/events.rs WorkflowCompleted/Failed 补 execution_id:String(#[serde(default)] 向后兼容,老事件空串)+ 3 emit 点填(executor.rs:185 self.execution_id + workflow.rs Lagged 合成三分支 forward_exec_id + 补发 exec_id)+ 消费点 workflow.rs matches! 加 exec_id 守卫(只 break 自己 forward_exec_id,他人终态继续循环)。 -- **维度**: ①字段补齐(Completed/Failed 向其他变体对齐)②#[serde(default)] 向后兼容 ③3 emit 点全覆盖 ④消费 exec_id 守卫(只 break 自己)⑤空串匹配语义(只漏不误)⑥前端契约零破坏 ⑦cargo 0+test 全绿(df-types 7/df-workflow 23/devflow 92)。 -- **commit**: bac3c76。**主代核查**: cargo check EXIT 0 + grep execution_id 字段(#[serde(default)])。 -- **审查 agent 待复审重点**: ①字段 #[serde(default)] 向后兼容 ②3 emit 点 execution_id 来源 ③消费守卫 exec_id 匹配(只 break 自己)④空串匹配语义 ⑤前端契约零破坏。 -- **待修项回流 todo**: **无**(跨 crate 治本完整,emit 3 处全补,消费守卫落地)。 +- **复审结论(2026-06-17·审查 agent 亲跑 cargo+test+独立 grep/read 核验)**: ✅ **PASS** — 🔴0 🟡0 ⚪0 + - **验证**: `cargo check --workspace` **EXIT 0**(6 warning 全 pre-existing dead_code)/ `cargo test -p df-types` **7 passed** / `cargo test -p df-workflow` **23 passed** / `cargo test -p devflow --lib` **92 passed**。 + - **①字段 #[serde(default)] 向后兼容 PASS**:events.rs:83-84 WorkflowCompleted `#[serde(default)] execution_id: String` + :91-92 WorkflowFailed 同字段。老事件/老 DB 反序列化无 execution_id 时填空串不炸,serde(default) 自动处理。 + - **②3 emit 点全覆盖 PASS**:executor.rs:185-186 WorkflowCompleted 发 `self.execution_id.clone()`(DagExecutor 字段:20) + workflow.rs:208-210 Lagged 补发 completed 发 `forward_exec_id.clone()` + :212-217 Lagged 补发 failed 发 `forward_exec_id.clone()` + :218-222 Lagged 补发 cancelled 发 `forward_exec_id.clone()` + :364-365 执行失败补发 WorkflowFailed 发 `exec_id.clone()`。**5 emit 点全覆盖**(executor 1 + Lagged 3 + 补发 1),全部携带正确 execution_id。 + - **③消费 exec_id 守卫只 break 自己 PASS**:workflow.rs:167-172 matches! 双守卫(`变体 Completed/Failed` + `if execution_id == &forward_exec_id`)。他人终态事件(execution_id != forward_exec_id)finished=false 继续循环,不误 break。源码佐证: :171 条件守卫 + :180 `if finished { break }`。 + - **④空串匹配语义只漏不误 PASS**:#[serde(default)] 老事件空串 + :171 条件 `execution_id == &forward_exec_id`,自家 forward_exec_id 永远非空(run_workflow_inner:73 生成 UUID),空串匹配永不触发 finished。语义正确:只可能漏(自家老事件空串),不会误 break(他人终态非空但不等)。 + - **⑤前端契约零破坏 PASS**:types.ts:140 WorkflowEventPayload.execution_id 已有字段 + workflow.ts:64-67 终态清空逻辑只看 `payload.event?.type` 不变。execution_id 在事件 payload 内,类型不变,前端零改动。 + - **⑥字段补齐对齐其他变体 PASS**:grep 核验 HumanApprovalRequest/Response(:98/:109)已有 execution_id 字段,Completed/Failed 现在对齐,契约统一。 + - **⑦注释完整 PASS**:events.rs:77-81 + 89 注释阐明 B-03b-R10 ③ 根因 + 解决方案 + serde(default) 理由。workflow.rs:159-166 注释阐明并发串扰 + 双重匹配 + 空串向后兼容语义。executor.rs:182-183 注释阐明 emit 携 execution_id 理由。 + - **待修项回流 todo**: **无**(7 维度全 PASS,零问题。跨 crate 治本完整,并发串扰根除)。 ### CR-260616-36 batch60 F-09A 隔离补清 + F-13 性能优化 + AE-03 write_file diff 预览(路径B) — ✅ 已审(PASS)