优化: todo销账(F-05/T-06/B-05误判)+夜间6决策记录+CR-65审查PASS回填
This commit is contained in:
@@ -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<ContentPart> 原案 [2026-06-17]
|
||||
- **决策**:Phase 2 多模态原设计 `content: Vec<ContentPart>` 改为 **`content: String` 保留不变 + 新增 `parts: Option<Vec<ContentPart>>`**。老 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<Arc<dyn LlmProvider>>)` ③ 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<ContentPart>(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 代码审查 | 📐 待需求驱动 |
|
||||
|
||||
Reference in New Issue
Block a user