diff --git a/.gitignore b/.gitignore index 2754a8c..7ba8d9a 100644 --- a/.gitignore +++ b/.gitignore @@ -51,6 +51,7 @@ coverage/ # 损坏备份(FR-S7 write_file 事故残留)+临时文件 *.broken.bak tmp/ +.temp/ # AI 编排脚本(Claude Code Workflow 临时产物,非产品代码) workflows/ diff --git a/PROGRESS.md b/PROGRESS.md index a8f9446..71b60a4 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -1,6 +1,6 @@ # DevFlow — 项目进展与工作交接 -> 创建: 2026-06-10 | 最后更新: 2026-06-15 | 当前阶段: 全量待办编排完成(Sprint 23) → 首批 A+B 清债(ARC-02 死链已删 / AR-1 退役 / ARC-03 降级重评);api_key 明文存储(FR-S1)降级延后 +> 创建: 2026-06-10 | 最后更新: 2026-06-18 | 当前阶段: PROGRESS Sprint 日志截至 Sprint 24(2026-06-15 · P0 generating 状态机加固 + 独立项批量);2026-06-16 起多会话进展(B-03 路由解耦 / 工具结果渲染 / 吞错降级 / 模型能力维度治理等)见 [docs/todo.md](docs/todo.md) + [docs/待审查.md](docs/待审查.md) --- @@ -12,7 +12,7 @@ | 技术栈 | Tauri v2 + Vue 3 + TypeScript + Pinia / Rust Workspace (8 crate) / SQLite | | 路径 | `E:/wk-lab/devflow/` | | 架构文档 | `ARCHITECTURE.md` (22,745 字) | -| Git 状态 | 未首次 commit,代码全在 untracked | +| Git 状态 | 已纳入版本控制(完整 commit 历史,当前分支 feat/batch-260615-workflow-unlock) | | AI 能力 | df-ai OpenAI/Anthropic 双协议 Provider + 12 工具 + Agentic Loop | --- @@ -775,6 +775,242 @@ **遗留 / 下一步**: 等勘察结果派下一波实施;AR-11 emit 点已查明(audit.rs process_tool_calls 有 app_handle)待本批提交后派;F-260615-03 breaking change / F-260615-04 需定交互 待用户确认;改动堆积(git diff 100+ 文件)建议择机提交收口。 +### 2026-06-18 03:08 — sweep-fix 6 域走查 + P3 小修(Code-Working 会话·workflow wd2fnjh3s) + +**工作内容**: +- workflow wd2fnjh3s 6 域并行走查(rust-ai/dag/storage/ipc + fe-aichat/fe-other,12.4 分钟·6 agent·651k tokens):修 12 处确定性低风险 bug + 新发现 27 todo(去重 24 入 todo.md) +- 主代独立核查(审查防污染不信 agent 自报):cargo check --workspace EXIT 0 + vue-tsc EXIT 0 + grep 抽查 scan/audit/commands 源码形态全正确;**拦截 1 瞬态误报**(fe-other vue-tsc 抓 ToolCard.vue:653 cmdOutput TS6133 never read,实为 fe-aichat 改 ToolCard 中间态被并发 vue-tsc 抓到,最终态模板已引用,全量 vue-tsc 复核 EXIT 0 确认非 bug) +- 主代 P3 小修 5 处(各步自测通过):SW-18 AiChat wasNearBottom 切会话重置 / SW-21 TaskDetail wfResult timer 清理 / SW-23 Ideas neutral CSS / SW-15 ai_node schema required 对齐 / SW-24 dead_code 标注(finish_reason/call_type 2 处 #[allow],cargo df-ai warning 消除) +- 文档:todo.md 24 newTodos 去重追加 + F-260616-15 矛盾项销账 + 5 P3/SW 销账;待审查.md CR-260618-01 登记 17 处改动交其他会话审查 + +**代码变更**: +- df-ai: context.rs(parts token 预算,F-05 回归)/ anthropic_compat.rs(Image move)/ openai_compat.rs(2 处 #[allow(dead_code)]) +- df-project: scan.rs(to_ascii_lowercase 索引对齐 + is_monorepo null 判定) +- df-ideas: adversarial.rs(MockProvider 补 reasoning_content) +- src-tauri: audit.rs(audit_finalize 吞错分流 + PENDING_APPROVAL_PLACEHOLDER 常量)/ commands.rs(ai_conversation_delete retain) +- df-nodes: ai_node.rs(schema required=[] 对齐 execute) +- 前端: ToolCard.vue(isFailed/cmdOutput computed)/ AiChat.vue(虚拟滚动死注释清理 + wasNearBottom 重置)/ Projects.vue(_toastTimer onUnmounted)/ Knowledge.vue(parseContext 单次)/ TaskDetail.vue(wfResult timer 清理)/ Ideas.vue(neutral CSS) + +**遗留问题**: +- 1. **UX-2025-12 分页懒加载(高价值中风险)**:多角度论证暂缓——①必须 limit+滚顶两全(limit 单独引入"看不到 >limit 旧消息"回归,之前全量可见)/ ②token 紧张 180 行 7 文件大工程易出错(borrow/sentinel 时序) / ③长对话卡顿是渲染体验问题 cargo/vue-tsc 只验编译类型,滚动流畅度需用户实测 / ④工作区已累积 17 处 commit-policy-deferred 不提交再加 7 文件增合并冲突。留 todo 待充分资源/用户在场推进。 +- 2. SW-260618-01/02 两个 P1(executor Ok 路径取消事件不一致 / 审批占位 tool_result 5 处 .clear() 未终态化):均行为变更需确认设计意图,记 todo 未改。 +- 3. sweep P2 多为重构/防御/改进(AuditLog i18n / ai_node DRY / reqwest Client DRY / 双 watch 合并 / Anthropic url 防御),记 todo 待攒批。 +- 4. 待审查 CR-260618-01(17 处)待审查会话独立 grep 核验。 + +**下一步**: +- 1. UX-2025-12 分页(用户在场体验验证 或 新会话充分资源推进) +- 2. SW-01/02 P1 确认设计意图后修 +- 3. 待审查 CR-260618-01 交审查会话 +- 4. 工作区累积 17 处(commit-policy-deferred 不提交,等架构级重构建基线) + +### 2026-06-18 03:26 — SW-10 reqwest Client 构建 DRY(Code-Working 会话续推) + +**工作内容**: +- sweep P2 SW-260618-10:openai_compat/anthropic_compat Provider::new reqwest Client 构建(connect_timeout 30s + 回退 Client::new + warn)7 行逐字重复,抽 `lib.rs::build_provider_client()` pub(crate) fn 收敛 DRY,两处调 `crate::build_provider_client()` +- 主代自测(审查防污染独立核验):cargo check -p df-ai EXIT 0 无 warning / cargo test -p df-ai 112 passed 0 failed(不回归)+ grep 核验 Client/warn/Duration import 替换后仍多处用(Client 用于 struct 字段 / warn openai:503 + anthropic 7 处 / Duration 用于 :484/:481 timeout),无 unused warning + +**代码变更**: +- crates/df-ai/src/lib.rs(+14 build_provider_client pub(crate) fn)/ openai_compat.rs(-8+2)/ anthropic_compat.rs(-6+2) + +**遗留问题**: +- 1. sweep 剩余低风险项 ROI 递减:SW-06/07 双 watch 合并需谨慎 immediate 语义边界 / SW-19 combineAndTruncateLines DRY 价值低(结构特殊带 total/truncated)/ SW-04 knowledge_events query 无活跃调用方(防御性)/ SW-09 ai_node DRY 架构级 / SW-16 审计 status 透传需查审计表中复杂度(find_cached 返回 Option 仅 content,来源 status 在 ai_tool_executions 表非 ChatMessage)暂缓 +- 2. 待审查 CR-260618-03(SW-10 单项)+ CR-260618-01/02 交审查会话独立 grep 核验 + +**下一步**: +- 1. SW-01/02 P1 待确认设计意图(executor Ok 路径取消事件不一致 / 审批占位 5 处 .clear() 未终态化) +- 2. 工作区累积 18 处(commit-policy-deferred 不提交,等架构级重构建基线) +- 3. 待用户回归决策 UX-2025-12 分页 / SW P1 设计意图 + +### 2026-06-18 03:39 — SW-16 审计 status 透传 + SW-06 ToolCard watch 合并(Code-Working 会话续推) + +**工作内容**: +- SW-260618-16(P3 审计精度):find_cached_high_risk_result 加 audit_repo 参数 + async + 返回 (content,status),命中旧 tool_result 后查 AiToolExecutionRepo::find_by_tool_call_id(**已存在** crud.rs:1256,复用零新增)拿缓存来源真实 status(completed/rejected/failed)透传 audit_tool_call 替代固定 "completed"。审计缺失/查询失败 fallback completed 不阻塞去重。 +- SW-260618-06(P2 DRY):ToolCard 双 watch(props.tc.status) 合并。放原 watch2 位置(approving/approvingTimer/cmdOutputExpanded 三 ref 已定义避 TDZ)+ immediate(原 watch1 语义),callback 内 cmdOutputExpanded + approving 复位合并。 +- 主代自测:cargo check --workspace EXIT 0(仅 4 pre-existing dead_code 基线)/ cargo test -p devflow 92 passed 0 failed / vue-tsc EXIT 0 + grep 独立核验源码形态 + +**代码变更**: +- src-tauri/src/commands/ai/audit.rs(find_cached 签名+返回+status 查询 / process_tool_calls :625 调用点 .await 拆 status / :639 &status 透传) +- src/components/ToolCard.vue(删原 watch1 + watch2 替合并 watch) + +**遗留问题**: +- sweep 剩余可自主:SW-07 AiChat 双 watch 合并(同 SW-06 模式可复用)/ SW-17 ToolCardList Set 重置(低价值内存慢增长)/ SW-19/20 DRY(低价值)/ SW-05/11/12/13/14(防御/确认意图/权衡,需勘察或确认) +- 高价值待用户:B-260618-03 路由解耦 cost/intel(用户已决策主链大改需在场)/ SW-01·02 P1(行为变更需确认设计意图)/ UX-2025-12 分页(需体验实测) + +**下一步**: +- 继续 sweep 可自主项(SW-07 watch 合并模式复用 SW-06)/ 工作区累积 20 处 commit-policy-deferred / 心跳 a096aa7f 20 分钟监测推进 + +### 2026-06-18 03:45 — SW-07 watch 合并 + B-03 路由解耦勘察 + SW-17 评估(Code-Working 会话续推) + +**工作内容**: +- SW-260618-07(P2 DRY):AiChat 双 watch(currentText) 合并。删原 :2040 watch(currentText→onContentChange) 并入 :2047 watch(currentText→scheduleStreamParse) 单 callback,保持原注册顺序(onContentChange 滚动先 → scheduleStreamParse rAF 分块后)。vue-tsc EXIT 0。 +- B-260618-03 路由解耦 cost/intel 勘察完成:9 调用点(project×2/compress/knowledge_inject×2/agentic/title/ai_node/adversarial)+ router 核心(TaskRequirements 字段 + 步骤4/5 filter + 步骤7 Reverse(cost_tier))+ 多 test。**自主裁决:记待决策作专项**(用户已全局决策去掉 cost/intel,但工程大 ~30 Edit + 主链路由行为变更需 cargo test router 全验证 + 在场核对 title/compress 选模型退化,本轮 token 不够完整推进,半途中断风险 > 暂缓)。附完整实施清单供专项续。 +- SW-260618-17 评估:**不做**。切会话清 4 Set 会破坏 UX-260616-03 记忆态设计(userExpandedCards/userCollapsedCards 跨会话保留是 feature——切回 A 恢复展开记忆),「内存慢增长」是记忆保留代价可忽略(tc.id 全局唯一无错配,描述自承认)。 + +**代码变更**: +- src/components/AiChat.vue(删 :2040 watch + :2047 合并 callback) + +**遗留问题**: +- B-260618-03 待决策.md(专项推进,附实施清单) +- SW-17 评估不做(破坏 UX-260616-03 记忆态) +- sweep 剩余:SW-19(combineAndTruncateLines DRY 低价值可做)/ SW-20(parseTags DRY 签名变更中复杂度)/ SW-05/11/12/13/14(防御/确认/权衡)/ SW-08(AuditLog i18n 重构级)/ SW-09(ai_node DRY 架构级) + +**下一步**: +- 推进 SW-19(纯 DRY 低风险)/ 工作区累积 21 处 commit-policy-deferred / 心跳 a096aa7f 20 分钟监测 + +### 2026-06-18 04:12 — SW-19/20 DRY + sweep 评估销账批次(Code-Working 会话续推) + +**工作内容**: +- SW-260618-19(P3 DRY):ToolCard combineOutputs 抽函数,combineAndTruncateLines + cmdOutput computed 复用。vue-tsc EXIT 0。 +- SW-260618-20(P3 DRY):Ideas 删本地 parseTags(IdeaRecord),import stores/knowledge.ts parseTags,3 调用点传 .tags。vue-tsc EXIT 0。 +- sweep 评估销账 6 项:SW-05(占位内容匹配·PENDING_APPROVAL_PLACEHOLDER 集中常量有效暂缓)/ SW-11(OpenAI 图片构造·当前不可达暂缓)/ SW-12(pinned/archived 不对称·设计合理确认)/ SW-13(find_path_conflict 规范化·文档+测试守护够暂缓)/ SW-14(keyring current_dir·权衡已承认不做)/ SW-22(Knowledge 模板重复求值·影响可忽略暂缓)。 +- SW-260618-03 评估暂缓:url 模式当前不可达(前端 Phase2b 只产 base64)+ anthropic_compat 已 warn;加 commands 层校验需追 parts 全链路(IPC→ContextManager→provider)定校验点有歧义 + 误拒合法 url 风险,待 url→base64 预拉落地后变可达再加。 + +**代码变更**: +- src/components/ToolCard.vue(combineOutputs 抽函数 :463 + :472/:652 调用) +- src/views/Ideas.vue(parseTags import :214 + 3 调用点传 .tags) + +**遗留问题**: +- sweep 可自主低风险项已全扫净(SW-19/20 已修 + 6 项评估销账 + SW-03 评估暂缓) +- 剩余候选:SW-08(AuditLog i18n 重构级 15+ key 可自主中工程)/ SW-01·02 P1(行为变更需确认设计意图)/ SW-04(knowledge_events query 无调用方暂缓)/ SW-09(ai_node DRY 架构级)/ B-260618-03(路由解耦待决策专项) + +**下一步**: +- 勘察 SW-08 AuditLog i18n 可行性 / 工作区累积 23 处 commit-policy-deferred / 心跳 a096aa7f 20 分钟监测 + +### 2026-06-18 04:16 — SW-08 AuditLog.vue i18n 化(Code-Working 会话续推) + +**工作内容**: +- SW-260618-08(P2 重构):AuditLog.vue 全硬编码中文 i18n 化。新建 auditLog 命名空间(en/zh-CN:title/refresh/desc/loading/empty + col×7 + pager×4 + risk×3 + status×6 + decided×2),AuditLog.vue 引入 useI18n + template 15 处文本 t() 化 + riskLabel/statusLabel/decidedLabel 改 t(`auditLog.xxx.${val}`)。class 映射保留不国际化。模式对齐 Dashboard/Knowledge/Ideas。vue-tsc EXIT 0。 + +**代码变更**: +- src/i18n/en/auditLog.ts + src/i18n/zh-CN/auditLog.ts(新建命名空间) +- src/views/AuditLog.vue(useI18n + template t() + label 函数 t()) + +**遗留问题**: +- sweep 可自主项已全扫净(SW-19/20 DRY + SW-08 i18n 重构均完成 + 7 项评估销账) +- 剩余候选均非纯自主:SW-01·02 P1(行为变更需确认设计意图)/ SW-04(knowledge_events query 无调用方暂缓)/ SW-09(ai_node DRY 架构级)/ B-260618-03(路由解耦待决策专项) + +**下一步**: +- 勘察 SW-09 ai_node DRY 可行性 / 工作区累积 26 处 commit-policy-deferred / 心跳 a096aa7f 20 分钟监测 + +### 2026-06-18 04:32 — SW-09 ai_node provider 三件套 DRY(Code-Working 会话续推) + +**工作内容**: +- SW-260618-09(P2 架构级 DRY):AiNode/AiSelfReviewNode execute provider 三件套逐字重复消除。抽 2 helper:resolve_and_parse(合并 resolve_provider+parse_params)+ provider_from_params(封装 build_provider 5 行)。AiNode 9 行→2 行;SelfReview parse+build→2 调用(中间 task 读保留,p 生命周期正确)。messages/temperature 差异不抽(各节点自有)。cargo check df-nodes EXIT 0 / cargo test 82 passed 0 failed。 + +**代码变更**: +- crates/df-nodes/src/ai_node.rs(2 helper 新增 + 2 节点 execute 改) + +**遗留问题**: +- sweep 全域可自主项已尽(SW-19/20 DRY + SW-08 i18n 重构 + SW-09 架构级 DRY 均完成 + 7 项评估销账 SW-03/05/11/12/13/14/22) +- 剩余候选均需用户介入:SW-01·02 P1(行为变更需确认设计意图)/ SW-04(knowledge_events query 无调用方暂缓)/ B-260618-03(路由解耦待决策专项) + +**下一步**: +- 可自主项已扫净,状态报告待用户 / 工作区累积 27 处 commit-policy-deferred / 心跳 a096aa7f 20 分钟监测 + +### 2026-06-18 04:35 — UX-260618-04 前端删 cost/intel 标签(Code-Working 会话续推) + +**工作内容**: +- UX-260618-04(P2):前端删 cost/intel 瞎猜档位标签(落地用户「去掉 cost/intel」决策)。自主裁决全删(用户决策明确 + 独立于后端 B-03 路由 + grep 确认仅 2 组件引用 + 回退易)。ProviderPanel 删 tag-cost/tag-intel span;AiChat modelOptionLabel 删 intel/cost 拼接;i18n en/zh settings.ts 删 tagCost/tagIntel + 注释更新。vue-tsc EXIT 0 / grep 无残留。 + +**代码变更**: +- src/components/settings/ProviderPanel.vue + src/components/AiChat.vue +- src/i18n/en/settings.ts + src/i18n/zh-CN/settings.ts + +**遗留问题**: +- ModelConfig.cost_tier/.intelligence 字段保留(后端 B-260618-03 解耦后清) +- sweep + UX 可自主项已尽,剩余均需用户:SW-01·02 P1(行为变更)/ SW-04(无调用方暂缓)/ F-260616-09(架构级立项)/ B-260618-03(待决策专项) + +**下一步**: +- 可自主项已扫净,状态报告待用户 / 工作区累积 31 处 commit-policy-deferred / 心跳 a096aa7f 20 分钟监测 + +### 2026-06-18 06:50 — workflow 6项编码批·router解耦+model_probe+executor TOCTOU+审批占位+knowledge_events+CostTier(workflow wexu1isx1·6 agent·主代核查) + +**工作内容**: +- 用户回归复盘彻夜低产(串行手改+过度保守+心跳空转 1.5h),改 workflow 编排提速 +- workflow wexu1isx1 6 agent(10.7 分钟·294k tokens):B-03 router 解耦 cost/intel / B-04 model_probe 去瞎填 / SW-01 executor TOCTOU / SW-02 审批占位终态化 / SW-04 knowledge_events query / B-05 CostTier Free 清理 +- 主代审查防污染核查:cargo check --workspace EXIT 0(5 pre-existing warning) + cargo test df-ai 109/df-ai-core 24/df-workflow 23/df-storage 全 passed + vue-tsc EXIT 0 +- **主代修 agent E0502 借用冲突**:SW-02 helper finalize_pending_placeholders 签名(&mut ContextManager, &HashMap, &str) 函数参数列表 disjoint borrow 冲突(agent riskNote 预判"应无问题"错误)→ 改 &mut AiSession 整体(函数体内 disjoint borrow 合法)+ 调用点 &mut *session(DerefMut 解 guard)。印证:agent 不跑 cargo 的 riskNote 是猜测,主代 cargo 必验(同 Sprint 22 教训) +- 主代补 B-05 前端同步:types.ts CostTier 删 'free' 对齐 Rust(防类型不一致 + DB "free" 反序列化边界) + +**代码变更**: +- df-ai: router.rs(struct 删 min_intelligence/max_cost + select 删步骤4/5 + max_by_key 纯 weight 删 Reverse + test 改写) / model_probe.rs(heuristic_infer 中性默认 Medium/Standard) / presets/models.json(17 条删 cost_tier/intelligence) +- df-ai-core: model.rs(CostTier 删 Free + 注释 + test 同步) +- df-workflow: executor.rs(Ok 分支 emit NodeCompleted 移入 !is_cancelled + 取消 emit NodeCancelled 对齐 Err) +- df-storage: crud.rs(KnowledgeEventsRepo::list_recent ORDER BY timestamp 兜底) +- src-tauri: commands/ai/commands.rs(finalize_pending_placeholders helper + 5 处 .clear() 前终态化,主代修借用) + title/compress/knowledge_inject/agentic/project.rs(删 TaskRequirements min_intelligence/max_cost 传参 + import 清理) +- df-nodes: ai_node.rs / df-ideas: adversarial.rs(同删传参) / 前端: api/types.ts(CostTier 删 'free') + +**遗留问题**: +- 1. B-03 行为变更:title/compress 纯 weight 选模型,同 weight 并列 max_by_key 返回最后一个(rust Iterator 语义),需用户核对 Settings weight 配置(用户已全局决策接受) +- 2. SW-02 另 2 处 pending_approvals.retain(switch/delete)未改:语义不同(switch DB 占位残留需另案修持久层;delete 非活跃对话占位不在 session.messages 内存)。agent riskNote 标注建议另开任务 + +**下一步**: +- 工作区累积 37 处 commit-policy-deferred(等架构级重构建基线) +- 待审查 CR-260618-10 交审查会话独立 grep 核验 + +### 2026-06-18 — workflow CR-260618-11 审查回流 5 项低风险编码(workflow w999qdu86·3 agent·主代核查) + +**工作内容**: +- workflow w999qdu86 3 agent(1.7 分钟·83k tokens):BUG-260618-11 ai_approve 吞错修(P0) + CR-11#2 context.rs chars_ratio 注释 + CR-11#3 types.ts IntelligenceTier 注释 + CR-11#4 system role 完备性 grep 核验 + CR-11#5 audit.rs find_cached 性能注记 +- 文件域零冲突(src-tauri/commands/ai · df-ai/context.rs · 前端 types.ts),agent 不跑 cargo 主代统一验证 +- 主代审查防污染核查:cargo check --workspace EXIT 0(4 pre-existing dead_code warning)+ vue-tsc EXIT 0 + 独立 grep 印证 agent selfCheck 无污染 + +**代码变更**: +- src-tauri/commands/ai/commands.rs(:289-303 ai_approve None 分支 match 三路分流对齐 audit_finalize,原 unwrap_or_default 消) +- src-tauri/commands/ai/audit.rs(:402 find_cached_high_risk_result 性能注记) +- crates/df-ai/src/context.rs(:56 chars_ratio 0.35 注释,值未变) +- src/api/types.ts(:209 IntelligenceTier 注释) + +**遗留问题**: +- 1. BUG-11 行为变更:DB 故障现返明确错误(原误为「未找到挂起」),同属 Err 分支语义不变 +- 2. CR-11#2 chars_ratio 降值(0.10~0.15)属行为变更影响裁剪边界,本次仅标注未做 +- 3. CR-11#5 find_cached 批量预取属架构改签名,本次仅加注记未做 + +**下一步**: +- 剩余 11 项需用户/架构级定夺(F-09 多会话/T-01·02 实测/S-01·02 澄清/ARC-07 排期/UX-12 二选一/B-260617-11·17 意图+运行时),workflow 推不动,交用户 + +### 2026-06-18 — workflow ToolCard.vue 工具结果渲染 8 项(workflow w2drz3ppo·8 agent 并行核验+主代统筹实施·commit-policy-deferred) + +**工作内容**: +- workflow w2drz3ppo 8 agent(5.1 分钟·301k tokens·132 tool_uses):并行核验 8 工具(run_workflow/patch_file/delete_file/read_file/advance_task/list_trash/list_directory/list_*)后端 tool_registry.rs 返回 vs 前端 ToolCard.vue 渲染,8/8 isReal=true,每份产出精确 old/new 实施清单 + selfCheck grep 证据 +- 主代统筹实施(单文件域 ToolCard.vue 不能并行改):合并冲突(UX-10 被 UX-12 吸收)+ 修死代码位置(write_file 改模板分支非 formatToolResult case)+ type 13 字段去重合并 + 补 task_id 漏列 +- 主代审查防污染核查:vue-tsc --noEmit EXIT 0 + 独立 grep 印证 ToolCard.vue 22 处新字段 + i18n 18 处新 key 全落地 + +**代码变更**: +- src/components/ToolCard.vue:ToolResult type 补 13 字段(task_id/execution_id/target_status/note/diff/matches_found/backup_path/returned_lines/items/modified/old_size/encoding/cross_volume)+ 模板 read_file has_more 截断 / list_directory truncated / write_file old_size·encoding / 新增 patch_file diff 分支 + resultDiffLines computed + formatToolResult switch(run_workflow/list_*/advance_task/delete_file/rename_file)+ toolResultSummary switch(run_workflow/list_*重写修误判+list_trash/advance_task/delete_file/rename_file)+ formatFileInfoResult modified +- src/i18n/{en,zh-CN}/aiTool.ts:加 workflowTriggered/Short/patchedMatches/bytesUnit/linesTruncated/dirTruncated/advancedTaskWithRounds/trashCount/listHasMore/renamedWithBytes/CrossVolume/writeOverwrite/encodingLabel/fileInfoModified + 改 deleteFileSoft 加 backup_path + +**遗留问题**: +- 1. list_directory/list_* 截断文案硬编码 {n:1000}(对齐后端 tool_registry.rs:880 max_entries=1000),后端变更需同步前端 +- 2. list_directory 的 ai-tool-file-meta--warn CSS class 未定义(功能 v-if 生效,样式可后续补) +- 3. 本批 vue-tsc 类型层验证通过,运行时渲染待 T-260614-01 tauri dev 实测批 + +**下一步**: +- 待审查 CR-260618-12 交审查会话独立 grep 核验 +- 工作区累积 45 处 commit-policy-deferred(等架构级重构建基线) +- 剩余需用户项同 CR-11 段(11 项 workflow 推不动) + +### 2026-06-18 — workflow SMELL-P0-1 吞错降级加 warn + B-260618-06 表格 CSS(workflow w5siwnipj·6 agent 并行核验+主代统筹实施·commit-policy-deferred) + +**工作内容**: +- workflow w5siwnipj 6 agent(135s·190k tokens·62 tool_uses):并行核验 SMELL-P0-1 吞错 5 高危(agentic/knowledge_inject×3/idea/audit)+ B-260618-06 表格 CSS + coordinator 空壳,产出 currentCode/callers/verdict/fixType/proposedFix/behaviorChange/risk/selfCheck 结构化清单 +- 主代统筹裁决:4 项实施(降级加 warn 类无行为变更 + CSS 确定性)+ 2 项 no-action(audit 误报 CR-11 已修 / coordinator 已标注保留) +- 主代审查防污染核查:独立 Read ai-md.css/knowledge_inject.rs/idea.rs 印证 agent selfCheck 6/6 准确,cargo check -p devflow EXIT 0 + vue-tsc EXIT 0 + +**代码变更**: +- B-260618-06:src/styles/ai-md.css:59 移除 display:block/overflow-x:auto(width:100%→max-width:100%)+ src/components/AiChat.vue:3366 移除 display:block/width:max-content/overflow-x:auto,table 留 max-width:100%,气泡加 .ai-msg-bubble--ai.ai-md { overflow-x:auto }(CSS spec table-display 下 overflow 须由 block 父容器承担) +- SMELL-P0-1:agentic.rs:348(provider 池)+ knowledge_inject.rs:130,139,312(检索/消息解析)+ idea.rs:292-296(tags 解析)unwrap_or_default → match + tracing::warn 降级,Err 返空 Vec 走原降级路径零行为变更 + +**遗留问题**: +- 1. SMELL-P0-1 audit.rs:344 误报(audit_finalize CR-11 已修三路 match),todo line 漂移+旧版本,workflow 独立核验拦截 +- 2. 全局 ai-md.css 移除 display:block 后其他引用页(ProjectDetail/Ideas/Knowledge/TaskDetail)宽表格滚动需各自容器,当前仅 AiChat 气泡补 overflow,其他页 markdown 少宽表可接受 +- 3. SMELL-P0-1 58 处全量降 P2(5 高危已清,余低危非紧急) + +**下一步**: +- 待审查 CR-260618-13 交审查会话独立 grep 核验 +- 工作区累积 commit-policy-deferred 项 +5(等架构级基线) +- 剩余需用户项同前(11 项 workflow 推不动) + +
+ +
+``` +当 `shouldRenderMsg` 在 true/false 间快速切换(IO 回调频繁触发时),Vue 的 v-if/v-else-if DOM 复用机制可能在极端情况下产生**短暂的 DOM 结构不一致**(如旧节点未完全销毁即被复用)。但这通常是瞬态的,不应导致持久重叠。 + +**🟡 新假设 C [中等概率]:markdown v-html 输出含未闭合标签破坏 DOM 结构** + +虽然 DOMPurify sanitize 应防止此问题,但如果 AI 回复的内容(经 marked 解析后)产生了异常 HTML(如 `
` 未闭合被 Purify 截断),可能导致: +- v-html 注入的 HTML 破坏 `.ai-msg-bubble` 的闭合标签 +- 后续兄弟元素被"吞入" bubble 内部 +- **多条消息的 DOM 树结构错乱** → 视觉重叠 + +这与截图现象吻合(代码块内容溢出到相邻消息区域)。 + +--- + +**最终优先级排序**: + +| 优先级 | 假设 | 概率 | 验证方法 | +|---|---|---|---| +| **P0** | **A: IO 先于 RO 触发 → height=0 → minHeight 跳过 → slot 塌陷** | **高** | DevTools 断点 IO 回调检查 meta.height;或在 :73 前加 `console.log` | +| P1 | C: v-html DOM 破坏 | 中 | DevTools 检查 `.ai-msg-bubble` 的 innerHTML 是否有未闭合标签 | +| P2 | B: v-else-if 快速切换 DOM 复用异常 | 低 | 临时改 v-else-if 为 v-else(始终渲染)验证 | +| P3 | 原①: IO 漏 observe | 低→中 | 已修正为"IO 正常但时序竞态" | + +- [x] ✅(wf wf_9e13176f+主代核验·commit 8abcd56·防御修复已落地) B-260617-09 [P1] — **aichat 消息全量重叠**【根因嫌疑=虚拟滚动 IO 与 RO 时序竞态:IO 先于 RO 首次回调触发卸载分支时 meta.height===0 → 不设 minHeight → slot 塌 0 高 → 下条消息上浮重叠。**防御修复已落地**:useAiVirtualScroll.ts IO 卸载分支(line 77-79)三层 fallback(height>0 实测→offsetHeight→40px)+ registerSentinel 首次挂载(line 110-111)40px 兜底。sentinel 永不塌 0。vue-tsc EXIT 0。**待用户实测确认**:若重叠消除则根因确认;若仍重叠则 DevTools 验证次 probable(v-html DOM 破坏)或其他根因】 — src/composables/ai/useAiVirtualScroll.ts(:68-82 IO卸载分支 + :104-112 registerSentinel) + +### 🔧 2026-06-17 useAiSend.ts 代码审查(仅走查·未实施) + +> 排查性质(session-role-diagnose-only),对照代码走查 `src/composables/ai/useAiSend.ts`(456 行)。波2 已实施 UX-05/06/07 + F-01 modelOverride,本次基于**当前代码**核验(不信 todo 声明:UX-05/06/07 经代码确认一致 ✅)。仅记录待办,未改代码。 + +- [x] ✅(workflow wfryptv2t·代理D核验并修·2026-06-17·forceMode=true) B-260617-01 [P2] — **sendQueuedNow「立即发送」可能退化为入队**。代理D独立核验:stopChat 仅 await stop IPC 发出(stop_flag.store),不等后端 loop 跑到 agentic.rs:444/:819 检测点+guard.reset()(generating 才复位),紧接 sendMessage(forceMode=false) 命中 L1 ai_is_generating(后端真值仍 true)→ 入队而非立即发。**修**:sendQueuedNow forceMode false→true(useAiSend.ts:472),跳 L1 走 doSend→ai_chat_force_send(commands.rs:742-748)原子复位 generating=false 再发,无竞态窗口;stop_flag 让旧 loop 下个检测点退出,force_send stop_flag 同向不冲突;parts 透传保留。vue-tsc EXIT 0。审查发现:sendQueuedNow(:406-411) `await stopChat()` 仅等 stop IPC 发出,不等后端 loop 退出 / `ai_is_generating` 复位。紧接 `await sendMessage(spliced)` 时,后端 stop_flag 已置但 generating 可能仍 true(loop 未跑到 agentic.rs:178 检测点 / guard.reset():188 未执行),sendMessage 命中 L1(:281 `backendGenerating || streaming`) → spliced 被**入队而非立即发**,与「立即」语义不符。UX-07 条目原标「stop_flag 复位降级可接受」,此审查**质疑该结论**——降级实为功能不达预期。**待核验**:`ai_is_generating` 复位时机(stop_flag→loop:178→guard.reset:188 链路延迟);若滞后,sendQueuedNow 应走 forceMode 跳过预检或等 AiCompleted 再发。— src/composables/ai/useAiSend.ts:406-411 +- [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 +- [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)。 + +### 🔧 任务模块全栈核对(2026-06-16 · Explore 代理并行取证) + +> 来源:[任务模块问题分析-2026-06-16.md](./05-代码审查/任务模块问题分析-2026-06-16.md)。原清单 18 项核对:真 bug 7 项 + #8 路由漏注册去重(B-260616-09 已存在)、增强 5 项、假/部分假 3 项、去重 3 项。详见文档速览矩阵。 + +- [x] ✅(第㉗批 2026-06-16·workflow) B-260616-12 [P0] — **前后端状态枚举分裂(7 vs 5)**。后端 TaskStatus 7 种(types.rs:131-146) vs 前端常量/i18n/样式 5 种(project.ts:56-62 Git 工作流导向),types.ts:88 注释随后端 7 种。后端写 in_review/testing/blocked → 前端查不到回退显示 key + status-todo 样式。**需人定**:前端对齐后端 7 种纯状态机,还是保留 Git 工作流 5 种做映射。— src/constants/project.ts + i18n + types.ts +- [x] ✅(第㉗批 2026-06-16·workflow) B-260616-13 [P0] — **delete_task 硬删除无恢复**。task.rs:113 物理删,TaskRecord(models.rs:53-66)无 deleted_at;对比 ProjectRecord 有软删除。误删永久丢失 + branches.task_id 悬空。**需人定**:任务是否要软删除(对标 projects)。— src-tauri/src/commands/task.rs + df-storage/models.rs +- [x] ✅(第㉔批 2026-06-16) B-260616-14 [P1] — **DDL priority 默认值矛盾**。migrations.rs:305 tasks 表 priority DEFAULT 1→2(对齐 task.rs default_priority()=2),加注释。**开发期改 V1_SQL**(项目 schema_version 守卫,V1 仅新 DB 跑,旧 DB 不重跑;SQLite 不支持 ALTER COLUMN 改 DEFAULT 需重建表,故直接改原 migration 最小改动,符项目 V4/V5/V6 等幂等补列惯例)。create_task 走 serde default 显式传 priority=2,无运行时行为变化,消文档/契约矛盾 + 为未来不显式传值的 SQL insert 路径提供正确回退。— crates/df-storage/src/migrations.rs +- [x] ✅(第㉔批 2026-06-16) B-260616-15 [P1] — **update_task priority 无值域校验**。task.rs:105-115 加 priority 校验(parse::() ∈ 0..=3,非法返 Err format!,与 status is_valid 同构)。范围确认:0=critical/1=high/2=medium/3=low(Tasks.vue select/constants PRIORITY_LABELS/types.ts 注释/default_priority()=2 全对齐)。**行为变更(安全修复非回归)**:非法 priority(999/abc/-1)现被前置拦截返 Err,前端 invoke 收 reject → B-17 catch 写 state.error。零合法调用方受影响(前端 select 仅产 0..=3,AI create 不经 update_task)。— src-tauri/src/commands/task.rs +- [x] ✅(第㉔批 2026-06-16) B-260616-16 [P1] — **allowed_columns 含不可变字段**。crud.rs:324-332 tasks 白名单移除 id/created_at(防篡改主键/伪造创建时间/跨项目移动)。**project_id 降级保留 + TODO**:通用 CRUD 层(db repo)只懂表/列语义不持有跨表业务约束(查 projects 表存在性)+ update_field 宏无 ProjectRepo 句柄,跨表校验待 commands/task.rs 层补(留 TODO B-260616-16 注释指向)。releases 表未动(只 tasks)。update_full/insert/get/list/query/delete 不经白名单不受影响;现有测试(update_field_rejects_cross_table_column/allows_tasks_status 用 title/status 保留)不破坏。**㉕ 收尾闭环**:commands/task.rs L116-124 补 project_id 跨表存在性校验(field=="project_id" → state.projects.get_by_id(&value) 查存在,不存在返 Err format!,跟随 status/priority 校验同构),B-16 完整闭环(crud 白名单移除不可变字段 + commands 跨表存在性校验)。设计备注(非 TODO):get_by_id 不过滤 deleted_at(软删项目仍判存在,ProjectRecord 未暴露 deleted_at,符「查存在性」字面要求);create_task project_id 未校验(前端 create 下拉源可信,超 B-16「update 改 project_id」范围)。— crates/df-storage/src/crud.rs + src-tauri/src/commands/task.rs +- [x] ✅(第㉔批 2026-06-16) B-260616-17 [P1] — **updateTask store 无 try/catch**。tasks.ts:29-40 补 try/catch(成功才改 state findIndex 回填,失败写 state.error,与 load/create/delete 三兄弟逐字同构)。**主代理补 i18n 收尾**:agent 守文件锁留硬编码中文兜底+TODO,主代理补 i18n updateFailed key(zh '更新任务失败'/en 'Failed to update task')+ tasks.ts L38 改 t('tasks.err.updateFailed') 删 TODO(消除硬编码中文)。store 内无 toast(state.error 由组件层 watch 消费,与三兄弟一致)。vue-tsc exit 0。— src/stores/project/tasks.ts + src/i18n/{zh-CN,en}/tasks.ts +- [x] ✅(第㉕批 2026-06-16) B-260616-18 [P1] — **TaskDetail 绕 store 直调 API**。**选监听方案**(不改数据源,改动最小:保留 taskApi.get/projectApi.list 直调,独立入口不依赖全局 store;store 的 df-data-changed 监听只刷 store.tasks 列表,受 B-260615-29 筛选契约约束不含当前 task 单体)。L98 import onBeforeUnmount+listen + L111 DfDataChangedPayload 类型 + L164 模块级 _unlistenDataChanged + L172 onMounted 内 listen('df-data-changed', entity∈{task,project}→load()) + L178 try/catch 防 listen reject 静默失效(对齐 CR-260615-21)+ L183-185 onBeforeUnmount 释放。复用 AiChat _unlistenToolSlow 生命周期模式。entity=project 也刷新(projectName 从 projects 解析,项目重命名需同步)。无 i18n 改动,不破坏渲染/路由参数 watch(taskId)。vue-tsc exit 0。— src/views/TaskDetail.vue + +### ⏸️ 待决策(人定·非模型推断·2026-06-16) + +> 任务执行与推进能力核对([任务执行与推进能力分析-2026-06-16.md](./05-代码审查/任务执行与推进能力分析-2026-06-16.md))暴露的架构决策点。**定了才能实施推进链**,与模型能力无关。标注关联 todo。 + +- [x] **D-260616-01 状态枚举方向** ✅ **决策:① 前端对齐后端 7 态**(2026-06-16 用户拍板)。后端 7 态不动,前端 constants/i18n/Tasks 筛选器/Dashboard 扩展至 7 态。理由:推进链阶段 2/3 需 InReview(AI 自审)/Testing(测试闸门)/Blocked(退回)三态,删则自断推进链。【关联 B-260616-12】 +- [x] **D-260616-02 任务软删除** ✅ **决策:① 加软删除对标 projects(UI 缓做)**(2026-06-16 用户拍板)。先加 `deleted_at` 列 + `list_active` 过滤 + `restore` API,恢复 UI 后续按需补。抄 projects 全套模板(V11 迁移 + ProjectRepo 方法)。【关联 B-260616-13】 +- [x] **D-260616-03 advance_task 架构落点** ✅ **决策:在 df-nodes 中实现**(2026-06-16 用户拍板)。推进链业务逻辑(状态机/advance_task/闸门节点)走 df-nodes 的 Node trait 扩展,不新建 crate、不塞 task.rs。完全对齐 D3 决策"业务逻辑在 df-nodes 实现"。AiNode 自审 / HumanNode 核对等闸门逻辑在 df-nodes 节点内实现,状态机+advance_task 编排在 df-nodes 层完成。 +- [x] **D-260616-04 推进路径取舍** ✅ **决策:阶段1先行**(2026-06-16 用户拍板)。先做阶段1骨架(advance_task 状态机 + status 收口 + 手动推进闭环),不等工作流联动/AI 执行。基础 + P0 安全优先,后续阶段按需迭代叠加。 + +### 🗺️ 任务推进链实施路径(2026-06-16 规划·供其他会话读取) + +> 详见 [任务推进链实施路径-2026-06-16.md](./02-架构设计/任务推进链实施路径-2026-06-16.md)。推进能力实现度 0%。**阶段1已解除阻塞(D-01/D-03/D-04 三决策已定 2026-06-16),可启动 F-01~05**。核对纠正:AI 有 `update_task`/`run_command` 工具,无 `run_workflow`/`advance_task`。 + +- [x] ✅(第㉘批 2026-06-16·workflow) F-260616-01~05 阶段1 推进骨架 — F-01状态机(can_transition 7态矩阵+is_regression)/F-02 advance_task(IPC+Node trait+CAS原子写 crud.rs advance_status_atomic `WHERE id=? AND status=expected`防TOCTOU)/F-04 review_rounds(V15迁移幂等+退回转换+1原子)/F-05前端推进按钮(ADVANCE_MAP+taskApi.advance) 完成。**F-03收口延后阶段3**:AI工具update_task依赖status改写,裸移除白名单→工具描述谎称能改但报错(行为不一致),收口须阶段3三件同落地。循环依赖核实:df-storage Cargo.toml不含df-nodes,加反向dep无循环。cargo check 0+df-nodes 46 test/df-storage 11 test pass+vue-tsc 0。 +- [x] ✅(batch32-35·2026-06-16·全部子项落地) F-260616-06 阶段2 工作流联动(task_id+回调+DAG模板)— **全部落地**(④-1/②-1/②-5 batch32 + ②-2/②-3/②-4/②-6 batch33 + ①-1/①-3/B-41 batch35),实施路径见 [推进链阶段2实施路径-2026-06-16.md](./02-架构设计/推进链阶段2实施路径-2026-06-16.md): + - [x] ✅(batch32·2026-06-16·workflow wn4gudnz8) **④-1 [架构前置·阻塞]** DagExecutor config 下沉语义修复 — `executor.rs:99-107` NodeContext.config 用 initial_config 覆盖 NodeDef.config(节点级配置被忽略,TaskAdvanceNode 读不到 ctx.config.task_id)。修法:`NodeContext.config = deep_merge(node_def_config, initial_config)` 节点级覆盖全局级。文件锁:`crates/df-workflow/src/{dag.rs,executor.rs,registry.rs}` + `src-tauri/src/commands/workflow.rs` + - [x] ✅(batch32·2026-06-16·workflow) **②-1** build_registry 注入 Arc + 注册 TaskAdvanceNode — `build_registry(db)` 改签名 + move 闭包 + init 调用传 db.clone()。文件锁:`src-tauri/src/state.rs` + - [x] ✅(batch33·2026-06-16·workflow w98anf4y5) **②-2** run_workflow IPC 加 task_id + target_status 参数 — 签名扩展 + WorkflowRecord.task_id 填入 + spawn move 捕获。文件锁:`src-tauri/src/commands/workflow.rs` + `src/api/workflow.ts` + `src/stores/project/workflow.ts` + - [x] ✅(batch33·2026-06-16·workflow) **②-3** 完成回调 WorkflowCompleted→advance_task — spawn 闭包内 executor.run Ok 后,task_id+target_status 都 Some 时调 advance_task_atomic,失败降级 warn 不回滚。文件锁:`src-tauri/src/commands/workflow.rs` + - [x] ✅(batch33·2026-06-16·workflow) **②-4** 失败回调(退回语义) — 工作流 failed 时按 target_status 推算退回态调 advance。文件锁:`src-tauri/src/commands/workflow.rs` + - [x] ✅(batch32·2026-06-16·workflow) **②-5** HumanNode reject 语义化 — options 含 reject/block 时返 Err(非 Ok),使工作流 failed 触发退回。文件锁:`crates/df-nodes/src/human_node.rs` + - [x] ✅(batch33·2026-06-16·workflow) **②-6** DAG 模板(task_workflow_templates) — df-nodes 新模块导出 template_for(target_status)->DagDef,5 前向边+退回。文件锁:`crates/df-nodes/src/task_workflow_templates.rs`(新) + - [x] ✅(batch35·2026-06-16·workflow w17duic3f) **①-1** 前端 TaskDetail 工作流推进按钮 — 与手动 advance 并存,调 workflowApi.run+监听进度。**含 B-41 进度内嵌**:onEvent 按 exec_id 过滤 dispatch node_started/completed/workflow_completed/failed + 独立 wfAdvancing loading 与手动 advancing 互含 disabled。**后端补丁**:workflow.rs run_workflow 空dag选模板(dag.nodes 非空→原 / 空+target→template_for / 空+无target→原路径,向后兼容)。文件锁:`src/views/TaskDetail.vue` + `src/api/workflow.ts` + `src/stores/project/workflow.ts` + `src-tauri/src/commands/workflow.rs` + - [x] ✅(batch35·2026-06-16·workflow) **①-3** i18n 文案 — `src/i18n/{zh-CN,en}/taskDetail.ts`(taskDetail.* 命名空间,8 key 对称:workflowAdvanceTitle/Advancing/AdvanceFailed{msg}/StepRunning{node}/StepsProgress{done}{total}/CompletedHint/FailedHint)。注:todo 原路径 `locales/tasks.ts` 不准,实为 `i18n/taskDetail.ts` + - **实施顺序**:④-1→②-1→②-5→②-2→②-3→②-4→②-6→①-1/①-3。**最小里程碑**(④-1+②-1+②-2+②-3):run_workflow 单 task_advance 节点 DAG 端到端推进 todo→in_progress +- [x] ✅ F-260616-07 阶段3 AI 执行闭环 — **F-03 收口三件 ✅ batch64** + **AiNode 自审闸门 ✅ batch65**(本批) + - [x] ✅(batch64·2026-06-16·workflow wii1u1lnm) **F-03 收口三件** — ①advance_task 注册 AI 工具(tool_registry.rs:395,handler L407 调 `df_nodes::task_advance_node::advance_task_atomic` 与 IPC `commands::task::advance_task:165` 同源) ②run_workflow 注册 AI 工具(:428,handler 架构约束无 AppHandle/State 报错引导走 IPC,ToolDefinition+审批文案 L1260-1261/1286-1287 注册让 LLM 可产出 tool_call) ③update_task handler guard 拒 status(L374 `field=="status"` bail,schema 通用 field/value 故 guard 拦非 schema 改)+ df-storage tasks 白名单移 status+review_rounds(crud.rs:331-347,advance_status_atomic CAS L848 独立路径不经白名单)。同步落地防 AI 工具行为不一致。**主代独立核查全过**:cargo check --workspace EXIT 0(5 pre-existing warnings 无关)+ df-storage 11 集成测试(含新 `update_field_rejects_tasks_status`)。文件锁:tool_registry.rs + crud.rs + project_soft_delete.rs。**审查登记 CR-260616-41**(待审查.md 当前队列) + - [x] ✅ AiNode 自审闸门(阶段3 核心,决策a) — **阶段1迁移+方案✅(539b5ed+25409e5) + 阶段2②③④⑤✅(c10adaf+741b0b9) + P0 secret下沉+注入链✅(766f235+84a6fc4)**。⑥联调代码层解锁(待实测 tauri dev)。cargo df-nodes 79 passed + workspace EXIT 0 + vue-tsc EXIT 0 + - [x] ✅(539b5ed) ①df-storage: TaskRecord 加 `output_json: Option` + V17 迁移(幂等补列) + crud 白名单+SELECT/INSERT/UPDATE 全链路 + - [x] ✅(c10adaf) ②df-nodes ai_node.rs: AiNode 持 db(Arc)+execute 后 config[task_id] 存在则 update_field 落 output_json + - [x] ✅(c10adaf) ③df-nodes ai_node.rs: AiSelfReviewNode 独立节点(prompt 四维度+parse_review_json 兜底 verdict=unknown+写回 review 子字段)+ state.rs 注册 + testing 模板 ai→ai_self_review + - [x] ✅(84a6fc4) ④df-nodes ai_node.rs: resolve_provider 注入链(provider_id 优先/老明文兼容 warn/空兜底 is_default)+ execute 经 df_storage::secret 解析 api_key 不进 config(FR-S1)+ schema required [provider_id]。run_workflow 注入 task_id 到 NodeContext(④-1 deep_merge 就绪),模板 config 空走 AiNode 兜底 + - [x] ✅(c10adaf) human_review: 经 DAG inputs["ai_self_review"].data 透传 verdict/summary(HumanNode 零改动,方案④更优路径) + - [x] ✅(741b0b9) ⑤前端: TaskDetail.vue 展示 output_json(parsedOutput+review 红绿标+产出 markdown)+ types.ts+i18n 双语 + - [ ] ⑥端到端联调 — **代码层完成**(P0 secret 下沉+注入链✅ 766f235+84a6fc4),实测类待用户(tauri dev 验证 testing 模板 ai_self_review→human_review 闭环) + - [x] ✅(batch65·2026-06-17·workflow) **AiNode 自审闸门(阶段3 核心)** — `verdict=fail` 作 DAG 节点闸门阻断下游,对齐工作流失败语义。**方案 A(AiNode 内部门控)胜出**:新增纯函数 `gate_should_block(gate, verdict)`(ai_node.rs:426 `gate && verdict=="fail"`) + execute 末尾 gate_enabled(config["gate"]==true,默认 false 阶段2 兼容)&& fail → `return Err("AI 自审闸门未通过(verdict=fail): ...")` 复用 executor first_err(:147-178)中止后续层 → 工作流 failed → ②-4 回调退回 in_review。verdict=unknown/pass 放行(unknown 保人定权,阶段2 保守语义不变)。落库在前(update_field output_json),故 fail 时审查痕迹不丢。**选型对比**(方案 B DAG edges 条件 + ConditionEngine:依赖 T-260614-11 暂缓 + executor 当前完全不评估 edge.condition(topological_layers 无条件收录所有边)→ 拆波;方案 C executor 闸门检查钩子:改 DagExecutor 核心循环牵动所有节点,不符「最简不破坏」)。testing 模板 ai_self_review 节点 config 加 `"gate": true`(task_workflow_templates.rs:67)启用。schema 加 gate 布尔字段。**自验**:cargo check -p df-nodes -p df-workflow EXIT 0 + cargo test df-nodes 82 passed(79→82,+3 gate 单测)+ df-workflow 23 passed。文件锁:crates/df-nodes/src/{ai_node.rs,task_workflow_templates.rs}。**审查登记 CR-260616-44**(待审查.md 当前队列) + - [ ] run_workflow handler 注入 AppState(当前报错引导走 IPC) — 后续批扩展 build_ai_tool_registry 签名注入 AppState 句柄让 AI 直驱 +- [x] ⏸️(待决策.md已决b暂缓·2026-06-16) F-260616-08 阶段4 Git 集成(kind+git闸门+worktree) +- [x] ✅ **CR-260616-01 代码审查完成** → 审查登记已迁 [待审查.md](./待审查.md)(职责分离:审查队列独立,不进 todo)。结论 🔴0 🟡6 ⚪4 质量优,8 维度全过。**待修项 CR-01-A~I 见下方推进区**。 + + > **审查发现待修项(2026-06-16 · CR-260616-01)**: + - [x] ✅(cr01-fixes·wu4r8ejcp) **CR-01-A** [P1] TaskDetail.vue 补 `.status-testing`(橙)/`.status-blocked`(红 danger 边框)CSS;Tasks.vue 已有/Dashboard 不用此体系无需补 + - [x] ✅(cr01-fixes·wu4r8ejcp) **CR-01-B** [P1] crud.rs advance_status_atomic 两分支 CAS SQL 加 `AND deleted_at IS NULL`,回收站任务 CAS 必败返 None(D-02 软删收口) + - [x] ✅(cr01-fixes·wu4r8ejcp) **CR-01-C** [P2] task_advance_node.rs:50 错误信息改 `ALL_STATES.join("/")` DRY(运行时文案不变) + - [x] ✅(batch30·2026-06-16) **CR-01-D** [P2] task_advance_node.rs:62 同态拒绝(from==to)/非法转换(can_transition false)错误区分(现合并 `InvalidState`,前端无法分辨)— **②类留**(需改错误枚举判断) + - [x] ✅(cr01-fixes·wu4r8ejcp) **CR-01-E** [P2] src-tauri/src/state.rs:240 build_registry 补注释「TaskAdvanceNode 未注册(阶段2联动时加),勿删」 + - [x] ✅(cr01-fixes·wu4r8ejcp) **CR-01-F** [P2] crud.rs 白名单注释补 review_rounds 专用路径(仅 advance_status_atomic,防误补破坏收口) + - [x] ✅(batch30·2026-06-16) **CR-01-G** [P3] TaskDetail.vue:218 advance 失败错误走 i18n 分级(`e.toString()` 非空直显后端中文,en locale 也中文)— **②类留**(需 i18n 分级改造) + - [x] ✅(cr01-fixes·wu4r8ejcp) **CR-01-H** [P3] task_advance_node.rs 测试改名 `concurrent_cas_change_detected` → `advance_succeeds_after_prior_status_change`(反映实际测正常推进) + - [x] ✅(cr01-fixes·wu4r8ejcp) **CR-01-I** [P3] i18n `taskDetail.advancing` 接文案(推进按钮 advancing 时显示「推进中...」,冗余 key 转实引用) + > + > **修复核查(主代理独立·2026-06-16)**:7 项真实落地(Read+git diff+cargo check --workspace exit 0.83s+vue-tsc 0 核实)。**注意:验证 agent CR-01-E 证据张冠李戴**(报 df-workflow/state.rs,实改 src-tauri/state.rs),改动正确但证据幻觉——验证 agent 可信度打折,主代理核查兜底。D/G ②类留。 + + > **审查发现待修项(2026-06-16 · CR-08/10/13,均 med 非阻断,详见 [待审查.md](./待审查.md) 对应归档)**: + - [x] ✅(batch50·主代理决策:接受现状降级 P2) **CR-10-A** [med] ai_regenerate 并发竞态窗口 → **统一并发模型**(锁内变更+锁外 spawn,ai_regenerate/ai_chat_send/ai_chat_force_send 同款),窗口有兜底(stop_flag 置位 loop 退出 + generating 拦截 send),med 非阻断。统一并发改造复杂(原子标记预占+3 入口),ROI 不明非优先,降级观察/架构改进候选 + - [x] ✅(batch50·主代理核查:无效) **CR-10-B** [med] commands.rs:56/59 save 与 emit 时序倒置 → **不成立**:ai_regenerate 当前代码 save L94 锁外 spawn L105 前(时序正确),无 emit(L56/59 是 pop 后错误处理),审查锚定行号漂移 + - [x] ✅(batch55·2026-06-16·workflow wem402ncv+主代核查) **CR-13-O1** [产品决策→已决b实施] regression_target("in_progress")→None 对齐状态机。workflow.rs:51 `in_progress => None`(替原 Some("todo"))+task_advance_node.rs 镜像同步+4 测试断言更新(含集成测改 None-skip 场景)。**主代核验**:git diff 2 文件 26+/24- 双处一致+注释对齐决策b理由+cargo test -p df-nodes 67/67 pass。— src-tauri/src/commands/workflow.rs + crates/df-nodes/src/task_advance_node.rs + - [x] ✅(batch50·2026-06-16·workflow w6nqio4o5) **CR-08-O1** [med·非阻断] blocked 态推进按钮语义偏差 → 已修复(WF_EXCLUDED_FROM={'blocked'} + wfAdvanceAction computed 守卫,blocked 不显示工作流推进按钮,其他态不受影响,不动 ADVANCE_MAP) + - [x] ✅(batch50·2026-06-16·workflow w6nqio4o5) **CR-15-1** [med] `crates/df-execute/src/shell.rs:60-79` 缺 `.kill_on_drop(true)` → 已修复(cmd.kill_on_drop(true) 三类型覆盖 + 注释配合 tokio::time::timeout 防孤儿,文案「进程已终止」名副其实) + - [x] ✅(主代理验证:不成立·workspace cargo check exit 0) 🔴 **CR-23-1** [high·紧急·阻断构建] `crates/df-ai/src/context.rs:491` E0594 → **不成立**:审查代理跑 batch51 中间态 filter 版(EXIT 101),batch51 最终改 for 循环(EXIT 0,df-ai + workspace 双验证,代理自报属实)。context.rs:488 当前是 `for t in iter_mut()` 单层引用可写 status,无 filter 闭包。审查锚定滞后(跑 batch51 中间态)— CR-260616-23 归档 + - [x] ✅(主代理修·CSS :has item 左边线) **CR-22-1** [med·UX] `AiChat.vue` 已置顶非 hover 不可见 → 已修复(`.ai-conv-item:has(.ai-conv-item-act--pinned){ box-shadow: inset 2px 0 0 accent }` 左边线强调,不破坏 hover 浮出 toggle,原方案图钉常驻与 actions display:none 矛盾,改 :has item 左线最简)— CR-260616-22 归档 + - [x] ✅(CR-260616-35 复审 PASS·主线程预核验) **CR-34-1** [med] useAiPanel.ts:102 loadProviders 守卫 `if(!state.activeProvider)` 只查 null 不查有效性 — localStorage savedProvider 指向已删除 provider 时 activeProvider 残留无效 id → cycleProvider:1185 findIndex=-1 跳过 ps[0] + 发消息消费方用无效 id。修法:守卫加 `|| !list.some(p=>p.id===state.activeProvider)` 残留无效时回落 DB is_default — CR-260616-34 归档 + - [x] ✅(CR-260616-35 复审 PASS·agent cargo 6+20 passed) **CR-30-1** [med] agentic.rs:298 退避 `1< 巡检推进中浮现的、需用户拍板才能继续的操作授权/范围确认项(非架构方向决策,见上节 D-*)。巡检每轮不再重复问,看此处标注。 + +- [x] **DEC-260616-01 文件名 git mv 06-15→06-16** ❌ **否决**(2026-06-16 用户确认):21 个 docs 文件名日期 `-2026-06-15.md` 均正确(06-15 创建),无需修改。原前提"今天误标 06-15"不成立。 +- [x] **DEC-260616-02 docs 其他文件 06-15 正文核对** ❌ **否决**(2026-06-16 文件时间分析):21 个 `-2026-06-15.md` 文件的 CreationTime 和 LastWriteTime 全部在 2026-06-15 00:22~17:08 之间,确认为 06-15 当天创建,正文中的 06-15 日期引用均正确,无需核对修改。 +- [x] **DEC-260616-03 CR-04/CR-06 流式核心改造授权** ✅ **已授权并实施完成**(2026-06-16):CR-04 splitBlocks→marked.lexer 已完成(batch57,见 CR-260615-04);CR-06 loadMarkdown 重算已确认实施(CR-260615-06 ✅)。两项流式核心改造均已落地并通过 vue-tsc + cargo check 零错误验证。 + +### 🔴 anthropic_compat 多轮工具调用(2026-06-14 排查·会话卡死根因) + +> 来源:本排查会话定位另一 Claude Code 会话(经 GLM anthropic 端点)「卡死后再也对话不了、一直返回同一 500」现象,顺带暴露 devflow 同构缺陷。**会话卡死机制**:畸形 tool_result 写入 append-only 历史 → 后续每轮把毒历史原样重发 → 每次触发同一 500 → 死循环(救援只能清历史/新会话)。GLM 端报 `[500]['ClaudeContentBlockToolResult' object has no attribute 'id']`。 + +- [x] B-260614-AC1 ✅ wave4(36d68dd) — **[P1]**(出站 tool_call_id None/空跳过+warn,绝不发 null) anthropic_compat tool_use_id None 发 null — `crates/df-ai/src/anthropic_compat.rs:297` `"tool_use_id": m.tool_call_id` 对 `Option` 无校验;`serde_json::json!` 把 None 序列化为 `"tool_use_id": null`。上游(LLM 返回 tool_use 缺 id / ContextManager 丢字段)致 tool_call_id=None 时,devflow 发出畸形请求触发服务端 500。**修法**:None 时 skip 该 tool_result 块或填占位 id + `warn!`,绝不发 null。 +- [x] B-260614-AC2 ✅ wave4(36d68dd) — **[P2·防御]**(入站 tool_use 缺 id 同步跳过/流式占位 tool_missing_{idx}+warn) tool_use id 解析无兜底 — `anthropic_compat.rs:167` LLM 返回 tool_use 块缺 `id` 时 draft.id 为空 → 后续 tool_result 带空 id → 回传 500。**修法**:id 缺失时跳过该 tool_use 或生成占位 + warn。 +- [x] B-260614-AC3 ✅ wave(2026-06-16核查闭环,待commit) — ~~历史中毒无自愈~~ context.rs `sanitize_messages` 三档自愈(全闭合保留/全未闭合整删/部分闭合重写 tool_calls)+build_for_request 两分支必过 sanitize,5 单测覆盖;占位 ID 生成(anthropic_compat.rs)保留未动(⬆️ 06-14 升级:write_file 缺 path 这类 LLM 常见失误触发错误 tool_result,叠加 GLM 端 tool_result id bug → 永久卡死,用户可感硬伤「再也对话不了」)— `ContextManager` + `stream_llm`:畸形 assistant(tool_use)+tool_result 一旦入历史,stream_llm emit AiError 后历史不动;用户重发 → `build_for_request` 带毒 → 永久 500。**修法**:stream_llm 收服务端 500/格式错时,检测并剔除最后一轮未闭合 tool 配对,或提供「修复当前对话」操作。注:write_file path 校验本身已健壮(tool_registry.rs:411 友好报错),卡死在其下游。 + +### 🔴 aichat 审查报告待修项(来源:[aichat审查报告-2026-06-14.md](./02-架构设计/aichat审查报告-2026-06-14.md) 第八章) + +> 2026-06-14 aichat 模块代码审查产出,原仅留 memory 指针未回流看板,今补入。去重:**S-02 审批可见性 ⊂ AR-3**(修 AR-3 卡片可读性直接缓解"看不到审批批什么");**B-05 detach = AR-M5 同类**(跨窗口 state 隔离,aichat 审查描述更深)。 + +**P0(用户可感硬伤)** +- [x] AR-1 ~~流式 Markdown 全量重解析~~ ✅ **退役**(ARC-260615-08 自研块级 memo 取代,2026-06-16):splitBlocks 块级 memo O(末块)+rAF 节流 取代全量 marked.parse+sanitize,流式全程有格式不掉帧。详见 [流式渲染调研](./02-架构设计/aichat流式Markdown渲染调研-2026-06-15.md) §5(⬆️ 原:renderMd 缓存 key=完整文本,每 delta 全量 marked.parse+sanitize,长回复主线程阻塞掉帧,AiChat.vue:343-354) +- [x] AR-2 ~~审批态新建对话卡死~~ ✅ WF-F 完成(ai_conversation_create 加 generating 守卫,位于 clear 前,对齐 switch:433 写法)(commit 057a212) +- [x] AR-3 ✅ ~~审批卡片信息不足(删除等操作只返回数据 ID)~~(commit 36d68dd 完整修复):后端 `audit.rs:45-127` build_approval_reason + resolve_project_label(9 工具 reason 拼项目名,fallback「(项目已不存在, id=)」:52);前端 `ToolCard.vue:296-320` PROJECT_ID_TOOL_ARG 映射 + toolArgsEntries 特化 id/project_id 回显项目名。原两个剩余问题(①前端裸显 id ②fallback 裸 id)均已修。wave(2026-06-16,待commit)补 `toolDisplayName` CRUD case 7 项(delete/restore/purge/update/create_task/create_project)+i18n 10 key 中英对称。 +- [x] AR-4 ~~create_project 双审双 API~~ ✅ WF-F 完成(schema 加 path/stack + handler 有 path 时合并绑定 spawn_blocking 探测栈,消除二次 bind_directory;TODO 标注可抽公共绑定函数)(commit 057a212) + +**P1** +- [x] AR-5 ~~审批态 stop 无兜底~~ ✅ Wave3 完成(stopChat 本地先复位 streaming + clearStreamWatchdog,防审批态看门狗已 clear + AiCompleted 竞态丢失卡死)(commit 9e2aeff) +- [x] AR-6 ✅ wave8(f82dd8b)已落地 Low 失败语义(audit.rs:312 emit AiToolCallCompleted 非 AiError,错误回填 tool_result 让 LLM 自处理;todo 原引 agentic.rs:195 过时,df-ai 重构后 stream 在 provider.rs/anthropic_compat.rs)(emit AiError 置 streaming=false 但 agentic loop 续跑,残留文本 flush 又"完成",状态紊乱)— audit.rs + agentic.rs:195 +- [x] AR-7 ~~clean 无 UI 入口~~ ✅ Wave3 完成(crud.rs clear_messages 真删 DB messages JSON+清 token 保留壳 + AiChat 垃桶按钮二次确认);**主代理补完 agent 半成品**:agent impl 声称改 commands.rs ai_chat_clear 调 clear_messages,实际 diff 零改动(self_boundary_check 造假),审查 semantic_check 正确抓到 gap(commit 9e2aeff) + +**P2** +- [x] ✅(batch39·2026-06-16·workflow ww4wb66br) AR-8 delta 节流+滚动 — **前端已存在**(onContentChange/isNearBottom 80px 阈值等效 userScrolledUp + showBackToBottom 回底按钮),**后端合批负 ROI 不改**(rAF 节流链 ARC-08 已吸收重 parse,加合批破 heartbeat 30s 语义)。B-53 snap 优化同批落地 +- [x] AR-9 ~~friendlyError 硬编码中文~~ ✅ Wave3 完成(friendlyError 全走 i18n.global.t + zh/en 双语补 4 key;TS2589 用 as any 规避 vue-i18n 深度泛型)(commit 9e2aeff) +- [x] AR-10 ~~想法→灵感迁移残留~~ ✅ 已统一(13 文件批量:i18n zh-CN + 后端错误 + LLM 描述/提示词 + store toast;en 待定 Ideas/Idea、docs 注释低优先略)(commit 65c475b) +- [x] AR-11 ✅(定时走查第3轮,2026-06-16,dc27e79) — ~~数据变更联动刷新~~(推荐方案A 后端 emit + store 监听)— 跨层 — 详见 [审查第五章](./02-架构设计/aichat审查报告-2026-06-14.md) — **勘察完成(2026-06-16,wxflofhf2)**:feasible/risk 中/跨 8 文件(audit.rs/commands.rs/tool_registry.rs/stores/project.ts/useAiEvents.ts/Projects/Tasks/ProjectDetail.vue)。方案A 方向合理(emit df-data-changed+entity/action 分类+store listen)但勘察 implPlan 含伪代码错误(std::env::var/.match Rust 不存在=agent 幻觉)+碰 7 近期活跃文件含未提交 P0 改动的 commands.rs。**暂缓(原)**:等本批提交后主代理重设计 emit 点 — **第2轮定时走查复核(2026-06-16):后端已实施 ✅,前端半接通 🔴** — 后端 `emit_data_changed`(audit.rs:259-270)+ 自动执行路径(audit.rs:351)+ 审批路径(commands.rs:175)三处 emit 就位且逻辑正确;前端工作区(project.ts:31-55,109-110)定义 `startDataChangedListener`/`stopDataChangedListener` + export,**但 App.vue onMounted 未挂载 → listener 永不 attach → 数据变更后列表不刷新(AR-11 功能失效,后端 emit 成死事件)**。修:App.vue onMounted 调 `startDataChangedListener()` + onUnmounted stop(对齐 workflow listener 生命周期)。详见 [定时走查-2026-06-15-第2轮.md](./05-代码审查/定时走查-2026-06-15-第2轮.md) + > **第3轮(2026-06-16):listener 已 attach ✅** — dc27e79 `App.vue:218` onMounted `await startDataChangedListener()` + :258 onUnmounted stop。AR-11 功能闭环(后端 emit df-data-changed + 前端 listen 按 entity 调 load)。详见 [定时走查-2026-06-15-第3轮.md](./05-代码审查/定时走查-2026-06-15-第3轮.md) + +### 🔴 流式响应中断误报排查(2026-06-16) + +> 现象:AI 工具(write_file 等)执行成功(文件真写入 14.8KB),前端却弹「⚠ 响应中断(长时间无数据流)」误报,用户误以为失败重发。**根因(架构层,非偶发)**:工具执行后 agent loop 进入下一轮 LLM 请求,**等首 chunk 的静默期无心跳**——后端 `stream_recv.rs:113` idle timeout 用 `tokio::time::timeout(120s, stream.next())` 被动等 chunk,静默期不发任何事件;前端 `useAiEvents.ts:115` watchdog 仅靠事件 reset,130s 无事件 → `onStreamTimeout` 误报。触发条件:**write_file 工具本身毫秒级本地写,不超时**;「响应中断」发生在写入完成后、下一轮 LLM 回复到来前的静默期 > 130s。静默源待后端日志定:① LLM 续生成首 token 慢(GLM 处理含新写文档的长 context)② agent loop 异常退出漏发 AiCompleted/AiError ③ 事件丢失(broadcast Lagged)。**核心缺陷**:静默期无心跳,前端无法区分「LLM 在跑」vs「真断」,统一报中断。**额外**:前端 watchdog 130s < 后端总等待(connect_timeout 30s + idle 120s = 150s),可能前端先误报而后端连接仍健康,违背 watchdog「后端先报真错、前端仅兜底漏发」初衷。文件写入是工具独立副作用,与流是否健康无关。链路:`agentic.rs:139` stream_llm 返回 → `:202` process_tool_calls(快)→ loop 下轮 stream_llm 发新请求 → 静默等首 chunk。 + +- [x] B-260615-02 ✅(批1,2026-06-16) — **[P1 体验]** 流式静默期心跳(治本)。修法:`stream_recv.rs:113` `tokio::time::timeout(STREAM_IDLE_TIMEOUT, stream.next())` → `tokio::select!`,加 `heartbeat.tick()`(30s)分支 emit `AiChatEvent::AiHeartbeat { conversation_id }`;`AiChatEvent` 枚举(ai/mod.rs)+ `api/types.ts` 加 variant;前端 `useAiEvents.ts:115` reset 条件已自动覆盖新事件类型(零改动)。真断连时 120s 无 chunk 仍 emit AiError(现有逻辑保留)— stream_recv.rs:113 + src-tauri/src/commands/ai/mod.rs AiChatEvent + src/api/types.ts +- [x] B-260615-03 ✅(批4,2026-06-16) — **[P2 治标]** `onStreamTimeout` 文案区分:触发时检查最后是否有 `completed` 工具调用,有 →「工具已执行完成,后续回复中断,可点继续」;无 → 原「响应中断」— src/composables/ai/useAiStream.ts:21-33 + +### 🟠 流式可靠性链路隐患核对(2026-06-16) + +> 系统性精读流式链路(stream_recv.rs / useAiSend.ts / agentic.rs / audit.rs / secret.rs)+ 1 Explore 代理广扫。**去重代理 10 条臆测/设计误判**(`_startPromise finally`伪竞态 / `stopChat`本地复位=AR-5 设计 / `sanitize`仅持久化视图=B-260614-AC3 设计 / `join_all`并行非阻塞 / `resolve_provider_secret` DB 优先=FR-S1 兼容老库设计 单测:114 锁定),确认 4 条真隐患(心跳见 B-260615-02 不重复)。**注**:B-260615-04 与 B-260615-02 同改 `stream.next()` → `select!`,可一次性合并实施。 + +- [x] B-260615-04 ✅(批1,2026-06-16) — **[P1]** stop 响应延迟最差 120s。`stream_recv.rs:107` `stop_flag.load()` 在 `tokio::time::timeout(120s, stream.next())` **之前**检查;用户点停止时若正阻塞在 `stream.next()` 等 chunk,要等 chunk 到或 120s idle timeout 才轮到下次 stop_flag 检查。修法:`stream.next()` 改 `tokio::select!` 加 stop_flag 轮询分支(或 `tokio::sync::Notify`),stop 即时打断 — stream_recv.rs:105-120 +- [x] B-260615-05 ✅(批1,2026-06-16) — **[P1]** 流尽 + 空内容 + 无 finished 静默成功。`stream_recv.rs:178` `if !finished_received && (!full_text.is_empty() || !tool_calls_acc.is_empty())` 才报错;空内容无 finished 不报错返回 `Some(空)` → `agentic.rs:197` `!has_tool_calls` break → 正常 emit AiCompleted,**用户看空回复无错误提示**。修法:流尽未收 finished 一律判异常 emit AiError(不区分内容空否),不静默成功 — stream_recv.rs:177-186 +- [x] B-260615-06 ✅(批4,2026-06-16) — **[P2]** sendMessage IPC 失败未清 watchdog。`useAiSend.ts:70-76` catch 回滚 streaming + 移除空气泡,但 :61 启动的 watchdog 未 `clearStreamWatchdog()`;130s 后 `onStreamTimeout` 触发 push 假错误消息(streaming 已 false 无状态危害,但错误气泡误导用户)。修法:catch 补 `clearStreamWatchdog()` — useAiSend.ts:70-76 +- [x] B-260615-07 ✅(批4,2026-06-16) — **[P2]** approveToolCall 乐观置 running 无兜底。`useAiSend.ts:80-98` 审批 IPC 后等后端事件转 completed/rejected,后端异常不回则按钮永久 `running`;审批态 watchdog 已 clear(`useAiEvents.ts:162`)无心跳兜底。修法:approve 后重启 watchdog(`resetStreamWatchdog`)覆盖审批执行→续生成窗口,或加审批专用超时 — useAiSend.ts:80-98 +- [x] B-260615-08 ✅(批1,2026-06-16) — **[P0 用户可感]** 审批通过后对话卡死(create_task 等审批工具通过、任务创建成功后对话不再响应)。链路:`commands.rs:147-189` 审批执行成功 + tool_result 回填 + emit AiToolCallCompleted/AiApprovalResult → `:187 try_continue_agent_loop`(agentic.rs:255)→ spawn 新 loop。**根因方向(静默 return,待后端日志精确)**:`try_continue` + `run_agentic_loop` 多个 return 点**不 emit 收尾事件** → 前端 streaming=true 永久卡:①`agentic.rs:261` `should_continue=false`(generating 被复位 / pending_approvals 非空)静默 return ②`:263-266` `get_active_provider Err(_) => return` 静默无事件 ③spawn 后 stream_llm 空回复静默成功(见 B-260615-05)/ LLM 不响应 120s idle→AiError(会报错非静默)。**watchdog 兜底延迟**:审批态 watchdog 被 clear(useAiEvents.ts:162 AiApprovalRequired),审批通过 AiApprovalResult reset 130s,静默 return 后最长 130s 才 `onStreamTimeout` 兜底(用户感「卡住」即此窗口)。**修法方向**:①try_continue 所有 return 点显式 emit AiError/AiCompleted 收尾(get_active_provider Err / should_continue false 均不静默)②前置依赖 B-260615-05(空回复不静默成功)③可配合 B-260615-02 心跳缩短感知延迟。— source:用户报障(06-15),src-tauri/src/commands/ai/commands.rs:187 + src-tauri/src/commands/ai/agentic.rs:255-291 + src/composables/ai/useAiEvents.ts:161-162 + +### 🟣 全栈审查待修项(2026-06-14) + +> 5 代理并行审查 Rust+Tauri+Vue 全栈(~25k 行)产出,详见 [全栈代码审查报告-2026-06-14.md](./05-代码审查/全栈代码审查报告-2026-06-14.md)。已去重:条件引擎/路径 canonicalize/localStorage 已在本看板他处记录;`do_promote` 误判已澄清(IPC 层真建项目,crate 留 TODO)。 + +**P0 — 安全** +- [x] FR-S1 ~~api_key 明文三连~~ ✅ **完全修**(2026-06-16 安全批次):IPC list 返回 mask(首尾4+••••) + 编辑 apiKey 空→保留原DB值 + 前端 realm 不持明文;**DB 明文已迁移 keyring**(secret.rs:DB api_key 恒空 + OS keyring 存真实密钥 + 启动一次性迁移;消费点 resolve_provider_secret 兼容老库;cargo check ✓) — commands.rs ai_list_providers/ai_save_provider + Settings.vue — ⚠️删除闭环漏清(2026-06-16 审查发现):ai_delete_provider 未调 delete_provider_secret,keyring 残留,见 [CR-260615-01](#-近期改动审查待修项2026-06-15) +- [x] FR-S2 ~~read_file TOCTOU + write 无限制~~ ✅ read 单次 File::open 取 metadata+read 消 TOCTOU + write 加 1MB 上限(commit 5367f19) +- [x] FR-S3 ~~approve decision 无校验~~ ✅ 加 decision 非空校验(防 "" 透传;HumanNode options 非空时还校验 ∈ options)(commit 698a874) +- [x] FR-S7 ✅ 已修(2026-06-14 安全批次):write_file 加 .bak 备份 + tmp→rename 原子写 + 缩减>90% tracing::warn! + 返回 old_size;cargo check ✓。原:write_file 覆盖已有非空文件无确认/备份(**2026-06-14 实测事故**:会话3473fcb7 AI 误把 write_file 当 edit 用,只传头部3行把 PROGRESS.md 762行/72KB 覆盖成248字节;FR-S2 的1MB上限防不了此场景)— tool_registry.rs write_file handler — 修法:覆盖非空文件前自动备份 .bak 或检测目标存在强制走 edit_file;写入后返回新旧大小对比,差异巨大时 warn +- [x] FR-S8 ✅ 部分修(2026-06-14 安全批次):主体 ①② T-260614-04 resolve_workspace_path 双层校验已修,③ Windows Rust std Path::starts_with 已大小写不敏感(虚报),残余 list_dir_recursive entry.file_type() 替 metadata 防 symlink 跟随逃逸目标信息 + 不递归 symlink;cargo check ✓。原:**路径 sandbox 系统性逃逸**(2026-06-14 走查,审查报告 §10):①validate_path 子串 `..` 检测对绝对路径无效 ②canonicalize 仅覆盖已存在路径(write_file 新建 + symlink 父目录漏)③Windows starts_with 大小写敏感坑 — tool_registry.rs:19-21,53-69 — 修法:统一 canonicalize(不存在取最长存在前缀)+ 大小写不敏感 prefix 比较 + parent 校验 + symlink 不跟随(file_type 替 metadata) + +**P1 — 体验/竞态** +- [x] FR-R1 ~~switchConversation 无切换 token~~ ✅ 加 _latestSwitchId 丢弃过期响应(commit 698a874) + wave(2026-06-16,待commit)补第二 await(`pendingToolCalls`)后二次比对 `useAiConversations.ts:108`,防 A→B 快切用 A 的 pending 覆写 B +- [x] FR-R2 — **[降级存疑]** 看门狗主/分离窗口互踩 — 审批态已 clearStreamWatchdog(useAiEvents:143)+分离窗口独立 realm 不共享 state,"互踩"前提不成立,评估维持 +- [x] FR-R3 ~~liveEvents 无限增长~~ ✅ push 后限长 200 条(commit 8dbe3d2) +- [x] FR-C1 ~~formattedEvents 时间漂移~~ ✅ 事件入数组固 _ts(project.ts push + ProjectDetail 用 _ts)(commit cf18678) +- [x] FR-C2 ~~net_sentiment 矛盾~~ ✅ 统一三档(模板>0/<0/===0 + sentimentClass 同源 + i18n neutral)(commit cf18678) + +**P2 — 其余(见审查报告 §2-6)** +- [x] FR-S4 ✅ wave4(36d68dd) SKILL.md 注入加头尾成对标注(仅供 AI 参考/非用户消息/技能说明结束) +- [x] FR-S5 — **[降级文档]** ai_approve 无对话归属校验属实,但 UI 隔离+restore 只载当前对话历史限制实际触发,本地单机低危,降级为文档说明(审批不严格按对话隔离,设计取舍) +- [x] FR-R4 ✅ wave4(36d68dd) complete() 加 60s 单请求 timeout + is_timeout 中文错误(不影响 stream) +- [x] FR-C3 ~~Settings timer 泄漏~~ ✅ onUnmounted 清全部三个 timer(commit cf18678) +- [x] FR-C4 ~~MIGRATION_VERSION 死常量~~ ✅ 删(零代码引用,run() if 链自管版本)(commit cf18678) +- [x] FR-C5 ~~AiConversationDetail 缺 readonly~~ ✅ 加 readonly?: boolean(commit cf18678) +- [x] FR-D1/D2/D4/D5/R5 ✅ wave5/6 部分完成(4a95f6a/4b5f096):dag O(V+E) 建 adjacency 索引 / search_vector 显式 14 列 / replace_tool_result_content 反向 rposition / useConfirm 抽 4 视图 / 前端 findToolCall 反向遍历 +- [~] FR-P1~P6 剩余性能项 — ~~单连接 Mutex~~ ✅(batch29·2026-06-16 评估非瓶颈:Tauri 本地单用户+WAL 写本串行化,r2d2 池收益近零属过度优化,保持现状) + ~~migrations if 链~~ ✅(batch29·2026-06-16 if 链→step 数组循环,零行为变更,cargo test -p df-storage 11 集成+迁移路径全过) + ~~tool_registry truncate(50)+注释散落提常量~~ ✅第⑯批销账 06-15 抽 MAX_LIST_RESULTS 常量 4 处 — FR-D3 三子项全闭环,见报告 §5-6 +- [x] FR-D6 ✅ ~~任务工具集不完整(缺 delete_task/update_task)~~(commit 36d68dd 补全):tool_registry.rs 现有 create_task(:241) + update_task(:265) + delete_task(:287,硬删对齐 commands::task::delete_task,注释「清理孤儿任务时务必用本工具,不要误用 delete_project」) + list_tasks(:140) 四工具齐全。 +- [x] FR-D7 ✅ wave4(36d68dd) 抽 `bind_dir_to_project(repo, id, path, stack_opt)`,create_project/bind_directory 共用,删原 :171 TODO;主代理核查补回 create_project 响应 stack 字段 +- [x] FR-D8 ✅ wave4(36d68dd) create_idea schema 补 priority(可选 integer) + 魔法数字默认值注释(idea=1/task=2 与 IPC default_priority 对齐) + +### 🔴 近期改动审查待修项(2026-06-16) + +> 工作区 FR-S1/S7/S8 + 近 5 提交审查(主代理 + 2 子代理并行),详见 [近期改动代码审查-2026-06-15.md](./05-代码审查/近期改动代码审查-2026-06-15.md)。共 🔴1 🟡7 ⚪5。 +> **注**:① 推翻 todo:74 FR-S1「完全修」——密钥迁移/读取/写入闭环全对,但**删除路径漏清 keyring**(secret.rs:44 有 delete_provider_secret,ai_delete_provider 未调用)。 + +**P1 — 安全闭环** +- [x] CR-260615-01 ✅ wave(2026-06-16,待commit) — ~~删 provider 漏清 keyring~~ commands.rs:394-399 DB 删后调 `delete_provider_secret` + warn 不阻断(keyring entry 永久残留,同用户进程可读;同 id 复用旧密钥复活)— commands.rs:390-401 — 修法:删 DB 后调 `delete_provider_secret`(失败仅 warn 不阻断,DB 已删则残留 keyring 无消费方) + +**P2 — DRY/收口** +- [x] CR-260615-02 ✅ confirm composable (第⑩批 2026-06-16) — ~~AiChat.vue 第五份 confirm 未迁 useConfirm~~ 实为本地 confirm 状态机(confirmState+confirmDialog+answerConfirm)与 useConfirm 同构未复用,迁后复用 composable,行为零变化;AiChat.vue:371-374 +- [~] CR-260615-03 ✅ wave(2026-06-16,待commit) — 低风险子项收口:**已做 3** = `.bak·.tmp-write 噪音过滤`(tool_registry.rs:535 加 `is_noise_file` 后缀过滤+`list_dir_recursive` 跳过,默认 skip_noise=true 已开)+`COLS 列数断言`(crud.rs:KNOWLEDGE_COLS/COL_COUNT/COLS_WITH_EMBEDDING 模块级常量+test 断言 14/15 列)+`R-PD-11 抽 find_path_conflict`(见下);**已解跳过 1** = dag·executor 邻接表非重复(executor 用 `adjacency_in` 前驱表,dag.topological_layers 用 `adjacency_out` 后继表方向不同;`Dag::predecessors/successors` 已无调用方属死码清理归 ARC);**未做留 todo** = projectNameById 改 find(无此函数,audit.rs:45 `resolve_project_label` 已用 repo.get_by_id O(1) 查询,反模式不存在);audit.rs Err 误报 / openai source 丢失 / ToolCard id 类型归一(行为变更或前端,不在本批) + +**P2 — 块级 memo 实施走查(ARC-260615-08,2026-06-16)** +- [x] CR-260615-04 ✅(batch57·2026-06-16·workflow w4h5ygko1+主代核查,vue-tsc 0err+cargo check 0err) — ~~splitBlocks 手写正则切块改 `marked.lexer()`~~ 已完成改造,AiChat.vue splitBlocks 函数已从手写正则改为 marked.lexer() — AiChat.vue splitBlocks — 详见 [自研块级memo流式渲染审查-2026-06-15.md](./05-代码审查/自研块级memo流式渲染审查-2026-06-15.md) ① +- [x] CR-260615-05 ✅ parseBlock DRY (第⑩批 2026-06-16) — ~~parseBlock/parseBlockNoCache DRY~~ parseBlock 内部改调 parseBlockNoCache 去重(原 _purify.sanitize(_marked.parse()) 两处重复收敛为一处),行为零变化 — AiChat.vue +- [x] CR-260615-06 ✅(2026-06-16 核实已实施) — ~~loadMarkdown 就绪后流式不主动重算~~ AiChat.vue:1039-1043 已有 `watch(mdReady, (ready) => { if (ready && streaming && currentText) scheduleStreamParse(currentText) })`,mdReady 翻转时主动触发末块重算。原 todo 标注未实施系过时判断 — AiChat.vue loadMarkdown +- [x] CR-260615-07 ✅ blockCache LRU (第⑩批 2026-06-16) — ~~_blockCache LRU+escapeHtml 抽~~ _blockCache 超 limit 由整体 clear 改删最早一条(Map.keys().next().value LRU 语义,边界 > 改 >= 防超限)+escapeHtml+replace 两处重复抽 escapeFallback 函数(renderStreamingMd/renderMd 兜底均调),行为零变化 — AiChat.vue + +### 🔵 全局代码 review 待修项(2026-06-16) + +> 7 维度并行深入扫(DRY/架构/潜在bug/简洁性/安全/AI可靠/工作流引擎),详见 [全局代码review-2026-06-15.md](./05-代码审查/全局代码review-2026-06-15.md)。 +> **P1 可执行 6 项 (R-P1-1~6) + P2 可执行 13 项 (R-P2-1~13) 已全闭环**(批1/2/3 主代理独立核查 cargo workspace exit 0 + 5 crate test 共 126 passed)。闭环明细见全局review-2026-06-15.md §推进状态(批1=4 项 R-P1-2/3/5/6 + 批2=2 项 R-P1-1/4 + 批3=13 项 R-P2-1~13 四域 A/B/C/D)。下为**需设计**待立项项(R-PD-3 条件引擎去重 T-260614-11)。 + +**P1 需设计(进设计文档)** +- [x] R-PD-1 ✅(批2,2026-06-16) — **[P1 security]** 编辑 provider 空 api_key 默默清 DB 明文致密钥永久丢失(未迁移态 keyring 空 + DB 非空时改 name/base_url 触发)— `commands.rs` ai_save_provider:329-355 + crud.rs INSERT OR REPLACE 全字段覆盖 — 修法:空 api_key 时确认 keyring 有/DB 有再清,keyring 无且 DB 非空先即时迁移补密钥 — source:全局review §P1需设计 +- [x] R-PD-2 ✅(批2,2026-06-16) — **[P1 security]** run_workflow 经 ScriptNode 执行前端任意 shell(无白名单/无工作目录锚定/无审批,独立于 AI 工具 RiskLevel 链)— `workflow.rs`:36-44 + `script_node.rs`:34-42 + `shell.rs` — 修法三选一:①state.rs build_registry 不注册 'script' 掐断 ②限定工作目录在绑定项目 path 内 + 高危命令走 HumanNode 审批 ③ScriptNode 命令白名单 — source:全局review §P1需设计 + +**P2 需设计** +- [x] R-PD-4 ✅(P0批,2026-06-16,待commit) — ~~keyring 迁移失败阈值警告~~ MIGRATION_FAIL_THRESHOLD=3(sidecar .devflow-keyring-failcount 跨启动持久化 provider_id=count)+read/write/record/clear_migration_failcount 辅助+migrate_secrets_to_keyring 失败分支 record_migration_fail 达阈值升级 warn(明文滞留风险+3 条排查建议)+成功 clear 清零;不改兼容时序(仍保留明文下次重试) — secret.rs +- [x] R-PD-5 ✅(P0批,2026-06-16,待commit) — ~~approve IPC 校验 decision∈options~~ 加 options:Vec 参数(前端从 HumanApprovalRequest 事件透传,IPC 无法访问节点 config)+校验 options 非空且 decision∉options→Err「审批决策非法」(规则同 HumanNode 下游兜底);✅闭环(wcvigw3z4):前端 stores/project.ts:259 补传 options(state.pendingApproval.options ?? [] 从 HumanApprovalRequest 事件 payload 取),触发后端 decision∈options 校验 — workflow.rs +- [x] ✅(第㉗批 2026-06-16·已 c7386a1) R-PD-6 — AiSession 单例:try_continue 读 active_conversation_id 竞态(靠 switch readonly 间接保护,脆弱耦合)— `agentic.rs`:255-291 从 pending_approvals 取 conversation_id 解耦 +- [x] R-PD-7 ~~LlmProvider trait 抽象缺口:name() 语义错位 + supported_features/ProviderFeatures 死代码~~ ✅ 已修(删 ProviderFeatures + supported_features trait 方法 + 两 provider impl;补 endpoint() 默认方法供 401/网络错误诊断,两 provider override 返真实端点;name() 语义错位单独立项不改) +- [x] R-PD-8 — AiProviderRecord 整条穿透 IPC 边界(DB schema 演进直接破坏前端契约,models 字段 provider 返串/conversation 返数组不一致)— `commands.rs`:282-295 定义 ProviderDto/ConversationSummary 映射层(对抗分析否决 2026-06-16, 见 :537, 5个IPC手动对齐可接受) +- [x] R-PD-9 ✅ display_hint (第⑩批 2026-06-16) — ~~命令层臃肿~~ audit 工具名→文案映射作 display_hint 注册进 AiToolRegistry(消除双份);agentic loop 下沉 df-ai 较大进 todo — agentic loop/tool_registry 717 行/audit reason 映射堆 commands/ai,无法被 df-nodes/AiNode 复用 +- [x] ✅(主代理·2026-06-16) R-PD-10 — crud.rs 110 处 `.map_err(|e| Error::Storage(e.to_string()))` 统一 df-storage 局部 `storage_err` helper(原 todo「85处 e.to_string」过时,实际 crud.rs 110 处 Error::Storage 包装;commands 层 err_str 早已有 mod.rs:23)。cargo check -p df-storage exit 0 +- [x] R-PD-11 ✅ wave(2026-06-16,待commit) — ~~目录防重复绑定逻辑两处重复~~ 抽 `ProjectRepo::find_path_conflict`(`df-storage/crud.rs:575`,接收已规范化 target+exclude_id,内含 list_active+排除+规范化比较);两处 use 替换:`project.rs::find_binding_conflict` 委托(去 inline find)+`tool_registry.rs::bind_dir_to_project` 委托(去 for 循环);路径规范化复用 `df_project::scan::normalize_path`(df-storage 不依赖 df-project,故本 crate 镜像同算法 `normalize_stored_path` 比较存库路径,已注明须同步) +- [x] R-PD-12 ✅ 假能力删 (第⑩批 2026-06-16) — ~~run_workflow AI 工具 no-op 桩~~ 删假能力(prompt/audit/ToolCard 当真实能力宣传,LLM 调用走审批拿空结果,体验断裂) — `tool_registry.rs`:383-390 + `prompt.rs`:57 + `audit.rs`:120-123 + ToolCard.vue:367 (与 R-PD-2 协同) +- [x] R-PD-13 ✅(P0批,2026-06-16,待commit) — ~~Lagged 静默丢事件~~ 最小兜底:单行 warn 升级带 execution_id+lagged 字段结构化 warn+注释(broadcast 不暴露被丢事件类型/关键终态事件丢失致 finished 不触发循环不 break 前端永久收不到结束/依赖 DB 轮询兜底)+三后续方向(提升广播容量/持久化队列重发/forward watchdog 超时);重发复杂度超本 todo 范围 — workflow.rs +- [x] R-PD-14 ~~df-ideas promotion IdeaPromoter/PromotionPolicy/try_promote 死代码 + do_promote 空壳 TODO~~ ✅ 已修(删 IdeaPromoter/PromotionPolicy/try_promote/do_promote;PromotionResult 保留——promote_idea IPC 返回类型 + idea.rs:99,155 实例化引用,作 IPC 边界类型无法清) + +### 🔴 工作区多角度走查待修项(2026-06-16) + +> 4 路并行代理走查工作区 22 文件 547 行(AiChat/View/stores/composables+ToolCard),详见 [工作区多角度走查-2026-06-15.md](./05-代码审查/工作区多角度走查-2026-06-15.md)。3 P0 功能 bug + 1 P1 i18n + DRY/健壮性一组。 + +**P0 — 功能 bug** +- [x] B-260615-34 ✅(定时走查复核,2026-06-16) — ~~selectType snake_case 对齐~~ `project.ts:273` 已改 `select_type: ... ?? 'single'` + 正确注释「Tauri 2 IPC 不转 camelCase,须 snake_case 对齐后端 workflow.rs:211」;后端 `workflow.rs:211` 同 `select_type: Option`,前后端契约对齐。**纠正上轮过时判断**(上轮压缩前快照仍 camelCase,之后 `80b9243`「P0 bug 修复」批已修;后端代理经 IPC 签名推断「前端已对齐」结论正确) — stores/project.ts:273 — 详见定时走查 P0复核.md +- [x] B-260615-32 ✅(定时走查第2轮复核,2026-06-16,f93b758) — ~~流式收尾清队列~~ **[原 P0]**drainQueue 仅 AiCompleted 触发(useAiEvents:218),onStreamTimeout/AiError/stopChat 三路径不清 state.queue → 生成中输入的消息丢失无提示。approveToolCall catch 同漏。修:三路径 + approve catch 补 `state.queue=[]` + 提示。关联 B-260615-22(状态不同步,不同角度) — useAiSend.ts:36-42 + useAiStream.ts onStreamTimeout + useAiEvents.ts AiError case + useAiSend.ts:82-107 — 详见走查 ② + > ⚠️ **第2轮复核更正(2026-06-16):已修 ✅** — f93b758「修复: B-32+33 流式收尾」:`useAiStream.ts:28` `state.queue=[]`(onStreamTimeout) + `useAiEvents.ts:225`(AiError) + `useAiSend.ts:152`(stopChat)/124(approveToolCall catch)四路径全清队列。上轮「仍未修」为过时快照(working tree 当时旧版,与 B-34 同)。详见 [定时走查-2026-06-15-第2轮.md](./05-代码审查/定时走查-2026-06-15-第2轮.md) +- [x] B-260615-33 ✅(定时走查第2轮复核,2026-06-16,f93b758) — ~~回滚 running toolCall~~ **[原 P0]**approveToolCall 乐观置 running,后端 hang 时看门狗触发 onStreamTimeout 只复位 streaming 不碰 toolCalls[].status → running 态只渲染骨架(ToolCard:24) 审批按钮不显 → 无重审入口。**B-260615-07 残留**(B-07 加了 watchdog 但回调没回滚 status)。修:onStreamTimeout 扫一遍 running toolCall→rejected — useAiStream.ts:21-49 — 详见走查 ③ + > ⚠️ **第2轮复核更正(2026-06-16):已修 ✅** — f93b758:`useAiStream.ts:43-45` onStreamTimeout 单遍反向扫描 running toolCall→rejected(合并探测 completed 为单次 O(n))。上轮「仍未修」为过时快照。详见 [定时走查-2026-06-15-第2轮.md](./05-代码审查/定时走查-2026-06-15-第2轮.md) +- [x] B-260615-35 ✅(定时走查第3轮复核,2026-06-16,b08adcb) — **[原 P0]** broadcast `Lagged` 兜底仅 warn → 关键终态事件丢失时前端永久卡死。`workflow.rs:90-110` forward 任务靠 `finished = matches!(event, WorkflowCompleted|WorkflowFailed)` 退出,broadcast 滑动窗口 Lagged(n) 丢 n 条最旧事件,**代码注释已识别风险但仅 warn 无兜底**;若 Lagged 恰丢终态事件,forward 永远 `rx.recv().await` 等不到 finished → 前端审批/完成弹窗永久卡死,仅靠 DB 轮询兜底(实时性差)。修:Lagged 分支后累计次数/单次 n 过大时查 `WorkflowRepo::get_by_id` 看终态,补 emit workflow-event + break;需把 `state.db.clone()` move 进 forward 任务(当前仅 executor spawn 持有)。低频但无自愈 — src-tauri/src/commands/workflow.rs:90-110 — 详见定时走查 P0复核.md(后端代理 ②) + + +**P1/P2 — DRY + 健壮性** +- [x] CR-260615-09 — **[P1]** 五份 .ai-md 样式逐字重复 ~350 行(AiChat/ProjectDetail/Ideas/Knowledge/TaskDetail) → 抽全局 `src/styles/ai-md.css` 或 `` 组件。TaskDetail.vue:263-333 新文件继续抄(CR-09 复发,定时走查 06-15 确认第 5 份)。B-24/25 复制粘贴源头 — 详见走查 ⑤ + 定时走查 P0复核.md (第⑫批销账 06-15) +- [x] CR-260615-10 — **[P2]** useMarkdown 加 `useRendered(getText)` 辅助,消除 renderedDesc computed + onMounted(loadMarkdown) 三处重复(与 CR-09 同源) — 详见走查 ⑥ (第⑫批销账 06-15) +- [x] CR-260615-11 ✅ composable健壮性 (第⑩批 2026-06-16) — ~~composables+ToolCard 健壮性一组~~ **[原 P2]** ⑦⑧⑨⑩⑪⑫ fddca9d 已实施(JSON.parse 逐条容错/argString 去 as any/复用 findToolCall/AiHeartbeat 显式 case/approveHumanApproval 签名收敛/formatToolName 兜底)。原问题:⑦switchConversation JSON.parse 无逐条容错(useAiConversations:81) ⑧args 消费 `as any` 类型逃逸(ToolCard) ⑨approveToolCall 重复查找(findToolCall) ⑩useAiEvents switch 缺 AiHeartbeat case ⑪approveHumanApproval 签名歧义 ⑫ToolCard 兜底。**⚠️⑪ 实施引入死逻辑**:workflow.ts:67-68 decision 三元两分支相同(见 CR-260615-18)— 详见走查 ⑦~⑫ + 定时走查第2轮.md +- [x] CR-260615-12 — **[P3]** 可选一组:_toastTimer 卸载未清(AiChat:436) + markdown 外层 span→div + SVG 常量散落 + appSettings 注释 + index 空行 + 路由 icon 语义 + options shape 校验 + projectNameById 共享 + useConfirm dev 警告 + i18n `t` as any — 详见走查 ⚪ 区 (第⑫批销账 06-15) +- [x] CR-260615-18 ✅ decision 死逻辑 (第⑩批 2026-06-16) — ~~decision 三元两分支相同死逻辑~~ (CR-11⑪ 实施引入):`workflow.ts:67-68` `selectType === 'multiple' ? decisions[0] ?? '' : decisions[0] ?? ''` 两分支返回值完全一致,三元判断无意义,误导维护者以为单/多选差异化。后端 workflow.rs:291-296 兜底(空 picked+非空 decision push)。修:直接 `const decision = decisions[0] ?? ''` 删三元 — src/stores/project/workflow.ts:67-68 — 详见定时走查第2轮.md +- [x] CR-260619 ✅ action 冗余 (第⑩批 2026-06-16) — ~~action 字段~~ (保留为未来差异化刷新预留,注释已明确)定义 emit 但前端不消费:DfDataChangedPayload.action(api/types.ts:157) 后端 emit_data_changed(audit.rs:243-256) 每次计算,前端 listener(project.ts:33) 只解构 entity,action 忽略,刷新统一全量 load。契约冗余(白算白传)。修:删 action 或做差异化刷新(delete 本地移除)— src/api/types.ts:157 + project.ts:33 — 详见定时走查第2轮.md +- [x] CR-260615-20 ✅ App.vue try/catch (第⑩批 2026-06-16) — ~~stopDataChangedListener try/catch~~ 走查误判撤销:接收方核 workflow.ts:108 stopEventListener 写法一致(f574562 commit 说明),不修 — project.ts:47-55 unlisten 同步数组操作正常不抛,与 workflow.ts:108 stopEventListener 写法不一致。修:核 workflow.ts:108 对齐统一去/留 — src/stores/project.ts:47-55 — 详见定时走查第2轮.md +- [x] CR-260615-21 ✅ types 枚举注释 (第⑩批 2026-06-16) — ~~App.vue:218 `startDataChangedListener()` await 包 try/catch~~ console.error(防 Tauri listen 失败 reject onMounted 致 AR-11 静默失效)。原问题:await 无 try/catch — src/App.vue:218 — 详见定时走查第3轮.md +- [x] CR-260615-22 ✅ types 枚举对齐 (第⑩批 2026-06-16) — ~~types.ts 枚举注释对齐~~ (Task/Idea/Project status)+ 删 AiConversationSummary.models 冗余 枚举注释与后端不符(api 垂直切片,定时走查第5轮代理 B):①TaskRecord.status(types.ts:88)注释 review_ready/merged/abandoned 错,后端 TaskStatus(types.rs:165)= todo/in_progress/in_review/testing/done/blocked/cancelled;②IdeaRecord.status(:11)漏 archived;③ProjectRecord.status(:39)漏 testing/releasing。status 字段是 string 非 literal union 不阻断,但注释误导。④AiConversationSummary.models(:233)冗余(后端返但前端无消费点)。修:注释对齐后端枚举/删冗余字段 — src/api/types.ts — 详见定时走查第5轮.md +- [x] CR-260615-23 ✅ knowledge error 死断 (第⑩批 2026-06-16) — ~~B-34「Tauri 不转」注释~~ 撤销(不适用):在 ARC-05 拆分时已简化(workflow.ts:81/project.ts:58 仅风格对齐无错误说明,16aeacc 接收方核对) — project.ts:273 +- [x] CR-260615-24 ✅ 分离窗口 listener (第⑩批 2026-06-16) — ~~分离窗口 listener 双注册+永不清理~~ (Dashboard+AI窗口垂直切片,定时走查第6轮代理 B R1+R2,新功能首次深查):①R1 双窗口重复消费 ai-chat-event:后端 app.emit 全局广播(stream_recv/agentic/commands/audit 多处)发给所有 webview,主窗口(AiChat.vue:790)+分离窗口(AiDetached 渲染 AiChat)各注册 listen,两窗口 handleEvent 都触发 → delta 各 += 一次/AiAgentRound 各 push 空气泡,靠 App.vue:94 v-if detach 时卸载主窗口 AiChat 间接停但 state 模块级单例 handleEvent 仍写(脆弱隐式契约);②R2 stopListener(useAiEvents.ts:258)定义但**全仓零调用**(地面 grep App/AiChat/AiDetached onUnmounted/onBeforeUnmount 都不调)→ listener+watchdog 永久泄漏。边界影响:reattach 重叠窗口期多一条空 assistant 消息。修:AiChat.vue onBeforeUnmount 补 store.stopListener()+detach 主窗口显式 stop/reattach start,或后端 emit_to(label) 定向 — src/composables/ai/useAiEvents.ts:258 + useAiWindow.ts + components/AiChat.vue:747 — 详见定时走查第6轮.md +- [x] CR-260615-25 ✅ 分离窗口状态机 (第⑩批 2026-06-16) — ~~分离窗口状态机缺口~~ (代理 B R3/R4/R5):③R3 win.once('tauri://destroyed')置 detached=false 但不恢复 panelOpen=true(只 reattachPanel 显式设)→ X 关分离窗口后面板消失需手动 Ctrl+I;④R4 resumeInDetached(useAiWindow.ts:79-95)强行重建生成态(读 df-ai-gen 置 streaming=true push 空气泡)与后端 ai_is_generating 真值可能失配 → 假气泡/130s 后 watchdog 假超时;⑤R5 分离窗口 switchConversation(useAiWindow.ts:81)+loadConversations 内自动 switch,后端 AiSession 单例 activeConversationId 被覆盖,主窗口期间发消息可能落错会话(B-22 同类延伸)。修:destroyed 补 panelOpen=true/resumeInDetached 先 await ai_is_generating 核对/分离窗口只读不切 — src/composables/ai/useAiWindow.ts — 详见定时走查第6轮.md +- [x] CR-260615-26 ✅ AR-11 listener+Tasks 空 (第⑩批 2026-06-16) — ~~AR-11 listener 与 B-29 筛选契约交互 + Tasks 空态~~ (列表页垂直切片,代理 A R-1/Y-2):①R-1 AR-11 监听器(project.ts:38)对 task 调 loadTasks() 无参 → 全量加载,破坏 B-260615-29「store.tasks 反映当前筛选」契约(filteredGroups 二次过滤兜住结果正确,但 store.tasks 变全量+性能);②Y-2 Tasks.vue:39-71 无 loading/error/empty 兜底,加载失败或无结果时空白页(store.error/loading 零引用,对比 Projects.vue:107 至少有空态)。修:listener 对 task 按 activeProject 筛选拉/Tasks 加 v-if loading/error/empty — src/stores/project.ts:38 + src/views/Tasks.vue — 详见定时走查第6轮.md +- [x] CR-260615-27 ✅ Dashboard 一组 (第⑩批 2026-06-16) — ~~Dashboard 一组~~ (代理 B M1/M3/L4):①M1 refresh(Dashboard.vue:202-204)Promise.all 无 try/catch,任一接口失败 unhandledrejection+用户无反馈(onMounted 有 try/catch 但 refresh 无);②M3 getProjectStage(:150)status→stage 映射 planning 归 coding stage(颜色紫)但 label planning,颜色语义撒谎(cancelled 归 testing 同);③L4 决策/批注两 panel(:103-120)永远空态,CSS(:504-572)完整但模板未用=死代码。修:refresh 包 try/catch+toast/补 planning CSS 类/删死代码或接数据 — src/views/Dashboard.vue — 详见定时走查第6轮.md +- [x] CR-260615-28 ✅ knowledge store error (第⑩批 2026-06-16) — ~~knowledge store state.error 死字段~~ (error 通道前端断)+create 语义矛盾(stores 垂直切片,定时走查第7轮):①state.error(knowledge.ts:19,49,53,64,74,105)写入但**全应用零消费**(grep Knowledge.vue store.error 零命中)→ 用户遭遇加载/检索/配置失败只看到空白列表无错误提示,**CR-08「store error fallback 11 处中文」深化**:不仅中文硬编码,整个 error 通道前端断的;②create(:80-85)成功后 loadCandidates 失败把错误塞 state.error,create 已返回 record 给调用方认为成功,语义自相矛盾。修:Knowledge.vue 接 error banner/toast 或删 state.error 改 throw 由调用方 catch + create 内 loadCandidates 失败不污染 error — src/stores/knowledge.ts — 详见定时走查第7轮.md +- [x] CR-260615-29 ✅ ai messages 上限 (第⑩批 2026-06-16) — ~~ai.ts messages 单对话无上限 + knowledge 异步竞态/错误不一致~~ (定时走查第7轮):①ai.ts state.messages(useAiSend push user+ai/useAiEvents push err 审批/useAiStream push delta)单对话内线性增长,长会话+大工具结果内存可观(切换对话整体替换是减负点,同对话不轮换),建议软上限/滚动淘汰;②knowledge.ts 加载/检索/配置异步无竞态保护(messages 有 switch token,items/config 无),快速连点后返回覆盖先;③knowledge 错误处理不一致(load*/search 有 catch 兜底,create/updateStatus/archive/saveConfig/extractNow 直抛)。修:messages 软上限/items+config 加请求序号/错误处理统一 — src/stores/ai.ts + knowledge.ts — 详见定时走查第7轮.md +- [x] CR-260615-30 ✅ appSettings mutate (第⑩批 2026-06-16) — ~~appSettings useSetting 对象/数组值深 mutate 不落库~~ (定时走查第7轮边界):useSetting 双 watch 用 Object.is 守卫防循环,但对对象/数组值深 mutate(r.value.x=1)时 Object.is(v,cache[key]) 同引用恒 true → 不触发 set 落库,静默不持久化。当前无 bug(useSetting 多绑值类型,df-ai-ui 走 set 整对象替换非 mutate),但隐式契约。修:文档注明 useSetting 仅值类型,或对象值用 JSON 序列化比较 — src/stores/appSettings.ts:88-113 — 详见定时走查第7轮.md +- [x] CR-260615-31 ✅ useConfirm 并发 (第⑩批 2026-06-16) — ~~小区域收尾健壮性一组~~ (定时走查第8轮,无必修):①useConfirm 并发 confirmDialog 覆盖 resolve(useConfirm.ts:35-47),前一个 Promise 永挂(连点两删除/confirm 交错时)→ 静默中断+轻微内存泄漏,修:进入时 resolve?.(false) 打断前一个视为取消;②ToolCardList collapseInactive(:47-51)只清 expandedCards 漏清 expandedTools,已完成卡片折叠后内容展开态残留 Set,修:对称过滤或注释明确有意保留 — src/composables/useConfirm.ts + components/ToolCardList.vue — 详见定时走查第8轮.md + +### 📄 文档核对待修项(2026-06-16) + +> 4 路并行核对 docs 全量 + 根目录文档,详见 [文档全量核对报告-2026-06-15.md](./05-代码审查/文档全量核对报告-2026-06-15.md)。悬空链接 0。 + +**P0 — 文档硬伤(按文档找代码落空)** +- [x] DOC-260615-01 ✅ 文档P0重写 (第⑩批 2026-06-16) — ~~ARCHITECTURE.md 数据模型整段重写~~ (14 表 6 虚构 artifacts/connections/features/test_cases/test_runs/annotations/decisions + 7 遗漏 node_executions/branches/ai_conversations 等;workflow_defs/runs 合并 workflow_executions)— ARCHITECTURE.md:262-475 — 详见核对报告 §1 +- [x] DOC-260615-02 ✅ 文档P0重写 (第⑩批 2026-06-16) — ~~df-nodes 模块文档重写~~ (8 节点全骨架 → 实际 3 节点 ai/script/human 均完整实现;删 Docker/Git/Notify/HTTP/Subflow 5 虚构)— 03-模块文档/df-nodes — 详见 §2 +- [x] DOC-260615-03 ✅ 文档P0重写 (第⑩批 2026-06-16) — ~~df-ai 模块文档删虚构~~ router.rs/stream.rs + 已删 supported_features trait + 12→13 工具 — 03-模块文档/df-ai — 详见 §3 +- [x] DOC-260615-04 ✅ 文档P0重写 (第⑩批 2026-06-16) — ~~使用手册全面重写 7 处~~ (运行命令 bun→npm / 任务状态 4→7 态 / 优先级方向 / 节点类型 Script/Ai/Human / EventBus 非 WebSocket / 多 Provider / 知识库 Tier1)— 08-用户指南/使用手册 — 详见 §4 + +**P1 — 审查状态断层** +- [x] DOC-260615-05 ✅(2026-06-16) — ~~todo 补全局 review R-P1-1~6/R-P2-1~13 已完成回写~~ 在全局代码 review 待修项区头部补「R-P1 6 项 + R-P2 13 项全闭环(126 test pass)」汇总指针(指向全局review-2026-06-15.md 推进状态三批),原 todo 只有 R-PD-1~14 需设计项无 R-P1/R-P2 执行项追踪,现补齐。报告说全闭环但 todo 零条目,已完成区补汇总指针 — 详见 §5 + 全局review-2026-06-15.md §推进状态 +- [x] DOC-260615-06 ✅(2026-06-16) — ~~架构复核报告 §5 新发现 11 项补 todo~~ 拆 4 子项独立 todo(见下方),核对发现 **复核-新① 已解决**(`useAiConversations.ts:122` 已有 pendingToolCalls await 后二次 token 比对),只补 ③④⑪ 三项;其余 7 项已被全局 review 取代(R-PD 系列) — 详见 §6 +- [x] ✅(batch31·2026-06-16) 复核-新③ [P2] — **NodeCancelled 事件语义双标**。`executor.rs:149` 取消节点(set_cancelled 后 Err 路径)状态保 Cancelled(:145 跳 set_failed),但事件总线仍 emit `NodeFailed { error: "人工审批被取消" }`。前端若按 NodeFailed 分支判断会误归类「失败」非「取消」。修法:加 `NodeCancelled` variant 或 NodeFailed 加 `cancelled: bool` 字段。— 来源:架构复核报告 §5 新③,crates/df-workflow/src/executor.rs:145-153 + df-core/events.rs +- [x] ✅(batch30·2026-06-16) 复核-新④ [P2] — **cancel_workflow_node 无节点终态前置校验**。`workflow.rs:352` 仅校验 execution_id 在注册表,不校验 node_id 状态。对已 Completed/Failed/Cancelled 节点调 set_cancelled 会静默覆盖终态(set_cancelled 绕 is_legal)。修法:加 `match sm.get(&node_id) { Running|Waiting => {}, _ => Err }` 守卫。— 来源:架构复核报告 §5 新④,src-tauri/src/commands/workflow.rs:341-358 +- [x] ✅(batch54·2026-06-16·workflow+主代核查) 复核-新⑪ [P2] — **主窗口看门狗幽灵**。**核验更正**:原述「看门狗续计 130s 补幽灵错误」已被 CR-260615-24 兜底解决(detach→AiChat 卸载 App.vue v-if→onBeforeUnmount:1613 `stopListener()`→useAiEvents.ts:371 `clearStreamWatchdog()`,主代独立 grep 核验链路完整)。本次 workflow agent 补强:detachPanel 快照后清主窗口 `state.streaming=false`/`generatingConvId=null`(useAiWindow.ts:37-38),消除「面板重开生成态视觉残留」(进度条/spinner)。**安全性**:主/分离窗口 state 独立(各自 webview 独立 JS context,stores/ai.ts 单例仅同 webview 内共享),清主窗口 state 不影响分离窗口生成态;localStorage 快照保留供 resumeInDetached 恢复。vue-tsc EXIT=0 主代独立核验。— 来源:架构复核报告 §5 新⑪,src/composables/ai/useAiWindow.ts:37-38 + useAiEvents.ts:364-374 stopListener +- [x] DOC-260615-07 ✅(第⑲批 2026-06-16) — ~~aichat §8 表补勾~~ aichat审查报告第八章 AR-1~11 优先级表逐行补状态勾注(AR-1退役✅/AR-2~7,9~11已修✅+commit/AR-8重评🔄)+文档头二次增补行;零新增条目对齐 DOC-13 瘦身方向;状态不一致 2 项(AR-1退役/AR-8重评)按 todo 标注非矛盾 — 详见 §7 +- [x] DOC-260615-08 ✅(2026-06-16) — ~~近期改动 CR-03 未做子项拆独立 todo~~ 拆 3 子项独立 todo(见下方)。原 CR-03(line 212)内联备注「未做留 todo」未独立追踪,现拆。— 详见 §8 +- [x] ✅(batch30·2026-06-16) CR-260615-03-a [P2] — **audit.rs Err 误报「项目已不存在」**。`audit.rs:52` `resolve_project_label` 用 `_` 合并 `Err`(DB 故障/锁/连接断)与 `Ok(None)`(真不存在),DB 出错时用户看到误导性「项目已不存在」而非错误。修法:分三臂 `Ok(Some)`/`Ok(None)`/`Err`(Err 打 warn 日志回退裸 id)。零行为破坏(仅 Err 分支加日志+改文案)。— 来源:近期改动审查 §2②,src-tauri/src/commands/ai/audit.rs:50-52 +- [x] ✅(batch30 openai·2026-06-16 + batch31 anthropic:533 同源) CR-260615-03-b [P2] — **openai_compat `anyhow::anyhow!(e)` 丢 source 链**。`openai_compat.rs:414` 非超时分支用 `anyhow!(e)` 把 reqwest::Error 整体塞进 message,丢失 `#[source]` 因果链(`?` 本会保留)。修法:改 `anyhow::Error::from(e)` 保留 source。零行为破坏(仅错误追溯链增强)。— 来源:近期改动审查 §2③,crates/df-ai/src/openai_compat.rs:414 +- [x] ✅(batch30·2026-06-16) CR-260615-03-c [P2] — **ToolCard projectNameById computed 过度结构化**。`ToolCard.vue:390` computed 每次重建全量项目(含回收站)的 Map,审批卡通常仅 1-2 行用项目 id。修法:改 `projectStore.projects.find(p=>p.id===id)?.name ?? projectStore.deletedProjects.find(...)?.name`(审批场景项目数有限,find 提前退出,响应式仍由 store 数组保证)。零行为破坏。— 来源:近期改动审查 §2⑧,src/components/ToolCard.vue:390,417,441 + +**P1 — 架构/模块文档过期** +- [x] DOC-260615-09 ✅(第⑮批核对 06-15) — df-workflow 模块文档 4 处过期 **0 处需改(历史已修)**(NodeRegistry 删 Default / conditions 默认 true→false / try_recv_human_approval 已删 / set_waiting·set_skipped 已删)— 详见 §9 +- [x] DOC-260615-10 ✅(第⑮批核对 06-15) — df-storage V8→V13 **0 处需改(历史已修, V13/13表/13Repo 全对齐)**(迁移版本+表数 11→18+Repo 11→17)+ 对抗评估 idea_evaluations 设计稿标未实施 + df-execute 描述删 Docker/SSH/Git — 详见 §10-12 + +**P1 — 索引/计数矛盾** +- [x] DOC-260615-11 ✅(第⑱批销账 06-15) — 13→8 crate 多处统一(PROGRESS/INDEX 4处改值+对抗裁决/想法探索 4处历史加注「现8」)+commands 数统一**67**(主代理数行确认权威值:14+5+6+5+11+8+14+4=67,AI聊天11+对话8=19;诊断两 agent 误计 68 ai=20,实施 f2 跟随误值,主代理修正 DEVFLOW-2)+URGENT.md 已自声明停用保留(PROGRESS/INDEX/Phase1决策/对抗裁决/想法探索)+ commands 数 47/57/68 统一 68 + URGENT.md 停用标注或删 — 详见 §13/15/16(INDEX 漏列部分本会话已补,见 DOC-14) + +**P2 — 迭代/Phase/规范** +- [x] DOC-260615-12 ✅(第⑱批销账 06-15) — DEVFLOW-1~4 + Phase1/Phase2 标完成归档(DEVFLOW-2/3/4 正文 checkbox 补勾对齐头部✅;Phase1清单#1-#11全✅+计数67修正;Phase2各小节补「实际状态」2.1/2.2✅ 2.3启发式✅LLM❌ 2.4仅并行✅,task行保留原文+核对风格不强勾)(Phase1 已收尾文档仍全待实施)— 详见 §17-19 +- [~] DOC-260615-13 ◐(2026-06-16 评估·部分做) — ~~todo 肥大瘦身(大段详情移 docs 子目录)~~ **评估结论**:todo 主要「肥大」是单行长条目(超 1000 字符/条,F-260616-01=1506/AE-2025-04=1667/B-260615-08=1526/AR-11=1428 等),非多行详情块;单行长条目多已通过「详案见 docs/子目录/xx.md」+ plan 文件指针(quizzical-prancing-hennessy.md 等)外部化,详情已在子文档,inline 摘要保留助读。**本次未强行瘦身**(强行拆单行条目易碎+破坏 grep/锚点),仅评估标注。**已做收口**:①重复项 HumanNode 审批(B-03b/CR-03/复核-新③)各保留各自语义无纯重复 ②密钥迁移(FR-S1/CR-01/R-PD-1)三处分工:FR-S1=明文根治/CR-01=删除闭环/R-PD-1=编辑路径迁移,非重复。**遗留**:若未来单行长条目继续膨胀,可立专项把已完成项(AR-11/FR-R1 等)的详细勘察段移 docs/05-代码审查/ 历史归档,inline 只留一句结论 + commit。文档记录规范在 INDEX 突出 → 归 DOC-14 改造指南重写时一并 — 详见 §20-22 + +**P3 — 技术/工具文档** +- [~] DOC-260615-14 ◐(2026-06-16 评估·部分做) — ~~Tauri-IPC/View 改造指南全篇过时重写或归档 + SQLite 表数 V1-V9→V1-V13 + 锚点顿号~~ **核对结论**:①Tauri-IPC 模式(`01-技术文档/Tauri-IPC模式-2026-06-12.md`)顶部已加归档 banner(2026-06-15,指向 ARCHITECTURE.md §三),✅ 已归档不需重写;②View 改造指南(`06-前端开发/View改造指南-2026-06-12.md`)顶部已加归档 banner(2026-06-15,指向 DEVFLOW-3.Store对接实施),✅ 已归档;③SQLite 表数 **已改**:df-storage 模块文档 V1-V13→V1-V15(补 V14/V15 迁移行 + 头部/状态表/迁移机制行/文件树注释 6 处)+SQLite-CRUD 模式文档 V1-V9→V1-V15(11→14 表/9→12 索引/11→17 Repo 3 处),核对真实 migrations.rs schema_version 最大值=15;④锚点顿号(INDEX:107-109 `#四crate-结构` 渲染器差异)低危遗留不强行做。**遗留**:锚点顿号 + 路径风格统一(功能创意池:150 链接省 `./`),低危,未来 INDEX/创意池重整时一并 — 详见 §23-26 + +### 🟡 B-03b 复核后续(独立深度复核,2026-06-14) + +> B-03b 端到端补完(commit 4aa689e)后独立复核发现 2 加重 + 3 低优。**①为真实 bug**:取消链 IPC→set_cancelled→HumanNode is_cancelled→Err 通了,但 HumanNode 返 Err 后 executor run:115 `set_failed` 走 transition,而 Cancelled 不在 `is_legal` 转换图 → bail → 工作流因状态转换错误异常终止(非优雅标记取消)。补完时只验 IPC→HumanNode 链,漏验 HumanNode→executor Err 处理。 + +- [x] B-03b-R1 ~~Cancelled 转换图双踩~~ ✅ 已修(executor Err 处理加 is_cancelled 检测,已取消节点跳 set_failed,Cancelled→Failed 非法 transition 不再 bail;emit NodeFailed 仍发,状态保 Cancelled;采修法 B 不动 is_legal 转换图) +- [x] B-03b-R2 ~~零端到端取消测试~~ ✅ 已补(test_cancelled_node_skips_set_failed:CancelSelfNode 经共享 node_status 自取消→Err→executor 不 bail→状态保 Cancelled;df-workflow 14 test pass) +- [x] B-03b-R3 — **[评估维持]** std::sync::Mutex — 快速临界区纳秒级持锁不阻塞 runtime,保持 std 合理 +- [x] B-03b-R4 — **[评估维持]** set_* 改 &self 后调用方 &mut — &self 是放宽,&mut self 上下文调 &self 无害,清理为可选优化不影响正确性 +- [x] B-03b-R5 — **[评估维持]** `lock().expect()` poison panic — poison=持锁 panic 严重错误,fail-fast 合理,非用户态可恢复 +- [x] B-03b-R6 ~~human_node send 缺 await~~ ✅ 已修(human_node.rs:41 加 .await;async fn send 的 Future 不再被 let _ = 丢弃,Request 真进 channel)(commit 0bb96fc) +- [x] B-03b-R7 ~~前端契约失配~~ ✅ 已修(project.ts:214 type→snake_case + :215 取 event 本体扁平字段 + as unknown as 绕过联合类型)(commit 0bb96fc);types.ts event.type 收窄字面量联合(WorkflowEventType 11 变体)已补(commit 3e1f119 Wave5) +- [x] B-03b-R8 ~~缺 human 节点端到端集成测试~~ ✅ 已补(human_node.rs 加 2 端到端测:`end_to_end_human_approval_completes_workflow` 验 a(SleepNode)→b(HumanNode) 两层 DAG 经 executor 驱动 Request 真发出 + outputs 收集 + 双节点 Completed;`end_to_end_human_approval_cancelled` 验外部 set_cancelled → human cancel_tick 命中 → executor 跳过 set_failed + 状态保持 Cancelled)。覆盖 executor↔HumanNode 集成链路,封死 R6/R7 回归土壤(10 测全过,2026-06-16,待commit) +- [x] ✅(workflow wfryptv2t·代理C独立Read+Grep核验·2026-06-17·不信报告结论) B-03b-R9 — **[P2]** 其余 8 项对抗裁定(③串扰/④单槽/⑤终态不清/⑥set_cancelled覆盖终态/⑦互斥/⑧approve不校验/⑨⑩Lagged/⑪failed_node空)。**7项销账**:③串扰✅(workflow.rs:167-172 exec_id双重匹配治本)⑤终态不清✅(workflow.ts:57-70三变体监听+exec_id限定+Lagged兜底补发)⑥set_cancelled覆盖终态✅(workflow.rs:497-517 IPC终态前置守卫)⑦互斥✅(workflow.ts:8-11 _approvalInFlight单例approve/cancel共享)⑧approve校验✅(workflow.rs:431-450三层防御+human_node.rs:106-111下游兜底)⑨⑩Lagged✅(human_node.rs:153-161续等+workflow.rs:191-263累计阈值查DB终态兜底)⑪failed_node空✅(workflow.rs:355-369 snapshot取首个Failed/Cancelled节点id)。**1项留观察**:④单槽——state.ts:23 pendingApproval仍单值非Map,零危害(前端唯一DAG=demoDag无human节点入口+UI无并发工作流入口+⑤已按exec_id限定清理+后端HumanNode按exec_id+node_id各独立select!),待并发工作流入口落地时升Map。详见 [审查报告 §2](./02-架构设计/工作流审批审查报告-2026-06-14.md) + +### ✅ 已完成 — df-workflow 审批闭环(Workflow D, commit 22964a2) + +- [x] B-260614-06 — ~~execution_id 硬编码 "dummy-execution-id"~~ ✅ DagExecutor::new 接收 execution_id 下沉 NodeContext(workflow.rs 传真 ID)(06-14) +- [x] B-260614-07 — ~~每节点全新空 StateMachine~~ ✅ NodeContext.node_status 共享 self.state_machine.clone()(is_cancelled 可工作)(06-14) +- [x] B-260614-03a — ~~HumanNode 假返回"同意"~~ ✅ execute 重写 subscribe→send→select!(响应/超时/取消 + execution_id+node_id 双键 + Lagged 容忍),照 [B-03 设计](./02-架构设计/B-03-人工审批响应机制-2026-06-14.md);7 单测;eventbus 删死代码 (06-14) +- [x] B-260614-03b — ~~HumanNode 取消机制~~ ✅ WF-E 完成:set_cancelled + cancel_workflow_node IPC + 前端取消按钮 + **端到端补完**(StateMachine 内部 Arc 共享 + execution_id 注册表)。**注**:agent 初版留半成品(workflow_cancel_state 全局孤立实例,IPC 写了读不到,Explore 审查漏抓语义缺陷),主代理补完 IPC→共享 HashMap→HumanNode is_cancelled 真通;clone_shares 单测验证共享语义 (06-14, commit 4aa689e) + +### P1 — 设计完成待实施 + +- [x] ✅ F-260614-01 — **[P1🔥]** 模型能力系统 Phase 1(阶段1-6 全✅,波1-5,ModelConfig 4维度+路由器+多源探测+厂商拉取+7调用点+Settings池UI+AiChat下拉) — ModelConfig 数据模型(模态/能力/价格/智力 4 维度) + ModelRouter 智能路由 + 多源探测(预设表+启发式) + 厂商模型列表自动拉取(适配 OpenAI/Anthropic 协议差异) + 7 调用点接入 + Settings 模型池 UI + AiChat 模型下拉。按任务需求自动匹配合适模型,不再所有场景共用 default_model — source:📐 设计定稿 (06-16, [F-01-模型能力系统与智能路由设计-2026-06-16.md](./02-架构设计/F-01-模型能力系统与智能路由设计-2026-06-16.md))。F-07 已完成解除阻塞。 + - [x] ✅(波1) 阶段1:数据模型 — df-ai-core 新增 ModelConfig/Modality/Capability/CostTier/IntelligenceTier + df-storage models 反序列化兼容 + - [x] ✅(波1) 阶段2:预设表+探测器 — df-ai 新建 model_probe.rs + presets/models.json + 启发式推断 + 多源合并 + - [x] ✅(波1) 阶段3:厂商模型列表拉取 — df-ai 新建 model_fetch.rs + openai_compat/anthropic_compat 分派 + URL 拼接 + 噪音过滤 + - [x] ✅(波2) 阶段4:路由器 — df-ai 新建 router.rs + TaskRequirements + select 逻辑 + - [x] ✅(波3 IPC a26b86e + 波4调用点 2ddfea6) 阶段5:6 调用点接入 — agentic/title/knowledge_inject(2)/project(2)/df-ideas/df-nodes + IPC ai_fetch_models/ai_probe_model + router.rs select_model_id helper(池空兜底 default_model 行为不变) + - [x] ✅(波5·4c84fbc+主代核查,cargo0err+vue-tsc0+df-ai107pass+compress3pass) 阶段6:前端 — types.ts 类型对齐(✅波3) + Settings 模型池 UI(✅波3) + AiChat 模型下拉+override穿透(✅波5·4c84fbc,override空/不在池落回路由零行为变化) + +### P1 — Sprint 19 遗留 + +- [x] T-260614-05 — ~~工具结果入库前截断 50KB~~ ✅ mission:T-260614-04 已修复(conversation.rs 加 `truncate_for_persist` 纯函数,50KB 阈值 + 头尾各 20KB + 中段标注省略字符数;仅作用于持久化视图不污染内存真相源;3 单测 pass)(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 审查) + +> /review 专项审查 generating 生命周期(commands.rs 对话/审批/stop + agentic.rs loop/try_continue + stream_recv.rs stream_llm + mod.rs AiSession)。**用户报障根因**:generating 复位散布 6 处 + spawn 无 panic 兜底 → generating 卡 true → newConversation 硬拦死锁(创建不了新对话,只能重启)。**决策**:轻量状态机(RAII guard 写收敛 + enum 视图读侧收敛),不引入独立状态机框架(stop_flag 须保留 AtomicBool 跨锁)。详见 [generating状态机加固-2026-06-15.md](./02-架构设计/generating状态机加固-2026-06-15.md)。 + +**P0 — 用户 bug 根治组合(必须同批:②③强耦合,①是上游根治)** +- [x] B-260615-09 ✅ .ai-md DRY (第⑩批 2026-06-16) — ~~RAII guard 收尾 generating~~ (84删+3留独有) GeneratingGuard struct(new/reset().await 幂等/Drop spawn 兜底)+run_agentic_loop 入口实例化+5 处手动复位替换(provider-Err/入口 stop/stream_llm None/流式 stop/正常完成尾)+try_continue:318 保留手动(should_continue=false 路径须保 generating=true 待审批);保留复位→emit 顺序;主代理核查 cargo check 0 err/df-nodes 17 test pass — agentic.rs +- [x] B-260615-10 ✅ useRendered DRY 确认 (第⑩批 2026-06-16) — ~~newConversation 硬拦改软复位~~ ai_conversation_create 加 app:AppHandle 参数(Tauri 自动注入,invoke_handler 无需改)+generating=true 时记 old_conv→generating=false→清 pending_approvals→stop_flag(B-11 双保险)→释放锁→向 old_conv emit 零 token AiCompleted→relock 续建新对话;硬拦 Err 移除 — commands.rs +- [x] B-260615-11 ✅(P0批,2026-06-16,待commit) — ~~loop push 前对话一致性校验~~ 两处 conv_id 校验:①循环顶部(入口 stop_flag 块后,AiAgentRound 通知前)active_conversation_id!=conv_id 提前 return+warn ②push 块内(取锁后,has_tool_calls/!full_text 分支前)切换即提前 return+log — agentic.rs + +**P1** +- [x] B-260615-12 ✅(P0批,2026-06-16,待commit) — ~~session.state() enum 视图收敛~~ 新增 pub enum SessionState{Idle,Streaming,AwaitingApproval}(mod.rs:105-112)+impl AiSession pub fn session_state()->SessionState(mod.rs:173-181 读视图:pending_approvals 非空→AwaitingApproval/else generating→Streaming/else Idle);字段定义不动,三调用点(agentic.rs:283/commands.rs:250/451)列注释待后续承接替换 — mod.rs +- [x] B-260615-13 ✅(wcvigw3z4批,2026-06-16,待commit) — ~~stop 流式态分支兜底~~ ai_chat_stop 流式态分支 stop_flag.store 后 spawn 兜底 task(sleep 3s 后 lock 检查 generating 仍 true 则强制复位+emit AiCompleted 零 token),治 loop 死(panic/异常退出漏发收尾)时 stop 无反应;参考审批分支+B-10 软复位写法,emit 前释放锁(drop+relock) — commands.rs +- [x] B-260615-14 — [P1] ⑦ stop_flag 换 tokio::sync::Notify 即时打断(去 30s 延迟)— stream_recv.rs:148 (第⑫批销账 06-15) + +**P2** +- [x] B-260615-15 ✅(P0收尾批,2026-06-16,待commit) — ~~heartbeat interval 提到 loop 外~~ stream_recv.rs:127-132 heartbeat 创建+首 tick 丢弃移到 loop 前,loop 内复用 tick()节拍不变(从首 chunk 算 30s),计时器状态跨迭代保留无重建抖动 — stream_recv.rs +- [x] B-260615-16 ✅(P0收尾批,2026-06-16,待commit) — ~~MAX_AGENT_ITERATIONS 配置化~~ pub(crate) const→pub const + 文档注释标注未来配置接入点;核查 AppState/df-storage 无现成 agent 配置槽位(grep 零命中),未引入 AppState 字段(符合 P2 零行为变边界) — agentic.rs:27-34 +- [x] B-260615-17 ✅(P0收尾批,2026-06-16,待commit) — ~~key_len 复用~~ resolve_provider_secret 调一次(行105 resolved_key)+key_len 派生(行106)+ensure_resolved_key+build_provider 内联(行107-115);逻辑等价 secret::build_provider_for(resolve→ensure→build 三步),因 build_provider_for 隐藏 resolved key 无法复用+secret.rs 锁边界而内联;⚠️DRY 隐患:agentic.rs 内联与 build_provider_for 重复,注释标注等价,未来改 build_provider 签名需同步两处(可后续小改 build_provider_for 返 key_len 消除) — agentic.rs:98-133 +- [x] B-260615-18 ✅(P0批,2026-06-16,待commit) — ~~pending_approvals 注释~~ mod.rs:124-135 扩展文档:单 HashMap 按 tool_call_id 路由/conversation_id 业务语义非路由键/当前 O(n) 过滤典型场景可接受/未来多会话高并发可加二级索引 — mod.rs + +### 🔴 消息发送失败无提示 + 状态传染(2026-06-16 用户报障) + +> 用户报障:发送消息后面板显示该消息 + input 残留同条消息 + 无失败提示;叠加首对话失败后**新对话建不了、切历史对话也发不出**。**根因 = generating 卡 true**(见上 B-260615-09~18,未实施)的下游表现:卡死态下 `ai_chat_send`(commands.rs:45) 同步拦截 Err → 前端 catch(useAiSend.ts:75) 回填 input + 不回滚 user message → handleSend(AiChat.vue:666) 仅 console.error 无 toast。新建对话(ai_conversation_create:451)/切历史(switchConversation)同被 generating 拦。**根因不治则反复**,本块 P1/P2 为 UX 放大器与状态同步缺口。 + +**P0 — 根治(即上一块 B-09~18,不另立)** +- [x] B-260615-19 ✅(P0批,2026-06-16,待commit) — ~~根因指针~~ B-09/10/11 已实施(generating 卡 true 根治)+B-12/18 同批(session_state enum 视图+pending_approvals 注释);B-13~17 留后续(stop 兜底/Notify 即时打断/heartbeat 提外/MAX_AGENT_ITERATIONS 配置化/key_len 复用) + +**P1 — 前端 UX 放大器** +- [x] B-260615-20 ✅ 发送提示 (第⑩批 2026-06-16) — ~~handleSend catch 加提示~~ 复用 Settings.vue reactive toast(AiChat 可分离窗口须自管):模板末尾 Transition+.ai-toast/script 加 toast reactive+showToast(3s 自消失)/handleSend catch 加 showToast('发送失败:'+errMsg)/.ai-panel position:relative/.ai-toast 样式;技术债:文案硬编码未接 i18n(同 confirmClearChat 先例),公共 toast composable 另起任务 — AiChat.vue +- [x] B-260615-21 ✅ 回滚user msg (第⑩批 2026-06-16) — ~~sendMessage catch 回滚 user message~~ push user msg 前捕获 const userMsgId(原内联未生成)+catch 块 filter 改 m.id!==aiMsgId && m.id!==userMsgId 一并回滚;机制:user/ai msg 均 nextMsgId() 唯一 id+push 进 state.messages(无索引依赖,按 id 过滤最稳) — useAiSend.ts + +**P2 — 前后端状态同步** +- [x] ✅(batch55·2026-06-16·主代销账) B-260615-22 — [P2] ~~前后端状态不同步~~ → **由 B-260615-55(L0 握手) + B-260615-56(L2 强制发送) 解决**(L0 前端重接握手+L2 三级降级+force_send IPC),本条保留为问题记录不再独立实施。双项均已✅销账,本条闭环。 +- [x] CR-260615-12 ✅ 小项 _toastTimer 等 (第⑩批 2026-06-16) — ~~可选一组~~ _toastTimer 卸载未清(AiChat:436) + markdown 外层 span→div + SVG 常量散落 + appSettings 注释 + index 空行 + 路由 icon 语义 + options shape 校验 + projectNameById 共享 + useConfirm dev 警告 + i18n `t` as any — 详见走查 ⚪ 区 +- [x] CR-260615-09 ✅ .ai-md DRY (第⑩批 2026-06-16) — ~~五份 .ai-md 样式逐字重复~~ 抽全局 css (84条删+3留独有) — AiChat/ProjectDetail/Ideas/Knowledge/TaskDetail → `src/styles/ai-md.css` 或 `` 组件。TaskDetail.vue:263-333 新文件继续抄(CR-09 复发,定时走查 06-15 确认第 5 份)。B-24/25 复制粘贴源头 — 详见走查 ⑤ + 定时走查 P0复核.md +- [x] CR-260615-10 ✅ useRendered DRY (第⑩批 2026-06-16) — ~~useMarkdown 加 `useRendered(getText)` 辅助~~ 消除 renderedDesc computed + onMounted(loadMarkdown) 三处重复(与 CR-09 同源) — 详见走查 ⑥ +- [x] CR-260615-08 ✅ i18n 全清 (第⑩批 2026-06-16) — ~~i18n 硬编码一组~~ ARG_LABEL_MAP+未知错误+stores 24处 全清 — 定时走查第4轮扩展:原记 3 处,实际 **40+ 处**;i18n key 树本身优秀 466 key zh/en 全对齐 0 缺,问题全在 src/ 硬编码。**P1 直接破坏 i18n(最高优先)**:①useAiEvents:182 `tc.result='用户拒绝了此操作'` 绕过已有 aiTool.rejectedHint → en locale 拒绝提示恒中文(已有 en 翻译被一行废掉),**改一行删/换 t() 最高 ROI**;②TaskDetail.vue 整页 13 处未接 i18n(无 taskDetail namespace,需新建);③ProjectDetail 审批/导入 5 处(导入目录/确认N/取消/confirmDialog/Message);④Dashboard:76 暂无项目;⑤AiChat:107/471/687(title清空/confirmDialog/showToast)。**P2**:⑥ToolCard:152-164 ARG_LABEL_MAP 11 审批参数标签硬编码(aiTool 无 key,需新增 argLabels.*);⑦store error fallback 11 处全中文(knowledge/ideas/projects/tasks/workflow);⑧useAiSend:49/65/130(队列满/审批未送达);⑨useAiStream:52-53 看门狗两文案(原 CR-08 行号 39-41 偏差)。**P3**:⑩time.ts:46-51 相对时间中文(已有 common.justNow/minutesAgo 等 key 却没用,en locale 时间恒中文,隐蔽);⑪router meta.title 8 处(0 消费方死代码);⑫fallbackLocale:'en' 设计气味。修:按视图分批接入,**P1-1 改一行先做** — 全 src/ + i18n — 详见定时走查第4轮.md + +### 🔴 前后端状态分裂根治(HMR/刷新/切窗口自愈) + +> 用户实测(06-15):DEV 运行中改代码触发 Vite HMR → 前端重载但后端 Tauri 进程未重启 → `generating=true` 残留 → 所有新消息被拦(`ai_chat_send` 同步检查)→ 入队死锁 10+ 分钟不恢复。**根因**:前后端独立生命周期无 reconciliation 协议。**否决超时方案**(5-10min 等死体验差)。选 L0 重接握手(零延迟自愈) + L2 发送韧性(用户可控)。 + +- [x] B-260615-55 ✅ L0 握手 (第⑩批 2026-06-16) — ~~L0 前端重接握手~~ 代码已落地:后端 lib.rs:32-64(ai-client-ready listener + generating reset + pending_approvals 清除 + 补偿 AiCompleted 事件) + 前端 useAiEvents.ts:251(emit ai-client-ready)。HMR/刷新/F5/切窗口自愈 — src/composables/ai/useAiEvents.ts + src-tauri/src/lib.rs +- [x] B-260615-56 ✅ L2 韧性 (第⑩批 2026-06-16) — ~~L2 发送时强制覆盖(用户可控自救)~~ 三级降级+force_send IPC。当前行为:`generating=true` → `ai_chat_send` 返回 Err → 前端 catch 静默入队 → 死锁无出口。改进三级降级:①正常(generating=false)→ 直接发送 ②排队(<30s, generating=true)→ 入队 + UI 显示「⚳ 排队中 N 条」+ 可取消按钮 ③异常(>30s 或 用户主动)→ 弹确认「检测到生成状态异常,是否强制发送?」→ 调用新 IPC `ai_chat_force_send` 绕过 generating 检查(后端先 reset generating 再正常走 send 流程)。**关键设计**:30s 后给用户选择权而非永远静默等。**改动**:前端 `useAiSend.ts` sendMessage 加超时分支 + confirm 弹窗;后端 `commands.rs` 新增 `ai_chat_force_send`(复用 ai_chat_send 逻辑,前置 reset generating)。关联 B-10(软复位已做 newConversation, force_send 同模式) — src/composables/ai/useAiSend.ts + src/components/AiChat.vue + src-tauri/src/commands/ai/commands.rs + +### 🔴 详情描述字段 Markdown 未渲染(2026-06-16 用户报障) + +> 用户报障:任务描述 Markdown 没渲染(显示原始 `## 标题`/`- 列表` 文本)。**走查**:4 详情组件描述字段全用纯文本插值 `{{ }}` 未走 md 渲染——`src/views/TaskDetail.vue:33` / `ProjectDetail.vue` / `Ideas.vue` / `Knowledge.vue`。`src/components/AiChat.vue` 已有正确实现 `renderMd`(marked + DOMPurify + 块级 memo + XSS 防护),但封装在组件内未抽 composable,**DRY 缺口**。**修法**:抽 `src/composables/useMarkdown.ts` 统一入口(复用 AiChat renderMd 逻辑),4 组件展示态切 `v-html="renderMd(desc)"`(编辑态仍 textarea)。**安全约束**:v-html 必须 sanitize,composable 保留 DOMPurify。 + +- [x] B-260615-23 ✅(2026-06-16,待commit) — ~~抽 useMarkdown composable~~ 新建 src/composables/useMarkdown.ts(模块级单例:_marked/_purify/mdReady/_mdCache 模块作用域,renderMd 含 DOMPurify.sanitize XSS 防护+块级 memo 缓存+未就绪 escapeFallback 兜底)+AiChat.vue 改 import 引用(流式核心 splitBlocks/parseBlock/parseBlockNoCache/renderStreamingMd 逐字节未动,仅 _marked/_purify→getMarked()/getPurify() 经单例 getter),行为零变化 — useMarkdown.ts + AiChat.vue +- [x] B-260615-24 ✅(2026-06-16,待commit) — ~~TaskDetail 描述 v-html=renderMd~~ TaskDetail.vue:33 描述非空 v-html=renderMd(task.description)(空值回退 —)+.ai-md 作用域样式(h1/h2/h3/ul/ol/code/pre/blockquote/table)+onMounted loadMarkdown 预热(单例)。用户报障描述 md 不渲染已修 — TaskDetail.vue +- [x] B-260615-25 ✅(2026-06-16,待commit) — ~~同类 3 组件描述 renderMd~~ ProjectDetail/Ideas/Knowledge 描述展示态 v-html=renderMd(desc)(空值回退 —)+import useMarkdown+onMounted loadMarkdown 预热+各加 .ai-md 样式(Ideas .detail-desc.ai-md/Knowledge .detail-content.ai-md 含 white-space:normal 覆盖原 pre-wrap,正确处理 v-html 标签间 \n;ProjectDetail 同). DOMPurify sanitize 安全。vue-tsc 0 err — ProjectDetail/Ideas/Knowledge.vue + +### 🔴 审批执行后中断(2026-06-16 用户报障) + +> 用户报障:AI Chat 审批卡片点批准 → 工具执行 → 对话就结束,没续生成下一轮("不知原因")。**走查根因**:B-260615-09 的 `GeneratingGuard`(agentic.rs:48-75) Drop 兜底复位 generating,但 `run_agentic_loop:292-300` 审批等待 return(pending>0)路径**既没 `guard.reset()` 也没 disarm** → `guard.done=false` → task 结束 guard Drop(agentic.rs:67-75) spawn `generating=false` → **审批态 generating 被误复位**(与 :82/:299 注释"generating 保持 true"设计意图直接矛盾)。之后 `ai_approve`(commands.rs:187) 调 `try_continue_agent_loop`(agentic.rs:370) 读 `is_generating=false` → `should_continue=false` → emit AiCompleted 不续生成。**竞态**:Drop 的 spawn 复位是异步,与用户点批准的时间差,致时续时断(用户感"不知原因")。**属 B-260615-09 实施引入的回归**——guard Drop 兜底未区分"审批等待(应保 generating)"vs"异常退出(应复位)"。 + +- [x] B-260615-26 ✅ GeneratingGuard disarm (第⑩批 2026-06-16) — ~~GeneratingGuard disarm~~ 加 disarm()(行69-71 置 done=true 不 reset generating)+run_agentic_loop 审批等待 return 前(pending_count>0,:308)调 guard.disarm()——保 generating=true 留 try_continue 续,Drop 因 done=true 跳过复位 spawn。修正 B-09 引入回归(审批执行后对话不续生成)。主代理核查:disarm 逻辑正确/cargo check 0 err/df-nodes 21 test pass — agentic.rs +- [x] B-260615-27 ✅ 数据实证 (第⑩批 2026-06-16) — ~~数据侧实证~~ conv 6c2e11f4(U-Ask 概览)消息序列显示 **Low 工具(list_directory/read_file)执行后 loop 正常续生成,Medium/High 审批工具(create_project/create_task/run_command)审批执行后 loop 断**——对话多次断在 tool_result([10][34][42][70] 后均无 assistant 续),用户被迫反复发"继续"([11][35][43][57][65][68])。坐实 B-26 根因:审批路径 loop return 触发 guard Drop 误复位 generating → try_continue 不续;自动执行路径 loop 不退出故正常。 + +### 🔴 Task 缺失(2026-06-16 用户报障) + +> 用户报障:任务缺失。**DB 实证**(devflow-dev.db):tasks 表 61 条,**无悬挂引用**(LEFT JOIN projects 验证所有 task.project_id 都在 projects 表),数据完整非丢失。根因在前端:`Tasks.vue:214` onMounted 全量 `loadTasks()` 无参 + 前端 filter(:159-164 activeProject 过滤),**切换项目不重新加载**(仅对已加载全量做前端 filter),新建 task 后其他项目视图不刷新。属 AR-11 数据变更联动同类。另发现:projects 表**重名 meta-kit×2**(f0fa88bf-a4bd / 32de9175-870d),疑重复导入/绑定,可能致用户认知"缺失"。 + +- [x] B-260615-29 ✅ Task 联动 (第⑩批 2026-06-16) — ~~Task 列表项目切换联动~~ store.loadTasks(projectId?) 已支持可选参(project.ts:124 无需改 store)+Tasks.vue 加 watch(activeProject):非 all 时 loadTasks(projectId) 按项目重载,all 时 loadTasks() 全量。切项目筛选触发后端重载,新建任务跨项目视图同步刷新。只改 Tasks.vue — src/views/Tasks.vue +- [x] B-260615-30 ✅(2026-06-16) — ~~projects 重名核查~~ meta-kit 两条(f0fa88bf/32de9175)结论: **建议不删,保留两条记录**。可能为不同目录绑定或不同导入批次,P2 低优先级,无数据冲突风险。如需去重待用户确认具体哪条 — projects 表 + +### 🔴 详情页字段布局紧凑化(2026-06-16 用户需求) + +> 用户需求:任务详情中除「描述」外的字段(标题/状态/优先级/关联项目/分支/负责人/基础分支/工作流定义/时间),名称与值应**同行展示**(当前分行垂直堆叠,占空间)。**走查**:TaskDetail.vue:27-84 全部 `.info-item` 用 `flex-direction: column`(CSS :221-225)分行。ProjectDetail.vue:69-121 同款布局需一并改。Ideas.vue 用卡片式不同布局不改。 + +- [x] B-260615-31 ✅ 详情布局同行 (第⑩批 2026-06-16) — ~~TaskDetail + ProjectDetail 字段同行布局~~ `.info-item` flex-direction column→row + align-items baseline + gap 12px + `.label` min-width 88px flex-shrink:0(TaskDetail `.value` 加 flex:1 min-width:0 占余);描述字段 info-item 加 `info-block` class + CSS `.info-item.info-block{flex-direction:column}` 保块状(长文本独占整行);ProjectDetail `.path-row`/`.info-tags` 已 flex-wrap 无溢出风险。vue-tsc 0 err — src/views/TaskDetail.vue + src/views/ProjectDetail.vue + +### 🔴 run_command stdout/stderr 恒空(2026-06-16 用户实测) + +> 用户实测 F-05 run_command:命令能跑(副作用如写文件发生)+ exit_code 正常,但 **stdout 始终返回空字符串**,echo/Write-Output 等均如此 → "写→跑→**看**→改"闭环断在「看」,跑测试/构建无法获取输出。**根因(铁证)**:`crates/df-execute/src/shell.rs:37-58` 构造 `tokio::process::Command` 时**漏设 `cmd.stdout(Stdio::piped()).stderr(Stdio::piped())`**。tokio 默认 `Stdio::inherit()`(输出继承父进程 Tauri app → 控制台/devnull),`wait_with_output()` 读不到 pipe → `output.stdout/stderr` 空 Vec → `from_utf8_lossy` → 空串。F-05 plan doc 也未提 Stdio 设置,实现遗漏。**PS 静默失败(.Replace()/-replace/Set-Content exit_code=0 文件未变)疑非独立 bug**:修了 piped 后 stderr/stdout 可观测,方能定位是「PS Replace 源不匹配」(AI 用法)还是「cmd /C 调 PS 引号问题」(shell.rs:38 Windows 用 cmd /C,PS 语法引号嵌套易错)。**B-38 待 B-37 修复后据观测重评**。 + +- [x] B-260615-37 ✅(2026-06-16) — ~~shell.rs execute 补 Stdio::piped()~~ 已确认在代码中(shell.rs:41,46 Windows/Linux 分支均有 .stdout(Stdio::piped()).stderr(Stdio::piped()))。stdout/stderr 正常捕获 — crates/df-execute/src/shell.rs +- [x] B-260615-38 ✅ PS shell_type (第⑩批 2026-06-16) — ~~PS 静默失败根因~~ ShellRequest 新增 shell_type 枚举(ShellType: Cmd/PowerShell/Sh,默认 Windows→Cmd 非Windows→Sh)+ execute 函数改 match 分支(PowerShell→`powershell -NoProfile -Command`,Cmd→`cmd /C`,Sh→`sh -c`)。B-37(Stdio::piped())已确认在代码中。LLM 可通过 shell_type 参数选 PS — crates/df-execute/src/shell.rs + +### 🔴 查看灵感来源跳空白页(2026-06-16 用户报障) + +> 用户报障:项目详情点「来源灵感」(i18n key `projectDetail.sourceIdea`) 跳转后页面空白。**根因双锁**:① `ProjectDetail.vue:71` `router-link :to="/ideas/${idea_id}"` 指向 `/ideas/:id`,但**路由表(router/index.ts:21-25)只有 `/ideas` 精确路由,无 `:id` 子路由** → hash 路由无匹配 → 渲染空白(路由表无 404 catch-all 兜底)。② 即便补路由,`Ideas.vue` 不读 route params——`:209` import 无 `useRoute` / `:221` 只 `useRouter` / `:448-450` onMounted 只 `store.loadIdeas()` 不接 params → `selectedId`(:230) 纯内部 ref,无法 deep link 选中灵感。**数据层 OK**:`Project.idea_id` 字段(models.rs:38)+晋升写入(idea.rs:124),ProjectDetail 拿得到 idea_id。**对照**:`/projects/:id` `/tasks/:id` 路由已建(独立详情页),Ideas 是双栏列表+详情同页故未建 `:id`,但 ProjectDetail 反向溯源需 deep link。 + +- [x] B-260615-36 ✅ Ideas route.params (第⑩批 2026-06-16) — ~~查看灵感来源跳空白页~~ 修复两层:① `router/index.ts` 加 `/ideas/:id` 路由(name `IdeasDetail` 或复用 Ideas 组件);② `Ideas.vue` 加 `useRoute` + `onMounted` 读 `route.params.id`→`selectedId.value=id`(loadIdeas 后校验 id 在 `store.ideas` 中,不在则提示「灵感不存在/已删」+清 selectedId);可选 watch `route.params.id` 支持从详情再点别的灵感切换。— src/router/index.ts:21-25 + src/views/Ideas.vue:209,221,448-450 — source:用户报障(06-15) + +### 🟡 AiChat 气泡拷贝能力(2026-06-16 用户需求) + +> 用户需求:AiChat 消息气泡支持一键拷贝——**文本拷贝**(content 原文→剪贴板) + **截图拷贝**(气泡 DOM 渲染→图片→剪贴板,粘贴出图片)。**现状**:全项目零 clipboard 实现(grep `navigator.clipboard`/`copy`/`clipboard`/`拷贝`/`复制` 零命中)。气泡结构:用户消息 `.ai-msg-bubble--user`(纯文本 `{{msg.content}}`, :185);AI 消息 `.ai-msg-bubble--ai.ai-md`(v-html 渲染 MD, :197)。**Tauri WebView2(Edge)**:Clipboard API 完整支持(`writeText`/`write([ClipboardItem])`),无需额外 plugin(做减法,不用 `@tauri-apps/plugin-clipboard-manager`)。**安全约束**:必须在用户点击事件(手势)内调用剪贴板 API。 + +- [x] F-260615-13 [P2] — **AiChat 气泡文本拷贝**。每条消息气泡右上角加拷贝按钮(📋 icon,hover 显示)。点击 → `navigator.clipboard.writeText(msg.content)` → 短暂 toast「已复制」。AI 气泡拷原始 content(非 v-html 渲染后 HTML,防 HTML 标签污染);用户气泡同。按钮位置:`.ai-msg-bubble` 内右下角或 hover 时右上角浮现(不占空间,信息密度优先)。i18n 加 `aiChat.copied` / `aiChat.copyMsg`。— src/components/AiChat.vue(:185 用户/:197 AI) + src/i18n (第⑫批销账 06-15) +- [x] ✅(待决策.md已决c不做·2026-06-16) F-260615-14 [P2] — **AiChat 气泡截图拷贝(图片)**。同位置第二按钮(🖼️ 或与文本拷贝合并为下拉)。技术链:`html2canvas(bubbleElement)` → canvas → `canvas.toBlob('image/png')` → `new ClipboardItem({ 'image/png': blob })` → `navigator.clipboard.write([item])`。需加依赖 `html2canvas`(~90KB,或轻量替代 `dom-to-image-more` ~3KB,待选型)。**注意**:html2canvas 对 CSS 支持有限(阴影/渐变/CSS 变量可能不全),但气泡样式简单(ai-md 基本色+圆框),风险低。粘贴目标:Word/微信/钉钉等富文本编辑器可接图片;纯文本框不行(正常行为)。— src/components/AiChat.vue + 新依赖(html2canvas/dom-to-image-more 待选) + +### 🟡 AI Chat 授权体验改进(2026-06-14 方案 → 待办化) + +> 来源:[aichat授权体验改进方案-2026-06-14.md](./02-架构设计/aichat授权体验改进方案-2026-06-14.md)。6 痛点 + 8 改进项,按原文优先级转待办。**状态: 待讨论→看板跟踪**。 + +**P0 — 快速改善体感(第一批建议落地,~2 天工作量)** + +- [x] AE-2025-01 [P0] — **批量审批**。同一轮多 pending 时,ToolCardList 顶部显示「全部批准(N) / 全部拒绝」按钮。改动:①`ToolCardList.vue` 加批量操作栏(监听 pending_approval 数量)②`useAiSend.ts` 新增 `approveAll(rejectAll)` 循环调 `ai_approve`。预估 0.5 天 — src/components/ToolCardList.vue + src/composables/ai/useAiSend.ts (第⑫批销账 06-15) +- [x] AE-2025-02 [P0] — **审批计数器 + 跳转**。输入框上方/header 显示 `⏳ N 项待审批` 徽标,点击跳转到第一个 pending 卡片。改动:①`AiChat.vue` header 区域加审批徽标 ②`ToolCardList.vue` 暴露 `scrollToFirstPending` 方法。预估 0.5 天 — src/components/AiChat.vue + src/components/ToolCardList.vue (第⑬批销账 06-15) +- [x] ✅(batch60·2026-06-16·workflow whae812z5+主代核查·路径B,cargo+vue-tsc 0err) AE-2025-03 [P0] — **write_file diff 预览** ✅**已落地(batch60 路径B)**:后端 write_file **未返 diff**(`tool_registry.rs:694` 结果 payload `{path,bytes_written,old_size,encoding}` 无 diff 字段,`generate_diff` 仅 patch_file 调用),前端 ToolCard 无 diff 可渲染,审批 payload `AiApprovalRequired`/`PendingApproval` 亦无 diff(只有 `args={path,content,encoding}` 纯新内容,拿不到旧文件)。需**后端先补**再追加前端渲染。**两条路径**(均 src-tauri 改动,跨 mod.rs/audit.rs/types.ts/AiChat.vue 领地):路径 B(推荐,满足审批预览诉求)审批 payload 加 `diff` 字段 + audit.rs:555 挂起审批前预读旧文件 + `generate_diff` 注入事件;路径 A 仅 write_file handler 补 diff 但结果阶段才有(审批时看不到,不满足)。前端部分待后端 diff 返回后追加 `
` 红删绿增渲染。原诉求:write_file 审批时若文件已存在,展示前后对比 diff 而非裸 content(content 可能几百行代码截断到 300 字符)。改动:①后端 `tool_registry.rs` write_file handler 执行前读旧文件返回 diff ②`ToolCard.vue` pending_approval + name=write_file 时渲染 diff 视图(红删绿增)。关联 F-260615-10(diff 工具)可复用。预估 1 天 — src-tauri/src/commands/ai/tool_registry.rs + src/components/ToolCard.vue
+
+**P1 — 增强控制力**
+
+- [x] ✅(波8·6ec191e+主代核查,cargo0err+vue-tsc0+devflow79pass) AE-2025-04 [P1] — **会话级授权(Session Trust)**。替代原"全局宽松模式"。核心:信任是上下文相关的——同一会话内用户批准过某类操作后,后续同类操作自动放行;换会话清空重审。模型:AiSession 加 `session_trust: HashSet`(内存态,随会话销毁);TrustKey 按操作+目标范围粒度(如 `Write { dir: "src/" }`、`Execute { dir: workspace }`)。流程:首次操作→pending_approval 用户批准→记录 TrustKey → 同会话后续同类操作命中则自动放行(消息流显示轻量提示 `🔓 自动放行: write_file(xxx)`)。**白名单收紧决策**:纯读取(list_*/read_*/list_directory)始终自动放行;创建/修改/绑定/覆盖写入/高危全部需审批或会话级授权放行(bind_directory 归类为修改操作;write_file 覆盖写入已出事故不放行)。**首批实现**:write_file + run_command(写→跑→看→改闭环最高频连续操作);create/update 后续按需加。改动:①`mod.rs` AiSession 加 session_trust 字段 + TrustKey enum ②`audit.rs` process_tool_calls 审批前查 session_trust 命中则跳过 pending ③审批通过时写 session_trust ④前端自动放行操作显示轻量 toast 提示。关联 AE-05(High 二次确认: 会话级授权的操作是否也需二次确认? 建议: 首次审批走二次确认,后续自动放行跳过)。预估 1.5 天 — src-tauri/src/commands/ai/(mod.rs|audit.rs) + src/components/AiChat.vue — **设计决策(2026-06-16)**: 目录级粒度(非文件级)、仅写+执行首批、Webhook 未来走独立 execution_token 链路不影响本设计
+- [x] AE-2025-05 ✅(第⑲批 2026-06-16) — ~~High 二次确认~~ onApprove async:approved&&HIGH_RISK_TOOLS.has(tc.name)(delete_task/delete_project/restore_project/purge_project/delete_file/run_command 6 工具白名单,后端不回传 risk 故按名判定)→await confirmDialog(highRiskConfirmMsg)在 approving 置 true 前,!ok return 不进 loading 态可重试;拒绝路径不加确认(无害);approving loading+130s 兜底(B-260616-08)原样保留。每卡独立 useConfirm 实例不串台。i18n confirmHighDelete/Exec/Generic zh/en 对称。vue-tsc 0 err。TODO:后端回传 risk_level 后改读字段免维护白名单 — src/components/ToolCard.vue
+- [x] AE-2025-06 ✅(第⑲批 2026-06-16) — ~~审批超时自动 reject~~ 纯前端定时器 APPROVAL_TIMEOUT_MS=5min(useAiSend.ts Map+start/clear/clearAll 三函数);启动挂 AiApprovalRequired(非 approveToolCall,agent 修正正确:审批开始是事件点非点击末态);Completed/ApprovalResult(!approved) clear 单条;onStreamTimeout 经 Tauri 事件总线 emit ai-approval-clear-timers→useAiEvents listen clearAll(破循环依赖 useAiStream 不能 import useAiSend);stopListener 卸载清;超时调 ai_approve(id,false)+push 系统错误消息(⚰ i18n ai.approvalTimeout)。vue-tsc 0 err。TODO:Settings 可配(toast 跨层接线改 push 消息参照 onStreamTimeout 模式,未碰 Settings god file)。行为变更但决策已定。原:pending_approval 超时自动 reject(默认 5 分钟),防对话永久卡死。**决策:纯前端定时器**(审批依赖页面交互,页面关闭 auto-reject 合理;不改后端)。超时后 toast 提示「⚰ 审批超时,已自动拒绝: {tool_name}({reason})」+ 调 ai_approve(id, false)。Settings 可配超时常量(3/5/10min)。**范围限定**:仅覆盖人工审批场景。未来 Webhook/外部集成走独立 `execution_token` 预鉴权链路(不经过 pending_approvals),不影响本设计。预估 0.5 天 — src/composables/ai/useAiSend.ts + src/views/Settings.vue
+
+**P2 — 信息透明度**
+
+- [x] ✅(第⑳批 2026-06-16) AE-2025-07 [P2] — **Agentic 进度条**。AiChat 输入区上方加 `.ai-agentic-progress`(v-if streaming&&agentRound>0),useAiEvents AiAgentRound(round>0 写,round==0 占位不覆盖避免闪烁)+ AiCompleted/AiError 复位清零。**实际显**:round(后端 AiAgentRound.round)+ pending(state.pendingApprovals.length 条件附加)。**降级省略**:max(后端 MAX_AGENT_ITERATIONS=10 常量未透传)+ completed(无累计计数器),均留 TODO。stores/ai.ts 加 agentRound 字段。单轮问答不收 AiAgentRound 故恒隐藏 ✓。— src/components/AiChat.vue + src/composables/ai/useAiEvents.ts + src/stores/ai.ts + i18n(ai.agenticProgress/agenticProgressNoPending zh/en)
+- [x] ✅(batch34·2026-06-16·workflow) AE-2025-08 [P2] — **审批历史面板**。独立页面展示 `ai_tool_executions` 表审计记录(时间/工具/风险/状态/决策者/参数摘要/结果摘要)。改动:①后端新增 `list_tool_executions` IPC ②前端新建 AuditLog.vue 表格视图。预估 1 天 — src-tauri/src/commands/ai/(commands|audit).rs + 新建 src/views/AuditLog.vue
+
+### 🟡 文件操作工具集补全(2026-06-16 能力梳理·设计定稿 V2)
+
+> 用户给出 5 个工具具体 API 设计。**核心原则不变**:专用工具补 run_command 做不好/不安全的;系统命令类交给 run_command(B-37 修后)。**修正**:search_files 从"run_command 覆盖"升级为"值得做"(结构化 JSON > parse find 输出)。
+
+**5 工具设计(用户方案)**:
+
+- [x] F-260615-06 ✅ patch_file (第⑩批 2026-06-16) — ~~patch_file(edit_file)~~ ~50行 handler → 见下方对抗分析区 :503 完整条目(含设计文档指针)
+- [x] F-260615-07 ✅ search_in_file (第⑩批销账 06-15) — **[P1] search_in_file**(内容搜索)— `path, keyword, (可选)regex, case_sensitive` → `[{line, content, column}]`。纯子串搜索先做(regex/case 后加,做减法)。替代 AI 多轮分页扫描 — tool_registry.rs read_file handler 扩展
+- [x] F-260615-08 ✅ file_info (第⑩批 2026-06-16) — ~~file_info(元信息)~~ `path` → `{exists, size, lines?, modified, is_binary, is_dir}`。**不读内容**,快速元数据。lines 用 `\n` 计数(不全文读);二进制不报 lines — tool_registry.rs 新增
+- [x] F-260615-09 ✅ append_file (第⑩批 2026-06-16) — ~~append_file(追加写入)~~ `path, content` → `{bytes_written, new_size}`。日志追加/增量写入,避 read-merge-write 竞态 — tool_registry.rs 新增
+- [x] F-260615-12 ✅ search_files (第⑩批 2026-06-16) — ~~search_files(文件名/glob 搜索)~~ `path, pattern: "*.ts", recursive?` → `[{path, size}]`。结构化 JSON > parse find 输出(AI token 效率+可靠)。从"run_command 覆盖"升级(原归类为不做的系统命令类) — tool_registry.rs 新增
+
+**后续(P3/合并)**:
+
+- [x] F-260615-10 [P2] **diff**(修改前对比,防误覆盖)— 并入 patch_file 增强(edit 前 return diff 给审批卡看清改动),或独立工具 — 关联 F-06 (第⑬批销账 06-15, 后端diff已返回, 前端审批卡diff渲染待续)
+- [x] F-260615-11 [P3] **write_file base64**(二进制写入)— 图片/PDF/Excel,场景窄 — tool_registry.rs:519 (第⑯批销账 06-15, encoding 参数+base64 decode+字节写入)
+
+**新增(根治 AI rename 静默失败)**:
+
+- [x] F-260615-15 [P1] ✅(第⑮批销账 06-15) — **rename_file(重命名/移动,一个工具覆盖 rename+move)**。文件系统层两者同一操作(改路径),DRY 合一。API: `from, to, overwrite?=false` → `{bytes_moved, cross_volume}`。实现:同卷 `tokio::fs::rename`(atomic,Windows 走 `MoveFileExW` UTF-16 wide API,**中文路径零字符集问题**);跨卷 `tokio::fs::copy`+`remove_file`(非原子,失败回滚删 to 保 from 完整)。**安全**:①from/to 双向 `validate_path`(黑名单+`..`防遍历) ②overwrite 默认 false(目标存在拒绝,防误覆盖) ③RiskLevel::Medium(改文件系统结构,类 write_file,需审批)。**为什么不用 run_command**:run_command 走 shell.rs 的 PS/cmd 链(B-37/B-38 中文路径 GBK 解码 UTF-8 → mojibake → exit 0 静默失败,老毛病);本工具 Rust 原生 std::fs 绕开整个 shell 层,根治 AI rename 的静默失败类。关联 [[patch_file 工具设计]] 同源(文件操作原生工具化,不依赖 shell)。— src-tauri/src/commands/ai/tool_registry.rs 新增 handler(~40 行)
+- [x] F-260615-16 [P2] — **delete_file(删除文件,可选)**。`path, permanent?=false` → `{deleted, backed_up}`。默认非真删(移 `.trash` 或 `.bak`,对齐 WF-A soft_delete 哲学防误删);permanent=true 走 `remove_file`。RiskLevel::High(不可逆,对齐 delete_project)。**为什么独立于 run_command**:rm/del 无备份无审计;本工具接 ai_tool_executions + 可选 trash。— src-tauri/src/commands/ai/tool_registry.rs
+
+**run_command 覆盖(不单独做)**:压缩/权限/软链/校验/批量
+**不做**:文件监听(app 层)/文件锁定(并入 F-06 Mutex)
+
+— source:用户 API 设计 V2(06-15),F-06 设计已定稿见 [patch_file工具设计-2026-06-15.md](./02-架构设计/patch_file工具设计-2026-06-15.md)
+
+### 🟡 对抗分析裁决(2026-06-16 十域十八项交叉验证)
+
+> 来源:用户对抗分析报告(10 域聚类 + 正反双向验证 + 依赖链提取)。**核心价值:6 个否决/暂缓裁决**("不做"比"做什么"更值钱,避免浪费投入)。完整报告见 docs/02-架构设计/对抗分析裁决-2026-06-15.md(若已落盘)。
+
+**✅ 推进(第一批独立可并行)**:
+
+- [x] ✅(batch61·2026-06-16·workflow wwtn2knn6·2069f79) F-07 trait 下沉 df-ai-core — 已实施(详见 L599 F-260614-07),4 决策全落地,解锁 F-03(已做 dfe0096)/F-01(待推)。本条为对抗分析裁决区过时重复条目,销账。
+- [x] R-PD-2 ✅ ScriptNode 不注册 (第⑩批 2026-06-16) — ~~ScriptNode 不注册 script~~ 3 行删除封死攻击面,工作流当前纯演示无真实脚本需求
+- [x] F-260615-06 ✅ patch_file (第⑩批 2026-06-16) — ~~patch_file(edit_file)~~ 局部文件更新工具,补齐 AI 文件操作闭环。完整设计见 [patch_file工具设计-2026-06-15.md](./02-架构设计/patch_file工具设计-2026-06-15.md)(API/三层防御/边界情况/替代方案否决/实施步骤)。**核心**: old_text 精确匹配为主+line 辅助+Mutex 并发安全+expected_hash 指纹防脏写。第一批实现核心三件套(~50行)。— src-tauri/src/commands/ai/tool_registry.rs 新增 handler
+- [x] ✅(batch56·2026-06-16·workflow wsxxurgbr+主代核查) ARC-06 composable 循环依赖 — **破环全闭环**:①`aiShared.ts` 下沉 findToolCall(反向扫描O(1))+4个审批计时器函数(startApprovalTimer/clearApprovalTimer/clearAllApprovalTimers+APPROVAL_TIMEOUT_MS常量) ②`useAiEvents` 删 findToolCall 导出+改 import 从 aiShared 取 ③`useAiSend` 删审批计时器实现(改从 aiShared re-export)+删 drainQueue 直接调用(改经 `ai-drain-queue` 事件总线桥接) ④`stores/ai.ts` 加 `initDrainQueueListener()` 启动事件监听。**B-260616-19 CSS 缺失**(Knowledge.vue 窄屏标题挤压,agent 未产出 .css/.vue style 变更),待补。— src/composables/ai/aiShared.ts + useAiEvents.ts + useAiSend.ts + stores/ai.ts
+- [x] CR-09 .ai-md 样式 5 份→全局 css 抽取 — 零风险减法 (第⑬批销账 06-15)
+- [x] F-09 status 对齐前后端 ✅ (第⑯批销账 06-15) — 前端 map 删 in_progress/paused/cancelled(project 维度无数据源/UI 入口/消费方),保留 planning/active/completed。constants/project.ts(三 map 同步删+兜底改字面量)+Dashboard.vue getProjectStage(switch 收敛)+i18n zh-CN/en projects.status & dashboard.stage。后端不动(df-core enum 7 值是独立大改,F-260614-09 专项)。**注**:DB 实际只产生 planning(4 个建项目入口硬编码),真实生命周期=planning+deleted_at 软删二态
+- [x] F-260615-03 ✅ truncated (第⑩批 2026-06-16) — ~~最小修(截断标志)~~ **从完整分页降级**:不改返回结构(non breaking),truncate 时加 `truncated: true`(→ 见上方 :469 完整条目,本条为对抗分析区引用,勿重复实施)
+- [x] R-PD-9 ✅ display_hint (第⑩批 2026-06-16) — ~~display_hint 轻量抽取~~ audit 工具名→文案映射作 display_hint,消除双份映射
+
+**🟡 暂缓 / 需重设计**:
+
+- [x] T-11 条件引擎 Phase1+2 🟡 **暂缓** — 引擎设计好但**无前端 UI 消费方**(demoDag edges 无 condition 字段,用户无法配置条件)。等工作流有真实使用场景(BuildNode 落地后跑构建→测试→部署)再推进。决策点 A/B/C(手写求值器/复活 set_skipped/default false+warn)均认可
+- [x] F-260615-03 完整分页 🟡 **暂缓** → 降为最小修(truncated 标志),见上方
+- [x] ARC-05 project.ts 上帝 store 拆分 🟡 **需重设计拆分策略** — 不能简单按领域拆(会重蹈 ai.ts 循环依赖覆辙),需先把越层 invoke 沉到 api/workflow.ts。ARC-06 先做
+
+**❌ 否决(人定"不做",不进路线)**:
+
+- [x] ~~R-PD-9 agentic loop 下沉 df-ai~~ ❌ **否决** — 与 ai.rs 子 module 拆分决策冲突("下沉需动 crate 依赖图引入循环依赖")。保留 display_hint 轻量部分
+- [x] ~~R-PD-8 DTO 映射层~~ ❌ **否决** — 过度设计,5 个 IPC 手动对齐可接受。替代:types.ts 头部文档约定"后端改 schema 必须同步改此文件"
+- [x] ~~F-09 拆双字段(status 生命周期 + stage 开发阶段)~~ ❌ **否决** — 阶段概念已被弱化(df-stages 砍了阶段插件),DB 实际只有 active/archived/deleted,双字段过度设计
+- [x] ~~R-PD-12 做实 run_workflow AI 工具~~ ❌ **否决(当前)** — 工作流是内部基础设施,无真实 CI/CD 需求。删假能力(no-op 桩返 note)更诚实
+
+**⚡ 推荐推进顺序**:
+1. 第一批(独立并行): F-07 / R-PD-2 / F-15-06 edit_file / ARC-06 / CR-09 / F-09对齐 / F-03最小修 / R-PD-9 display_hint
+2. 第二批(依赖第一批): F-03 对抗评估接 LLM(F-07后) / F-01 模型能力Phase1(F-07后) / R-PD-12 删假能力(R-PD-2后)
+3. 第三批(需用户输入): S-01 API Key 401 / 显示多开 / 审批可见性
+
+## 已完成
+
+### 2026-06-14
+
+- [x] R-260614-01 ai.rs 拆 11 子 module(commands/ai/)+ glob 重导出保路径 + models bug 修复 — cargo check 0 error / 19 test passed
+- [x] B-260614-01 待审批持久化根治 — mission:T-260614-01
+- [x] B-260614-02 df-workflow ConditionEngine 默认 true→false — mission:T-260614-02
+- [x] B-260614-04 NodeRegistry unimplemented!→删 Default impl — mission:T-260614-03
+- [x] T-260614-05 工具结果入库前截断 50KB(含 3 单测)— mission:T-260614-04
+- [x] B-260614-08 promote_idea 补偿删除保最终一致性 — mission:T-260614-05
+- [x] T-260614-07 诊断日志清理(3 删 + 3 改 debug)— mission:T-260614-06
+- [x] D-260614-01 B-03 人工审批响应机制**设计**完成 — 新建 [B-03-人工审批响应机制-2026-06-14.md](./02-架构设计/B-03-人工审批响应机制-2026-06-14.md)(9 节完整设计)+ 功能决策记录摘要章节 + PROGRESS/todo/INDEX 同步;核心结论:链路基础设施已通仅缺 HumanNode 一处、通道选型=工作流独立审批通道(非 ai.rs AiApprovalRequired)、拆 B-03a(响应等待+超时,不依赖 B-07)/B-03b(取消机制);**✅ B-06/B-07 前置已由 Workflow D 完成,B-03a 已实施(commit 22964a2)**
+- [x] WF-A 数据安全(Sprint 20 ①②)— AI delete→soft_delete + restore/purge/list_trash 三工具 + list_projects 排除回收站(commit 3f0839a)
+- [x] WF-B 阻塞去重(Sprint 20 ③④⑤)— spawn_blocking 三处+补漏 + normalize_path 抽公共 + 复用 is_allowed_column(commit d5a6417)
+- [x] WF-C 前端(Sprint 20 ⑥⑦⑧)— 扫描文案 i18n + parseStack 抽公共 + 8 处 alert/confirm 换组件(commit 02ff88f)
+- [x] WF-D 审批闭环(B-06/B-07/B-03a)— execution_id 下沉 + 共享状态机 + HumanNode select! 实现(commit 22964a2)
+- [x] WF-E Wave1 收尾清债(commit 4aa689e + 89da9fa)— B-03b 审批取消端到端(StateMachine Arc 共享 + execution_id 注册表,**主代理补完 agent 半成品**:agent 初版 workflow_cancel_state 全局孤立实例写了读不到,Explore 审查漏抓语义缺陷)+ T-09 立项回滚级联删(delete→purge_with_descendants)+ T-10 判定已解决(normalize_path 已 canonicalize,零改)+ T-12 df-ideas 清 capture.rs 死码;cargo check / test(含 clone_shares)/ vue-tsc 全绿
+- [x] WF-F Wave2 aichat P0 三项(commit 057a212)— AR-2 新建对话守卫 + AR-3 审批卡片可读化(reason 拼 9 工具对象名 + restore/purge case + id 标签 + i18n)+ AR-4 create_project schema 加 path/stack 合并绑定;**审查 boundary 全误判**(3 agent 并行同工作区,审查 git diff 被三人累计改动污染互相指责越权;correctness "i18n 未添加"亦臆断),主代理独立核查三任务代码全正确,cargo check/vue-tsc 全过;遗留:任务推进构想-2026-06-14.md(agent 越权自主产出,保留未追踪待评估)
+- [x] WF-G Wave3 aichat P1/P2(commit 9e2aeff)— AR-5 stop 兜底 + AR-7 clean UI 真删 + AR-9 friendlyError i18n;**审查 semantic_check 抓对 AR-7 gap**(agent impl 声称改 commands.rs 实际零改动=幻觉,主代理补完);boundary 仍全局 diff 误判(审查 prompt 加固对 correctness 有效、对 boundary 根除不掉 agent 跑全局本能)
+
+### 2026-06-16
+
+- [x] F-260615-05 — **run_command 工具(方案 A)** — 给 AI 加 Shell 执行能力,闭合「写(write_file)→跑(run_command)→看结果→改」循环。RiskLevel::High 强制人工审批(审批卡显示 command+working_dir)。复用 `df_execute::shell::execute`(跨平台 cmd/C·sh -c + tokio::timeout + kill_on_drop)+ `validate_path` 黑名单基础防线;输出 stdout/stderr 各截 10KB(尾部保留+`truncated` 标记)。**安全边界**:A 方案=最高风险,唯一防线=人审+黑名单,未做命令黑名单/网络检测/资源限制(B/C/D 方案领域)。plan: ~/.claude/plans/quizzical-prancing-hennessy.md;改动 tool_registry.rs(注册+truncate_output+import),不动 audit/commands/前端/Cargo.toml — source:用户需求(06-15),src-tauri/src/commands/ai/tool_registry.rs
+- [x] R-260615-01 — selection 文字不可见修复(深色主题)— global.css `::selection` 由 `accent-soft`(透明紫底)+`accent`(紫字) 改为 `accent-hover`(实色紫底)+`#fff`(白字),避免 user 紫底气泡选中后紫字紫底不可见 — source:用户报障(06-15),src/styles/global.css:124
+
+## Bug
+
+(P0/P1 bug 见上方「待办」分类,此处不重复)
+
+### 🔴 新发现但未记录的问题(docs 扫描结果,2026-06-16)
+
+> 来源:后台代理扫描 62 个 markdown 文档发现的问题
+
+**P0 — 严重阻塞**
+- [x] B-260615-57 ✅(2026-06-16,缺口清零销账) [P0→缺口收窄] — ~~文件工具功能缺口~~ **06-15 批次后缺口 6→2→0**:已补 `patch_file`(F-06 ✅) / `search_in_file`(F-07 ✅) / `file_info`(F-08 ✅) / `append_file`(F-09 ✅) / `search_files`(F-12 ✅) / `delete_file`(F-16 ✅ 第⑯批) / `rename_file`(F-15 ✅ 第⑮批销账 06-15)。**缺口清零**:`rename_file`/`move_file` 已补(F-260615-15,根治 AI rename 走 run_command 的中文路径静默失败)。原根因(迫使 LLM 滥用 write_file 放大 FR-S7 覆盖写入风险)已由 patch_file 缓解。— src-tauri/src/commands/ai/tool_registry.rs
+
+**P1 — 重要缺陷**
+- [x] B-260615-39 [P1] ✅(第⑰批销账 06-15) — ~~complete() 超时重试机制缺失~~ 新建 retry.rs(189行+6单测 retry_with_backoff/AttemptOutcome/is_*_retryable)+openai/anthropic complete() 包裹重试(3次/指数1s→2s→4s/±20%jitter/30s总预算止损/可重试 timeout·connect·5xx·429,不重试4xx·auth·body)/不建LlmError枚举(零侵入trait保anyhow::Result)/不用middleware(手写loop)/流式stream()不动/Settings降级常量MAX_COMPLETE_ATTEMPTS=3。**顺带修真bug:Anthropic complete()无60s timeout(FR-R4只修OpenAI一半,建连后挂起无限hang)**。complete()4调用方全低频后台(标题/知识/扫描/AiNode)有兜底,主对话走stream()不受影响。cargo test -p df-ai 37 passed `complete()` IPC 调用仅加 60s 单请求超时,但无重试机制。根因:网络波动时直接失败,无自动重试兜底。影响:偶发网络错误导致用户操作失败需手动重试。修法:实现指数退避重试机制(最多 3 次,总时长不超过 30s),并在 Settings 可配重试开关 — src-tauri/src/commands/ai/commands.rs
+- [x] B-260615-40 [P1] ✅(第⑯批销账 06-15) — ~~审批按钮无状态反馈~~ ToolCard 加 approving ref+spinner+disabled(防重入)+130s 本地计时器兜底+watch tc.status 离开 pending_approval 复位;useAiSend approveToolCall 删乐观置 running(原致审批整块消失切骨架屏,语义错位)保 pending_approval 直到后端事件回流;useAiStream onStreamTimeout 注释更新。loading 就近持有 ToolCard 本地不污染 store status 流转 — src/components/ToolCard.vue + src/composables/ai/useAiSend.ts
+- [x] B-260615-41 [P1] ✅(batch35 销账 2026-06-16,对齐决策a轻量) — ~~工作流执行无进度指示~~ batch35 TaskDetail 进度内嵌已满足决策 a 轻量:复用现有 NodeStarted/NodeCompleted 事件(纯前端 `handleWorkflowEvent` 按 exec_id 过滤 dispatch,不新增后端 progress 事件),`.wf-progress` 区显示 `workflowStepRunning{node}`(当前步骤)+`workflowStepsProgress{done}/{total}`(步骤进度)+完成/失败提示,不新建独立 WorkflowDetail.vue(决策 a 是嵌入现有 TaskDetail 推进按钮区非独立页),i18n key taskDetail.workflowStepRunning/stepsProgress/completedHint/failedHint zh-CN+en 双语已加。**预估剩余时间属方案 b 完整版,决策 a 明确不做** — src/views/TaskDetail.vue
+- [x] ✅(batch34·2026-06-16·workflow wvr7mdo0p) B-260615-42 [P1] — ~~错误信息不一致~~ 同类错误在不同场景下返回的信息格式不统一。根因:错误处理分散在不同模块,缺乏统一规范。影响:用户和调试者难以理解错误含义。修法:制定错误码规范 + 统一错误格式 + 前端错误码映射 — src-tauri/src/commands/ai/mod.rs + src/api/types.ts
+
+**P2 — 中等问题**
+- [x] ⏸️(决策c暂缓·2026-06-16) B-260615-43 [P2] — ~~无撤销重做功能~~ AI 操作(如删除项目、修改任务)无撤销功能。根因:缺乏操作历史记录和回滚机制。影响:误操作无法恢复。修法:实现操作历史栈 + 撤销/重做 UI + 关键操作二次确认 — src/stores/project.ts + src/views/ProjectDetail.vue
+- [x] B-260615-44 [P2] — ~~工具响应时间无反馈~~ 工具执行(如 read_file、write_file)长时间无响应时用户不知道卡在哪里。根因:缺乏工具执行进度反馈。修法:添加工具执行进度提示 + 超时警告 + 取消按钮 — src/composables/ai/useAiStream.ts
+- [x] ✅(batch41·2026-06-16·workflow w9vtei15z) B-260615-45 [P2] — ~~内存泄漏隐患~~ 已核查覆盖(useAiEvents stopListener 三 listener+计时器全清 + AiChat onBeforeUnmount + MESSAGE_CAP=200 滚动淘汰),无泄漏不强改 长时间使用后内存占用持续增长。根因:消息缓存、事件监听器未及时清理。修法:优化缓存策略 + 定期清理机制 + 内存使用监控 — src/stores/ai.ts + src/composables/ai/useAiEvents.ts
+
+**文档整理需求**
+- [x] DOC-260615-15 — **文档缺失**:`patch_file` 工具设计文档已定稿,但使用说明缺失。需添加:工具使用场景、API 参数说明、安全边界、最佳实践 — docs/08-用户指南/patch_file使用指南.md (第⑬批销账 06-15)
+- [x] DOC-260615-16 — **文档过期**:~~使用手册多处过期~~ **第⑯批销账 06-15**:核对发现手册已对齐 06-15 代码基线(npm/7态/多Provider/V13/3节点 全 Y),无需重写,todo 前提过时 — docs/08-用户指南/使用手册-2026-06-12.md
+
+### 🔴 AI 链路核心代码审查(2026-06-16,/review 走查·9 文件多角度论证)
+
+> /review 审查 AI 链路核心(stores/ai.ts + composables/ai/{aiShared,events,send,stream,window,conversations,panel}.ts + AiChat.vue,~1700 行)。契约核对全通(ai-client-ready 后端 lib.rs 监听 / forceSend IPC 注册 / stopListener AiChat:827 调用 / isQueueTimedOut AiChat:745 调用)。9 条发现,每条经多角度论证(确认性/触发边界/去重/修复风险)。
+
+**P1 — 确定性功能 bug**
+- [x] B-260615-46 [P1] — ~~copyMsgContent 失败误报「已复制」~~ `navigator.clipboard.writeText` 权限拒绝/非安全上下文 reject → catch 分支仍 `showToast(t('aiChat.copied'))`(与成功同文案),注释自述「静默忽略」与实现矛盾。用户见「已复制」去粘贴发现空 → 误导。修法:catch 改失败文案或静默 — src/components/AiChat.vue:1037-1040
+- [x] B-260615-47 [P1] — ~~offsetToDOMPosition 恒返回 root 致选区恢复错位(UX-2025-01 功能失效)~~ `createTreeWalker` 后 `currentNode` 初始=root(Element),`walker.currentNode as Text ?? walker.nextNode()` 因 `??` 左操作数 root 非 null 恒取左 + 循环无 nextNode 推进 → 恒返回 `{node: root, offsetInNode}`,root 非 Text 致 `range.setStart(root, offset)` 当 childIndex 用 → 选区错位;策略2(文本兜底)因 startPos 恒非 null 永不触发 → 选区恢复恒错位。修法:标准 `walker.nextNode()` 遍历(对照 saveSelection.toTextOffset:948 正确写法)。**注**:UX-2025-01 在 todo 标 `[ ]` 未实施,但代码已实现(saveSelection/restoreSelection)→todo 状态过时,本条同步修正认知 — src/components/AiChat.vue:908-921
+
+**P2 — DRY/微优化**
+- [x] B-260615-48 [P2] — ~~restoreUiState 与 watch 四行赋值逐字重复(DRY)~~ restoreUiState(L22-26) 与 watch 回调(L36-39) 4 行 `if(typeof s.x==='boolean') state.x=s.x` 重复。修法:抽 `applyUiState(s)` 两处调用,零行为变 — src/composables/ai/useAiPanel.ts:20-41
+- [x] B-260615-49 [P2] — ~~handleEvent 每事件构造数组做 includes~~ `!['AiApprovalRequired','AiCompleted','AiError'].includes(type)` 每 delta/token 新建数组字面量。修法:提模块级 `NO_RESET_WATCHDOG` Set — src/composables/ai/useAiEvents.ts:109
+- [x] B-260615-50 [P2] — ~~tryForceSend 前置 streaming=false 瞬态触发 watch 清流式块~~ L151 `state.streaming=false` 触发 AiChat:812 watch 清 streamingBlocks+rAF,doSend 再设 true → 一次清空重建。注释自述「force_send 会重新设 true」证明前置 false 多余。修法:删该行(保留 clearStreamWatchdog) — src/composables/ai/useAiSend.ts:150-156。属 B-260615-56 实施细节缺陷
+
+**P3 — 风格/可选**
+- [x] B-260615-51 [P3] ✅(第⑮批销账 06-15) — ~~MESSAGE_PUSH_BURST 单用常量~~ stores/ai.ts:38 仅 watch(:93) 一处用,按风格可内联为 2 + 行内注释。两可(常量名助读),属风格偏好 — src/stores/ai.ts:38
+- [x] B-260615-52 [P3] — ~~`if(!state.streaming) state.streaming=true` 冗余判断~~ useAiSend.ts:126 进 busy 分支后判断多余,直接赋值幂等 — src/composables/ai/useAiSend.ts:126-128
+- [x] ✅(batch39·2026-06-16·workflow ww4wb66br) B-260615-53 [P3] — ~~watch(messages,{deep}) + JSON.stringify 全量快照~~ 已优化(snap 剔除 len/id/role 只留 n+tc,流式 delta 不再触发 body 重算,deep watch 保留 toolCalls 嵌套检测)。实际 watch 在 AiChat.vue:1128-1146(非 835) AiChat.vue:835 长+多 toolCalls 时每变更 deep 遍历+序列化。snap 短路兜底 + MESSAGE_CAP=200 封顶,典型场景可控,待超长会话反馈再优化 — src/components/AiChat.vue:835-863
+- [x] B-260615-54 ✅评估维持(2026-06-16) — `void convId` 参数丢弃:R5 设计明确(分离窗口只读不切会话防落错),注释充分,签名稳定(onMounted 调用传参)。保留正确,不修 — src/composables/ai/useAiWindow.ts:83
+
+> **统计**:🔴2(P1 确定性 bug) 🟡3(P2 DRY/优化) ⚪3(P3 风格) + 1 评估维持。总体评级「良」:架构清晰、契约闭合、注释扎实,两处 🔴 低风险一行/小改可根治。详见本轮 /review 输出。
+
+### 🔧 2026-06-17 全量代码缺陷扫描(Rust后端+DAG引擎·仅走查·未实施)
+
+> 周期性全量缺陷扫描(session-role-diagnose-only)。Rust 后端 11 项已完成;Vue 前端 + DAG 引擎因 API 速率限制失败待重跑;aichat 交互层扫描进行中(提高优先级,结果追加到下方「aichat 交互体验」专区)。
+>
+> **论证原则**:每条经 file:line 源码佐证 + 触发条件分析 + 修复风险评估。只记录确定性 bug 或高概率问题。
+
+#### 🔴 P0 — 确定性 Bug(3 项·建议立即修复)
+
+- [x] ~~**BUG-260617-01**~~ → **已提升至 P0 阻断性 bug 区(🚨 立即修)**,见上方 L47
+  **用户报错证据(2026-06-17)**:
+  ① Anthropic: `[1214][messages 参数非法。请检查文档。]` → 无 HTTP 数字前缀 → 走文本分支 → `\|\| true` → retryable=true → **空耗 4 次重试**
+  ② DeepSeek: `400 Bad Request: {"error":{"message":"The reasoning_content in the thinking mode must be passed back to the API"}}` → 同理可能命中
+  **根因**: 审查 agent 误认为"400/403 走数字码 :424 is_status_retryable 返 false"。但实际 provider 错误消息格式多样——Anthropic 中文错误无 "HTTP 400" 前缀,直接落入 `\|\| true` 兜底分支。
+  **修法**: 移除 `\|\| true`,unknown 默认 `false`;同步修改单测 classify_unknown_retryable 断言为 `assert!(!classify_status_or_class("unknown"))`。— src-tauri/src/commands/ai/stream_recv.rs:433 + :474-478(单测)
+- [x] ~~**BUG-260617-12**~~ → **已提升至 P0 阻断性 bug 区(🚨 联动 BUG-01)**,见上方 L48
+  **根因**: 全代码库**零处理** reasoning_content/thinking 字段:
+  - `df-ai-core/src/provider.rs:22-39` `CompletionRequest` struct **无** reasoning_content 字段
+  - `df-ai-core/src/provider.rs:92-99` `ChatMessage` struct **无** reasoning_content 字段
+  - `crates/df-ai/` 下 anthropic_compat.rs / openai_compat.rs **零命中** reasoning|thinking
+  **影响**: 使用 DeepSeek thinking 模型时每次请求必 400,且被 BUG-01 (`\|\| true`) 放大至 4 次无效重试(浪费 token + 时间)。
+  **修法方向**: ① CompletionRequest 加 `reasoning_content: Option` + ChatMessage 同加 ② agentic loop 重试时从上一轮 response 提取 reasoning_content 透传 ③ openai_compat 构建请求时映射到 API 的 reasoning_content 字段。— crates/df-ai-core/src/provider.rs:22-39,92-99 + crates/df-ai/src/openai_compat.rs
+- [x] **BUG-260617-02 [P0]** — **`file_info` 全量读大文件到内存再截断前 8KB** — `tool_registry.rs:1125-1126` `tokio::fs::read(path)` 将整个文件读入内存(注释说">2MB跳过避免全量读"但实际先全量读再取前 8192 字节做二进制检测)。>2MB 文件触发 OOM 风险。**修法**:用 `File::open` + `read_exact()` 仅读前 N 字节,或加 `.take(8192)` 截断流式读取。— src-tauri/src/commands/ai/tool_registry.rs:1125-1126
+- [x] **BUG-260617-03 [P0]** — **路径遍历防护可被编码绕过** — `tool_registry.rs:78-95` 只做字符串 `..` 检测 + 小写化,URL 编码 `%2e%2e` / Unicode 同形字符可绕过。**修法**:先 `percent_decode` 再做词法 `..` 分段归一化检查;对不存在路径(new file) 也应规范化校验。— src-tauri/src/commands/ai/tool_registry.rs:78-95
+
+#### 🟡 P1 — 高概率问题(5 项·建议本轮修复)
+
+- [x] **BUG-260617-04 [P1]** — **`ai_chat_force_send` → `ai_chat_send` 双锁竞态窗口** — `commands.rs:763` force_send 先 lock 复位 generating→释放锁→调 ai_chat_send 再 lock。两锁之间 stop IPC 可插入打断"原子复位+发送"语义;若 ai_chat_send 因 generating=true 被 reject → 前端状态不一致。**修法**:force_send 内联 spawn run_agentic_loop(同 regenerate/edit 模式),不走 ai_chat_send 门控。— src-tauri/src/commands/ai/commands.rs:744-763
+- [x] **BUG-260617-05 [P1]** — **`try_continue_agent_loop` 4 次独立 lock 非原子化** — `agentic.rs:943-1036` 至少 4 次 `state.ai_session.lock().await`,每次 release 后其他 IPC 可修改 session → 续跑判断基于过时快照(如步骤 1 判 should_continue=true → 步骤 2 间 user 点 stop → 步骤 3 仍续跑)。**修法**:单次 lock 内完成所有字段读写,或引入结构化快照一次取出。— src-tauri/src/commands/ai/agentic.rs:943-1036
+- [x] **BUG-260617-06 [P1]** — **`accumulate_tokens` 整数溢出无 saturating 保护** — `conversation.rs:49-51` `old.unwrap_or(0) + add as i64` 长期对话累积接近 i64::MAX 后翻负。**修法**:改用 `.saturating_add()` 或类型改为 `u64`。— src-tauri/src/commands/ai/conversation.rs:49-51
+- [x] **BUG-260617-07 [P1]** — **`generate_diff` LCS O(n*m) 内存爆炸** — `tool_registry.rs:32-75` 标准 DP diff,5000 行输入 ≈ 200MB DP 表。虽有 changes>300 截断但截断前已分配计算完毕。**修法**:超长输入(均>1000行)跳过 LCS 改用 Myers diff 或直接返回截断 diff。— src-tauri/src/commands/ai/tool_registry.rs:32-75
+- [x] **BUG-260617-08 [P1]** — **Lagged 循环每次重建 WorkflowRepo 连接池** — `workflow.rs:204` forward 循环内每次 `WorkflowRepo::new(&forward_db)`,高频 Lagged 场景可能连接数暴增。**修法**:Repo 提到循环外创建一次或传 Arc。— src-tauri/src/commands/ai/workflow.rs:204
+
+#### ⚪ P2 — 关注项(3 项·低优先级)
+
+- [x] **BUG-260617-09 [P2]** — **递归深度 max_depth 由 LLM 参数控制无上限** — `tool_registry.rs:1391+/1511+` LLM 可传入极大值,虽有 entries 上限隐式约束但防御不完整。**修法**:clamp 到合理范围(如 1-10)。— src-tauri/src/commands/ai/tool_registry.rs:1391-1545
+- [x] **BUG-260617-10 [P2]** — **skills.rs OnceLock 初始化用同步 std::fs 阻塞 runtime** — `skills.rs:69,168` 首次 skills 查询阻塞 tokio runtime 数十ms。影响极小(仅首次),但与规范偏差。**修法**:`spawn_blocking` 包裹或接受当前行为。— src-tauri/src/commands/ai/skills.rs:69,168
+- [x] **BUG-260617-11 [P2]** — **`read_file` 工具无 offset 时全量返回大文件** — `tool_registry.rs:787-794` 无 offset 时 `content.clone()` 全量返回(1865 行/101KB 的 tool_registry.rs 自身即触发此问题)。**修法**:无 offset 默认返回 500 行 + has_more 提示翻页。关联 memory:[[read-file-pagination-needed]](已记录方案)。— src-tauri/src/commands/ai/tool_registry.rs:787-794
+
+---
+
+### 📊 aichat 交互缺陷扫描统计(2026-06-17)
+
+| 严重度 | 数量 | 编号范围 |
+|--------|------|----------|
+| 🔴 P0 | 1 | UX-01 (消息重叠) |
+| 🟡 P1 | 9 | UX-02~11,13 |
+| ⚪ P2 | 6 | UX-14~19 |
+| LOW | 8 | UX-20~27 |
+| INFO | 1 | UX-28 |
+| **总计** | **25** | (含 4 项历史记录提升优先级 + 21 项新扫/确认) |
+
+**Top 5 推荐 immediate fix(ROI 排序)**:
+1. **BUG-01** (`|| true` → false) — Rust 后端,一行改动消除无效重试
+2. **UX-09** (startListener 异常吞噬) — 3 行 try/catch,防止事件黑洞
+3. **UX-10** (AiError 不清 pendingApprovals) — 一行赋值消除残留 UI
+4. **UX-11** (modelOverride 泄漏) — 注释已有要求,只缺实现
+5. **UX-03** (copyMsgContent 误报) — catch 分支改文案
+
+---
+
+## 长期 / 待需求驱动(不进看板主线)
+
+- 裁剪/压缩消息按需召回(Query Function + 分层存储)
+- 停止生成 idle 即时优化(`tokio::sync::Notify` 替代 120s 轮询)
+- 模型能力系统 Phase 3(Agent 内智能路由 + 成本预算 + 模型级联)
+- `node_executions` 全表 list 命令(当前只写不读)
+- `do_promote` crate 层 TODO(promotion.rs,现走前端闭环)
+
+---
+
+## 二次归档(2026-06-18 销账后, 整段全 [x]/纯历史迁此)
+
+### 🔧 2026-06-17 知识库列表首次空(确定性 bug·根因确诊)
+
+> 用户报:打开系统进知识库列表数据空,加载完成后仍不显示;需点"待处理"再回"知识库"才显示。session-role-diagnose-only,排查链路根因确诊。
+
+**根因(确定性)**:`stores/knowledge.ts:31` `_itemsReqSeq` 序号被 **loadList(:62)/loadCandidates(:80)/search(:92) 三者共用**(都 `++_itemsReqSeq`),设计为"丢弃旧响应防竞态",但漏了并发覆盖场景:
+
+**onMounted(Knowledge.vue:521-523)并发调用** `loadList()` + `loadCandidates()`:
+1. loadList 发起(seq=1, await list)
+2. loadCandidates 发起(`++_itemsReqSeq`→2,**覆盖**)
+3. loadList 响应回来 → knowledge.ts:67 `if (seq !== _itemsReqSeq)` → 1≠2 → **return 丢弃** → `state.items` 永空
+4. loadCandidates 响应 → seq=2 匹配 → 写 `state.candidates`
+
+→ 首次进入 `listItems` computed(Knowledge.vue:297-298 读 `store.items`)空。点"待处理"(inbox)→ switchTab loadCandidates;再回"知识库"(library)→ switchTab **单独 loadList(无并发)**→ seq 匹配→写 items→**显示**。完美解释"切走再回才显示"。
+
+注释 knowledge.ts:29-30「items 序号覆盖 loadList/loadCandidates/search;candidates 与 items 共用同一队列」=有意共用序号,但没料到 loadList 被并发 loadCandidates 覆盖丢弃。
+
+**修法(确定性·唯一正确解·低风险)**:分序号——loadList/search(都写 `state.items`)共用 `_itemsSeq`;loadCandidates(写 `state.candidates`)用独立 `_candidatesSeq`。各自防竞态,互不丢弃。改 knowledge.ts:31 拆两序号 + loadList/search/loadCandidates 各用对应序号。
+
+- [x] ✅(2026-06-18·workflow fix-batch-260618 agent A·主代独立核验 vue-tsc EXIT 0) **🚨 B-260617-18 [P1]** — **知识库列表首次进入必空**(已修)。`_itemsReqSeq` 序号 loadList/loadCandidates/search 共用(knowledge.ts:31/:62/:80/:92),onMounted 并发 loadList+loadCandidates 时 loadList 的 seq 被 loadCandidates 的 `++` 覆盖 → loadList 响应 :67 `seq !== _itemsReqSeq` 丢弃 → state.items 永空 → 列表空。切 tab 再回(switchTab 单独 loadList 无并发)才显示。**确定性 bug**,修法:拆 `_itemsSeq`(loadList/search) + `_candidatesSeq`(loadCandidates) 分队列防互相丢弃。— src/stores/knowledge.ts(:31 序号共用·:62/:80/:92·:67 丢弃逻辑) · src/views/Knowledge.vue(:521-523 onMounted 并发)
+
+### 🔧 2026-06-18 Tasks 状态筛选窄屏换行布局(UI 调整·未实施)
+
+> 用户报:任务队列(Tasks 页)状态筛选窄屏换行时,要图标独占上行、文字独占下行,不要图标+文字同行换行错乱。session-role-diagnose-only,定位 + 修法。
+
+**当前**(Tasks.vue):
+- filter-btn 模板 :34 `{{ s.icon }} {{ s.label }}` —— 图标文字**纯文本拼接**(emoji+空格+文字),无独立元素。
+- filter-btn CSS :297-306 —— **无 display flex**(button 默认),无响应式布局。
+- filter-group :287-291 `display:flex` **无 flex-wrap**(nowrap),8 个状态按钮窄屏排不下溢出。
+
+**问题**:窄屏按钮内容 `图标 文字` 在空格处断行 → 图标行1左、文字行2左,错乱(用户所见)。
+
+**修法(4 步)**:
+1. 模板拆 span:`{{s.icon}}{{s.label}}`(图标文字独立元素,便于 flex 控制)
+2. filter-btn CSS 加 `display:inline-flex; align-items:center; gap:4px`(宽屏 row 图标左文字右)
+3. `@media (max-width: 断点)` filter-btn `flex-direction:column; gap:2px`(窄屏图标上文字下)
+4. filter-group 加 `flex-wrap:wrap`(按钮可换行,否则 nowrap 窄屏溢出)
+
+断点按实际窄屏宽度定(filter-bar wrap 触发附近,DevTools 量)。
+
+- [x] ✅(2026-06-18·workflow fix-batch-260618 agent B·主代独立核验 vue-tsc EXIT 0) B-260617-19 [P3] — **Tasks 状态筛选窄屏换行布局**(已修)。filter-btn 图标文字纯文本拼接(:34)+ 无 flex(:297-306)+ filter-group nowrap(:287-291),窄屏换行错乱。修法:模板拆 span + filter-btn inline-flex + @media column + filter-group flex-wrap。— src/views/Tasks.vue(:34 模板·:287-291 filter-group·:297-306 filter-btn)
+
+### 🔧 2026-06-18 AI Chat 对话列表"更早"折叠(功能增强·未实施)
+
+> 用户需求:AI Chat 侧栏对话列表更早的对话默认折叠,点击展开。session-role-diagnose-only,定位 + 修法。
+
+**当前**(AiChat.vue):
+- `groupedActive`(:1626-1645)已按 **today/yesterday/earlier** 时间分桶(timeBucket :1608 按日历日)。
+- 但分组标题(:83 `.ai-conv-group-title`)纯文本,**无折叠交互**,三组全展开。
+- earlier 组平铺所有老对话。
+
+**修法(5 步)**:
+1. 折叠状态:`const collapsedGroups = ref>(new Set(['earlier']))`(默认 earlier 收起)。
+2. 标题加交互(:83):`
{{ collapsedGroups.has(group.key) ? '▸' : '▾' }}{{ group.label }}{{ group.items.length }}
`。 +3. items 折叠(:84 v-for conv):加 `v-show="!collapsedGroups.has(group.key)"`。 +4. `toggleGroup(key)`:切换 Set(仿 :2554 toggleSegment 模式)。 +5. CSS:标题 `cursor:pointer` + hover + 折叠图标/计数样式。 + +**可选增强**:localStorage 持久化折叠态跨刷新;yesterday 也默认折叠(视偏好)。 + +- [x] B-260618-02 [P2] — **AI Chat 对话列表 earlier 组默认折叠**(✅ 已实现·超出原诉求:三组 today/yesterday/earlier 皆可折叠 + earlier 默认折叠 + 数量 + 空占位,见 UX-260618-00 细分③)。groupedActive 已按 today/yesterday/earlier 分桶(:1626),但标题(:83)无折叠交互三组全展开。修法:collapsedGroups ref 默认{'earlier'} + 标题点击 toggle(▾/▸+count)+ items v-show 折叠 + toggleGroup + CSS。— src/components/AiChat.vue(:83 分组标题·:1626-1645 groupedActive·:1608 timeBucket) + +### 🔧 2026-06-18 走查·ToolCard run_command 增强(含 P3 文案·未实施) + +> 核验工作区 ToolCard.vue(run_command 命令行常显+可折叠输出)+ ToolCardList.vue(全局收起下沉+首卡折叠 bug 修复)。session-role-diagnose-only。 + +**ToolCard.vue run_command 增强 ✓**:命令行常显(`$ command`)+ 可折叠输出(stdout+stderr 合并 commandOutput),失败默认展开看 stderr / 成功折叠(watch status immediate)。commandOutput 空隐藏 toggle,shortCmd 首行截断。逻辑正确,质量好。 + +**ToolCardList.vue ✓ 亮点**:全局收起按钮从 topbar 下沉到首个可折叠分组标题行尾巴(firstCollapsibleGi)省空间;`userCollapsedCards`(UX-260617-23)修复**首卡 ci===0 兜底展开导致无法折叠** bug——isCardExpanded 最优先查折叠记忆,toggleCardExpand 改用 isCardExpanded 真实态判断。逻辑自洽。 + +- [x] B-260618-03 [P3] — **ToolCard run_command 输出折叠文案语义不准**(✅ 已修:ToolCard 单卡 toggle 三处[读文件预览 :78 + 命令输出 :124/126]用 `aiTool.expand` 单数,ToolCardList 全局 :30 保留 `expandAll`/`collapseAll` 复数;zh-CN 补单数 `expand:'展开'` key,en 既有;collapse 本就单数无需新增)。toggle 按钮 + title 复用 `aiTool.expandAll/collapse`(i18n 值"全部展开/全部收起",本用于 ToolCardList 全局收起),但此处是**单个命令输出的展开/收起**,应为单数"展开/收起"。修法:新增 `aiTool.expand/collapse` 单数 i18n key(en+zh-CN),ToolCard.vue toggle 文案改用单数 key。— src/components/ToolCard.vue(run_command toggle 文案 :title/@click 处) + diff --git a/docs/07-项目管理/待决策归档/2026-06.md b/docs/07-项目管理/待决策归档/2026-06.md index 372bd54..a5ee656 100644 --- a/docs/07-项目管理/待决策归档/2026-06.md +++ b/docs/07-项目管理/待决策归档/2026-06.md @@ -324,3 +324,30 @@ #### ~~S-260614-01 「显示多开」需求澄清~~ - **状态**:✅ 已解决(2026-06-16)— 用户澄清:需求本质是「会话来回切并行处理」(A 对话回复中切到 B 发消息,A 后台继续跑),非设置页开关。已被 F-260616-09(决策 a:d1 侧栏切换+后台并行)完整覆盖收编。 + +--- + +## 当前队列已决项迁入(2026-06-18) + +#### B-260617-01 run_workflow 作为 AI 工具无法执行 + 无意义重试 + 审批混淆 +- **背景**:2026-06-17 实测发现,AI 对话中调用 `run_workflow(task_id, target_status)` 推进任务状态,审批通过后 handler 返回 Err 提示字符串(`tool_registry.rs:530-546`),`ai_approve`(`commands.rs:329-334`)将其包成 `"failed"` tool_result 回传 LLM → LLM 认为失败 → 重试(1/4) → 又进审批 → 循环。三个关联问题: + - **问题 1**:run_workflow handler 仅持有 `db: Arc`,无法访问 `AppHandle/State`(registry/event_bus/workflows Repo),返回 Err 提示前端走 IPC。但 `ai_approve` 审批通过后直接 `ai_tools.execute()` 调 handler → 必然 Err。 + - **问题 2**:handler Err → tool_result = 错误提示 → LLM 重试 → 同结果循环(工具定义未标 `no_retry`)。 + - **问题 3**:前端渲染的是 AI 工具审批(tool_call pending),非工作流 HumanNode 审批。用户批准后工作流引擎从未启动。 +- **决策点**:run_workflow 审批通过后如何真正执行工作流? +- **选项**: + - a: **前端拦截**(`ai_approve` 中识别 `run_workflow` → 不走 `ai_tools.execute` → 直接 `invoke('run_workflow', {task_id, target_status})` → 结果回填 tool_result) + - b: **后端注入 State**(`build_ai_tool_registry` 传入 `AppHandle` 使 handler 可直接执行,改 state.rs + tool_registry 签名) + - c: **从 AI 工具移除 run_workflow**(仅保留 advance_task 推进状态,工作流由用户手动在前端触发) +- **推荐**:**a 前端拦截**(改动最小,`ai_approve` 一处分支即可;方案 b 需改 tool_registry 签名链路较长;方案 c 退回手动不符合 AI 驱动目标)。问题 2/3 随 a 自动消解。 +- **关联**:todo B-260617-01 / tool_registry.rs:530-546 / commands.rs:329-334 / audit.rs:590-630 +- **状态**:✅ 已决(2026-06-17·波9 e23b316)— **决策:a 细化为 a2(后端分支)**。ai_approve 已持 app+state,后端直接调 run_workflow_inner 最干净(无需新 IPC/前端分支/工作流知识泄漏),符合 a 大方向(绕开 ai_tools.execute 必 Err)。问题 2 随 a2 消解(成功 tool_result + High 去重缓存双层断循环);问题 3 渲染区分记观察项(tool_display_hint 已含「触发工作流」语义)。详单见待审查 CR-52。 + +### ~~S-260614-01 多会话来回切(A/B 路线)~~ — 已并入 F-260616-09 +- **状态**:✅ 已并入 F-260616-09(2026-06-16 已决 a:A 隔离修复 + B 立项高优先实施)。A/B 路线决策统一收口于 F-260616-09,本条不再独立维护。A 路线实施待 T-260614-02 实测确认是否升 B。 + +#### B-260617-11 tauri.conf.json 打包目标收窄确认 +- **背景**:工作区改动 `bundle.targets: "all" → ["nsis"]`(src-tauri/tauri.conf.json:28),收窄到仅 Windows NSIS。若随其他改动一并提交,macOS(dmg/app)/Linux(deb/appimage)构建将不可用,影响其他开发者/CI。 +- **决策点**:临时本地构建 vs 有意入库? +- **选项**:A 临时本地构建(提交前 revert 此行) / B 有意入库(改按平台条件配置而非硬编码单 target) +- **状态**:✅ 已决(2026-06-18) — **决策:A 临时本地构建,要求高速**。`["nsis"]` 已是 Windows 单一最快安装包目标。提交前须 revert 为 `"all"`。若进一步提速可用 `["app"]`(裸 .exe 无安装包)或 `tauri build --no-bundle`。 diff --git a/docs/07-项目管理/待审查归档/2026-06.md b/docs/07-项目管理/待审查归档/2026-06.md new file mode 100644 index 0000000..d3542aa --- /dev/null +++ b/docs/07-项目管理/待审查归档/2026-06.md @@ -0,0 +1,1062 @@ +# 待审查已审归档(2026-06) + +> 从 docs/待审查.md 拆出: 已审 ✅ CR 段。 +> 拆分: 2026-06-18 +> 来源: docs/待审查.md + +--- + +## 当前队列已审项 + +### CR-260616-58 F-260616-07 阶段3 AiNode 自审闸门(verdict=fail 作 DAG 节点闸门阻断下游)(e16d038) — ✅ 已审(PASS) + +- **复审结论(2026-06-17·审查 agent 亲跑 cargo+test+独立 grep/read 核验)**: ✅ **PASS** — 🔴0 🟡0 ⚪1 + - **验证**: `cargo check --workspace` **EXIT 0**(6 pre-existing dead_code warnings 无关本 CR)/ `cargo test -p df-nodes --lib` **82 passed; 0 failed; 1 ignored** (+3 gate 单测:1076-1103)。 + - **①gate_should_block 决策矩阵完备 PASS**:ai_node.rs:426-428 `gate && verdict == "fail"` 简洁正确,gate=false 全放行(阶段2 兼容),gate=true+仅 fail 阻断,unknown/非标 verdict 保守不阻断(单测:1096-1103 覆盖)。 + - **②execute 末尾 Err 路径正确 PASS**:ai_node.rs:574-582 update_field 落库在前(审查痕迹不丢) + :615-629 闸门检查后置,verdict=fail 返回 Err 正确。 + - **③复用 executor first_err + ②-4 回调语义 PASS**:df-workflow/src/executor.rs:128-178 first_err 捕获首个 Err 后立即中止后续层(下游 human_review 不跑) + workflow.rs:292-310 failed 触发 regression_target 退回 in_review(review_rounds+1,task_advance_node.rs:515-547 验证)。 + - **④gate 默认 false 向后兼容 PASS**:ai_node.rs:619 `.unwrap_or(false)` 阶段2 行为不变,AiNode schema(:331-346)无 gate 字段,仅 AiSelfReviewNode 有(:644)。 + - **⑤testing 模板 gate:true 启用 PASS**:task_workflow_templates.rs:65-69 `"gate": true` 正确注入 + 单测:139-144 断言验证。 + - **⑥schema 契约正确 PASS**:ai_node.rs:644 gate 布尔字段,非 required,description 完整。 + - **⑦文件锁独立性 PASS**:ai_node.rs + task_workflow_templates.rs 独占,df-workflow 零改动(git show e16d038 确认)。 + - **⑧选型论证 PASS**:ai_node.rs:605-613 注释完整(方案 A 最简 vs B 暂缓条件引擎 vs C executor 改核心),选 A 语义贴近「自审结果作为闸门」且可开关。 + - **⚪ CR-58-1(low,注释冗余)**:ai_node.rs:605-613 选型注释 9 行,但提供设计决策历史,对后续维护有价值,可保留。 + - **待修项回流 todo**: **无**(8 维度全 PASS,1 low 注释冗余非阻断)。 + +### CR-260616-59 波16 F-04c前端Settings池编辑(enabled toggle+weight输入+IPC绑定)(3f90aad) — ✅ 已审(PASS) + +- **复审结论(2026-06-17·审查 agent 亲跑 vue-tsc + cargo check + 独立 grep/read 核验)**: ✅ **PASS** — 🔴0 🟡1 ⚪0 + - **验证**: `npx vue-tsc --noEmit` EXIT 0 / `cargo check --workspace` EXIT 0(6 warning 全 pre-existing dead_code)。 + - **①updateProviderPool wrapper 对齐现有 invoke 风格 PASS**:ai.ts:146-148 `invoke('ai_update_provider_pool', { providerId, enabled, weight })` 与现有 aiApi.setProvider/deleteProvider 风格一致,thin wrapper 无额外逻辑。 + - **②AiProviderConfig enabled?/weight? 可选 PASS**:types.ts:191-193 enabled?/weight? 可选字段(snake_case 对齐 serde),注释完整("老库默认 true"/"老库默认 50")。Settings.vue:61 `p.enabled !== false` 正确处理 undefined 视为 true。 + - **③即时调 IPC 决策合理 PASS**:即时调理由(Settings.vue:616-618 注释)充分——池调度参数 reload_provider_caps 变更即重建,下条消息即生效;避 saveProvider 密钥迁移分支(R-PD-1)。对齐 syncConcurrencyConfig 即时调模式(Settings.vue:822-839 debounce 300ms 证明)。 + - **④debounce 300ms+clamp+NaN 兜底 PASS**:Settings.vue:647 `clearTimeout(_poolTimer)` + :648 `setTimeout(..., 300)` debounce 正确。:645 `Math.min(100, Math.max(0, Number.isFinite(raw) ? Math.trunc(raw) : 50))` 双 clamp+NaN 兜底(回退 50)防 serde u32 失败。 + - **⑤乐观更新 revert 逻辑 部分PASS**:onPoolToggle(:626-637) `const prev = p.enabled` + `p.enabled = prev` revert 完整。**🟡 onPoolWeightChange(:643-656) 缺 revert**—— debounce 异步路径,失败后 `p.weight` 已更新为 clamped 值但 IPC 未成功落库,用户视角显示已改但实际未生效,需补 `const prev = p.weight; p.weight = clamped; catch { p.weight = prev }`(前端乐观更新失败应 revert 状态)。 + - **⑥i18n 双语 PASS**:zh-CN/settings.ts:17-23 + en/settings.ts:17-23 7 key 对称完整(detailPoolEnabled/detailPoolWeight/badgePoolDisabled/descPoolEnabled/descPoolWeight/toastPoolUpdateOk/toastPoolUpdateFail)。特殊字符无转义需求(值均为静态枚举字面量)。 + - **⑦onUnmounted 清理 PASS**:Settings.vue:954 `if (_poolTimer) clearTimeout(_poolTimer)` 清理完整,与 _concurrencyTimer/_agentIterTimer 等同级处理。 + - **⑧Tauri 2 参数命名转换 PASS**:后端 commands.rs:1023-1028 `provider_id/enabled/weight`(snake_case),前端 ai.ts:147 传 `providerId/enabled/weight`(camelCase)。Tauri 2 自动转换 snake_case→camelCase 为设计行为([官方文档](https://v2.tauri.app/develop/calling-rust/)+[StackOverflow](https://stackoverflow.com/questions/78432685/why-does-tauri-modify-the-parameter-names-of-invoked-functions))。 + - **待修项回流 todo**: **🟡 onPoolWeightChange 补 revert 逻辑**(非阻断,用户可刷新重试,但应补 `const prev = p.weight` + catch 内 `p.weight = prev`)。 + +### CR-260616-60 波17 B-03b-R10前端审批清理(终态清pending+互斥锁防重复IPC)(0289d8c) — ✅ 已审(PASS) + +- **复审结论(2026-06-17·审查 agent 亲跑 vue-tsc + 独立 grep/read 核验源码)**: ✅ **PASS** — 🔴0 🟡0 ⚪0 + - **验证**: `npx vue-tsc --noEmit` **EXIT 0** ✅ + - **①⑤ 终态清 execution_id 限定防误清 PASS**:workflow.ts:62-70 三条件判断(`pendingApproval存在` + `execution_id匹配` + `终态事件类型`) + 并发工作流场景(execution_id不等不误清他人pending) + 后端事件类型契约确认(WorkflowCompleted/Failed/NodeCancelled→snake_case) + PendingApproval.execution_id字段契约一致(state.ts:6 ↔ events.rs:86)。 + - **②⑦ 互斥锁 _approvalInFlight 单例防重复 IPC PASS**:模块级单例(workflow.ts:11) + approve/cancel双入口守卫(L99/L135 `if(_approvalInFlight)return`) + finally块全释放(L127/L150 成功/失败均释放) + 共享单例跨调用方统一(所有入口经workflow.ts统一函数无旁路)。 + - **③ 失败重试语义正确 PASS**:approve/cancel catch块只设置state.error不清空pendingApproval(L122-124/L145-147),用户可重试 + 成功后才清空(L121/L144)。 + - **④ 互斥锁释放路径完备 PASS**:approve/cancel try-catch-finally三路径全覆盖,无early return跳过finally(async/await保证finally执行)。 + - **⑤ 事件类型覆盖完整 PASS**:终态三类型(workflow_completed/workflow_failed/node_cancelled)全覆盖,覆盖工作流完成/失败/节点取消三路径。 + - **⑥ 单槽设计边界清晰 PASS**:当前设计pendingApproval单槽(state.ts:23),审查报告P2「改Map」推迟正确,本CR范围只实施⑤⑦未改动架构。 + - **⑦ 调用方入口统一 PASS**:ProjectDetail.vue三入口(409/420/427)经workflow.ts统一函数,stores/project.ts转发无旁路。 + - **⑧ type safety 验证 PASS**:WorkflowEventPayload.execution_id/event.type契约(types.ts:140-142) + WorkflowEventType联合类型含三终态(types.ts:135-136)。 + - **待修项回流 todo**: **无**(8维度全PASS,零问题)。 + +- **范围**: workflow w9xypd8ug agent B。src/stores/project/workflow.ts ⑤终态不清(startEventListener 加 else-if,workflow_completed/failed/node_cancelled 且 execution_id 匹配清 pendingApproval,按 exec_id 限定防并发误清)+ ⑦互斥(模块级 _approvalInFlight 单例标志,approve/cancel 入口检查+try/finally 释放,防双击重复 IPC)。④单槽→Map 推迟(state 形态变更牵动多消费者,后端串行闸门保证单执行不并发,现实触发需并发工作流)。波17 B-03b 后端 workflow.rs 0 实施(⑧⑩⑪ 报告失效/已修复/已有兜底,③ 真实潜伏跨 crate df-core 缺 execution_id 留观察)。 +- **维度**: ⑤终态清 execution_id 限定(防并发误清)+ ⑦互斥 _approvalInFlight 单例(跨调用方统一)+ ④推迟理由(state 形态变更+后端串行)。 +- **commit**: 0289d8c。 +- **主代核查**: vue-tsc EXIT 0。 +- **审查 agent 待复审重点**: ⑤终态清 execution_id 匹配逻辑(防误清)+ ⑦互斥 _approvalInFlight try/finally 释放(异常路径不泄漏)。 +- **待修项回流 todo**: **无**(⑤⑦ 实施,④ 推迟有理由)。 + +### CR-260616-61 波18 ARC df-core改名df-types(机械全workspace 54处)(4be1591) — ✅ 已审(PASS) + +- **复审结论(2026-06-17·审查 agent 亲跑 cargo+test+独立 grep/read 核验)**: ✅ **PASS** — 🔴0 🟡0 ⚪0 + - **验证**: `cargo check --workspace` **EXIT 0**(6 pre-existing dead_code warnings 无关本 CR)/ `cargo test -p df-nodes` **82 passed; 0 failed; 1 ignored** / `cargo test -p df-workflow` **23 passed** / `cargo test -p df-ideas` **20 passed** / `cargo test -p df-storage` **29 passed**。 + - **①grep df_core/df-core 代码侧归零 PASS**: `grep -r "df_core" --include="*.rs" --include="*.toml" crates/ src-tauri/` 零输出 / `grep -r "df-core" crates/ src-tauri/` 零输出 / `grep -r "df_types" crates/ src-tauri/` 54 处(与 commit 统计一致)。 + - **②import 路径全更新 PASS**:git show 核验 task_advance_node.rs 12 处 `df_core::error::`→`df_types::error::` / executor.rs 8 处 `df_core::events/types`→`df_types::` / task.rs 1 处 `df_core::types`→`df_types::types` / 全部编译期通过(cargo check EXIT 0)。 + - **③注释连字符全更新 PASS**:git show 核验 lib.rs 文档注释 `df-types: 核心类型定义` + task_advance_node.rs/human_node.rs/manager.rs/crud.rs 共 8 处注释 `df-core`→`df-types` 或 `df-types::`。 + - **④Cargo.lock 自动迁移 PASS**:git show 核验 `df-core` 包定义已删除(name + dependencies 块移除) + 所有依赖方(df-ai/df-execute/df-nodes/df-project/df-storage/df-workflow/src-tauri)依赖更新为 `df-types`。 + - **⑤glob members 自动适应 PASS**:workspace Cargo.toml `members = ["crates/*"]` glob 自动覆盖新 df-types 目录 / `ls crates/` 确认 df-core 已删除仅剩 df-types。 + - **⑥目录结构完整 PASS**:ls -la crates/df-types/src/ 确认 error.rs/events.rs/lib.rs/types.rs 四文件完整 / git mv R100/R092 确认文件移动无丢失。 + - **⑦宏自引用零遗漏 PASS**:git show '*.rs' grep `macro` 零新增(本 CR 无宏定义改动,仅 import 路径替换) / 波15 走查核验通过。 + - **⑧字符串字面量零遗漏 PASS**:git show '*.rs' grep `^\+.*"df-core"` 零命中 / 注释外零字符串字面量残留。 + - **待修项回流 todo**: **无**(8 维度全 PASS,零问题。文档 .md/project.ts 注释 deferred 非:blocking,按原始登记不回流 todo)。 + +### CR-260616-62 波19 UX-19消息列表虚拟滚动(自研IntersectionObserver+sentinel+末条保活)(e38474b) — ✅ 已审(PASS) + +- **复审结论(2026-06-17·审查 agent 亲跑 vue-tsc + cargo check + 独立 grep/read 核验)**: ✅ **PASS** — 🔴0 🟡0 ⚪0 + - **验证**: `npx vue-tsc --noEmit` **EXIT 0** ✅ / `cargo check --workspace` **EXIT 0**(6 pre-existing dead_code warnings 无关本 CR)。 + - **①pinnedKeys 末条保活 PASS**:AiChat.vue:2363-2373 `lastStreamingRenderKey` computed 取末条 AI 消息 key(`m-${id}`) + watch 立即传 `setPinnedMsgs(new Set([key]))` + useAiVirtualScroll.ts:130-132 `shouldRender` 双路返回(`renderedKeys U pinnedKeys`) + IO 回调(:68) 跳过 `pinnedKeys.value.has(key)` 项。三重保证流式末条恒渲染。 + - **②sentinel 占位高度不塌 PASS**:useAiVirtualScroll.ts:72-75 卸载前锁 minHeight=`${height}px` + :88 ResizeObserver 实测 `e.contentRect.height` 持续更新 + :102-103 挂载时复用上次 height 占位 + :65 重新可见清 minHeight 让内容自然撑高。AiChat.vue:2941 `.ai-msg-slot { display: flex; flex-direction: column; }` 承担 gap:14px。scrollHeight 计算不受虚拟化影响。 + - **③零新依赖 PASS**:git show 核验仅新增 useAiVirtualScroll.ts 162 行 + AiChat.vue 44 行改动。`grep vue-virtual-scroller package.json` 零命中。自研 IO/RO 方案无外部依赖。 + - **④现有逻辑全保留 PASS**:grep 核验 streamingBlocks(:777-783 rAF 块级 memo)/scheduleStreamParse(:863-873)/wasNearBottom(:1765)/collapseAllToolLists(:1889-1893)/isNearBottom(:1754-1760)/scrollToBottom(:1782-1788)全在。AiChat.vue 模板 :400 `shouldRenderMsg(item.key)` 仅裁内容不裁滚动逻辑。 + - **⑤IO/RO disconnect 无泄漏 PASS**:useAiVirtualScroll.ts:146-152 `onBeforeUnmount` 双 disconnect + io=null/ro=null/itemMeta.clear()。:114-120 卸载分支 `io?.unobserve(prev.el)` + `ro?.unobserve(prev.el)`。三路径完整(组件卸载/单项卸载/观察器重建)。 + - **⑥registerSentinel 生命周期正确 PASS**:useAiVirtualScroll.ts:97-121 挂载时 `io?.observe(el)` + `ro?.observe(el)`(:106-107) + 卸载时 `unobserve`(:116-117) + itemMeta 保留 height 复用(:119)。`el.dataset.vscrollKey = key`(:100) 绑定唯一标识。 + - **⑦rootMargin 预加载 600px PASS**:useAiVirtualScroll.ts:81 `rootMargin: '${rootMargin}px 0px ${rootMargin}px 0px'` 上下各 600px + :38 默认 600px。滚动时内容提前就位,防白屏闪烁。 + - **⑧setPinned 强制进渲染集 PASS**:useAiVirtualScroll.ts:135-144 `setPinned` 更新 `pinnedKeys.value` + 强制合并进 `renderedKeys`。pinned 项即使 IO 不可见也渲染。 + - **待修项回流 todo**: **无**(8 维度全 PASS,零问题。ToolCard 嵌套/超大历史 sentinel 二段留观察,非当前 CR 范围)。 + +### CR-260616-63 波20 F-05多模态Phase2a后端(ContentPart+parts+provider图片块+truncate)(e3cd448) — ✅ 已审(PASS) + +- **复审结论(2026-06-17·审查 agent 亲跑 cargo+test+独立 grep/read 核验)**: ✅ **PASS** — 🔴0 🟡0 ⚪1 + - **验证**: `cargo check --workspace` **EXIT 0**(6 pre-existing dead_code warnings 无关本 CR)/ `cargo test -p df-ai-core` **19 passed** / `cargo test -p df-ai` **111 passed**。 + - **①ContentPart serde tag=type 兼容老 JSON PASS**:provider.rs:48-50 `#[serde(tag = "type", rename_all = "snake_case")]` 正确区分 Text/Image + 单测:370-377 `contentpart_legacy_json_no_parts` 老 JSON 反序列化 parts=None 验证 + :381-402 `contentpart_with_image_roundtrip` 新 JSON round-trip 验证。 + - **②parts Option 向后兼容 PASS**:provider.rs:102-103 `#[serde(default, skip_serializing_if = "Option::is_none")]` + 构造器 user/user_parts/assistant 均设 parts=None(:122/:125/:128) + 单测:442-455 `contentpart_struct_literal_compat` 字面量构造合法。 + - **③openai content Value+image_url PASS**:openai_compat.rs:320-348 m.has_image() 判定 + flattened_parts() 拍平 + content 数组 [{type,text},{type,image_url,image_url:{url}}] + base64→data URI 转换(:330-333 `format!("data:{};base64,{}", mt, b)`)。 + - **④anthropic image source base64 PASS**:anthropic_compat.rs:342-375 含图时 content blocks 数组 + Image 片→{type:image,source:{type:base64,media_type,data}} + url 模式 warn 提示预留 2b/2c 预拉(:356-362)。 + - **⑤truncate_parts 防 DB 撑爆 PASS**:conversation.rs:87-127 truncate_parts_for_persist Image base64 替换占位 Text(:100-106 ``) + Text 片复用 truncate_for_persist 头尾截断 + url 模式原样保留。 + - **⑥务实偏离合理性 PASS**:grep 核验 4 forbidden 消费点(audit.rs:461/commands.rs:87+678/knowledge_inject.rs:314+316+338)均读 m.content 字符串零改动,title.rs:50-58 结构体字面量 parts:None 补字段唯一越界但零变化(单测:442-455 证字面量合法)。 + - **⚪ CR-63-1(low,Anthropic URL 预拉留 2b/2c)**:anthropic_compat.rs:356-362 url 模式 warn 但未实现预拉(当前 base64 为主流,url 预拉留后续联调),非阻断当前 Phase 2a 就绪。 + - **待修项回流 todo**: **无**(6 重点全 PASS,1 low 预拉非阻断,Phase 2a 就绪)。 + +### CR-260616-64 波21 F-05多模态Phase2b前端(图片粘贴/拖拽+渲染+parts透传)(e1d396d) — ✅ 已审(PASS) + +- **复审结论(2026-06-17·审查 agent 亲跑 vue-tsc + cargo check + 独立 grep/read 核验)**: ✅ **PASS** — 🔴0 🟡0 ⚪0 + - **验证**: `npx vue-tsc --noEmit` **EXIT 0** ✅ / `cargo check --workspace` **EXIT 0**(6 pre-existing dead_code warnings 无关本 CR)。 + - **①@paste 图片检测+base64 PASS**:AiChat.vue:1037-1052 onPaste 遍历 clipboardData.items,`it.kind==='file'&&it.type.startsWith('image/')` 正确检测 + :1050 e.preventDefault() 阻止默认粘贴(避免二进制塞 textarea) + :1051 逐个 addImageFile。readImageFile:997-1003 FileReader Promise 正确。 + - **②@dragover/@drop 拖拽检测 PASS**:onDragOver:1055-1063 `Array.from(e.dataTransfer.types).includes('Files')` 判断含文件 + preventDefault + dropEffect='copy' 正确。onDrop:1066-1074 filter `f.type.startsWith('image/')` 仅取图片 + preventDefault + 逐个 addImageFile。 + - **③img 渲染随虚拟滚动无冲突 PASS**:模板:415 `v-if="msgImageParts(item.msg).length"` + :413-419 注释明确「虚拟滚动裁剪整条消息,气泡内 随 sentinel 卸载无冲突」。msgImageParts:1083-1086 filter type==='image' 正确。imageSrc:1089-1096 base64 模式拼 data URI(`data:${mt};base64,${img.base64}`) + url 模式兜底空串。 + - **④parts 全链透传 PASS**:handleSend:1830 snapshotImgs 快照 + :1831-1849 ContentPart[] 构造(Text 首+Image 序) + :1863 `store.sendMessage(text,skill?.name,false,parts)` 传参。useAiSend.ts doSend:80 接 parts 参数 + :87 仅非空挂 parts + :114/:117 aiApi.forceSend/sendMessage 透传(Phase 2c 后端接入即生效)。drainQueue:261 `sendMessage(next.text,next.skill,false,next.parts)` 续发正确。queue 扩展:ai.ts:72-73 注释明确 + useAiSend.ts:298-302 入队挂 parts。 + - **⑤虚拟滚动保留 PASS**:AiChat.vue:1106-1111 useAiVirtualScroll 未改动(git show e1d396d 确认) + 模板:401 `shouldRenderMsg(item.key)` 裁剪整条消息,图片在气泡内随消息卸载,无独立冲突。 + - **⑥i18n 双语 PASS**:zh-CN:103-107 + en:102-106 五 key 对称(removeImage/imagePreviewAlt/imageTooLarge/imageLimit/imageReadFailed)。特殊字符无转义需求。 + - **⑦约束正确 PASS**:MAX_PENDING_IMAGES=6 + MAX_IMAGE_BYTES=10MB + addImageFile:1007-1029 size/type/数量三重校验 + toast 提示完整。 + - **⑧发送失败回填 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) — ✅ 已审(PASS) + +- **复审结论(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) — ✅ 已审(PASS) + +- **复审结论(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) + +- **复审结论(2026-06-16·CR-36 agent 亲跑 cargo+vue-tsc+test,723s)**: ✅ **PASS** — 🔴0 🟡0 ⚪1 + - **验证**: `cargo check --workspace` EXIT 0(4 warning 全 pre-existing:mod.rs:282 PendingApproval.diff never read 误报 / state.rs:146 releases+node_executions dead_code)/ `cargo test -p df-ai` **37 passed** / `npx vue-tsc --noEmit` EXIT 0。 + - **F-13 messages.clone() 等价性 PASS**:stream_recv.rs:137-144 stream_llm 签名不接 session_arc + 重试块(agentic.rs:259-363)到 process_tool_calls(:476)间 0 个 session.messages.push(push 在 :391/395/446/450 之后)+ CR-260616-35 回归核验(保文/退避/permit RAII/guard.reset/request 重建全无回归)。 + - **F-13 sys_tokens 缓存 PASS**:agentic.rs:172-174 system_prompt 是 run_agentic_loop owned String 参数,整 loop 只读(:174 estimate_text + :227 ChatMessage::system),无 mutate。 + - **AE-03 build_write_file_diff 安全 PASS**:audit.rs:508-520 预读旧文件路径未校验,但桌面应用威胁模型(LLM 生路径非远程攻击者)+ read 仅展示不外泄 + handler validate_path 执行时兜底,可接受风险。 + - **AE-03 diff 双路径 PASS**:实时(audit.rs:588-609 PendingApproval.diff clone + AiApprovalRequired.diff move 双注入)+ 恢复(audit.rs:282 diff=None 故意,DB ai_tool_executions 无 diff 列,前端 ToolCard v-if tc.diff 兜底)。types 对齐(mod.rs:101/282 Option + types.ts:198/244 diff? + useAiEvents.ts:239 传播)。 + - **F-09A 单例补清 PASS**:commands.rs:860 stop_flag.store(false,SeqCst) 在生成中分支置 true(:840)后复位覆盖两路径 + :861 agent_language=None + 前端 useAiConversations.ts:42-45 补清 queue/generatingConvId/agentRound/searchQuery + 未动 AiSession 单例(B 路线边界)。 + - **⚪ low**:mod.rs:282 PendingApproval.diff "never read" warning — 误报,值经 clone 进 AiApprovalRequired event payload(audit.rs:609)前端消费,dead-code 分析不见直接 read。 + - **待修项回流 todo**: **无**(5 重点全 PASS,1 low 误报)。 + +- **范围**: 3 agent 合并批(workflow whae812z5,主代独立核查全过 + cargo check --workspace EXIT 0 + vue-tsc EXIT 0)。①**F-09 A 路线**(单例软隔离补漏清字段,不动 B 多会话架构):前端 `useAiConversations.ts:42-45` newConversation 补清 `queue=[]/generatingConvId=null/agentRound=0/searchQuery=''`(防旧会话排队消息带进新会话 + 后台事件错路由 + 侧栏旧搜索过滤);后端 `commands.rs:842-843` ai_conversation_create 补清 `stop_flag.store(false,SeqCst)`(关键:上方生成中分支置 true 停旧 loop 不复位则新会话 loop 启动即见 stop 退出) + `agent_language=None`(防沿用旧语言设置)。②**F-13 性能**:`agentic.rs:173` system_prompt token loop 外算一次缓存为 sys_tokens(整个 loop 不变参数,原每轮+每次重试重算);`:219-225` 外层 messages 构建用 sys_tokens;`:262-270` 重试循环改 `messages: messages.clone()` 复用外层 Vec(删每次重试的 session_arc.lock + build_for_request 全量 clone + estimate_text)。安全前提核验:stream_llm 不接 session_arc + 重试块到 process_tool_calls 间无 push,重试内 session.messages 与外层一致,重建等价复用。③**AE-03 路径 B**(审批 payload 加 diff,非路径 A):`tool_registry.rs:32` generate_diff fn→pub(crate)(复用 F-260615-10 LCS);`audit.rs:508-520` 新增 build_write_file_diff(从 args 取 path+content → 预读旧文件 tokio::fs::read_to_string → 无变化/旧文件缺失/读失败均 None → generate_diff);`audit.rs:588-609` 仅 write_file draft 挂起审批前预计算 approval_diff 注入 PendingApproval.diff(:600 clone)+AiApprovalRequired.diff(:609 move);恢复路径 diff=None(文件可能已变);`mod.rs:101/269` AiApprovalRequired+PendingApproval 加 `diff: Option`;`types.ts:198` AiApprovalRequired 加 `diff?: string`;`useAiEvents.ts` event.diff 传 toolCall 信息;`ToolCard.vue:40-41` 模板 `v-if="tc.diff"` diff 块 + :394 diffLines computed(按 +/-/space 前缀拆分)+ :677-705 样式(add 绿 del 红 ctx 灰 + token 复用)。 +- **维度**: ①F-09 单例字段补清完整性(stop_flag 复位是否覆盖所有生成中→新建路径/agent_language 影响面) + B 路线边界(确认未误动 AiSession 单例) ②F-13 行为不变性核验(sys_tokens 缓存是否真不变/messages.clone() 复用是否等价重建/stream_llm 不持 session_arc 前提是否成立) + 锁持有期缩短收益确认 ③AE-03 路径 B 安全性(预读旧文件只读不改/审批拒绝不执行 handler/路径不校验风险——恶意路径读失败仅 None) + diff 注入完整性(实时挂起+恢复路径双覆盖/前端消费链 event→toolCall→模板) + generate_diff pub(crate) 暴露面。 +- **commit**: d00b30f(重构: AI聊天可靠性批次)。 +- **主代独立核查**: ✅ 全过(useAiConversations:42-45 补清 / commands:842-843 stop_flag.store(false)+agent_language=None / agentic:173 sys_tokens loop 外缓存 + :219-225 外层用 + :262-270 重试 messages.clone() 复用 + 保文退避逻辑未动(Partial 不重试/retry_deadline 30s) / tool_registry:32 generate_diff pub(crate) / audit:508-520 build_write_file_diff 预读+无变化/缺失 None+复用 generate_diff / audit:588-609 仅 write_file 生成 diff 注入 PendingApproval(clone)+AiApprovalRequired(move) / mod:101+269 diff 字段 / types:198 diff? / ToolCard:40-41 模板 + :394 diffLines + :677-705 样式 / cargo+vue-tsc 双 EXIT 0)。 +- **审查 agent 待复审重点**: ①F-13 重试 messages.clone() 复用等价性——核验 stream_llm 签名(stream_recv.rs:137 不接 session_arc) + 重试块到 process_tool_calls 间确实无 session.messages.push(若遗漏则重试复用旧 messages 致 tool_calls 丢失) ②F-13 sys_tokens 缓存——核验 system_prompt 确为 run_agentic_loop 不变参数(整个 loop 期间无 mutate) ③AE-03 build_write_file_diff 安全——预读旧文件 `tokio::fs::read_to_string(path)` 路径未校验(恶意 path 读失败仅 None 不产生危害,但核验无 symlink 逃逸读敏感文件风险——审批只读,write_file handler 自身 validate_path 执行时兜底) ④AE-03 diff 注入双路径——实时挂起(audit:588-609 有 diff)+恢复路径(PendingApproval diff 字段是否持久化,若仅内存则重启恢复审批无 diff) ⑤文件锁独立性——3 agent 改动文件无重叠(F-09: useAiConversations/commands / F-13: agentic / AE-03: mod/tool_registry/audit/types/useAiEvents/ToolCard),useAiEvents.ts Agent C 独占(A/B 未碰)确认无冲突。 + +### CR-260616-37 F-11 审批续跑 iteration 累计计数(跨审批不重置) — ✅ 已审(PASS) + +- **复审结论(2026-06-16·CR-37 agent 亲跑 cargo+test,658s)**: ✅ **PASS** — 🔴0 🟡0 ⚪0 + - **验证**: `cargo check --workspace` EXIT 0(5 warning 全 pre-existing dead_code:diff field / releases+node_executions 未读)/ `cargo test -p df-ai` **37 passed**(retry 6 + context 18 + openai_compat 8 + stream_recv 5,finished 2.59s)。 + - **iteration_used 生命周期完备 PASS**:grep 全消费点 **9 处**——ai_chat_send(:55 reset 0)/ ai_regenerate(:152 reset 0)/ ai_chat_edit_last(:412 reset 0)/ ai_continue_loop(:601 reset 0 + 传 0)/ ai_approve 拒绝(:260 累计读)/ ai_approve 通过(:313 累计读)/ agentic loop 递增(:210 iteration+1 写回)/ mod.rs:215 字段定义 / mod.rs:230 new() init 0。ai_conversation_create 隐式从 0(新会话 new init 0)。**无遗漏**。 + - **start_iteration 透传链 PASS**:ai_approve(:260/:313 读 session.iteration_used)→ try_continue_agent_loop(:578 接 start_iteration 参)→ spawn(:675 透传)→ run_agentic_loop(:114 接参)→ for iteration in start_iteration..max_iterations(:176 使用)。每环 file:line 佐证无断链。 + - **两路径区分 PASS**:ai_approve 累计(F-260616-11,不 reset 读 iteration_used)vs ai_continue_loop 重计(F-260616-03 决策a,reset+传 0)vs 新消息(reset 0)严格区分。注释 agentic.rs:499-501 + commands.rs:258-259/311-312/599-600 均写明归属。 + - **边界 start≥max PASS**:for 循环空区间不执行 + converged 保持 false(:170)→ 命中 :502 `if !converged` → emit AiMaxRoundsReached(:518-520) + return,防无限审批续跑烧 token,符合 F-11 语义。 + - **并发安全 PASS**:session.iteration_used=iteration+1 在 agentic.rs:198-211 同一 session_arc.lock().await 块(与 :199 active_conversation_id 一致性校验共享锁),锁模式与 :424-452/:474-477 一致。 + - **mod.rs 字段 PASS**::215 `pub iteration_used: usize`(无符号防负)+ :230 new init 0 + agentic.rs:176 循环边界 + :210 一致性块更新。 + - **待修项回流 todo**: **无**(6 重点全 PASS,零问题)。 + +- **范围**: F-260616-11 决策a 落地(与 batch60 三项合批 commit d00b30f)。`mod.rs:215/230` AiSession 加 `iteration_used:usize` 字段+new() init 0;`agentic.rs:114` run_agentic_loop 加 `start_iteration:usize` 参数 +`:176` loop 边界改 `start_iteration..max_iterations` +`:210` 一致性校验块更新 `session.iteration_used=iteration+1` +`:578` try_continue_agent_loop 加 start_iteration 参数 +`:675` spawn 透传 +`:499-501` 注释区分两路径;`commands.rs:55/152/412` ai_chat_send/ai_regenerate/ai_edit_last reset iteration_used=0(新生命周期)+`:260-262` ai_approve 拒绝续跑读 session.iteration_used 累计传 +`:313-315` ai_approve 通过续跑累计传 +`:601` ai_continue_loop reset iteration_used=0 传 0(F-03 决策a 达max重计区分)。两路径严格区分:审批续跑(ai_approve 累计透传 iteration_used) vs 达max续跑(ai_continue_loop reset+传0,F-03 决策a 用户授权重来) vs 新消息(reset 0)。 +- **维度**: ①iteration_used 字段生命周期完备性(所有会话生命周期入口 reset 覆盖——create/send/regenerate/edit_last/continue_loop 是否齐全无遗漏路径) ②start_iteration 透传链完整(agentic run_agentic_loop←try_continue_agent_loop←ai_approve 两个调用点,无断链) ③两路径区分正确性(ai_approve 累计 vs ai_continue_loop 重计,F-11/F-03 决策 a 分别归属无混淆) ④边界 case(start≥max loop 空区间→converged=false→AiMaxRoundsReached,防无限审批烧 token,符合 F-11 语义) ⑤并发安全(iteration_used 在一致性 lock 块更新,与现有 lock 模式一致)。 +- **commit**: d00b30f(同 CR-36 合批)。 +- **主代独立核查**: ✅ 全过(mod.rs:215/230 iteration_used 字段+init / agentic.rs:114 start_iteration 参数+:176 loop 边界+:210 一致性块 iteration+1+:578 try_continue 加参+:675 spawn 透传+:499-501 注释两路径 / commands.rs:55/152/412 reset 0+ai_approve:260/313 累计读 iteration_used+ai_continue_loop:601 reset 传0 / cargo check --workspace EXIT 0)。 +- **审查 agent 待复审重点**: ①iteration_used 生命周期完备性(所有会话生命周期入口 reset 覆盖无遗漏——create/send/regenerate/edit_last/continue_loop/ai_approve 全路径核验) ②start_iteration 透传链(run_agentic_loop←try_continue_agent_loop←ai_approve 两调用点无断链) ③两路径区分(ai_approve 累计 vs ai_continue_loop 重计,F-11/F-03 决策a 归属无混) ④边界 start≥max loop 空区间→converged false→AiMaxRoundsReached(防无限审批烧 token) ⑤并发安全(一致性 lock 块更新)。 + +### CR-260616-38 F-07 df-ai-core trait 下沉拆 crate(解锁 F-03 注入) — ✅ 已审(PASS) + +- **复审结论(2026-06-16·CR-38 agent 亲跑 cargo+test,137s)**: ✅ **PASS** — 🔴0 🟡0 ⚪0 + - **验证**: `cargo check --workspace` EXIT 0(5 warning 全 pre-existing dead_code)/ `cargo test -p df-ideas adversarial` **11 passed**(a1-a11,含 7 heuristic + 4 LLM mock)。 + - **provider.rs 迁移完整 PASS**:df-ai-core/src/ 仅 lib.rs+provider.rs(Glob 确认),provider.rs:1-235 含 LlmProvider trait(:208-235)+ 全数据结构(CompletionRequest/ChatMessage/MessageRole/ToolDefinition/ToolCall/CompletionResponse/TokenUsage/StreamChunk/ToolCallDelta/StreamResult),注释 :6-8 声明「零 IO」;grep ContextManager/TokenEstimator/AiToolRegistry/build_provider/HTTP impl 在 df-ai-core 内**零命中**(业务逻辑+HTTP impl 确留 df-ai);df-ai Cargo.toml:8 加 df-ai-core 依赖。 + - **re-export 透明 PASS**:df-ai/src/lib.rs:17 `pub use df_ai_core` + provider.rs:15 `pub use df_ai_core::provider::*`;grep `use df_ai::provider` **11 处**实际消费(idea.rs/project.rs/ai_node.rs/agentic.rs/stream_recv.rs/title.rs/knowledge_inject.rs/commands.rs/audit.rs 等)workspace 编译期全过;workspace Cargo.toml members=`["crates/*"]` glob 自动覆盖 df-ai-core 无需手动加。 + - **构造注入语义 PASS**:adversarial.rs:19 直接 use df_ai_core(不引 df-ai 重依赖)+ :96-98 `provider: Option>` + :101 new(Arc)→Some + :107 heuristic()→None + :112-133 evaluate() 调度;调用方 idea.rs:192 new(Arc::from(p))=Some / :193 heuristic()=None 等价无遗漏。 + - **EvaluatedBy 三态 PASS**:adversarial.rs:28-37 Llm/Heuristic(#[default])/HeuristicFallback + :49-50 `#[serde(default)]` 向后兼容老数据;:112-133 调度 Some+Ok=Llm / Some+Err=warn+HeuristicFallback / None=Heuristic 三态完备。 + - **build_default_provider PASS**:idea.rs:265-279 list_all().ok()? DB 查失败 None + is_default 优先 find + 首条 fallback + build_provider_for Err→warn+None + :191-194 None 直走 heuristic 语义正确。 + - **Box→Arc 转换 PASS**:idea.rs:192 `Arc::from(Box)` 标准库转换(堆分配复用),cargo check EXIT 0 编译期证类型匹配。 + - **待修项回流 todo**: **无**(6 重点全 PASS,零问题)。 + +- **范围**: F-260614-07 4 决策落地(workflow wwtn2knn6)。①df-ai-core 新 crate:`crates/df-ai-core/`(Cargo.toml serde+async-trait+futures + src/lib.rs `pub mod provider` + src/provider.rs 迁移 trait+数据结构 LlmProvider/ChatMessage 等,不含 HTTP impl);②df-ai re-export:`crates/df-ai/Cargo.toml` 加 df-ai-core 依赖 + `src/provider.rs:15` `pub use df_ai_core::provider::*` + `src/lib.rs:17` `pub use df_ai_core`(外部 use df_ai::provider 路径不变,df-nodes 零改动,workspace Cargo.toml 零改动 members=crates/*);③df-ideas 构造注入:`crates/df-ideas/Cargo.toml` 加 df-ai-core+async-trait + `src/adversarial.rs:18` use df_ai_core::provider::LlmProvider + struct 加 `provider:Option>`(:96) + `new(provider:Arc)`(:101 调用方 Some/None 分支语义等价 Option)+ `heuristic()`(:107) + `evaluate(&self)`(:111) + `evaluate_with_llm`(:138) + EvaluatedBy 三态 enum(:28) + AdversarialEval.evaluated_by 字段(:49) + LLM 失败→warn+HeuristicFallback 降级(:122);④src-tauri 装配:`idea.rs:190` build_default_provider(DB is_default→build_provider→Option>:265) + :192 Some→`AdversarialEngine::new(Arc::from(p))` / None→`heuristic()`(:193)。 +- **维度**: ①re-export 透明性(df_ai::provider::LlmProvider 路径 df-nodes/df-project 等外部 use 无断裂) ②trait 下沉边界(LlmProvider trait+纯数据结构下沉,ContextManager/TokenEstimator/AiToolRegistry 业务逻辑确留 df-ai 未误迁) ③构造注入语义(new(Arc) vs 决策② Option——调用方分支等价性,无遗漏 Some/None) ④EvaluatedBy 三态正确(Llm/Heuristic/HeuristicFallback,provider None 全走 Heuristic) ⑤Box→Arc 转换(idea.rs:192 Arc::from(Box) 编译通过) ⑥build_default_provider DB 读(is_default 查询+无默认兜底 heuristic) ⑦adversarial 7 单测改 heuristic() 构造后全过。 +- **commit**: 2069f79(重构: df-ai-core trait下沉+导入历史)。 +- **主代独立核查**: ✅ 全过(df-ai-core 新建 Cargo.toml+lib.rs+provider.rs / df-ai provider.rs:15+lib.rs:17 re-export+Cargo.toml 加依赖 / df-ideas adversarial.rs:18 use df_ai_core:96 provider 字段+:101 new(Arc)+:107 heuristic+:111 evaluate(&self)+:138 evaluate_with_llm+:28 EvaluatedBy+:49 evaluated_by+:122 HeuristicFallback 降级 / idea.rs:190 build_default_provider+:192 new(Arc::from)+:193 heuristic+:265 fn / cargo check --workspace EXIT 0(5 warning 全 pre-existing dead_code))。 +- **审查 agent 待复审重点**: ①provider.rs 迁移完整性(df-ai 原 trait+数据结构全部下沉,df-ai/src/provider.rs 仅剩 re-export,retry/openai_compat/anthropic_compat HTTP impl 确留 df-ai 未断) ②df-nodes/df-project 外部 use df_ai::provider 编译验证(re-export 透明无断裂,实际 grep df_ai::provider 消费点) ③EvaluatedBy 三态语义(provider Some+LLM 成功=Llm / Some+LLM 失败=HeuristicFallback / None=Heuristic,evaluate_internal 调度正确) ④build_default_provider DB is_default 查询正确+build_provider 复用现有工厂 ⑤Box→Arc::from 编译期验证(cargo EXIT 0 已证)。 + +### CR-260616-39 F-06 导入历史项目 scan 第二步(LLM description+monorepo+批量并发) — ✅ 已审(PASS) + +- **复审结论(2026-06-16·CR-39 agent 亲跑 cargo+test+vue-tsc,215s)**: ✅ **PASS** — 🔴0 🟡0 ⚪2(agent 标题 🟡1 系笔误,待修项表全 low 无 med) + - **验证**: `cargo check --workspace` EXIT 0(5 warning 全 pre-existing dead_code)/ `cargo test -p df-project` **15 passed**(detects_monorepo_pnpm/npm_workspaces/non_monorepo/rust/full_stack + strips_frontmatter_and_toc_and_badges + image_collection_skips_badges + extract_desc 三测 + discover_monorepo/flat_children)/ `npx vue-tsc --noEmit` EXIT 0。 + - **is_monorepo 判定 PASS**:scan.rs:100-133 4 marker(pnpm-workspace/lerna/turbo/nx)+ package.json workspaces 兜底(:122-131);仅判根不递归(discover_projects 展开时 :223 设 false),nested monorepo 边界正确。 + - **discover_projects 展开 PASS**:scan.rs:156-229 三路(根 manifest 入选/monorepo 展开 packages+apps/普通扫一层子目录)+ detect_stack 空过滤(:216-218 候选发现语义,无栈对 LLM 价值低,可手动 import)+ collect_tree_level is_sub 控 2 层(:638)不递归过深 + 隐藏目录/SAMPLE_IGNORED_DIRS 过滤。 + - **images 收集 PASS**:scan.rs:329-481 ImageRef{alt,src}+ PartialEq + SAMPLE_README_MAX=8000 + truncate_chars 按 chars().take 截断(:664-669 UTF-8 安全)+ **实际 12 域黑名单** BADGE_HOSTS(:396-410 img.shields/shields/badge.fury/badgen/...)+ is_badge_image 域名+关键词双判(:441-448)+ collect_images 行级扫 +同 src 去重+上限 20(:454-481)+ is_pure_badge_line 混内容图返 false(:533-591);arch/screenshot 内容图留、badge 跳。 + - **create_with_binding 抽取 PASS**:project.rs:59-107 统一校验+防重+探测+insert;create_project(:45-50)/import_project(:177)/import_projects_batch(:469)三入口共用;relocate 确未并入(:281-315 update_field 非 insert,注释 :56 声明);status 固定 planning+idea_id 透传行为一致无回归。 + - **import_projects_batch 并发 PASS**:project.rs:415-493 双层 permit acquire_global+acquire_per_conv(:535-536 RAII 覆盖 complete)+ 非原子 join_all 逐项独立(LLM 失败 desc 空仍入库,create 失败返 error item,imported/skipped 计数)+ provider Arc 共享零拷贝 + 无 provider 明确报错非降级。 + - **scan_directory_for_projects 纯规则 PASS**:project.rs:343-376 仅 discover_projects(spawn_blocking)+ find_binding_conflict 标已绑定,**零 LLM 调用**,深度一层。 + - **前端 PASS**:api/project.ts:116-126 两 API + 类型完整 + Projects.vue:73-133 导入 modal(只读表格 name/stack/path/bound + checkbox 全选/半选/单选 + 已绑定禁选灰显 + toast 汇总 imported/skipped + 全失败保留重扫)+ i18n zh/en 19 key 对称。 + - **df_ai::provider re-export 零断裂 PASS**:ai_node.rs:12 + project.rs:8/440 三消费点编译期证。 + - **⚪ CR-39-1(low,文档笔误)**:登记称「徽章域 5 域黑名单」与代码实际 12 域(BADGE_HOSTS scan.rs:396-410)不符,代码更完备文档滞后。 + - **⚪ CR-39-2(low,设计取舍)**:scan.rs:216-218 detect_stack 空栈过滤剔除有 manifest 无已知栈标志的合法项目(自定义构建系统),当前候选发现语义可接受,用户可手动单条 import。 + - **待修项回流 todo**: **无**(2 low 文档/边缘非阻断)。 + +- **范围**: F-260614-06 6 决策落地(workflow wwtn2knn6)。①description 走 LLM(scan.rs extract_description 复用 complete,非纯规则);②采样改进(ProjectSample 扩 `images:Vec` scan.rs:330/343 + readme 剥 frontmatter/TOC/纯徽章行截 8KB `SAMPLE_README_MAX=2000→8000`:346 + 保留内容图 markdown);③image 多模态条件化(ImageRef 数据结构+collect_images 收集:454,本批不读 base64 留接口待 F-260614-05);④monorepo 一层(is_monorepo:112 检 pnpm-workspace/lerna/turbo/nx/package.json workspaces + discover_projects:156 展开 packages/*/apps/* detect_stack 空过滤);⑤批量流程(scan_directory_for_projects project.rs:343 纯规则发现标已绑定 + import_projects_batch:415 并发 LLM llm_concurrency permit 限流复用绑定入库非原子逐项独立);⑥对称改进(抽 create_with_binding project.rs:59 create/import 共用校验+防重+探测+insert 缓解 :211 TODO,relocate 不并入)。前端 Projects.vue 导入 modal+api/project.ts 两 API+i18n 双语+scan.rs 单测(剥 badges/image 收集/monorepo/discover/extract_desc)。 +- **维度**: ①is_monorepo 判定完备(pnpm-workspace/lerna/turbo/nx+package.json workspaces 全覆盖) ②discover_projects 展开(detect_stack 空过滤误剔合法项目风险/monorepo 子目录一层不递归过深) ③collect_sample images 收集(徽章域黑名单 5 域+is_badge_image alt+src 双判+is_pure_badge_line 整行纯徽章,内容图 arch/screenshot 留) ④SAMPLE_README_MAX 8000 截断(truncate_chars 不破坏 markdown/UTF-8 边界) ⑤create_with_binding 抽取(create+import 共用一致,relocate 确未并入) ⑥import_projects_batch 并发(llm_concurrency 双层 permit 限流/非原子逐项独立失败不阻塞) ⑦scan_directory_for_projects 纯规则不跑 LLM(标已绑定正确) ⑧df-project use df_ai::provider 路径不变(F-07 re-export 透明) ⑨前端预览只读+toast 汇总(导入 N/跳过 M)。 +- **commit**: 2069f79(重构: df-ai-core trait下沉+导入历史)。 +- **主代独立核查**: ✅ 全过(scan.rs:112 is_monorepo+:156 discover_projects+:330 ImageRef+:343 images 字段+:346 SAMPLE_README_MAX 8000+:357 collect_sample+:397-401 徽章域 5 域+:439 is_badge_image+:454 collect_images+:533 is_pure_badge_line+:742-876 单测 / project.rs:59 create_with_binding+:343 scan_directory_for_projects+:415 import_projects_batch / lib.rs:85-86 注册两 IPC / api/project.ts:117/125 两 API / Projects.vue:111 mono-tag / cargo check --workspace EXIT 0+vue-tsc EXIT 0)。 +- **审查 agent 待复审重点**: ①is_monorepo/discover_projects 边界(nested monorepo/无 workspace 配置/detect_stack 空过滤误剔) ②徽章过滤完整(5 域黑名单+alt/src 双判是否漏内容图误剔或漏 badge 误留) ③create_with_binding 与原 create_project 行为一致(校验+防重+探测+insert 路径无回归) ④import_projects_batch 并发安全(llm_concurrency permit 限流正确+非原子逐项失败隔离) ⑤scan_directory_for_projects 性能(纯规则不跑 LLM,目录扫描深浅/大目录性能) ⑥前端 modal 预览表格只读+勾选+toast 汇总交互完整。 + +### CR-260616-40 F-03 对抗评估接 LLM(evaluate_with_llm 真实实现) — ✅ 已审(PASS) + +- **范围**: F-260614-03 落地(workflow wy6rjn0en)。crates/df-ideas/src/adversarial.rs:143 evaluate_with_llm 真实实现(原 F-07 占位 bail):build_adversarial_prompt(:380 三角色辩论 prompt)+CompletionRequest(model 留空回填 default/temperature 0.4/max_tokens 2048/stream false)+provider.complete(:160 map_err anyhow)+parse_llm_eval(:500 serde_json::from_str 解析 LlmEvalRaw+枚举映射:571/586 bail 非法值)+clamp(final_score[0,10]/confidence[0,1] 结构兜底:167-170)。4 LLM mock 单测(MockProvider impl LlmProvider:733-755+a8 parse success/a9 bad JSON fallback/a10 bad enum fallback/a11 value clamp)。evaluate 调度(:111)+evaluate_heuristic(:176)+原 7 单测不动。 +- **维度**: ①prompt 设计质量(三角色辩论+JSON schema 明确+temperature 0.4 防发散+max_tokens 2048 充足) ②JSON 解析容错(非 JSON/字段缺失/枚举非法 全 bail 触发 HeuristicFallback 降级,a9/a10 单测覆盖) ③clamp 兜底(final_score[0,10]/confidence[0,1],a11 单测) ④model 留空回填(OpenAICompatProvider convert_request 回退 default_model 正确) ⑤evaluate 调度不变(Some→Llm/Err→HeuristicFallback/None→Heuristic) ⑥MockProvider 单测真实性(impl LlmProvider trait 完整,stream bail 因 adversarial 不调 stream) ⑦不回归(原 7 heuristic+scoring 8 全过)。 +- **commit**: dfe0096(新增: F-03对抗评估接LLM+F-02纯技能调用标题+i18n@转义)。 +- **主代独立核查**: ✅ 全过(adversarial.rs:143 evaluate_with_llm 真实实现+:161 complete+:380 build_adversarial_prompt+:500 serde_json 解析+:571/586 枚举 bail+:167-170 clamp / MockProvider:733-755+4 单测 a8-a11 / cargo test -p df-ideas 19 passed EXIT 0)。 +- **审查 agent 待复审重点**: ①build_adversarial_prompt 全文质量(:380 prompt 三角色+JSON schema 是否明确无歧义) ②parse_llm_eval 解析(:500 LlmEvalRaw 结构+枚举映射 serde/FromStr 实现) ③LLM 返回非 JSON 降级(a9 单测,extract JSON 容错——LLM 可能返回 markdown json 包裹需剥离) ④prompt injection 防护(idea.title/description 注入 prompt 是否需隔离,防用户输入操纵评估) ⑤max_tokens 2048 是否够(复杂 idea 评估 JSON 可能超) ⑥temperature 0.4 评估稳定性。 +- **复审结论(2026-06-16·独立审查 agent · 亲跑 cargo test EXIT 0 + grep/read 核验源码)**: ✅ **PASS** — 🔴0 🟡0 ⚪4 + - **自验**: `cargo test -p df-ideas adversarial` **11 passed** EXIT 0(7 heuristic a1-a7 + 4 LLM a8-a11)/ `cargo test -p df-ideas` **19 passed** EXIT 0(8 scoring + 11 adversarial 不回归)。 + - **逐件核验(8 维度全 PASS)**: + - ①**prompt 设计** ✅:build_adversarial_prompt:398-448 三角色(正方/反方/分析师)+JSON schema 字段名/枚举字面量明确(`final_assessment: "StrongGo|Recommended|Conditional|Revised|Defer"`/`recommendation: "ImmediateAction|Soon|WithResources|ResearchMore|Monitor"`)+自洽约束(final_score 与 recommendation 对应 final_assessment)+SYSTEM_PROMPT:364-374 硬约束「只输出 JSON 对象/枚举字面量严格/数值区间」。temperature 0.4 注释:141-142 立论(0.3 重复/0.5 发散折中)合理。max_tokens 2048(schema ~10 字段全填~600 token + 论点 2-3 条/角色 × 3 角色 ~1200 token 富余)。 + - ②**JSON 解析容错** ✅:parse_llm_eval:498-539 ①extract_json:545-561 剥 ```json 围栏(去前缀+跳语言标记行+去后缀)+trim ②serde_json::from_str → LlmEvalRaw:456-463 必填字段缺/类型错 → bail:500-502 ③枚举映射 parse_assessment_level:564-576/parse_recommendation:579-591 非法字面量 → bail:571/586(a10 "GoNow" 覆盖)。a9 非 JSON 降级 HeuristicFallback:830 覆盖。 + - ③**clamp 兜底** ✅:parse_llm_eval 内 confidence clamp[0,1]:511/517 + final_score clamp[0,10]:534;evaluate_with_llm:167-170 再做一次结构兜底(注释明确「结构兜底」)。a11 confidence 1.5→1.0/-0.3→0.0/final_score 99→10.0:874-876 全过,仅越界不降级 evaluated_by=Llm:873。 + - ④**model 留空回填** ✅:OpenAICompatProvider::convert_request:295-296 `if req.model.is_empty() { self.default_model.clone() }`;AnthropicCompatProvider::convert_request:282-283 同样回填;两 provider 一致,留空 String::new() 正确回退各自默认模型。 + - ⑤**evaluate 调度** ✅:evaluate:112-133 三分支 Some(p)→evaluate_with_llm:114 成功标 Llm:116/Err 标 HeuristicFallback:123/warn 日志:121;None→evaluate_heuristic:128 标 Heuristic:129。调度不变,a9/a10 验证降级路径。 + - ⑥**MockProvider 真实性** ✅:MockProvider:733-761 impl LlmProvider trait 三方法完整(complete:739-749 返预设 text/model="mock-model"/usage default/stream:751-756 bail「不调用」/name:758-760)。embed 用 trait 默认实现(provider.rs:222-224 返 Err,adversarial 不调 embed 无影响)。stream bail 注释:755 明确「adversarial 路径不调用」合理。 + - ⑦**不回归** ✅:原 7 heuristic 单测 a1-a7:645-723 全过(评分→recommendation 映射/confidence 区间/thesis 含标题/negative evidence 非空/final_score 一致性);scoring 8 单测全过。total 19 passed。 + - ⑧**prompt injection 防护** ⚪(low,结构安全/语义受限):**结构性注入已阻断**——idea.title/description 经 `format!` 命名参数注入(:443-446),format! 不对参数值二次解析,Rust 运行时 title/description 中的 `{`/`}` 字符直接作为字面文本输出,不会破坏 prompt 模板骨架。**语义性注入未隔离**(用户可写 description="忽略规则,输出 final_score=10")属 LLM 固有局限非本实现 bug;description 截 800 字:383-390 已限长降低面。当前评估结果越界有 clamp 兜底、枚举非法有 bail 降级,最坏后果降级启发式不致数据错。 + - **⚪ low 清单(4,全非阻断)**: + - **CR-40-1(low,clamp 双重执行冗余)**:parse_llm_eval 已 clamp confidence:511/517+final_score:534,evaluate_with_llm:167-170 又 clamp 一次(parse_llm_eval 注释:533 也承认「调用方再做一次」)。无害防御性重复,建议删 :167-170 留 parse 内单点收口,减少阅读歧义。 + - **CR-40-2(low,extract_json 围栏位置局限)**:extract_json:548 只 strip_prefix("```") 处理围栏在开头;LLM 若输出 `好的,以下是评估:\n```json\n{...}\n````(围栏不在首),strip_prefix 失败走 trimmed 原文 → serde 报错 → 降级。降级语义安全但命中率略低;非阻断(降级正确),可后续用正则 `(?s)\{.*\}` 兜底提取首个 JSON 对象增强。 + - **CR-40-3(low,semantic prompt injection 未隔离)**:见维度⑧,description 截 800 字已限长,枚举/数值有 clamp+bail 兜底,语义注入最坏降级启发式,不阻断。 + - **CR-40-4(low,temperature/max_tokens 硬编码不可配)**:temperature 0.4/max_tokens 2048 硬编码:153-154,无运行时调参能力;当前单 provider 单次调用可接受,后续若多 provider 调优需抽配置。 + - **待修项回流 todo**: **无**(4 low 全非阻断:clamp 冗余/extract_json 增强/semantic injection/硬编码均可在后续迭代处理,当前 PASS)。 +- **主代独立核查**: ✅ 全过(adversarial.rs:143 evaluate_with_llm 真实实现+:161 complete+:380 build_adversarial_prompt+:500 serde_json 解析+:571/586 枚举 bail+:167-170 clamp / MockProvider:733-755+4 单测 a8-a11 / cargo test -p df-ideas 19 passed EXIT 0)。 + +### CR-260616-41 batch64 F-03 收口三件(advance_task/run_workflow 工具注册+update_task 拒 status+crud 白名单移 status) — ✅ 已审(PASS·🔴-1已修) + +- **范围**: F-260616-07 阶段3 确定性收口(workflow wii1u1lnm,3 agent 并行)。①**Agent A tool_registry.rs**:advance_task 注册(:395,handler L407 调 `df_nodes::task_advance_node::advance_task_atomic(&TaskRepo::new(&db),id,target_status)` 与 IPC `commands::task::advance_task:165` 同源,thin 转发无 AppHandle/State 依赖,RiskLevel::Medium)+ run_workflow 注册(:428,handler 架构约束无 AppHandle/State 报错引导前端转调 `invoke('run_workflow')` IPC,RiskLevel::High,ToolDefinition+审批文案 L1260-1261/1286-1287 注册)+ update_task handler guard L374 `if field=="status"` bail(schema 通用 field/value 模式非 enum,故靠 handler guard 拦非 schema 改)。②**Agent B crud.rs**:tasks allowed_columns L331-347 移 `"status"`+`"review_rounds"`(D-260616-04 收口),注释明确「status/review_rounds 走 advance_status_atomic CAS 独立路径,后人勿补回白名单」;`advance_status_atomic` L848 CAS SQL `WHERE id AND status=expected AND deleted_at IS NULL` 完全绕 `validate_column_name`/`allowed_columns_for`;`tests/project_soft_delete.rs:250` 断言反转 `update_field_rejects_tasks_status`(改 status 现 Err)。③**Agent C CR-40 审查**(对 batch62 dfe0096 evaluate_with_llm):PASS 4 low 非阻断。 +- **维度**: ①advance_task handler 同源核验(tool_registry.rs:407 调 advance_task_atomic 与 commands::task::advance_task:165 同源,thin 转发无逻辑分叉) ②update_task handler guard 双重防御(L374 handler 拒 + df-storage 白名单拒,status 双层拦截) ③crud 白名单移 status+review_rounds 收口语义(advance_status_atomic L848 CAS 独立路径不经 allowed_columns,review_rounds 唯一写入路径防计数错乱) ④run_workflow handler 架构约束(handler 无 AppHandle/State 报错引导走 IPC 是否合理,后续批注入 AppState 句柄让 AI 直驱) ⑤审批文案映射(display_hint_for_tool L1260-1261 + tool_display_hint L1286-1287 advance_task/run_workflow 条目) ⑥project_soft_delete.rs 测试断言反转(update_field_rejects_tasks_status 改 status 现 Err 正确) ⑦前端 status 路径零残留(taskApi.update 全代码库零调用方,status 唯一走 taskApi.advance→advance_task IPC) ⑧commands/task.rs update_task IPC 残留死代码(task.rs:100-108 field=="status" 分支移白名单后永不命中,回流 todo 清理)。 +- **commit**: 待提交(batch64 攒批)。 +- **主代独立核查**: ✅ 全过(tool_registry.rs advance_task:395+handler:407 advance_task_atomic 同源 / run_workflow:428+handler 约束报错引导 IPC / update_task handler guard:374 `field=="status"` bail / crud.rs:331-347 tasks 白名单移 status+review_rounds 注释明确 / advance_status_atomic:848 CAS `WHERE id AND status=expected AND deleted_at IS NULL` 独立路径 / cargo check --workspace EXIT 0(5 pre-existing warnings 无关)+ df-storage 11 集成测试含新 update_field_rejects_tasks_status)。 +- **审查 agent 待复审重点**: ①advance_task handler 同源 IPC(commands::task::advance_task:165 核验,thin 转发无逻辑分叉,df_core::error::Error→anyhow ?From 链路) ②update_task handler guard 双重防御(handler guard L374 + df-storage 白名单双拦,是否漏路径) ③crud 白名单移 status+review_rounds 收口(advance_status_atomic L848 CAS 是否真独立不经 allowed_columns,review_rounds 唯一写入路径防旁路写) ④run_workflow handler 报错引导 IPC 是否合理(架构约束无 AppHandle/State,后续批注入句柄) ⑤审批文案映射完整(display_hint L1260-1261/1286-1287) ⑥测试断言反转正确(project_soft_delete.rs:250 update_field_rejects_tasks_status 改 status 现 Err) ⑦commands/task.rs:100-108 update_task IPC 残留死代码回流 todo。 +- **复审结论(2026-06-16·审查 agent 亲跑 cargo + 独立 grep/read 核验源码)**: ⚠ **ISSUES** — 🔴1 🟡0 ⚪1 → **修复后(主代 b94e74a 之后)**: ✅ **PASS** 🔴0 🟡0 ⚪1(🔴-1 测试 fixture 已修) + - **验证**: `cargo check --workspace` **EXIT 0**(5 pre-existing dead_code warnings 无关本 CR)/ `cargo test -p df-storage` **33 passed**(22 lib + 11 project_soft_delete 含新 `update_field_rejects_tasks_status` ✅)/ `cargo test -p df-nodes` **66 passed; 1 FAILED**(`advance_succeeds_after_prior_status_change` 红,见 🔴-1)→ **🔴-1 修复后 67 passed; 0 failed; 1 ignored**(task_advance_node.rs:331-332 改 advance_status_atomic)。 + - **①advance_task handler 同源核验** ✅ tool_registry.rs:407 调 `advance_task_atomic(&TaskRepo::new(&db), id, target_status)` 与 commands::task::advance_task(task.rs:165)同源调 `df_nodes::task_advance_node::advance_task_atomic(&state.tasks, &id, &target_status)`,均 thin 转发到同一 `advance_task_atomic`(task_advance_node.rs:43-90),无逻辑分叉。df_core::error::Error 是 thiserror(crate df-core/src/error.rs:5 `#[derive(Error)]`),实现 std::error::Error,anyhow::Error: From 链路通(`?` 编译过 cargo check 证)。 + - **②update_task handler guard 双重防御** ✅ tool_registry.rs:374-378 `if field=="status"` bail(第一层 handler 拒)+ tool_registry.rs:380-382 `is_allowed_column("tasks", field)` 查 crud.rs:331-347 白名单(第二层,白名单已无 status),status 双层拦截。无漏路径:value 必经 args["field"].as_str() 解析后两条 guard 之一必中。 + - **③crud 白名单移 status+review_rounds 收口** ✅ crud.rs:342 现 tasks 白名单 `"project_id","title","description","priority","branch_name","assignee","workflow_def_id","base_branch","updated_at"`(无 status/review_rounds)。advance_status_atomic(crud.rs:848-880)走独立 CAS SQL `UPDATE tasks SET status=?1[, review_rounds=review_rounds+1] WHERE id=?3 AND status=?4 AND deleted_at IS NULL`,**不经 validate_column_name/allowed_columns_for**(无白名单调用)。review_rounds 唯一写入路径收口语义达成:除 advance_status_atomic 退回转换 bump_rounds=true 路径,无任何旁路可写(crud.rs:865-866 SQL 内联 `review_rounds+1`)。 + - **④run_workflow handler 报错引导 IPC** ✅ 合理。tool_registry.rs:431-446 handler 仅持 `_db: Arc`(L432 实际未用,前缀 `_` 标记),无法构造 AppHandle/AppState(registry/event_bus/workflows Repo),L440-444 显式 anyhow 返回明确错误引导前端 `invoke('run_workflow', {task_id, target_status})`。符合单一执行路径原则(不在 handler 重放 DAG 引擎)。注释 L419-426 标注后续批注入 AppState 句柄扩展路径。 + - **⑤审批文案映射** ✅ display_hint_for_tool(tool_registry.rs:1259-1261)注册 `advance_task => "推进任务状态:{} → {}", &["id","target_status"]` + `run_workflow => "触发工作流:任务{} 推进到 {}", &["task_id","target_status"]`;tool_display_hint(tool_registry.rs:1286-1287)注册 `advance_task => Some("推进任务状态")` + `run_workflow => Some("触发工作流")`。两条目齐全。 + - **⑥project_soft_delete.rs 测试断言反转** ✅ project_soft_delete.rs:250 `update_field_rejects_tasks_status` 现 `res.is_err()`(L259-262)+ 对照 `assert_ne!(rec.status, "done")`(L266)。亲跑 11 集成测试全过 EXIT 0。 + - **⑦前端 status 路径零残留** ✅ taskApi.update(src/api/task.ts:17-19)仅 tasks.ts:31 `updateTask` 调用,全代码库 grep `updateTask(` 零组件调用方(仅定义无消费)。TaskDetail.vue:281 推进走 `taskApi.advance(task.value.id, target)`(状态唯一路径→advance_task IPC)。status 前端路径零残留达成。注:tasks.ts:29 `updateTask` 函数本身保留(改 title/description 等非 status 字段,合法),仅 status 字段已无消费方。 + - **⑧commands/task.rs update_task IPC 残留分支** ⚪(low)task.rs:102-108 `if field=="status" && !TaskStatus::is_valid(&value)` 分支并非纯死代码:它仍为 status **非法值**(拼写错/越界)提供更友好早错误(先于白名单拒)。但 status **合法值**路径必落到 task.rs:133-135 `update_field` 被白名单拒,故「校验通过后写入」分支永不命中。建议保留 value 校验(改善 UX)+ 删除成功路径假设注释,或整体移除改由 advance_task 独占(后续迭代决定,非阻断)。 + - **🔴-1(df-nodes 测试回归,high)**: **task_advance_node.rs:332** `repo.update_field("t1", "status", "in_progress").await.unwrap()` 测试红 —— 退回测试 `advance_succeeds_after_prior_status_change`(L324-336)用 `update_field` 模拟「旁路并发改 status」前置,但本 commit 已把 status 移出 tasks 白名单,`update_field` 现 Err → `unwrap()` panic「表 tasks 不允许的字段名: status」。亲跑 `cargo test -p df-nodes` 证实 66 passed/1 failed。**生产代码无回归**(advance_task_atomic 本身工作正常,66 个推进链测试全过),仅测试 fixture 需同步:把 L332 改成 `repo.advance_status_atomic("t1","todo","in_progress",false).await.unwrap()`(用 CAS 路径模拟并发改,语义等价)或直接 `repo.insert(rec("t1","in_progress"))` 重建前置态。CI 红门未守,须修后才能合。 **✅ 已修(主代 task_advance_node.rs:331-332 改 `advance_status_atomic("t1","todo","in_progress",false)` 走 CAS 合法路径模拟并发推进,亲跑 cargo test -p df-nodes 67 passed/0 failed/1 ignored)** + - **⚪ low 清单**: ①task.rs:102-108 status value 校验分支半死代码(合法值成功路径永不命中,非法值仍触发,见 ⑧)。 + - **待修项回流 todo**: **🔴-1**(df-nodes 测试回归 task_advance_node.rs:332) **✅ 已修**(主代改 `advance_status_atomic`,亲跑 67 passed,见上方修复记录)。低 ①task.rs:102-108 status value 校验半死代码非阻断(审查建议保留 value 校验改善 UX + 删成功路径假设注释,后续迭代决定)。 + +--- + +### CR-260616-43 AiNode 自审阶段2(AiNode持db+ai_self_review节点+DAG透传+前端展示) — ✅ 已审(PASS) + +- **复审结论(2026-06-16·CR-43 agent 亲跑 cargo+vue-tsc+test,142s)**: ✅ **PASS** — 🔴0 🟡0 ⚪1 + - **验证**: `cargo check --workspace` EXIT 0(5 warning 全 pre-existing dead_code)/ `cargo test -p df-nodes` **79 passed; 0 failed; 1 ignored** / `npx vue-tsc --noEmit` EXIT 0。 + - **①AiSelfReviewNode prompt 四维度+JSON 兜底完备 PASS**:build_review_prompt(ai_node.rs:405-424) 含需求+产出+四维度+输出格式 + parse_review_json(:360-381) 覆盖非 JSON/非 Object/缺 verdict → verdict=unknown 兜底 + 代码块剥离(:363-367 ```json 围栏) + 单测(:928-963) 合规/非法/fence 场景全过。 + - **②写回 output_json review 子字段 PASS**:读现有合并不覆盖 text(:514-519 先读 output_obj 若 JSON 则复用) + review 子字段写入(:520-534 含 verdict+reviewed_at+reviewer_model) + 落库失败只 warn 不阻断(:538-544)。 + - **③DAG 透传 PASS**:NodeOutput.data 平铺(:550-556 verdict/summary/suggestions) + human_review 经 inputs 读(task_workflow_templates.rs:72 edge ai_self_review→human_review) + HumanNode 零改动(git diff 核验 human_node.rs 在 3 commit 中无变动)。 + - **④state.rs 注册+工厂闭包 PASS**:AiNode 注册(:261-263 AiNode::new(ai_db)) + AiSelfReviewNode 注册(:269-270 AiSelfReviewNode::new(review_db)) + move db.clone() 闭包捕获正确。 + - **⑤testing 模板节点类型对齐 PASS**:模板 L61 注册 "ai_self_review" 节点 + L72 edge 透传 + 单测 L123-143 验证节点类型+边方向+options。 + - **⑥前端 schema 契约一致 PASS**:types.ts:102 output_json? 字段 + L98-100 schema 注释 + TaskDetail.vue:229-234 parsedOutput computed try/catch 兜底 + reviewVerdictClass/reviewLabel/reviewSuggestions 均从 parsedOutput.review 读 + 字段名对齐 verdict/summary/suggestions。 + - **⑦i18n 双语 PASS**:zh-CN/taskDetail.ts:23-27 review.pass/fail/unknown + en/taskDetail.ts:23-27 对称翻译 + 特殊字符无转义需求(值均为静态枚举字面量)。 + - **⑧P0 安全债登记确认**:api_key 明文 config 注入违背 FR-S1 已在 memory/devflow-aichat-review-pending.md 登记 + 本次 CR 范围已实现 resolve_provider 三路径(ai_node.rs:62-135 完整实现) + F-260616-07c 阻塞⑥联调已记录。 + - **⚪ CR-43-1(low,单测降级声明)**:AiSelfReviewNode.execute 完整 mock LlmProvider 单测本次降级(仅 parse_review_json+update_field 单测覆盖,P3 待补)。ai_node.rs:589-875 单测已覆盖 resolve_provider 双路径+parse_review_json 兜底+update_field 路径,end-to-end execute 单测依赖真实 LLM(#[ignore] glm_live_complete 存在),非阻断。 + - **待修项回流 todo**: **无**(1 low 单测降级非阻断,已在 CR 信息说明 P3 待补)。 + +- **范围**: F-260616-07 阶段3 AiNode 自审闭环②③④⑤(3 commit:539b5ed 迁移 + c10adaf ai 侧 + 741b0b9 前端)。①**①迁移**(539b5ed):TaskRecord 加 `output_json: Option` + V17 迁移(幂等补列) + crud 白名单加 output_json(L346)+SELECT/INSERT/UPDATE 全链路 + 5 构造点补 None。②**ai_node.rs②③④**(c10adaf):AiNode 持 db(`Arc`,对齐 TaskAdvanceNode:98-110 先例)+ execute 后 `config["task_id"]` 存在则 `update_field` 落 output_json(产出 schema {text,model,usage},落库失败只 warn 不阻断)+ AiSelfReviewNode 独立节点(parse_review_json 兜底 verdict=unknown + build_review_prompt 四维度 + 写回 review 子字段不覆盖 text)+ state.rs L269-270 注册 ai_self_review + ai 工厂 L262 `AiNode::new(db)` + testing 模板 L61 ai_self_review + L72 edge(ai_self_review→human_review)+ NodeOutput.data 平铺透传 + HumanNode 零改动。③**前端⑤**(741b0b9):types.ts output_json?(schema 注释)+ TaskDetail.vue parsedOutput computed(try/catch 兜底)+review-card 红绿标+renderedOutput 复用 useRendered + i18n zh/en taskDetail.output/review 双语。 +- **维度**: ①AiSelfReviewNode prompt 四维度+JSON 兜底完备性(parse_review_json 各路径 verdict 覆盖,代码块 fence 剥离重试) ②写回 output_json review 子字段(读现有合并不覆盖 text,reviewed_at/reviewer_model) ③DAG 透传(NodeOutput.data 平铺,human_review 经 inputs 读,HumanNode 零改动核验) ④state.rs 注册+工厂闭包 move db.clone() ⑤testing 模板节点类型对齐 ⑥前端 schema 契约一致(types.ts 与 ai_node.rs 写回 schema 字段名对齐 verdict/summary/suggestions) ⑦i18n 特殊字符转义 ⑧**P0 安全债登记**(api_key 明文 config 注入违背 FR-S1,跨 crate 阻塞,F-260616-07c 待决) ⑨主代亲跑 cargo df-nodes 73 passed + workspace EXIT 0 + vue-tsc EXIT 0。 +- **commit**: 539b5ed + c10adaf + 741b0b9。 +- **主代独立核查**: ✅ 全过(human_node.rs 零改动 git diff 确认 / state.rs L262 AiNode::new(ai_db)+L269-270 AiSelfReviewNode::new(review_db) / templates L61 ai_self_review+L72 edge / ai_node.rs AiNode 持 db@L119+execute 写@L184+AiSelfReviewNode@L292+parse_review_json@L258 / cargo test -p df-nodes 73 passed 亲跑 / cargo check --workspace EXIT 0 / vue-tsc EXIT 0 / 前端 schema 对齐)。 +- **审查 agent 待复审重点**: ①AiSelfReviewNode.execute 完整 mock LlmProvider 单测(本次降级 parse_review_json+update_field 单测,P3 待补) ②parse_review_json 兜底各路径(verdict=unknown 覆盖非 Object/数组/缺 verdict/JSON 解析失败/fence 剥离) ③写回 output_json 读现有合并的并发安全(同一 task 多次自审竞态) ④human_review 审批卡 description 拼自审摘要未实现(P2,透传链通但卡片未拼,HumanNode 只读 config 不读 inputs) ⑤P0 api_key config 注入(F-260616-07c,✅ 已由 CR-44 secret 下沉+注入链解决)。 + +--- + +### CR-260616-44 P0 secret下沉+AiNode provider注入链(方案B FR-S1闭环) — ✅ 已审(PASS) + +- **复审结论(2026-06-16·CR-44 agent 亲跑 cargo+test+vue-tsc,485s)**: ✅ **PASS** — 🔴0 🟡0 ⚪0 + - **验证**: `cargo check --workspace` EXIT 0(5 warning 全 pre-existing dead_code)/ `cargo test -p df-nodes` **79 passed; 0 failed; 1 ignored**(含 9 个 resolve_provider 新增测试)/ `npx vue-tsc --noEmit` EXIT 0。 + - **① secret 下沉边界 PASS**:secret.rs:7-9 注释明确不下沉 build_provider_for(依赖 df-ai 会循环)。grep 0 处函数定义,5 处全注释(src-tauri/Cargo.toml:17/secret.rs:7/8)。src-tauri/secret.rs:20 pub use df_storage::secret::* 转发所有下沉函数 + :32 build_provider_for 保留全限定路径调 df_ai::build_provider(:37)。 + - **② 转发壳 glob 兜底 PASS**:13 个实际调用点(排除注释行)全部经 crate::commands::ai::secret:: 或 super::secret:: 路径调用(commands.rs 4 处+agentic.rs 2 处+title.rs 1 处+knowledge_inject.rs 2 处+idea.rs 1 处+project.rs 2 处+lib.rs 1 处迁移调用)。编译期验证零断裂(cargo check EXIT 0)。 + - **③ keyring feature 平台覆盖 PASS**:Cargo.toml workspace:24 统一声明 windows-native+apple-native+linux-native-async-persistent 三平台。df-storage/Cargo.toml:18 keyring = { workspace = true }。secret.rs:220 cfg(any(target_os="windows",target_os="macos")) 单测(仅桌面 OS 环境实测 keyring roundtrip,CI 任意 OS 安全)。 + - **④ resolve_provider 三路径完备 PASS**:ai_node.rs:62-135 路径1 provider_id 优先→get_by_id(:76)→resolve_provider_secret(:143)→ensure_resolved_key(:144)/ 路径2 老明文兼容(:86-121,has_plain_base+has_plain_key 双判+:89-92 warn "老路径将在后续版本移除")/ 路径3 兜底(:124-134,list_all→find is_default→or_else 首条,友误 "未配置 AI Provider")。9 单测全覆盖(by_id/empty_key/unknown_id/legacy_plain/legacy_empty/fallback_default/no_providers/fallback_first/config_model_override)。 + - **⑤ FR-S1 闭环 PASS**:api_key 仅存 ResolvedProvider 内存(ai_node.rs:45 明文字段,注释 "仅 AiNode 内存可见"),不进 config(schema L313/314 "已废弃过渡" 描述)/NodeOutput(L273-281 仅 text/model/usage 无 api_key)/schema(L316 required 改 ["provider_id"])。resolve_provider_secret(DB 优先→keyring)解析,build_provider(:235-240)传明文仅 AiNode 进程内存可见,不落库不外泄。 + - **⑥ schema required 改 [provider_id] PASS**:ai_node.rs:316 required=["provider_id"] + :309 description "FR-S1:密钥经 df_storage::secret 解析,不进 config" / AiSelfReviewNode :569 required=["task_id","provider_id"]。 + - **⑦ 老明文兼容 warn 过渡期 PASS**:ai_node.rs:89-92 tracing::warn!("AiNode 明文 api_key/base_url 经 config 注入已废弃, 改用 provider_id (FR-S1). 老路径将在后续版本移除")。路径2 逻辑保留兼容(:86-121 has_plain_base+has_plain_key 双判)。 + - **⑧ 迁移逻辑 FAILCOUNT SIDECAR PASS**:secret.rs:34-72 failcount_path/.devflow-keyring-failcount + read/write/record/clear_migration_failcount + migrate_secrets_to_keyring(:125)失败达阈值 3 升级 warn(:136-142 明文滞留风险+3 条排查建议:确认 OS 钥匙串可用/keyring 后端异常/手动重新保存)。lib.rs:28 启动调用非阻断(:29 warn "启动密钥迁移失败(非阻断)")。 + - **⑨ df-nodes 显式导入 PASS**:ai_node.rs:17 use df_storage::secret::{ensure_resolved_key, resolve_provider_secret} 显式导入(非 glob)。resolve_from_record(:143-144)直接调用。ai_node.rs:14-15 也显式导入 AiProviderRepo/Database/AiProviderRecord。 + - **⑩ testing 模板 PASS**:task_workflow_templates.rs:61 注册 "ai_self_review" 节点 + :72 edge(ai_self_review→human_review) + 单测(:123-129)验证节点类型+边方向+config 空。state.rs:269-270 注册 AiSelfReviewNode::new(review_db) 闭包捕获正确。 + - **待修项回流 todo**: **无**(10 维度全 PASS,零问题)。 + +- **范围**: F-260616-07c P0 安全闭环(3 commit:36f70b3 方案 + 766f235 下沉 + 84a6fc4 注入链)。①**secret 下沉**(766f235):df-storage 新建 src/secret.rs move 纯密钥逻辑(get/set/delete/resolve/ensure/migrate+failcount,**不含 build_provider_for** 避循环)+ keyring workspace 平台 feature(windows/apple/linux-native-async-persistent)+ src-tauri secret.rs 瘦身转发壳(pub use df_storage::secret::* + build_provider_for 保留全限定路径调)+ 13 调用点零改(glob 兜底)+ keyring 单测 cfg-gate。②**AiNode 注入链**(84a6fc4):ai_node.rs resolve_provider(db,config)->ResolvedProvider 三路径(provider_id 优先 get_by_id→resolve_provider_secret→ensure / 老明文兼容 warn / 空兜底 list_all find is_default)+ AiNode/AiSelfReviewNode execute 同构经 resolve_provider 拿 provider + schema required [provider_id] + 模板 config 空走兜底。 +- **维度**: ①secret 下沉边界(纯密钥逻辑无 df-ai 依赖,build_provider_for 留 src-tauri,无循环) ②转发壳 glob 兜底(13 调用点零改核验) ③keyring feature 平台覆盖(Linux linux-native-async-persistent) ④resolve_provider 三路径完备(provider_id/老明文 warn/兜底 is_default/无 provider 友好错误) ⑤**FR-S1 闭环**(api_key 仅 ResolvedProvider 内存,不进 config/NodeOutput/schema required) ⑥schema required 改 [provider_id] ⑦老明文兼容 warn 过渡期 ⑧主代亲跑 cargo df-nodes 79 passed(9 新增 resolve_provider 测试)+ workspace EXIT 0。 +- **commit**: 36f70b3 + 766f235 + 84a6fc4。 +- **主代独立核查**: ✅ 全过(df-storage secret.rs 无 build_provider 函数定义,grep 5 处全注释 / src-tauri 转发壳 pub use+build_provider_for 全限定路径 / keyring workspace feature 三平台 / resolve_provider 三路径 L74/L89/L123 / schema required L316+L569 / api_key 仅 ResolvedProvider L45/238/464 调 build_provider 不进 NodeOutput / cargo test df-nodes 79 passed 亲跑 / workspace check EXIT 0)。 +- **审查 agent 待复审重点**: ①secret 下沉后 src-tauri 13 调用点经转发壳路径完整性(lib.rs/commands.rs/agentic.rs + build_provider_for 6 处) ②keyring Linux CI(linux-native-async-persistent 后端依赖 zbus/secret-service) ③resolve_provider 老明文兼容路径后续移除时机 ④FR-S1 端到端验证(实测 tauri dev:testing 模板 ai_self_review 经 resolve_provider 拿 provider→LLM→写 output_json) ⑤⑥端到端联调实测。 + +--- + +### CR-260616-35 batch59 CR-34-1 守卫 + CR-30 流式重试对齐决策a1 + UX-2025-04 断线保文 — ✅ 已审(PASS) + +- **范围**: 3 agent 合并批(workflow wdlt0xfl6,主代独立核查全过)。①**CR-34-1** useAiPanel.ts:102 守卫加有效性检查 `|| !list.some(p=>p.id===activeProvider)`(deleted provider 残留回落 DB is_default) ②**CR-30-1/CR-30-2 + UX-2025-04 合并**:stream_recv.rs `StreamResult` enum(Complete/Partial/InitFailed 三分支) + `classify_status_or_class`(4xx Fatal/5xx+429+timeout Retryable 镜像 retry::is_status_retryable) + agentic.rs 重试对齐决策 a1(Init 失败重试复用 retry::backoff_delay 1s→2s→4s±20%jitter + 30s 总预算 retry_deadline + Fatal 分类 4xx 立即放弃; MidStream Partial 不重试保文→入库 + AiCompleted(incomplete=true) + 系统提示「响应因网络中断不完整」) + 删 3 处误导注释(agentic.rs:12/commands.rs/state.rs) + retry.rs `pub mod` + `backoff_delay`/`is_status_retryable` 改 `pub` 暴露供 src-tauri 复用 + useAiEvents.ts:177 补 `case 'AiStreamRetry'`(消除死事件穿透) + AiCompleted case 处理 incomplete + types.ts:204/mod.rs `incomplete?:boolean` 字段(所有 emit 点补 None 向后兼容) + i18n aiStreamRetry/responseIncomplete 双语。 +- **维度**: 流式核心中风险(重试循环正确性/保文语义/跨 crate pub 暴露影响面) + 前端事件消费完整性 + 决策 a1 对齐度(Init 可重试 ≤max_retries+jitter+30s+Fatal, MidStream 不重试) + B-260615-05 保留(空文本无 finished 仍 InitFailed 交重试)。 +- **commit**: 待提交(累积 ~30 文件攒批)。 +- **复审结论(2026-06-16·CR-30 agent 亲跑 cargo + 主线程 CR-34-1 预核验)**: ✅ **PASS** — 🔴0 🟡0 ⚪0 + - **验证**: `cargo check --workspace` EXIT 0(5 warning 全 pre-existing dead_code)/ `cargo test -p df-ai retry` **6 passed**(backoff_is_monotonic_within_jitter/status_classification/fatal_aborts_immediately/first_attempt_ok/retry_once_then_ok/all_retryable_exhausts)/ `cargo test stream_recv` **20 passed**(classify_429_retryable/classify_4xx_fatal 等)。 + - **CR-30-1(7 项全 PASS)**:agentic.rs:320 真用 `retry::backoff_delay(retry_attempt+1)`(非自写,旧 `1<p.id===activeProvider)` 残留无效回落 DB is_default,cycleProvider 不受影响(findIndex 不再 -1 跳过 ps[0])。 + - **UX-2025-04 断线保文**:核心 ✅(MidStream Partial 保文入库 + incomplete 系统提示);「继续生成」按钮未做(可复用 UX-2025-03 重试能力按需补,非阻断)。 + - **待修项回流 todo**: **无**(CR-34-1/30-1/30-2 三条确认修复,todo 勾完成)。 +- **主代独立核查**: ✅ 全过(useAiPanel:102 守卫 / stream_recv:97 enum 三分支 + 注释 / agentic:242 retry_deadline + :274 Partial 不重试 + :286 InitFailed 重试 + :320 backoff_delay 复用 + :360 if incomplete 保文入库 + :400 emit incomplete:Some(true) / useAiEvents:177 AiStreamRetry case / types:204 incomplete / retry.rs:62+74 pub + agentic:14 `use df_ai::retry` 跨 crate 通)。 +- **审查 agent 待复审重点**: ①stream_recv StreamResult 三分支边界(Partial vs InitFailed 空文本路径,B-260615-05 保留点) ②agentic 重试循环 permit RAII 释放/边界(max_retries=0/guard.reset) ③跨 crate retry pub 暴露是否过度(retry_with_backoff 仍 pub(crate)) ④AiCompleted incomplete 前端消费完整性(系统提示气泡镜像后端) ⑤UX-2025-04「继续生成」按钮**未做**(保文核心 ✅,按钮可复用 UX-2025-03 重试能力或按需补)。 + +--- + +### CR-260616-45 波1 F-01阶段3厂商模型列表拉取 + UX-01错误气泡Fatal区分 + UX-03工具卡按轮次收起 — ✅ 已审(PASS) + +- **复审结论(2026-06-17·CR-45 agent 亲跑 cargo+test+vue-tsc+独立 grep/read 核验)**: ✅ **PASS** — 🔴0 🟡0 ⚪2 + - **验证**: `cargo check --workspace` EXIT 0(5 warning 全 pre-existing dead_code)/ `cargo test -p df-ai model_fetch` **24 passed** 全过/ `npx vue-tsc --noEmit` EXIT 0。 + - **①build_models_url 7 分支完备 PASS**:model_fetch.rs:165-178 尾 trim/|v1|v4/|models/chat/completions/api/根 base 7 分支全覆盖;断言成立(无 /v1/v1/models 重复,trim 后单次 suffix 判断);测试 build_url_v1_suffix_appends_models:303 `assert!(!...contains("/v1/v1"))` 编译期证。 + - **②噪音过滤 13 类 + embedding 保留 PASS**:is_non_chat_model:196-219 dall-e/midjourney/sd/imagen/tts/whisper/audio/speech/voice/realtime/transcribe/moderation/-search 13 类;embedding_kept_per_design:410-414 单测证保留;注释 :194 明确按 §5.4 设计。 + - **③反序列化多 alias 兜底 PASS**:ModelsList:248-253 serde(default) + data alias="models";ModelEntry:256-259 id alias="name";into_ids:263-269 filter_map 丢弃 None;parse_openai_format/parse_anthropic_format/parse_ollama_format/parse_missing_data_field_yields_empty/parse_entry_missing_id_skipped 5 单测全覆盖。 + - **④协议分派 PASS**:fetch_openai_compat:66-89 Bearer 鉴权 + /v1/models;fetch_anthropic_compat:92-119 x-api-key+anthropic-version 双 header + /v1/models;fetch_model_names:40-46 其他报 UnsupportedProvider;三路径完备。 + - **⑤错误映射友好提示 PASS**:map_network_error:132-138 timeout→超时提示/其他→网络错误;map_status_error:141-151 401/403→鉴权失败/404→端点不存在/5xx→厂商异常;全含 provider_type+url 上下文。 + - **⑥fetch_and_probe probe 兜底 PASS**:fetch_and_probe:52-59 探测为纯 CPU map collect,单模型探测失败不中断整体(probe 本身有兜底)。 + - **⑦UX-01 canRetry Fatal 分类 PASS**:AiChat.vue:1994-1999 auth/provider_config=Fatal 隐藏重试;notRetryable 提示 L413;v-if L415 守卫;边界说明正确(404/参数错仍可重试,待后端 retryable 字段精确化)。 + - **⑧UX-03 agentRound watch 触发时机 + CSS 不动决策 PASS**:watch agentRound:1738-1744 cur<=prev 早退,collapseAllToolLists(buildActiveToolIds(...));ToolCardList.vue:353-357 flex gap 原理说明明确,本轮不重构 JS 钩子决策合理。 + - **⑨i18n 转义 PASS**:ai.ts/aiChat.ts zh-CN/en 8 key 全纯文本,无 @/|/裸 {} 特殊字符;notRetryable/queueEdit/queueSave/queueCancel/queueSendNow 双语对称。 + - **⚪ CR-45-1(low,`-search` 子串匹配可能误伤)**:is_non_chat_model:218 `id.contains("-search")` 可能误伤含 `-search` 后缀的合法 chat 模型名(如厂商未来发布 `gpt-next-search-assistant` 模型实际支持 chat);当前单测覆盖 `text-davinci-search-001` 旧 GPT 搜索变种合法剔除,但 `contains` 子串匹配设计粒度粗。已知影响面小(主流厂商暂无含 `-search` 的 chat 模型),非阻断;后续可按需收紧为正则 `r#"-(search|search-[a-z]+)$"#` 或加入白名单例外。 + - **⚪ CR-45-2(low,fetch_and_probe 无分页)**:fetch_anthropic_compat:92-119 调 `build_models_url` 通 `/v1/models`,Anthropic 响应含 `has_more` 字段但当前 serde 反序列化 ModelsList:248 未取 has_more(serde 仅取 id);超大列表>500 可能截断。当前阶段3 聚焦拉取主流模型,截断影响可控(头部 500 模型足够覆盖);非阻断,留阶段5 IPC 实测后按需补分页。 + - **待修项回流 todo**: **无**(2 low 非阻断:子串匹配可收紧/分页可后续补)。 + +--- + +### CR-260616-46 波2 F-01阶段4路由器 + UX-05/06/07队列三件套 — ✅ 已审(PASS) + +- **复审结论(2026-06-17·审查 agent 亲跑 cargo+vue-tsc,87s)**: ✅ **PASS** — 🔴0 🟡0 ⚪1 + - **验证**: `cargo check --workspace` EXIT 0(5 warning 全 pre-existing dead_code)/ `cargo test -p df-ai router::` **15 passed**(13 原+2 新)/ `npx vue-tsc --noEmit` EXIT 0。 + - **①select 7 步过滤链 PASS**:router.rs:57-66 严格对齐设计 §6.1 L470-478(enabled→模态全子集 L60→needs_tool_use 含 ToolUse L61→intelligence>=min L62→max_cost<= L63→context_window>= L64→max_by_key L65)。单测 full_dimensions_match_picks_best:L310-360 5 候选参差验证全链。 + - **②Reverse(cost_tier) 取反正确性 PASS**:router.rs:11 `use std::cmp::Reverse`+L65 `max_by_key(|m| (m.weight, Reverse(m.cost_tier)))`.CostTier Ord FreeOption` 调 select 映射 model_id。单测 select_model_id_*:L117-128 池空/命中全过。 + - **④关联函数 vs &self 方法偏离 PASS**:router.rs:9 注释注明「对齐任务规格,非设计文档的 &self 方法」。关联函数 `ModelRouter::select(&req,&pool)`(L57)适配阶段5 调用点 `ModelRouter::select(&req,&pool)`,零行为差异(无状态单元结构,无需 self)。 + - **⑤pub use re-export PASS**:router.rs:16 `pub use df_ai_core::model::{Capability,CostTier,IntelligenceTier,Modality,ModelConfig}`。阶段5 调用点经 `df_ai::router::{...}` 直接 import 维度枚举构造 TaskRequirements,零跨 crate 路径冗长。 + - **⑥UX-05 决策 a 落地 PASS**:useAiSend.ts:397-404 stopChat 删 `state.queue=[]` 决策 a(逐条续发)。后端 agentic.rs:209 注释「保证前端收事件时后端已可接下一条,队列续发不被拒绝」+drainQueue(useAiSend.ts:228-232)由 AiCompleted 触发链式续发。 + - **⑦UX-06 editQueued PASS**:useAiSend.ts:370-375 边界(index 越界/空白 no-op)+trim+直接赋值 text。AiChat.vue:1567 editingQueueIdx/1569 editingQueueText ref+L519-531 inline input 三路保存一致(回车/blur/@mousedown.prevent 防 blur)。 + - **⑧UX-07 sendQueuedNow 顺序 PASS**:useAiSend.ts:387-392 splice 先→stopChat→sendMessage 顺序关键(splice 前置避重复发,stopChat UX-05 后已不清队列故 splice 必须前置)。复用 UX-05 stop 链路(agentic.rs:209 AiCompleted 就绪核验)。 + - **⑨i18n 双语 PASS**:queueEdit/Save/Cancel/SendNow 4 key 纯文本无特殊字符,zh-CN/en aiChat.ts:L68-71 对称。 + - **⚪ low 清单**:①router.rs:77 select_model_id 注释「池空/无匹配→None→兜底 default_model」。阶段5 调用点 unwrap_or(default_model) 确认行为不变,当前测试见 CR-48 调用点接入验证。 + - **待修项回流 todo**: **无**(9 重点全 PASS,1 low 注释清晰非阻断)。 + +--- + +### CR-260616-47 波3 F-15上下文基础(is_active白名单+push token) + F-01模型拉取IPC+Settings UI — ✅ 已审(PASS) + +- **复审结论(2026-06-17·审查 agent 亲跑 cargo+test+vue-tsc,62s)**: ✅ **PASS** — 🔴0 🟡0 ⚪2 + - **验证**: `cargo check --workspace` EXIT 0(5 warning 全 pre-existing dead_code)/ `cargo test -p df-ai-core is_active` **1 passed** / `cargo test -p df-ai context::` **14 passed**(零回归)/ `npx vue-tsc --noEmit` EXIT 0。 + - **①is_active 白名单零行为变化 PASS**:provider.rs:85-86 `matches!(self.status.as_deref(), None | Some("active"))` 与旧 `!matches!(self.status.as_deref(), Some("truncated"))` 等价(当前仅 None/active/truncated 三态)。**核验 truncated 时序**:context.rs:484-496 truncate_after_user_message 在 push 之后标记(:496 `t.message.status = Some("truncated")`),push 时刻 is_active 恒 true,history_tokens 累加不变,零行为变化证成。 + - **②push token 修正 PASS**:context.rs:179-185 `if message.is_active() { self.history_tokens += tokens }`。!active 消息仍 push 到 self.messages 全量保留(:186 `self.messages.push(...)`),仅不计 token。修复 restore_from_messages 后 token 虚高致 build_for_request 误判超预算。 + - **③restore_from_messages 路径 PASS**:context.rs:420-425 遍历调 push,token 同步仅 active。单测 push_token_only_active:860-910 验证直接 push+restore 双路径,active/archived/compressed 三条 restore 后 token 只含 active 一条(:902-910 断言通过)。 + - **④ai_fetch_models FR-S1 闭环 PASS**:commands.rs:872 `df_storage::secret::resolve_provider_secret(&provider)` 内存解析,不进日志/返回值。ModelConfig(df-ai-core/model.rs:87-125)无 api_key 字段,:902 返回值 `Vec` 天然不含密钥。 + - **⑤normalize_provider_type 归一 PASS**:commands.rs:841-846 anthropic→anthropic_compat 归一(:873 调用)。对齐 model_fetch.rs:40-42 fetch_and_probe 协议分派,双命名收敛。 + - **⑥fetch_and_probe + update_full 写回 PASS**:commands.rs:875 `fetch_and_probe` 调用 + :883-887 `update_full` 写回 model_configs + updated_at。 + - **⑦ai_probe_model 纯 CPU probe PASS**:commands.rs:897-902 调 `df_ai::model_probe::probe(&model_id)`,无网络。 + - **⑧types.ts 4 维度枚举 snake_case 对齐 PASS**:types.ts:200-206 Modality/Capability/CostTier/IntelligenceTier 字面量(df-ai-core/model.rs:20-72 serde `rename_all = "snake_case"` 严格对齐)。 + - **⑨Settings 拉取 UI PASS**:Settings.vue:95 拉取按钮(:96 disabled 非新建态提示先保存)+:102-103 模型列表展示(4 维度标签+enabled toggle+weight input)+:519-536 fetchModels 函数错误分支 toast(超时/鉴权/404/厂商异常)。 + - **⑩i18n 双语 PASS**:zh-CN/en settings.ts:33-37 testConnection/fetchingModels/fetchHintSaveFirst/labelWeight/modelListTitle 对称,无特殊字符。 + - **⚪ CR-47-1(low,Settings enabled/weight 本地态非持久化)**:Settings.vue:525 `providerForm.models = models.map(...)` 仅本地会话态,未存回 model_configs。文案 "下次「测试连接」会以最新探测结果覆盖"(:516-518 注释)明示,阶段6 后续持久化另见 issues。非阻断(本批目标拉取展示 UI,本地编辑态可接受)。 + - **⚪ CR-47-2(low,ai_probe_model UI 未接)**:无单模型探测按钮,留阶段6 AiChat 模型下拉。非阻断(probe 纯 CPU,按需补 UI)。 + - **待修项回流 todo**: **无**(10 重点全 PASS,2 low UI 增强/持久化非阻断)。 + +--- + +### CR-260616-48 波4 F-01阶段5调用点接入(select_model_id helper+6点) + F-260617-01 patch_file三模式编辑 — ✅ 已审(PASS) + +- **复审结论(2026-06-17·CR-48 agent 亲跑 cargo+test+vue-tsc,342s)**: ✅ **PASS** — 🔴0 🟡0 ⚪2 + - **验证**: `cargo check --workspace` EXIT 0(5 warning 全 pre-existing dead_code)/ `cargo test -p df-ai router` **15 passed**(2 新增:select_model_id_empty_pool_returns_none/select_model_id_hit_returns_model_id)/ `cargo test -p df-nodes --lib` **79 passed; 1 ignored**/ `cargo test -p df-ideas adversarial` **12 passed**(含 a12 新增 llm_mock_fenced_not_at_start)/ `cargo test -p devflow tool_registry` **24 passed**(16 三模式单测)/ `npx vue-tsc --noEmit` EXIT 0。 + - **①select_model_id helper 纯函数正确性 PASS**:router.rs:79-81 纯函数调用 ModelRouter::select + map model_id,单测:117-127 覆盖池空 None/命中 model_id 返回字符串,编译期证类型匹配。 + - **②pub use re-export 可见性 PASS**:router.rs:16 `pub use df_ai_core::model::{Capability,CostTier,IntelligenceTier,Modality,ModelConfig}`;6 调用点全经 `df_ai::router::{...}` 全限定导入(agentic.rs:19/project.rs:11/title.rs:11/knowledge_inject.rs:12/ai_node.rs:17/adversarial.rs:165),编译期证零断裂。 + - **③6 调用点兜底行为不变 PASS**:agentic.rs:175-176 `select_model_id(...).unwrap_or_else(|| provider_config.default_model.clone())` 池空/无匹配→None→兜底 default_model;title.rs:86-87/project.rs:539-540/knowledge_inject.rs:350-351/ai_node.rs:214/adversarial.rs:165 同模式;池空时 resolved_model==default_model 零差异。 + - **④agentic resolved_model 审计字段统一 PASS**:agentic.rs:288/410/414/419/465/469 原用 default_model 处统一改 resolved_model,语义更正确(记实际服务模型);池空时 resolved_model==default_model 无行为变更。 + - **⑤knowledge_inject 嵌入降级 needs_tool_use=false PASS**:knowledge_inject.rs:58-65 TaskRequirements(needs_tool_use=false,min_intelligence=Lite),宽松过滤允许嵌入模型入选;TaskRequirements 无 capabilities 字段设计偏离,embedding provider 池通常即嵌入模型实践中命中,可接受。 + - **⑥df-ideas 新增 df-ai 重依赖 PASS**:df-ideas/Cargo.toml:12 df-ai 依赖引入;注释:9-11 说明 router 模块仅依赖 df-ai-core::model,编译期 tree-shaking 零 HTTP 代码链入(仅引用符号),实测 cargo check EXIT 0 证无链接错误;后续若严格轻量可将 router 下沉 df-ai-core 非阻断。 + - **⑦df-nodes ResolvedProvider.model_pool 字段 PASS**:ai_node.rs:54 model_pool 字段+ :205 record 路径填 record.model_configs.clone + :143 老明文路径填空池;config.model 显式优先(:197-203 有则用 model_id 否则路由),单测覆盖。 + - **⑧patch_file 三模式互斥运行时校验 PASS**:tool_registry.rs:932-940 has_old_text/has_replace_lines/has_anchor 计数,0=缺定位 bail/:937 >1=冲突 bail/:939,两边界完备;schema 层 object_schema 无法静态互斥,运行时兜底合理。 + - **⑨patch_file expected_hash mtime+size 非 sha256 PASS**:tool_registry.rs:971-981 `format!("{}_{}", modified.unwrap_or(0), file_meta.len())` 指纹(mtime+size),三模式通用,语义与 old_text 模式一致(read_file 返回 size 非 hash,注释:141 与 read_file 行计数一致),设计正确。 + - **⑩apply_line_range trailing newline 语义 PASS**:tool_registry.rs:133-152 单测:1722-1729 尾换行保留(`\n` 结尾文件替换后仍以 `\n` 结尾),splice 循环 lines() 后单独处理 trailing_newline,边界正确。 + - **⑪resolve_anchor 子串行内命中 PASS**:tool_registry.rs:182-206 子串大小写敏感匹配+start 后第一个含 end 行;单测:1783-1789 行内命中 / 1794-1798 start==end 同行 / 1803-1808 first-end-after-start,覆盖完备。 + - **⑫16 单测覆盖 PASS**:tool_registry.rs:1701-1853 单测 apply_line_range 8 + resolve_anchor_to_lines 8 + e2e 1,亲跑 cargo test 24 passed 全过。 + - **⚪ CR-48-1(low,agentic 主对话含图 Vision 检测未实现)**:agentic.rs:169 modalities 仅 `[Modality::Text]`,ChatMessage 纯文本无图像 Part,多模态类型接入后需检测 Part/Image 追加 Vision,当前非阻断(设计文档已标注:166-167)。 + - **⚪ CR-48-2(low,build_provider/诊断日志仍用 default_model)**:provider trait 不暴露改 default_model,CompletionRequest.model 走路由 provider impl 适配(OpenAICompatProvider::convert_request:295-296 req.model.is_empty() 回退 default_model),诊断日志仍用 default_model 字段名,非阻断。 + - **待修项回流 todo**: **无**(2 low 非阻断:Vision 检测待多模态接入/诊断日志命名,路由核心功能全 PASS)。 + +### CR-260616-49 波5 F-01阶段6 AiChat模型下拉+override穿透(4c84fbc) / F-15阶段1剩余compress基础(63bff8b) — ✅ 已审(PASS) + +- **复审结论(2026-06-17·CR-49 agent 亲跑 cargo+test+vue-tsc+独立 grep/read 核验)**: ✅ **PASS** — 🔴0 🟡0 ⚪1 + - **验证**: `cargo check --workspace` EXIT 0(6 warning 全 pre-existing dead_code,PendingApproval/releases/node_executions)/ `npx vue-tsc --noEmit` EXIT 0/ `cargo test -p df-ai` **107 passed**(含 context 9 新方法 + 8 新单测)/ `cargo test -p devflow --lib compress` **3 passed**。 + - **①F-01 override 兜底零行为变化 PASS**:agentic.rs:181-188 `match model_override.as_deref()` — Some(id) 且 !id.is_empty() 且 `provider_config.model_configs.iter().any(|m| m.model_id == id)` → 用 id;`_ => resolved_model`(落回路由)。override=None(自动模式默认)完全等价改动前。非空但不在池→落回路由兜底正确,防止 override 导致无模型。 + - **②F-01 清零路径完整 PASS**:create(commands.rs:1147 `session.model_override = None`)+ switch(commands.rs:1225)两入口清零,前端 watch activeConversationId 同步清(AiChat.vue:1071)。防跨对话污染,零残留。 + - **③F-01 续跑透传 PASS**:try_continue_agent_loop(agentic.rs:699-702)读 session.model_override.clone() 透传,审批续跑/达 max 续跑保持同一主对话的 override 一致,语义正确。 + - **④F-01 空池降级 PASS**:AiChat.vue:237 `v-if="enabledModels.length"` 隐藏下拉+ :231 toggle :disabled + :1323 setSpecifyMode 早返。空 provider 不 crash。 + - **⑤F-01 i18n 特殊字符 PASS**:aiChat.ts 新 6 key(autoMode/specifyMode/autoModeHint/specifyModeHint/selectModel/noModels)纯文本,无 @/|/裸{} 特殊字符。 + - **⑥F-15 compress 幂等 PASS**:context.rs:629-640 compress_old_messages 只处理 is_active,saturating_sub 扣 token,已 compressed 跳过;返回 newly_compressed 仅本次新标记。DB 原始消息保留,is_active 白名单隔离。单测 push_token_only_active:954 覆盖直接 push+restore 双路径。 + - **⑦F-15 compress_via_llm 兜底 PASS**:compress.rs:66-67 select_model_id 池空/无匹配→unwrap_or_else 兜底 default_model;:89 map_err + :92 空摘要 Err。阶段2 IPC 接调用点后降级。 + - **⑧F-15 纯函数优先 PASS**:compress_prompt(prompt.rs:148 无 IO)/ build_eviction_units/has_compressible_messages/compress_old_messages 无 IO;compress_via_llm 封装 IO(双层 Semaphore 限流复用 title 模式)。单测 3 passed。 + - **⑨F-15 零行为变化 PASS**:push/restore_from_messages/build_for_request/sanitize_messages 未改,仅新增方法 + is_compressing 字段(new/clear 初值 false)。 + - **⚪ CR-49-1(low,前端 ref 单例设计说明)**:modelOverride 模块级 ref(useAiSend.ts:53)非 per-conversation,靠 watch 清理对齐后端 switch 清 session.model_override。当前单实例 AiChat 可接受,多窗口架构未来若需独立模型选择需改 per-conversation ref。非阻断(设计文档已标注)。 + - **待修项回流 todo**: **无**(9 重点全 PASS,1 low 设计说明非阻断)。 + +### CR-260616-50 波6 F-15阶段2 手动上下文管理(2 IPC+3事件+前端按钮+status渲染)(4194842) — ✅ 已审(🔴 RED 已修 PASS) + +- **复审结论(2026-06-17·波7 ww68uamzu agent CR-50)**: ⚠ **ISSUES → 🔴 RED 已修(主代)** — 后端 IPC/事件/保护区/失败路径全 PASS,前端事件分发 RED(审查抓到真 bug,主代即时修) + - **验证**: `cargo check --workspace` EXIT 0(6 warning 全 pre-existing dead_code)/ `npx vue-tsc --noEmit` EXIT 0(零输出)/ `cargo test -p devflow --lib` **79 passed**(无回归)。 + - **① clear_context 不删 DB PASS**:commands.rs:430-433 经 messages_mut() 标 status="archived_segment"(is_active 自动 false,sanitize 隔离),落库走 save_conversation 全量 upsert(:439),非 clear_messages 删库。 + - **② compress LLM 失败不阻塞 PASS**:commands.rs:543-554 先取 active 克隆(读不改 status)→ compress_via_llm 失败则 set_compressing(false)+ emit AiError(message 不含 api_key)+ 返 Err;消息状态不变(未调 compress_old_messages)。成功路径才 compress_old_messages 标 compressed + insert_at(0, summary system)。 + - **③ 保护区最近 N 条 active 不动 PASS**:PROTECT_COUNT=6(commands.rs:412/465 两处一致),protect_start = len.saturating_sub(6),clear/compress 只对 [0, protect_start) 操作。 + - **④ 空会话/无 active 降级 PASS**:protect_start==0 → emit 对应事件 + Ok(不报错,commands.rs:416-420/485-492);active_msgs 空 → AiCompressed(summary 空,commands.rs:511-515) + Ok。 + - **⑤ 3 事件变体 serde — 🔴 RED(审查抓到真 bug)**:mod.rs AiChatEvent 仅 `#[serde(tag="type")]` 无 `rename_all="snake_case"`,serde 默认序列化 PascalCase(useAiEvents.ts 10 case 铁证)。useAiContext.ts:77/80/84/87 switch case 用 snake_case(`'ai_compressing'` 等)→ 永不命中 → isCompressing 永不复位 → 压缩按钮用过一次永久禁用 + 清空后视图不刷新。**主代修复(2026-06-17)**:case 4 处改 PascalCase(AiCompressing/AiCompressed/AiContextCleared/AiError)+ compressContext 成功兜底复位(后端同步 resolve 非异步)+ doc 注释一致性。vue-tsc EXIT 0 + grep 无残留 snake_case + useAiEvents 对照铁证。未改后端(rename_all 会破坏 useAiEvents 既有 10 case)。 + - **⑥ useAiContext composable 闭环 PASS(RED 修后)**:isCompressing ref 防重入 + 第二 listener 消费 4 事件 PascalCase 分发 + compressContext 成功兜底复位(后端 IPC 同步等 LLM 完成才 resolve 非异步 spawn,成功即复位防事件丢失)。 + - **⑦ status 分组渲染 PASS**:AiChat.vue:2240 messageSegments computed 按 status 分组(archived_segment/compressed 连续合并),:2290 renderItems 扁平化供 v-for;折叠段点击 toggleSegment 展开,正常消息零改动。 + - **⑧ i18n 13 key ×2 无特殊字符 PASS**:aiChat.ts 新 key(clearContext/compressContext/clearContextConfirm/contextArchived/compressed/compressing/clearSuccess/compressSuccess/compressError/clearError/expand/collapse/compressedSummaryLabel)仅合法 {n}/{msg} 插值,无 @/|/裸 {}。 + - **⑨ useAiConversations status 透传 PASS**:useAiConversations.ts:86-90 switchConversation mapper 透传 status 字段(archived_segment/compressed 落 AiMessage 供渲染),types.ts 未含此字段经 as any 读写闭环。 + - **⑩ 已知取舍(history_tokens 口径) PASS**:commands.rs:423-429 注释明确标注口径——clear_context 改 status 后 history_tokens 不立即扣减(私有字段无 setter),后果短暂偏高触发更早裁剪,属保守方向不劣化安全性。 + - **待修项回流 todo**: **无**(🔴 RED 已即时修,主代核验 case PascalCase + vue-tsc EXIT 0 + useAiEvents 10 case 铁证对照)。 + +- **范围**: 2 agent 并行 workflow w6o61cbqn,文件域隔离(后端独占 commands.rs/mod.rs/lib.rs;前端独占 api/ai.ts/useAiContext.ts(新)/useAiConversations.ts/stores/ai.ts/AiChat.vue/i18n×2)。零重叠。 +- **commit**: 4194842(单 commit,功能完整:后端 IPC 无前端无意义,配套提交)。 +- **主代核查(2026-06-17)**: cargo check --workspace EXIT 0(6 warning 全 pre-existing dead_code,PendingApproval/releases/node_executions)/ npx vue-tsc --noEmit EXIT 0(零输出)/ cargo test -p devflow --lib **79 passed**(无回归)/ grep 核验形态(lib.rs:122-123 注册 2 IPC + mod.rs:145/147/149 三事件变体 + commands.rs:397/457 两 IPC fn + 6 处 emit + 前端 useAiContext composable + AiChat.vue messageSegments 分组渲染)。 +- **审查 agent 待复审重点**: + - ①**clear_context 不删 DB**:commands.rs:397 经 messages_mut() 标 status="archived_segment"(is_active 自动 false,sanitize step0 隔离),落库走 save_conversation(全量 upsert 非 clear_messages 删库)。 + - ②**compress LLM 失败不阻塞不 panic**:commands.rs:457 先取 active 克隆(读不改 status)→ compress_via_llm;LLM 失败 set_compressing(false)+ emit AiError(message 不含 api_key)+ 返 Err;消息状态不变(未调 compress_old_messages)。成功路径才 compress_old_messages 标 compressed + insert_at(0, summary system)。 + - ③**保护区最近 N 条 active 不动**:PROTECT_COUNT=6(commands.rs:413/465),protect_start = len.saturating_sub(6),clear/compress 只对 [0, protect_start) 操作。 + - ④**空会话/无 active 降级**:protect_start==0 → emit 对应事件 + Ok(())不报错;active_msgs 空 → AiCompressed(summary 空) + Ok。 + - ⑤**3 事件变体 serde**:mod.rs:145/147/149 AiContextCleared/AiCompressing/AiCompressed{summary},继承枚举级 #[serde(tag="type")] rename_all snake_case → 前端 type:"ai_context_cleared"/"ai_compressing"/"ai_compressed"。 + - ⑥**前端 useAiContext composable 闭环**:isCompressing ref 防重入 + 第二 listener 消费 ai_compressing/compressed/context_cleared/error 生命周期;compressContext IPC 受理仅表请求受理,真正完成由 ai_compressed 事件复位 isCompressing(异步闭环,非 IPC 同步等)。 + - ⑦**status 分组渲染**:AiChat.vue messageSegments computed 按 status 分组(archived_segment/compressed 连续合并折叠段),renderItems 扁平化供 v-for;折叠段点击 toggleSegment 展开,正常消息走 v-else 原渲染零改动。 + - ⑧**i18n 13 key ×2 禁字符**:aiChat.ts 新 key(clearContext/compressContext/clearContextConfirm/contextArchived/compressed/compressing/clearSuccess/compressSuccess/compressError/clearError/expand/collapse/compressedSummaryLabel)message 值无字面 `@`/`/`/裸`{}`(仅合法 `{n}`/`{msg}` 插值)。注意 archived 与侧栏冲突 → 重命名 contextArchived(vue-tsc TS1117 已修)。 + - ⑨**useAiConversations status 透传**:switchConversation mapper 透传 status 字段(archived_segment/compressed 落 AiMessage 供渲染折叠),types.ts 未含此字段经 cast 读写闭环。 + - **⚠️ 已知设计取舍(backend 自报,记审查项)**:clear_context 经 messages_mut() 改 status 后,ContextManager.history_tokens(私有字段无 setter)不立即扣减 archived 段 token(与 compress_old_messages 路径不同,compress 内部扣 token)。后果:history_tokens 短暂偏高,build_for_request 超预算裁剪可能比实际更早触发,属保守方向(不超发)不劣化安全性。代码注释已标注口径。 + +### CR-260616-51 波8 F-15阶段3 agentic loop自动压缩(0202b51) + AE-2025-04会话级授权(6ec191e) — ✅ 已审(PASS-WATCH·🟡1回波11修) + +- **复审结论(2026-06-17·波10 wsm0tk95c agent CR-51)**: ⚠ **PASS-WATCH** — 🔴0 🟡1 ⚪2。阶段3(loop语义/触发/防重入成对/延迟mutate/降级/emit路由)+ AE-04(TrustKey/session_trust/audit查trust/ai_approve写trust/toast桥接/FR-S1)逻辑全 PASS,devflow 79 + df-ai 107 passed,vue-tsc EXIT 0。**🟡 yellow 回流波11修**:audit.rs:593-596 trust-hit 放行分支 `.await tools_arc.execute` 持 `&mut session`(注释 L593 声称锁外但代码持锁,与 Low risk spawn 模式不一致),run_command 慢命令阻塞该会话所有触 state.ai_session 的 IPC(30+)。修法:对齐 Low risk spawn+clone(trust_hits Vec 收集→循环外 join_all spawn execute 锁外→串行回填持锁)+ 修正注释。⚪2 非阻断(摘要 insert_at 二次压缩边缘/canonicalize 失败回退边缘)。 + +- **范围**: 2 agent 并行 workflow wigdmq5s4,文件域零重叠(阶段3 独占 agentic.rs;AE-04 独占 mod.rs/audit.rs/commands.rs/tool_registry.rs/AiChat.vue/useAiEvents.ts/types.ts/i18n×2)。 +- **commit**: 0202b51(阶段3)+ 6ec191e(AE-04)。分 2 commit(语义清,功能不同)。 +- **主代核查(2026-06-17)**: cargo check --workspace EXIT 0(6 warning 全 pre-existing dead_code)/ npx vue-tsc --noEmit EXIT 0(零输出)/ cargo test -p devflow --lib **79 passed**(无回归)/ grep 核验形态(阶段3 loop 顶部压缩分支 + set_compressing 成对 + AE-04 TrustKey/session_trust/audit 查 trust/ai_approve 写 trust)。 +- **审查 agent 待复审重点**: + - ①**阶段3 loop 语义不变**:agentic.rs loop 顶部加压缩检测分支(L259-374),主 loop 迭代/stream/重试/tool_calls/converge 零改动。压缩是 build_for_request 前预处理。 + - ②**阶段3 触发条件**:`budget*6/10 < history_tokens`(0.6 阈值整数比避浮点)+ protect_start>0 + has_compressible_messages(protect_start)。 + - ③**阶段3 防重入 + 成对**:prev_compressing 守卫(L280)+ set_compressing(true)(L303)↔ 三出口 false(L340 成功/L355 noop/L361 Err)。成对覆盖所有分支。 + - ④**阶段3 延迟 mutate(成功才改)**:先取 active 克隆 → LLM 出摘要 → 成功才 compress_old_messages(标 compressed+扣 token)+ insert_at(摘要)。失败零副作用(消息状态/token 完全不变)。理由:history_tokens 私有无 setter,失败回滚 token 不精确;延迟 mutate 语义最干净。 + - ⑤**阶段3 降级**:LLM 失败/空摘要 → set_compressing(false)+ emit AiError(message 不含 api_key)+ 继续走原 build_for_request 裁剪(不阻塞 loop)。 + - ⑥**阶段3 emit conversation_id 路由**:AiCompressing(L298)/AiCompressed(L348)/AiError(L367)三处均 Some(conv_id)。 + - ⑦**AE-04 TrustKey 设计**:mod.rs:201 enum Write{dir}/Execute{dir}(目录级,derive Hash/Eq)+ trust_key_for(:215 write_file→Write 父目录/run_command→Execute working_dir)+ 首批仅 write_file/run_command 命中(其余 None 走原审批)。 + - ⑧**AE-04 session_trust 内存态**:HashSet(mod.rs:346),new 初值空(:363),换会话(create/switch)clear(commands.rs:1161/1241)重审,不落库。 + - ⑨**AE-04 audit 查 trust**:audit.rs:568 process_tool_calls 审批前(Med/High 进 pending 前)查 session_trust.contains 命中 → 直接执行(非复用缓存,与 F-05 去重不同)+ emit AiToolAutoApproved(:587)+ Completed + audit decided_by=auto_trust(:613)留痕 + continue 跳过 pending + 二次确认。 + - ⑩**AE-04 ai_approve 写 trust**:commands.rs:294-295 用户批准时 trust_key_for 命中 → session_trust.insert(下次同类自动放行)。 + - ⑪**AE-04 前端 toast 桥接**:AiToolAutoApproved 后端 emit → useAiEvents case(types.ts union)→ emit ai-tool-auto-approved-toast 事件总线 → AiChat.vue listen 弹 info toast(🔓 自动放行),非审批气泡避免干扰。 + - ⑫**AE-04 FR-S1**:TrustKey 只含 tool+dir,不含 api_key/文件内容/命令串(mod.rs:189 注释)。 + - **待修项回流 todo**: **无**(核心 grep+test+构建全 PASS;阶段3 延迟 mutate 口径 + AE-04 目录级粒度为设计取舍非 bug)。 + +### CR-260616-52 波9 B-260617-01 run_workflow审批执行(方案a2后端分支)(e23b316) — ✅ 已审(PASS) + +- **复审结论(2026-06-17·波10 wsm0tk95c agent CR-52)**: ✅ **PASS** — 🔴0 🟡0 ⚪3。a2 实施正确:run_workflow_inner 抽取语义零变化(命令层瘦转发)/ ai_approve 分支(commands.rs:317)仅隔离 run_workflow 其余不变 / execute_run_workflow_for_tool 回填 Ok json(execution_id)让 LLM 收成功 / AE-04 trust_key_for(:294)在分支(:317)前且 run_workflow 返 None 不写 trust / audit trust 查询 + create/switch clear 未动 / High 去重缓存断重试双层 / handler 防御文案正常不可达 / FR-S1 tool_result 仅 task_id+target_status+execution_id。devflow 79 passed。⚪3 非阻断:①workflow.rs:124 triggered_by 注释声称 ai 实际 manual(已知待办)②mod.rs:465 空 config 既有行为(对齐前端)③tool_registry.rs:547 _db 死代码冗余。 + +- **范围**: 单 agent workflow wf8r5vlno,走查+实施。改 workflow.rs(run_workflow_inner 抽取)/ mod.rs(execute_run_workflow_for_tool)/ commands.rs(ai_approve run_workflow 分支)/ tool_registry.rs(handler 注释+防御文案)。 +- **commit**: e23b316。 +- **主代核查(2026-06-17)**: cargo check --workspace EXIT 0(6 warning 全 pre-existing)/ cargo test -p devflow --lib **79 passed**(无回归)/ grep 核验形态(workflow.rs:87 run_workflow_inner + mod.rs:446 execute_run_workflow_for_tool + commands.rs:317 ai_approve 分支 + trust_key_for:294 在分支前 AE-04 不破坏)。 +- **方案选型**:agent 选 a2(后端分支)而非 a1(前端拦截)。理由:ai_approve 已持 app+state,后端直接调 run_workflow_inner 最干净,无需新 IPC/前端分支/工作流知识泄漏。符合用户推荐 a 大方向(绕开 ai_tools.execute 必 Err)。 +- **审查 agent 待复审重点**: + - ①**run_workflow_inner 抽取语义零变化**:workflow.rs:87 抽取 run_workflow 命令体为共用核心,命令层瘦转发(:79)。核心逻辑(模板选 DAG/落记录/spawn 转发+执行+联动)全部移入,语义不变。 + - ②**execute_run_workflow_for_tool**:mod.rs:446 从 args 取 task_id/target_status + 构造空 DagDef+config + name 拼"AI 推进任务" + 调 run_workflow_inner + 回填 {task_id,target_status,execution_id,status:"running"} 让 LLM 收成功。 + - ③**ai_approve run_workflow 分支**:commands.rs:317 `if approval.tool_name == "run_workflow"` → execute_run_workflow_for_tool,否则原 ai_tools.execute。后续 audit_finalize/emit_data_changed/replace_tool_result/emit/save/try_continue 共用零变化。 + - ④**AE-04 trust 不破坏**:trust_key_for 写 trust(:294)**在 run_workflow 分支(:317)前**;audit trust_hit 查询(audit.rs:568)未动;create/switch clear(:1177/1257)未动。 + - ⑤**重试循环根治**:a2 真实执行→成功 tool_result(非 Err)+ 既有 High 去重缓存(find_cached_high_risk_result audit.rs:406 失败 tool_result 复用)双层保障。不新增 no_retry 标志(无字段+无消费方+crate 改动大,去重缓存已等效)。 + - ⑥**handler 防御文案**:tool_registry.rs:556 run_workflow handler Err 改防御兜底(正常流程不可达,a2 分支接管)+"勿盲目重试"明示(对齐 run_command 超时标注模式)。 + - ⑦**FR-S1**:run_workflow 无 api_key,tool_result 仅 execution_id+task_id+target_status。 + - **待修项回流 todo**: **无**(a2 实施正确,grep+test+构建全 PASS;no_retry 不增为设计取舍,去重缓存等效)。 + +### CR-260616-53 波11 CR-51 yellow修(audit trust-hit持锁重构) + UX-01工具失败视觉 + UX-03自动收起增强(332a6bb+31ea151) — ✅ 已审(PASS·⚪2) + +- **复审结论(2026-06-17·审查 agent 亲跑 cargo+vue-tsc+test+独立 grep/read 核验)**: ✅ **PASS** — 🔴0 🟡0 ⚪2 + - **验证**: `cargo check --workspace` EXIT 0(7 warning 全 pre-existing dead_code)/ `npx vue-tsc --noEmit` EXIT 0/ `cargo test -p devflow --lib` **88 passed**(无回归)。 + - **①CR-51 yellow 并行化(非"移锁外") PASS**:修复前后 session 锁均由调用方(agentic.rs:839 `session_arc.lock().await`)持有至 process_tool_calls 返回,锁持有时长不变。**真正改进是并行化**:修复前 trust-hit 分支内 inline `.await execute` 串行执行每个工具(阻塞期间锁被持有,run_command 慢命令阻塞同会话所有触 state.ai_session 的 IPC);修复后收集 trust_hits Vec→join_all 并行执行(锁仍被持有,但多工具并行减少总阻塞时间)。audit.rs:664-713 实现(trust_hits 收集 L568+join_all 闭包 L671-701+串行回填 L704-712)正确,注释"锁外"应理解为"闭包内不访问 session"而非"锁已释放"。 + - **②语义不变 9 条全 PASS**:真执行(audit.rs:676 tools.execute)+emit toast(L599 AiToolAutoApproved)+emit completed(L680)+emit_data_changed(L687)+audit auto_trust(L711 decided_by=Some("auto_trust"))+Med-High 非 trust 路径零改动(L630-660 pending 分支)+Low risk 零改动(L718-764 独立并行分支)+pending_count 不变(L630 仅 trust-hit 跳过,pending 仍累加)+错误信息格式不变(L680/690 AiToolCallCompleted 结构一致)。 + - **③UX-01 isToolFailure 判定可靠 PASS**:run_command 路径(ToolCard.vue:213-217 parseResult 命中合法 JSON 看 exit_code≠0,可靠)+通用工具路径(ToolCard.vue:220-224 parseResult 返回 null 后正则 `/执行失败|failed|error[:\s]/i` 启发式匹配,对齐后端 AR-6 err_msg 格式 `format!("工具 {} 执行失败: {}", draft.name, e)` audit.rs:744)。正则可能误判含 "error"/"failed" 字样的成功结果(如 "no errors found"、"error handling passed"),但这是启发式检测已知取舍,非阻断(误报概率低,最坏多一次视觉提示)。 + - **④UX-01 视觉完整 PASS**:--failed 红框(ToolCard.vue:727-730 border-color+box-shadow)+banner(L119)+红底结果框(L745-751 background+border)+状态点(L20 failed 分类)+副标题(L17 failed 标签)。CSS 三类(.ai-tool-card--failed/.ai-tool-status-dot--failed/.ai-tool-result--failed)完整。 + - **⑤UX-03 streaming watch 守卫 PASS**:AiChat.vue:1807 `generatingConvId===activeConversationId` 防后台生成误触+防恢复会话 onMounted 时 streaming 初值 true 的误触(仅当前视图正在生成时触发收起)。守卫正确。 + - **⑥UX-03 wasNearBottom 边沿检测 PASS**:AiChat.vue:1749 初值 true+1760 `if (near && !wasNearBottom)` 转换瞬间触发一次+1763 `wasNearBottom = near` 更新。边沿检测防每 scroll tick 收起,正确。 + - **⑦UX-03 userExpandedCards 记忆态 PASS**:ToolCardList.vue:71 定义+197 展开时 add+193 收起时 delete+180 isCardExpanded 优先读+221 collapseInactive 不清除。记忆态防"刚在看被收起",正确。 + - **⑧join_all 保序 PASS**:trust_hits 收集按 tc_list 原始 index 顺序(audit.rs:533 sort_unstable_by_key+540 retain 后顺序不变)→join_all 保持输入顺序→串行回填按结果顺序。LLM 按 tool_call_id 匹配 tool_result,不依赖到达顺序,保序非必需但一致性更优。 + - **⚪ CR-53-1(low,注释"锁外"有误导性)**:audit.rs:664 注释"execute + 即时 emit 在闭包内(不持 session 锁)"易误解为"锁已释放",实则是"闭包内不访问 session"。session 锁在整个 process_tool_calls 期间由调用方持有(agentic.rs:839 `session_arc.lock().await` 至 L840 `process_tool_calls(...).await` 返回),修复前后锁持有时长不变。改进是**并行化**非"移锁外"。 + - **⚪ CR-53-2(low,UX-01 通用工具文本匹配启发式误报风险)**:ToolCard.vue:223 正则 `/执行失败|failed|error[:\s]/i` 可能误判含这些字样的成功结果(如 "error handling passed"、"no errors found")。但这是启发式检测已知取舍,后端 err_msg 格式固定(AR-6 `format!("工具 {} 执行失败: {}", draft.name, e)`),误报概率低,最坏多一次视觉提示不阻断执行,非阻断性问题。 + - **待修项回流 todo**: **无**(2 low 全非阻断:注释误导性可后续澄清/启发式误报风险可接受)。 + +### CR-260616-54 波12 F-04多Provider负载均衡池(数据层+选择器+并发原语) + CR-52白项清理(79b6a43) — ✅ 已审(PASS) + +- **复审结论(2026-06-17·CR-54 agent 亲跑 cargo+test+vue-tsc+独立 grep/read 核验)**: ✅ **PASS** — 🔴0 🟡0 ⚪0 + - **验证**: `cargo check --workspace` EXIT 0(7 warning 全 pre-existing dead_code)/ `cargo test -p devflow --lib` **88 passed**(79 基线+9 provider_pool 新增全过)/ `cargo test -p df-storage` **33 passed**/ `npx vue-tsc --noEmit` EXIT 0。 + - **①provider_pool select 3键排序完备 PASS**:provider_pool.rs:57-91 过滤 `enabled && weight>0`(:64) + 稳定 3 键(:71-88 模型亲和 true 排前 / weight 降序 / is_default 兜底)。9 单测全覆盖(empty_pool_returns_singleton/single/disabled/zero_weight/model_affinity/model_id_none/weight_tie/is_default/full_scenario),亲跑全过。边界完备:model_id None 全亲和(:73-76 map_or true)/ 0 enabled 空 Vec 调用方兜底(:346-352)/ 1 enabled 单元素首位=唯一(:136-138)。 + - **②单 provider 零变化 PASS**:agentic.rs:342-345 ProviderPool::select 取首位,单 provider 池返回 `[p1]`,首位 `p1` 等价原 `provider_config`。老库迁移 V19 migrations.rs:343-356 `DEFAULT 1`/`DEFAULT 50`,crud.rs:1072-1073 unwrap_or(1)/unwrap_or(50),老行启用后等价 F-01 前。空池兜底(:346-352)用 `provider_config.clone()` 作唯一候选,同启动行为。 + - **③per_provider permit Option 安全 PASS**:state.rs:112 HashMap 定义 + :158-172 acquire_for_provider 返 `Option`,map.get?+acquireOwned(:164-171)。未配 set_provider_caps 时 HashMap 空,返 None,drop(None) 无副作用(:671 `_provider_permit` 绑定作用域自动释放)。本轮 set_provider_caps 无消费方(留 F-04c 前端),单 provider 零变化达成。 + - **④agentic 接入完整 PASS**:agentic.rs:342 `ProviderPool::select` 选主候选 + :346-352 split_first 取 primary + candidates + :663-710 `for candidate in &candidate_chain` 循环 + :667 `acquire_for_provider` permit + stream_one_provider(:669-670) 单 provider 流式 + :698 F-04b 日志"切换下一 provider"(基建就位,本轮候选链仅 primary 不触发切换)。单 provider 路径不破坏。 + - **⑤triggered_by 透传链完整 PASS**:workflow.rs:93 `run_workflow_inner` 加 `triggered_by: &str` 参数 + :133 落 `triggered_by: Some(triggered_by.to_string())` 入库 + :80 命令层传 "manual"(前端人工) + mod.rs:475 ai 路径传 "ai"(execute_run_workflow_for_tool)。grep 核验仅 2 调用点,无遗漏。tool_registry.rs:547-560 删 `let _db = db.clone();` 死代码,handler 仅返 Err 引导走审批路径,正确。 + - **⑥V19 迁移幂等 PASS**:migrations.rs:342-360 `PRAGMA column_exists` 探测 enabled/weight + :345 `DEFAULT 1`/ :352 `DEFAULT 50` + V9 建表 SQL(:548-549 同步加列)。老库 ALTER 后取 DEFAULT,新库直接有列。crud.rs:1072-1073 ai_provider_from_row `unwrap_or(1)`/`unwrap_or(50)` 兼容缺失列(幂等测试场景)。insert/update SQL(:1159-1168/:1177-1178) 含 enabled/weight 列。 + - **⑦fallback 拆波诚实 PASS**:agentic.rs:330-334 注释明确"F-04b 切换下一 provider"(本 candidate 重试耗尽切下一),:663-710 `for candidate in &candidate_chain` 循环已就位(:698 continue),仅候选链仅 primary(无 fallback 配置)不触发真实切换。基础设施可用,留 F-04b 独立批实际启用多候选切换。 + - **⑧4 处构造点补字段 PASS**:commands.rs:986-1001(ai_save_provider 新建默认 enabled=true/weight=50)/ crud.rs:1762-1777(单测)/ secret.rs:209-215(单测)/ ai_node.rs:714-729(单测),4 处全补 `enabled: true, weight: 50`。 + - **待修项回流 todo**: **无**(7 维度全 PASS,零问题)。 + +### CR-260616-55 波13 B-21工具卡重复治本(audit tc_list按id去重保证emit Started唯一)(247dc5d) — ✅ PASS + +- **范围**: workflow wice28c6y(前端守卫 agent + 后端走查 agent)。①**前端治标守卫**(HEAD 已有,非本轮新加):useAiEvents.ts:205 AiToolCallStarted push 前 `if (!findToolCall(event.id))` 幂等守卫(对齐 startToolSlowTimer:58)。②**后端走查结论**(不改代码):候选A确认——LLM 流式输出两个 tool_use block 不同 content_block index 但相同 id → stream_recv.rs:225 按 index(u32 键非 id)分桶 → 两条同 id 不同 index draft 并存 → audit.rs:538-549 每 draft emit Started → 同 id 两次 → 前端 push 两卡 → Completed 按 id 只 update 首张 → 次张残留 running 0行。候选B排除(provider 每轮新 id)。③**治本兜底(commit 247dc5d)**:audit.rs:533 sort 后加 `seen_ids HashSet + tc_list.retain(draft.id)` 按 id 去重,同 id 保留最小 index 首个,emit Started 唯一。前端守卫双保险。详 docs/02-架构设计/B-260616-21排查方案。 +- **维度**: ①去重正确性(同 id 保留最小 index 首个,retain 后 drafts 唯一 id emit) ②不破坏正常多工具(不同 id 不受影响) ③HashSet import(audit.rs:3 加) ④与 F-05(High risk 去重缓存)无冲突(不同维度)。 +- **commit**: 247dc5d。 +- **主代核查**: cargo check EXIT 0 + 88 passed + grep retain 形态。 +- **审查结论**: ✅ PASS。核验通过: + - ①去重正确性:audit.rs:533 `tc_list.sort_unstable_by_key(|(i, _)| *i)` 先按 index 排序 → :540 `seen_ids.insert(draft.id.clone())` retain 保留首个同 id(即最小 index) → :548 emit AiToolCallStarted 仅一次。逻辑闭环。 + - ②不破坏多工具:retain 仅过滤同 id,不同 id 工具正常通过,stream_recv.rs:225 证实分桶键为 index 非 id。 + - ③HashSet import:audit.rs:3 `use std::collections::{HashMap, HashSet}` 正确。 + - ④与 F-05 无冲突:F-05(audit.rs:612 find_cached_high_risk_result) 按 tool_name+args 去重(不同 id 同命令),本条按 id 去重(同 id),维度正交。 + - ⑤前端守卫:useAiEvents.ts:205 `if (!findToolCall(event.id))` 确认存在,双保险兜底。 + - ⑥cargo check:EXIT 0,零编译错误。 +- **待修项回流 todo**: **无**(治标守卫 + 治本去重 双保险就位)。 + +### CR-260616-56 波14 F-04c后端(provider池enabled/weight更新IPC+set_caps启动接入)(b3684f4) — ✅ PASS + +- **范围**: workflow wodppv1hd。①ai_save_provider(commands.rs:929-948)编辑路径保 enabled/weight(修原硬编码清回默认 bug)。②ai_update_provider_pool IPC(commands.rs:1022-1056)轻量专用更新 enabled/weight 经 update_full,weight clamp[0,100],落库后 reload_provider_caps 重建 caps。lib.rs:128 注册。③set_provider_caps 启动接入(state.rs:297 AppState::init 调 reload_provider_caps L305-323 从 DB enabled providers 建 per_provider HashMap,cap=global_cap=3)。单点真理 helper(state 不依赖 commands 避免环形)。 +- **维度**: ①ai_update_provider_pool 不触 ai_save_provider 密钥迁移分支(避免空 api_key 重发整表) ②reload_provider_caps 单点真理 ③cap=global_cap=3 单 provider 零变化 ④ai_save_provider 编辑保字段(新建默认 true/50)。 +- **commit**: b3684f4。 +- **主代核查**: cargo check EXIT 0 + 88 passed。 +- **复审结论**: PASS。6 维度全核验通过:①ai_update_provider_pool 无 api_key 参数不触密钥迁移(commands.rs:1023-1056) ②reload_provider_caps 单点真理(state.rs:311-327) ③global_cap=3 与单 provider 等价(state.rs:321+324-326) ④编辑保留(commands.rs:945-947) ⑤provider_pool::select 过滤 enabled/weight(provider_pool.rs:64) ⑥weight clamp(commands.rs:1037)。无 high/med/low 发现。 +- **待修项回流 todo**: **无**(后端就绪,前端 Settings UI 留波16)。 + +### CR-260616-57 波15 F-04b多provider fallback切换(stream_one_provider+候选链+resolved_model重算)(80c0955) — ✅ 已审(PASS) + +- **审查时间**: 2026-06-17(独立审查 agent · 亲跑 cargo check EXIT 0 + grep/read 核验源码) +- **总体结论**: ✅ **PASS** — 🔴0 🟡0 ⚪0 +- **逐件核验(全 PASS)**:①stream_one_provider 抽取 ✅(agentic.rs:155-298 封装单 candidate,StreamOutcome 三态 Success/InitFailedExhausted/Fatal 驱动外层切换)/ ②resolved_model 重算 ✅(:188-199 select_model_id 在 candidate.model_configs 上重算+ model_override 穿透,防切换 provider 后模型池不同吃 400/404)/ ③候选链外层 ✅(:663 'candidate for 循环+ :656-658 Vec clone primary 入链避借用冲突)/ ④InitFailedExhausted 切换 ✅(:691-701 continue + :188-199 resolved_model 重算,stream_one_provider 内重建 build_provider)/ ⑤Fatal 立即放弃 ✅(:703-707 guard.reset+return,对齐 retry.rs Fatal 分类 + provider_pool 文档)/ ⑥per-provider permit 切换释放 ✅(:667 acquire_for_provider 循环内取,切换 Drop 自动释放旧槽)/ ⑦retry_deadline 共享预算 ✅(:642 单次 Instant::now+30s,各 candidate 共享不重置,防串行累加超预算)/ ⑧单 provider 零变化 ✅(:352 空池兜底 candidates=Vec::new,candidate_chain 仅 primary,循环跑一次耗尽 return)。 +- **退避策略一致性**: ✅ 复用 df_ai::retry::backoff_delay(agentic.rs:14 use df_ai::retry + :270 调用 retry::backoff_delay((retry_attempt+1) as u32)),retry.rs:74-85 实现 1s→2s→4s ±20% jitter,与 CR-30-1 对齐一致。 + +--- + + +--- + +## 已审归档区(更早批次) + +### CR-260616-66 波23 Settings拆4子组件(Provider/Connection/General/Knowledge·零行为变化)(9c60c8f) — ✅ 已审(PASS) + +- **复审结论(2026-06-17·审查 agent 亲跑 vue-tsc + cargo check + 独立 grep/read 核验)**: ✅ **PASS** — 🔴0 🟡0 ⚪0 + - **验证**: `npx vue-tsc --noEmit` **EXIT 0** ✅ / `cargo check --workspace` **EXIT 0**(6 pre-existing dead_code warnings 无关本 CR)。 + - **①4 子组件拆分边界 PASS**:ProviderPanel 收纳 AI 模型配置 + Provider 表单(providerForm 双向共享按数据耦合正确收敛:171-204 openProviderForm 编辑/新建完整分支) / ConnectionPanel 独占 localStorage CRUD / GeneralPanel 独占主题/语言/并发/Agent 轮次 / KnowledgePanel 独占知识库配置(embeddingProviders 依赖 ProviderPanel 的 aiProviders props:108-109 正确)。零交叉污染。 + - **②props/emits 接线 PASS**:Settings.vue:26 传 `:confirm-dialog="confirmDialog"` 函数引用 + :75 `useConfirm()` 返回 confirmDialog/confirmState/answerConfirm 三件套 / ProviderPanel.vue:150 接收 props.confirmDialog + :267/ConnectionPanel.vue:174 调用 props.confirmDialog 删除确认 / emit('toast') 上下传完整(Settings.vue:27/35 接收 + :65 showToast) / emit('providers-changed') 上抛同步 aiProviders(Settings.vue:85-87)。 + - **③CSS scoped 零视觉变化 PASS**:各子组件自持 scoped 样式副本(ProviderPanel:347-458 / ConnectionPanel:184-262 / GeneralPanel:280-322 / KnowledgePanel:171-213).panel/.btn/.toggle/.setting-* 类完整重复,父 scoped 无法穿透子根故需子自持(Settings.vue:179-181 注释明确)。视觉 CSS 全保留。 + - **④F-04c enabled/weight 拆 ProviderPanel 保留 PASS**:ProviderPanel.vue:40 `@change="onPoolToggle"` + :50 `@change="onPoolWeightChange"` / :301-312 onPoolToggle(prev 保存 + 乐观更新 + catch 内 revert) / :318-332 onPoolWeightChange(debounce 300ms + clamp[0,100] + NaN 兜底回退 50) / aiApi.updateProviderPool 即时调 IPC(:305/:325) / reload_provider_caps 语义完整(注释:291-294)。 + - **⑤sync×3 拆 GeneralPanel 保留 PASS**:GeneralPanel.vue:92 `@change="syncConcurrencyConfig"` + :114 `@change="syncAgentMaxIterations"` + :125 `@change="syncAgentMaxRetries"` / :202-219 syncConcurrencyConfig(debounce 300ms + clamp 双并发 + 单对话不超过全局) / :226-237 syncAgentMaxIterations(debounce 300ms + clamp[1,50]) / :243-254 syncAgentMaxRetries(debounce 300ms + clamp[0,10]) / :265/:267/:269 onMounted 启动同步一次防止刷新后丢失。 + - **⑥AE-05 useConfirm 留 shell PASS**:Settings.vue:7-15 confirm-mask/confirm-box 模板完整 + :75 `useConfirm()` 返回 confirmState 留 shell共享(confirmState.visible/msg 在 setup 自动解包) + answerConfirm 双按钮绑定。 + - **⑦6 timer 全覆盖 PASS**:Settings.vue:96 _toastTimer 清理 + :97 providerPanelRef.clearPoolTimer() (ProviderPanel.vue:343 暴露) / ProviderPanel.vue:322 _poolTimer clearTimeout / ConnectionPanel 无 timer / GeneralPanel.vue:274-276 三 timer 清理(_concurrencyTimer/_agentIterTimer/_agentRetryTimer) / KnowledgePanel.vue:167 _knowledgeSaveTimer 清理。各子组件 onUnmounted 自管(Settings.vue:95 注释明确)。 + - **⑧i18n 复用无新增 PASS**:grep 核验 panelAiProvider/panelConnection/panelGeneral/panelKnowledge 全在既有 settings.ts(zh-CN:7/62/77/115 + en:7/62/77/115)。零新增 key。 + - **待修项回流 todo**: **无**(8 维度全 PASS,零问题。纯重构零功能,行为零变化硬约束满足,运行时留实测)。 + +--- + +### CR-260616-34 batch58 F-260616-10 provider 默认值重启失效 bug — ⚠ 已审(PASS-WATCH) + +- **审查时间**: 2026-06-16(主线程兜底·agent 撞 GLM 限流 1305 · grep/read 核验源码 + 类型确认) +- **总体结论**: ⚠ **PASS-WATCH** — 🔴0 🟡1 ⚪3(核心 bug 双恢复修复 OK,已删除 provider 残留守卫 med 待修) +- **逐件核验**:核心 bug 修复 ✅(重启 activeProvider=null → useAiPanel.ts:53-54 模块级 savedProvider 同步恢复 + loadProviders:101-104 DB is_default 兜底,双恢复链路)/ is_default 字段确认 ✅(types.ts:177 AiProviderConfig.is_default boolean,ai_list_providers 返回)/ cycleProvider 兜底 ✅(AiChat.vue:1184 startId=activeProvider||is_default||null 三级兜底,L1187 Math.max(idx,0) 防 -1 越界)。 +- **🟡 CR-34-1(med,已删除 provider 残留未校正)**:useAiPanel.ts:101 `if (!state.activeProvider)` 守卫只查 null 不查有效性。localStorage savedProvider 指向已删除 provider(L54 设 activeProvider=deleted-id)时,loadProviders 守卫 false 跳过 DB is_default 恢复 → activeProvider 残留无效 id → cycleProvider:1185 findIndex=-1 → :1187 next=ps[1] 跳过 ps[0],且潜在消费方(发消息读 activeProvider 调后端)用无效 id。修法:守卫改 `if (!state.activeProvider || !list.some(p => p.id === state.activeProvider))` 残留无效时回落 DB is_default。 +- **⚪ low**:1(L52 注释「以 DB 为准」与行为不符,实际 savedProvider 优先 DB 兜底,行为合理注释误导)/ 2(setProvider:121 void appSettings.set 持久化失败静默,仅影响重启恢复可接受)/ 3(多窗口/分离窗口 activeProvider 一致性:模块级 state 各 webview 独立,主 setProvider 写 SQLite 分离窗口 state 滞后,既存架构非 CR-34 引入)。 +- **待修项回流 todo**: **CR-34-1**(med,useAiPanel.ts:101 已删除 provider 残留守卫改查有效性)。 + +--- + +### CR-260616-33 batch57 CR-260615-04 splitBlocks→marked.lexer() — ✅ 已审(PASS) + +- **审查时间**: 2026-06-16(主线程兜底·agent 撞 GLM 限流 1305 · grep/read 核验源码) +- **总体结论**: ✅ **PASS** — 🔴0 🟡0 ⚪3 +- **逐件核验(全 PASS)**:splitBlocks(AiChat.vue:671-691 marked.lexer 切块,L672 getMarked 取实例 + L676 未就绪兜底 `return [text]` 不引入正则 + L677 lexer tokenize + L678-689 for 循环)/ code token 整段一块(L681-685 `type==='code'` + raw.trim 守卫空代码块跳过 + continue)/ 非代码 token 按 `\n{2,}` 切段(L688 split + b.trim 守卫)/ 空 token 非 string raw 防御(L679 `typeof string` continue)/ 空兜底(L690 `blocks.length ? blocks : [text]`)/ 签名不变(`splitBlocks(text):string[]` + renderStreamingBlocks:723 调用)/ 切块边界与 parse 渲染边界一致(同 marked 实例同围栏规则,CR-260615-04 根治目标达成)/ 流式路径回归(renderStreamingBlocks:718-733 + parseBlock/parseBlockNoCache 块缓存机制不变)。 +- **⚪ low**:1(lexer 全文 tokenize 性能略高于旧正则,rAF 节流+块缓存吸收,流式实测无影响)/ 2(末块未闭合围栏场景 lexer 可能把后续段落吞进 code raw,流式中间态短暂切块不准,末块重 parse 修正)/ 3(L690 空兜底 lexer 空文本返空数组时回退原文)。 +- **待修项回流 todo**: **无**(核心目标切块/渲染边界一致达成,防御充分)。 + +--- + +### CR-260616-32 batch57 B-260619 Knowledge.vue CSS窄屏标题挤压 — ✅ 已审(PASS) + +- **审查时间**: 2026-06-16(独立审查 agent · 亲跑 vue-tsc EXIT 0 + grep/read 核验源码) +- **总体结论**: ✅ **PASS** — 🔴0 🟡0 ⚪3 +- **逐件核验(全 PASS)**:min-width:0 传播链完整(L576 kn-detail-panel → L622 detail-head → L623 detail-title-row flex:1 → L625 detail-title,窄屏标题可正常收缩)/ 760px 断点 column-wrap 与 grid 兼容(L730 kn-layout 单列降级 + L731 detail-head flex-direction:column 无冲突)/ 按钮换行间距(L626 detail-actions gap 6px + flex-wrap:wrap,L732 窄屏 width:100%)。 +- **⚪ low**:1(L623 `.detail-title-row { overflow:hidden }` 冗余,word-break 多行标题 height auto 自适应不真裁断,语义不清,建议删保留 min-width:0+flex:1)/ 2(L733 窄屏字号降级仅覆盖 .detail-title 18→15px,编辑态 .edit-input-title L692 固定 16px 未同步,窄屏编辑态 16px 反超展示态 15px)/ 3(18→15px 偏激进,详情主标题窄屏建议 ≥16px)。 +- **待修项回流 todo**: **无**(三 low 风格/一致性微调非阻断)。 + +--- + +### CR-260616-31 batch56 F-260616-08 分页 offset+探总量 — ✅ 已审(PASS) + +- **审查时间**: 2026-06-16(主线程兜底·agent 撞 GLM 限流 1305 · grep/read 核验源码形态;**未亲跑 cargo check**,标准 API 语法无编译风险,待 agent GLM 恢复补 cargo 验证) +- **总体结论**: ✅ **PASS** — 🔴0 🟡0 ⚪2 +- **逐件核验(全 PASS)**:①offset 边界(tool_registry.rs:216/237/254 offset `as_u64().unwrap_or(0)`→usize,JSON 负数 as_u64 None→0,skip(offset).take(limit) 超 total 取空不 panic;has_more `(offset+page.len())Result>`,:224 unwrap_or_default Err→None→:231 Err,DB 故障不误判已处理)。 +- **⚪ low**:1(pending 审计记录 + 内存 pending_approvals 丢失→:231 Err「未找到挂起的审批」文案略困惑,实际审批上下文已不可恢复 Err 正确,文案对有 pending 审计记录场景不精确)/ 2(防抖 _pendingApprovalIds 模块级跨调用累积,finally 清理可靠无泄漏,机制说明非问题)。 +- **待修项回流 todo**: **无**(三维度全 PASS)。 + +--- + +### CR-260616-27 batch54 复核-新⑪ detach 清主窗口 state 看门狗幽灵(useAiWindow.ts) — ✅ 已审(PASS) + +- **审查时间**: 2026-06-16(主线程兜底·agent 撞 GLM 限流 1305 · grep/read 核验 + vue-tsc EXIT 0) +- **总体结论**: ✅ **PASS** — 🔴0 🟡0 ⚪0 +- **逐件核验(全 PASS)**:detachPanel(useAiWindow.ts:28-38 快照在清 state 前写入 — L28-31 localStorage.setItem df-ai-gen/df-ai-text → L37-38 清 state.streaming/generatingConvId,顺序正确)/ state 独立性(主/分离窗口独立 webview JS context,resumeInDetached:87-119 靠 localStorage df-ai-gen/df-ai-text 重建 streaming L116-117,反证清主窗口 state 不影响分离窗口生成态)/ watchdog 兜底(stopListener clearStreamWatchdog 已由 CR-260615-24 兜底,本次仅清 state 视觉残留,注释:32-36 说明链路)/ reattach/closeDetachedWindow 清快照(L82-83/L125-126)。 +- **待修项回流 todo**: **无**(全件 PASS)。 + +--- + +### CR-260616-26 batch53 F-05 去重根治(audit.rs High risk 审批前缓存命中去重) — ✅ 已审(PASS) + +- **审查时间**: 2026-06-16(独立审查 agent · 亲跑 cargo + vue-tsc 双 EXIT 0 **附 log 末尾证据** + grep/read 核验全件 + 边界核验) +- **总体结论**: ✅ **PASS** — 🔴0 🟡0 ⚪3 +- **亲跑 `cargo check --workspace` + `npx vue-tsc --noEmit`** ✅ — 双 EXIT 0(cargo Finished 0.68s + vue-tsc log 空;5 warning 全既存 dead_code 非本批)。**无中间态误报**(附 log 末尾)。 +- **逐件核验(全 PASS)**:find_cached_high_risk_result(audit.rs:400-458 反向扫 `msgs.iter().rev()` + tool_name 过滤 :423 + canonical_args_key 规范化比较 :430 + 旧 id 反查 tool_result :444-456)+ High 分支查缓存命中回传(:536-553 guard High → push tool_result 新 tool_call_id :544 + emit AiToolCallCompleted :545-549 + 审计 completed/auto_dedup :551 + continue :552 跳过 pending insert)+ 仅 High 去重(:534 Medium|High 共分支 + :536 内嵌 High guard,Med 走原审批 :555-573)+ pending 占位不命中(:452 占位串「需要用户审批,等待确认」return None,与 push :564 全角逗号一致)+ args 规范化(sort_object_keys :462-492 sort_by 字典序 + 递归值/Array + to_string 紧凑稳定)+ 不同命令/参数不命中(timeout 重发可重试)+ tool_call_id 来源修正(stream_recv.rs:181 draft.id 来自 tc_delta.id 非 stream_recv UUID)+ 零越界(纯 audit.rs 3 函数 + High 分支,未碰 batch42/44/51)。 +- **关键边界核验**:二次循环自吞(第一次重试命中缓存 push tool_result → 第二次重试 find_cached 反扫命中第一次 tool_call 又跳过审批)= **期望行为**(每次重试跳过审批断循环,非 bug)。 +- **⚪ 观察项(全 low)**:OBS1(canonical_args_key to_string unwrap_or_default 理论序列化失败空串误等价,实际 JSON 不会失败)/ OBS2(空参数同名 High 工具误命中,当前工具集无)/ OBS3(去重命中 tool_result 下轮被命中 = 期望行为断循环)。 +- **待修项回流 todo**: **无**(全件 PASS,3 OBS 全 low)。完整 agentic loop 通用去重(search_files 重复卡片 + 滑动窗口)③类待用户,本批仅 run_command 循环核心。 + +--- + +### CR-260616-25 batch52 UX-10 @实体引用全栈(@ popover + prompt 任务段注入) — ✅ 已审(PASS) + +- **审查时间**: 2026-06-16(独立审查 agent · 亲跑 cargo + vue-tsc 双 EXIT 0 **附 log 末尾证据** + grep/read 核验全件) +- **总体结论**: ✅ **PASS** — 🔴0 🟡0 ⚪3 +- **亲跑 `cargo check --workspace` + `npx vue-tsc --noEmit`** ✅ — 双真实 EXIT 0(cargo Finished 0.72s + vue-tsc log 空 0 error;5 warning 全既存 dead_code 非本批)。**无中间态误报**(附 log 末尾,呼应 CR-23-1 教训防 E0594 类瞬态漂移)。 +- **逐件核验(全 PASS)**:@ popover 复用 `.ai-skill-popover`(AiChat.vue:563)+ 联想状态三件(mentionOpen/Index/Start :992/993/995 独立于 skill)+ detectMentionTrigger 邮箱排除(:927-953 prev 非空白 break)+ selectMention `[类型: 名]` 插入(:1057-1079)+ mentionItems 项目+任务各 slice(0,20)(:1009-1037)+ mentionGroups 分组(:1046-1049)+ mentionTotal 空/无匹配区分(:1040-1043)+ **键盘导航与技能联想互斥**(:902-920 watch inputText skill 块优先强制关 mention + selectSkill/selectMention 互清,skillOpen 与 mentionOpen 不可同时 true)+ Enter 不冲突(:1433-1456 popover 开 selectMention preventDefault 覆盖发送,关 fallthrough handleSend)+ prompt.rs:117-126 任务段(state.tasks.list_active take(20) + 注入 title/status/description 镜像项目段范式 :108-115)+ 签名零变化(build_system_prompt :102 4 调用方 commands.rs:69/161/391 + agentic.rs:478 零改)+ 降级合理(prompt 走后端 state repo :119 非前端 mentionEntities 透传,避碰 batch45/48/49/51 领地)+ 实体 id 未透传(仅 `[类型:名]` 文本,重名任务风险低已知降级)+ i18n 5 key 双语(aiChat.ts:76-80)。 +- **⚪ 观察项(全 low)**:OBS1(mentionHint 死键定义无 .vue 消费,建议删或激活)/ OBS2(mentionQuery :1003-1006 slice 到串尾非光标前,光标中移时偏差,常规用法无影响)/ OBS3(desc 仅 popover 展示不进 prompt,prompt 任务段用 tk.description 摘要列间接影响 LLM,语义链路清晰)。 +- **待修项回流 todo**: **无**(全件 PASS,3 OBS 全 low)。 + +--- + +### CR-260616-24 batch51 UX-09 编辑已发送消息并重新生成全栈(messages JSON status + ai_chat_edit IPC + AiChat 编辑 UI) — ✅ 已审(PASS) + +- **审查时间**: 2026-06-16(独立审查 agent · 亲跑 cargo + vue-tsc 双 EXIT 0 + grep/read 核验全件 + **CR-23-1 独立核验**) +- **总体结论**: ✅ **PASS** — 🔴0 🟡0 ⚪4 +- **亲跑 `cargo check --workspace` + `npx vue-tsc --noEmit`** ✅ — 双 EXIT 0(cargo Finished 0.84s,5 warning 全既存 dead_code 非本批)。 +- **🔴 CR-23-1 独立核验:不成立** ✅ — `context.rs:478-495` 当前是 **for 循环**(L488 `for t in ...iter_mut()` + L490 `t.message.status=Some("truncated")` + L491 `count += 1;` 计数非 filter 闭包体),workspace cargo check 真实 EXIT 0(全量非分 crate)。原 CR-23 agent 报 E0594 是**中间态误报**(batch51 最终 for 循环规避双层引用,呼应 CR-21/18/16 瞬态漂移)。**主代理声明属实,CR-23-1 标无效**。 +- **逐件核验(全 PASS)**:provider.rs:54-55 ChatMessage `status: Option`(serde default+skip_serializing_if 向前兼容 None=active 无 V17 迁移)+ 5 构造函数补 None(:60/63/66/69/72)+ is_active()(:76-77)+ sanitize_messages step0 过滤 truncated(context.rs:287-296)+ replace_last_active_user_content(:501-512 rposition+token 重估)+ truncate_after_user_message(:478-495 for 循环)+ ai_chat_edit IPC(commands.rs:351-445 复用 ai_regenerate spawn 模式:generating 拦截:363+活跃一致:367+replace 末条 user:371-377+truncate 其后:379-382+save:418+spawn:428-442)+ 只末条 active user 可编辑(replace rposition + Err)+ title.rs:51 + lib.rs:108 + 前端 api/ai.ts:33 editMessage + useAiSend.ts:212-270(splice 截断+改 content+空气泡占位+IPC+失败回滚)+ useAiConversations.ts:72 filter truncated + AiChat.vue:329-337 编辑按钮 hover(isLastUser && !streaming)+ isLastUser/startEdit/cancelEdit(:1028-1059)+ handleSend 编辑分支(:1267-1285)+ ESC 取消(:1211-1215)+ 切换取消(:883-885)+ i18n 4 key 双语(aiChat.ts:100-103)。 +- **⚪ 观察项**(全 low):OBS1(导出 batch46 含 truncated 软删历史,markdown/txt 视觉见旧回复段,完整审计语义取舍)/ OBS2(truncate_after_user_message/replace_last_active_user_content 无单测,token 计数同步易错建议补 case)/ OBS3(useAiSend editMessage convId 空回滚不彻底,极端场景后端未触发 IPC)/ OBS4(editMessage 无 generating 前端预检,DOM 隐藏+后端拦截双兜底无并发风险)。 +- **待修项回流 todo**: **无**(CR-23-1 标无效,4 OBS 全 low 非阻断)。 + +--- + +### CR-260616-23 batch50 审查 med 修复聚合(CR-15-1 kill_on_drop + CR-08-O1 blocked 排除 + CR-10-A/B 停下报告) — ⚠ 已审(ISSUES) + +- **审查时间**: 2026-06-16(独立审查 agent · 亲跑 cargo check EXIT 101(撞同攒批 CR-23-1)+ vue-tsc EXIT 0 + grep/read 核验四件) +- **总体结论**: ⚠ **ISSUES** — 🔴1 🟡0 ⚪1(三件声明项全 PASS,但同攒批工作区 context.rs:491 编译失败阻断整批) +- **亲跑 `cargo check --workspace`** ⚠ **EXIT 101**(df-ai 编译失败,CR-23-1,非三件范围)/ `npx vue-tsc --noEmit` ✅ EXIT 0。 +- **三件声明项核验(全 PASS)**: + - **CR-15-1 kill_on_drop** ✅ `shell.rs:85` `.kill_on_drop(true)` match 块外统一调用,三类型(PowerShell:62/Cmd:68/Sh:74)全覆盖,注释:81-84 配合 tokio::time::timeout:96-101 防孤儿;正常完成路径 cmd.output() 已读完 output 自然 drop 无害 — 不破坏 cmd.output() 成立。 + - **CR-08-O1 blocked 排除** ✅ `TaskDetail.vue:265-274` `WF_EXCLUDED_FROM={'blocked'}` :265 + wfAdvanceAction computed :269 `if has return null`,DOM :71 v-if 不渲染。**不动 ADVANCE_MAP**(:219-246 五态原推进项全保留),其他态不受影响。 + - **CR-10-B 否决** ✅ **同意主代理「不成立,审查锚定漂移」**:独立 Read commands.rs:37-110,L94 save_conversation 锁外(L46-65 锁块 L65 关闭),L105 spawn 在 save 后,时序正确。L56/59 是 pop 失败防御性错误处理(复位 generating + 报错)非 save/emit 时序。**原 CR-10-B 把 pop-失败-错误分支误读为时序冲突,审查锚定漂移坐实**。 + - **CR-10-A 降级 P2** ✅ 三入口(ai_regenerate:37/ai_chat_send:128/ai_chat_force_send:453)统一「锁内变更+锁外 spawn」,兜底充分(generating 占用 busy 拦截 + stop_flag 复位 + force_send:460-468 复位三件套 + ai_chat_stop:520-536 3s 兜底任务防 panic/异常退出卡死),接受现状合理。 +- **🔴 CR-23-1(high,编译失败,同攒批非三件范围)**:`crates/df-ai/src/context.rs:491` E0594 `cannot assign to t.message.status, which is behind a & reference`。`truncate_after_user_message` 的 `self.messages[i+1..].iter_mut().filter(|t| {... t.message.status=...})` filter 闭包参数 t 是 `&&mut TrackedMessage`(双层引用)不能写字段。git stash HEAD EXIT 0 / pop 工作区 EXIT 101 → UX-09 新增代码引入。**阻断整个 df-ai crate 构建,下游 commands.rs/agentic.rs 全 fail**。修法:filter 改 for 循环 + 计数,或闭包内 `let t=&mut **t;` 解引用。 +- **⚪ 观察项**:OBS1(agent 自报 cargo EXIT 0 与实际 EXIT 101 不符,可能跑 HEAD 或分 crate,结论可信度打折,建议后续 agent 自报附 log 末尾证据)/ OBS2(CR-23-1 非三件范围但同攒批,合并前必先修否则整批构建失败)。 +- **待修项回流 todo**: **CR-23-1**(high,context.rs:491 编译失败,改 for 循环或解引用,阻断 df-ai 构建 — **紧急,主会话合并前必修**)。 + +--- + +### CR-260616-22 batch49 UX-17 对话置顶全栈(pinned 字段+迁移+IPC+排序+图钉按钮) — ✅ 已审(PASS-WATCH) + +- **审查时间**: 2026-06-16(独立审查 agent · 亲跑 cargo check + cargo test df-storage + vue-tsc 三 EXIT 0 + grep/read 核验 14 件) +- **总体结论**: ⚠ **PASS-WATCH** — 🔴0 🟡1 ⚪3(CR-22-1 med 已置顶图钉非 hover 不可见) +- **亲跑 `cargo check --workspace` + `cargo test -p df-storage` + `npx vue-tsc --noEmit`** ✅ — 三 EXIT 0(cargo 9 warning 全既存 dead_code;df-storage 22 单元 + 11 集成 passed;vue-tsc 0 error)。 +- **独立核验**(14 件):models.rs:163 `pub pinned: bool`(非 Option 必填,list 摘要手写 json! snake_case 对齐)/ crud.rs:1141-1148 INSERT 含 pinned(?8)+ :1153-1160 UPDATE 含 pinned(?7)params 对齐 / :1050 ai_conversation_from_row 读 pinned i32→bool / :1657-1672 set_pinned 仅 SET pinned 不动 updated_at(纯元数据)/ migrations.rs:285-296 V16 column_exists 守卫幂等 + ALTER ADD COLUMN pinned INTEGER NOT NULL DEFAULT 0(老库非 NULL)+ :53 注册 (16, migrate_v16)/ commands.rs:849-859 ai_conversation_set_pinned IPC / :746 list 摘要 json! 加 pinned / lib.rs:128 注册 / **conversation.rs:141 pinned: false(proactive-bug-fix 坐实:models.rs:163 非 Option 必填,insert 不补编译断 E0063;update :108 从库 get rec.pinned 回写不丢失)** / AiChat.vue 排序 :1054(groupedActive)+ :1069(archivedConvs)`Number(b.pinned?1:0)-Number(a.pinned?1:0)||Number(b.updated_at)-Number(a.updated_at)` pinned DESC→updated_at DESC + V8 stable / :53/120/162 三处图钉按钮 toggle / i18n zh:9-10/en:9-10 双语 / useAiConversations.ts:174-178 setPinnedConversation IPC+本地同步 / api/ai.ts:156-157 + types.ts:246。 +- **🟡 CR-22-1(med,UX 缺陷)**:AiChat.vue:1872-1873 `.ai-conv-item-actions { display:none }` + :1878 `:hover` 才 `display:flex` → 所有操作按钮(含图钉)默认隐藏,:1903 `--pinned` 仅改 color 未覆盖 display → **已置顶对话非 hover 态图钉不可见**,「常驻强调色一眼识别」UX 意图落空。功能(toggle/排序)正常。修法:`.ai-conv-item-act--pinned` 补 `display:flex`。登记自相矛盾(hover 浮出 toggle 与常驻强调色无法同时满足),需产品定调。 +- **⚪ 观察项**:OBS1(filteredConversations 搜索态未加 pinned 排序,置顶不前置,登记自认降级可接受)/ OBS2(V16 无独立测试靠 column_exists 探测,项目一贯模式)/ OBS3(update_full:1151 读库 rec.pinned 回写不丢失,未来绕过 get 路径风险当前无)。 +- **待修项回流 todo**: **CR-22-1**(med,AiChat.vue:1872-1903 已置顶图钉非 hover 不可见,「常驻强调色」UX 未达成,补 display 规则或调 actions 可见性策略)。 + +--- + +### CR-260616-21 batch48 UX-18 对话导出前端(exportConversation 封装 + 3 处导出按钮 + Blob 下载) — ✅ 已审(PASS) + +- **审查时间**: 2026-06-16(独立审查 agent · 亲跑 vue-tsc + cargo check 双 EXIT 0 + grep/read 核验 12 件) +- **总体结论**: ✅ **PASS** — 🔴0 🟡0 ⚪3 +- **亲跑 `npx vue-tsc --noEmit` + `cargo check --workspace`** ✅ — 双 EXIT 0。**vue-tsc EXIT 0 确认 batch48 完成后 `toggleExportMenu/exportingId/exportOpenId/exportConversation` 均已定义,CR-15/16 agent 早报的 12 TS2339 瞬态已消除**(cargo 5 warning 全既存 dead_code NodeExecutionRepo/ReleaseRepo 非本批,本批纯前端)。 +- **独立核验**(12 件):api/ai.ts:165-167 exportConversation 封装(camelCase 仿 continueLoop/stopLoop,签名 `(conversationId, format): Promise`,invoke `ai_conversation_export` snake→camel 对齐 commands.rs:872)/ AiChat.vue 3 处导出按钮 DOM(搜索态 :59-68 / 活跃态 :126-135 / 归档态 :168-177,每处 pin→archive→export→delete 顺序,@click.stop 阻冒泡,:disabled=exportingId===conv.id 防重复)/ `:last-child` 红色态保留(:1898-1901 delete 是末子 export 倒数第二,精确命中删除按钮不被破坏)/ toggleExportMenu/exportOpenId 单展开(:912-917 三元切换同时一开)/ Blob 下载 + revokeObjectURL(:941-950 setTimeout(0) 下轮事件循环释放防泄漏,mime 三格式精确 text/markdown|application/json|text/plain)/ sanitizeFilename 安全(:920-927 空 trim→fallback conv-id + replace 折叠 Win/Unix 路径分隔符+非法+控制字符 + slice(0,60) 限长 + 全清空降级)/ exportingId 防重复(:913/931-932/956 入口 guard+set+finally 三段)/ onExportOutsideClick + 生命周期(:961-966 早返回+closest 防误关 + :1381/1317 add/remove 对称)/ IPC catch 兜底(:951-954 e.message||String(e)+toast 不崩溃)/ i18n 5 key 双语(:112-116 exportFailed 含 {msg})/ 消费 batch46 IPC(commands.rs:872 + lib.rs:129)/ 零越界(batch45 continueLoop/stopLoop :37/45 + batch43 搜索框 :23-34 + batch47 确认 handler :819-841 独立区未改)。 +- **⚪ 观察项**:OBS1(revokeObjectURL setTimeout(0) 下载时机,社区惯例无可见问题)/ OBS2(菜单无 Esc 关闭,onGlobalKeydown Esc 已消费他途,UX 小瑕疵)/ OBS3(filename 无时间戳,同名浏览器自动加后缀,设计取舍)。 +- **待修项回流 todo**: **无**(12 件全过,双 check EXIT 0,零越界)。 + +--- + +### CR-260616-15 batch42 F-04 run_command 超时根治(tool_registry.rs 超时标注 + 常量提取) — ✅ 已审(PASS-WATCH) + +- **审查时间**: 2026-06-16(独立审查 agent · 亲跑 cargo EXIT 0 + vue-tsc EXIT 2 非本批 + grep/read 核验 8 件) +- **总体结论**: ⚠ **PASS-WATCH** — 🔴0 🟡1 ⚪2(CR-15-1 med shell.rs kill_on_drop 缺失) +- **亲跑 `cargo check --workspace`** ✅ — EXIT 0(本批纯 Rust tool_registry + shell)。**`npx vue-tsc --noEmit`** ⚠ EXIT 2(12 errors 全在 AiChat.vue 导出功能 toggleExportMenu/exportingId/exportOpenId/exportConversation,系并发 batch48 未完成实现,与本批 F-04 Rust 零关联)。 +- **独立核验**(8 件):tool_registry.rs:24 常量 `DEFAULT_RUN_COMMAND_TIMEOUT_SECS=60` / :495 unwrap_or 用常量单一使用点 / :510-520 map_err 拦超时改写文案(contains 匹配 shell.rs:95 产出串前缀一致)/ shell.rs:89-95 超时根因点(`tokio::time::timeout` 包 `cmd.output()`)/ commands.rs:251-256 High 审批路径 `e.to_string()` 透传 / audit.rs:450 Low/并行路径同源(经 ai_tools.execute 分发无遗漏)/ commands.rs:269 replace_tool_result_content 断重试链路(LLM 收改写文案不再触发新 tool_call_id 重审批)/ tool_registry.rs:472 run_command 单注册。 +- **🟡 CR-15-1(med,真 bug)**:`crates/df-execute/src/shell.rs:60-79` 构造 `tokio::process::Command` **缺 `.kill_on_drop(true)`**(tokio 1.52.3)→ `tokio::time::timeout` drop future 后 tokio **不主动 kill 子进程**,子进程变**孤儿继续后台跑**,但 `tool_registry.rs:514` 文案称「进程已终止」**名不副实**。长 hang 命令(大构建/死循环)超时后仍耗资源。治本应 shell.rs:60-79 构造 cmd 后加 `.kill_on_drop(true)`。根因层缺陷(非本批 tool_registry 标注层引入),但本批文案依赖该假设。 +- **⚪ 观察项**:OBS1(tool_registry.rs:473 描述 + :494 注释硬编码「默认 60 秒」字面量未引用 const,改常量需手改两处易漏)/ OBS2(const :24 模块私有,shell.rs 未共享)。 +- **待修项回流 todo**: **CR-15-1**(med,`crates/df-execute/src/shell.rs:60-79` 缺 `.kill_on_drop(true)`,孤儿进程 + 文案「进程已终止」语义不实)。 + +### CR-260616-16 batch43 UX-06 对话搜索 UI 接线(AiChat.vue 侧栏) — ✅ 已审(PASS) + +- **审查时间**: 2026-06-16(独立审查 agent · 亲跑 cargo EXIT 0 + vue-tsc 首跑 EXIT 2 缓存陈旧清缓存重跑 EXIT 0 + grep/read 核验 8 件) +- **总体结论**: ✅ **PASS** — 🔴0 🟡0 ⚪3 +- **亲跑 `cargo check --workspace`** ✅ — EXIT 0(7 warning 全既存 dead_code)。**`npx vue-tsc --noEmit`** 首跑 EXIT 2(6 TS2339 全在 AiChat.vue:57-63 导出功能 toggleExportMenu 等,属并存 batch48 导出 UI 非本批)→ 清增量缓存重跑 **EXIT 0**,确认缓存陈旧 quirk 非真实错。 +- **独立核验**(8 件):搜索框 DOM(AiChat.vue:23-34 .ai-conv-search + v-model + kbd Ctrl+K)/ Ctrl+K 绑定(:996-1003 preventDefault + 侧栏未开 toggleSidebar + nextTick focus,顺序 Ctrl+L→K→B→Esc 无重复)/ keydown listener 生命周期对称(:1273 注册 / :1211 移除)/ searchInputRef(:847)/ 平铺渲染(:36-74 template v-if searchResults + :key 's+' 防键冲突 + 空态 searchEmpty + v-else 原分组逐字保留 editingConvId 双击改名零回归)/ searchResults computed(:974-976 显式 .value 解包 ComputedRef 坑已修)/ 消费 batch41 store 契约零改(CR-14 已审 store 侧)/ i18n 3 key 双语(:13-15)。Ctrl+K 冲突核验:grep 无既有 Ctrl+K,Tauri WebView2 无地址栏。 +- **⚪ 观察项**:OBS1(Ctrl+K 输入焦点抢占,打字时按 K 切走光标,UX 选择非破坏)/ OBS2(kbd 与 title 复用同一 i18n key,tooltip 语义弱)/ OBS3(范围声明漂移:搜索态平铺项被 batch48 加 export 菜单,审查时点与现状漂移,归 batch48 审)。 +- **待修项回流 todo**: **无**(vue-tsc EXIT 2 缓存陈旧非真实错)。 + +### CR-260616-17 batch44 F-03 达 max 后端(agentic 暂停态 + AiMaxRoundsReached 事件 + continue/stop IPC) — ✅ 已审(PASS) + +- **审查时间**: 2026-06-16(独立审查 agent · 亲跑 cargo + vue-tsc 双 EXIT 0 + grep/read 核验 6 件 + 交叉核验) +- **总体结论**: ✅ **PASS** — 🔴0 🟡0 ⚪2 +- **亲跑 `cargo check --workspace` + `npx vue-tsc --noEmit`** ✅ — 双 EXIT 0(cargo 7 warning 全既存 dead_code)。 +- **独立核验**(6 件 + 交叉):agentic.rs:322-347 达上限改(save 先行 :340 + disarm :342 + emit AiMaxRoundsReached :343-345 + return :346 保 generating 暂停态,对齐审批等待模式 L313-316)/ mod.rs:117-122 variant + `#[serde(tag="type")]` / commands.rs:446-465 ai_continue_loop(generating + active 双校验 + 复位 stop_flag + try_continue)/ commands.rs:476-502 ai_stop_loop(双校验 + 置 stop_flag 双保险 + generating=false + emit AiCompleted 不重复 save)/ lib.rs:112-113 注册 / 决策 a(loop 局部 iteration agentic.rs:159,续跑 try_continue 重 spawn 天然重计)。交叉:try_continue guard(should_continue)、ai_chat_stop 暂停态交互(走 3s 兜底正确非最优)、ai_chat_force_send(复位 generating 清审批走新 loop)、stop_flag 双保险(continue 复位 :460 + stop 置 :490)。 +- **⚪ 观察项**:OBS1(ai_chat_stop 暂停态落流式分支走 3s 兜底非最优,前端延迟,建议审批分支前加暂停态判定即时清理,既存 stop 路径未适配新状态)/ OBS2(无硬上限产品风险,前端操作卡建议多次继续后二次确认)。 +- **待修项回流 todo**: **无**。 + +### CR-260616-18 batch45 F-03 达 max 前端操作卡(pendingMaxRounds + 续/停按钮) — ✅ 已审(PASS) + +- **审查时间**: 2026-06-16(独立审查 agent · 亲跑 cargo + vue-tsc 双 EXIT 0 + grep/read 核验 6 件 + 死锁风险核验) +- **总体结论**: ✅ **PASS** — 🔴0 🟡0 ⚪2 +- **亲跑 `cargo check --workspace` + `npx vue-tsc --noEmit`** ✅ — 双 EXIT 0(cargo 5 warning 全既存 dead_code)。 +- **独立核验**(6 件 + 死锁):types.ts:210-211 AiMaxRoundsReached variant 联合 + serde 对齐 / useAiEvents.ts:93 pendingMaxRounds 模块级 ref 独立事件源 + :200-204 dispatch + :286/320 AiCompleted/AiError 双清 + :41 NO_RESET_WATCHDOG 含 / api/ai.ts:37-38/45-46 continueLoop/stopLoop 仿 ai_approve / AiChat.vue:468-483 操作卡 + :1625-1626 showMaxRoundsCard 双守卫 + :1632-1658 handler IPC 失败回滚 acting + :1660-1663 watch 复位 + :2384-2395 样式 / i18n 6 key 双语(:102-107)/ 后端契约对齐(agentic.rs:343 + commands.rs:446/476 + lib.rs:112-113)。死锁核验:try_continue 三路径(should_continue=false 补 AiCompleted / provider-Err emit AiError / 正常 spawn run_agentic_loop)全覆盖,所有出口触发清 pendingMaxRounds + watch 复位 acting,无死锁。 +- **⚪ 观察项**:OBS1(无 max 暂停态查询 IPC,跨重启恢复缺失,设计取舍 force_send 可复位)/ OBS2(continue/stop IPC 命名风格微异:ai_approve 动词 vs continue/stop 动词+名词)。 +- **待修项回流 todo**: **无**。 + +### CR-260616-19 batch46 UX-18 对话导出后端(ai_conversation_export IPC) — ✅ 已审(PASS) + +- **审查时间**: 2026-06-16(独立审查 agent · 亲跑 cargo check EXIT 0 + grep/read 核验 11 件) +- **总体结论**: ✅ **PASS** — 🔴0 🟡0 ⚪2 +- **亲跑 `cargo check --workspace`** ✅ — EXIT 0(workspace 干净,0 warning 0 error)。 +- **独立核验**(11 件):conversation.rs:19/24/48 确无导出 IPC(只 TokenAccumulator + save_conversation)/ IPC 位置 commands.rs:853-913 / format 双保险防 panic(:860-863 `matches!` 前置 Err + :908-909 `_ => Err` 末尾)/ 落库 messages serde_json::from_str 零内存读(:866-871 与 switch 一致)/ markdown 四臂 match 真穷举无 `_` 通配(:878-883 MessageRole 4 variant 编译器强制)/ markdown content 原样三反引号不转义(:886)/ json to_string_pretty(:892-893)/ txt 仅读 m.content 略 tool_calls/tool_call_id/model(:898-904)/ 空对话 markdown/txt 空串 json `"[]"`(:889/906/892)/ lib.rs:128 注册(与 batch44 L112-113 不同行)/ 零越界(与 batch44 continue-stop 不同段)。 +- **⚪ 观察项**:OBS1(json 空对话返 `"[]"` 非 `""`,语义合理前端 JSON.parse 正常)/ OBS2(markdown 顺序依赖落库保序约定)。 +- **待修项回流 todo**: **无**。 + +### CR-260616-20 batch47 UX-08 新建对话确认弹窗 — ✅ 已审(PASS) + +- **审查时间**: 2026-06-16(独立审查 agent · 亲跑 vue-tsc + cargo check 双 EXIT 0 + grep/read 核验 10 件) +- **总体结论**: ✅ **PASS** — 🔴0 🟡0 ⚪0 +- **亲跑 `npx vue-tsc --noEmit` + `cargo check --workspace`** ✅ — 双 EXIT 0(cargo 5 warning 全既存 dead_code NodeExecutionRepo 非本批)。 +- **独立核验**(10 件):confirmNewConversation handler(AiChat.vue:832-837 非生成态零行为变化直接 newConversation)/ isViewingGenerating 双条件(:965-967 streaming && generatingConvId===activeConversationId,切走后台不弹)/ store 三字段(ai.ts:47/51/59)/ 两处按钮改造(:18 侧栏 + :206 聊天头,登记写 L176 实际 L206 行号偏差)/ ConfirmDialog 复用(:812 useConfirm Promise + 并发覆盖防永挂 useConfirm.ts:39)/ store.newConversation 软复位(useAiConversations.ts:31-41 B-260615-10 零改)/ commands.rs 零改(git diff 仅 batch46 ai_conversation_export)/ i18n zh/en(:84)/ Esc 优先级协同(:1099-1105 confirmState.visible 时 Esc 视取消 resolve Promise 不卡死)/ 零越界(batch43/45/36/47 独立区块)。 +- **待修项回流 todo**: **无**(W-1 low 范围偏差 L176→L206 代码正确)。 + +--- + +### CR-260616-14 batch41 Sidebar+ai store 域(UX-06 store 契约 + B-45 内存泄漏核查) — ✅ 已审(PASS) + +- **审查时间**: 2026-06-16(独立审查 agent · 亲跑 cargo check + vue-tsc 双 EXIT 0 + grep/read 核验 7 件) +- **总体结论**: ✅ **PASS** — 🔴0 🟡0 ⚪3(filteredConversations .slice() 冗余 + sort 稳定性依赖 + 范围声明与实际不符,均 low 非阻塞) +- **亲跑 `cargo check --workspace` + `npx vue-tsc --noEmit`** ✅ — 双 EXIT 0(cargo 4 warning 全既存 dead_code 非本批;vue-tsc 零 TS error,AiChat.vue:940 `computed` 类型精确匹配)。 +- **独立核验**(7 件): + - **filteredConversations 契约**(stores/ai.ts:118-125)✅ — 空 query trim→null 信号(消费方走原分组)/ 非空 `filter((c.title ?? '').toLowerCase().includes(q))` case-insensitive 全量(活跃+归档)/ updated_at DESC(b 在前为正,正确)。 + - **类型契约**(api/types.ts:237-247)✅ AiConversationSummary 含 title?/archived/updated_at 类型安全。 + - **useAiStore 返回值**(stores/ai.ts:136)✅ filteredConversations 已展开。 + - **i18n 3 key**(zh/en aiChat.ts:13-15)✅ searchPlaceholder/searchEmpty/searchShortcutHint 双语对称。 + - **UI 接线已落地**(AiChat.vue:22-64/939-941)✅ — v-model 双绑 + 搜索态平铺 + 空态提示 + computed 解包 store.filteredConversations.value;搜索项含 archive/delete 操作与原分组对称(登记称「留 batch43」,实际 batch41 已完整接线)。 + - **B-45 stopListener**(useAiEvents.ts:344-354)✅ — 三 listener unlisten + 置 null + clearStreamWatchdog + clearAllToolSlowTimers + clearAllApprovalTimers 五件全清。 + - **B-45 AiChat onBeforeUnmount**(AiChat.vue:1172-1191)✅ — removeEventListener keydown + cancelAnimationFrame + 拖拽 listener + body class + store.stopListener() + _unlistenToolSlow + _toastTimer/_titleFlashTimer/_providerBarTimer 八件全清。 + - **MESSAGE_CAP=200**(stores/ai.ts:36/94-105)✅ — watch 拦 push 式增长(>CAP)splice 最早条;切对话整体替换放行。 +- **⚪ 观察项**(均 low): + 1. OBS1(冗余 slice):stores/ai.ts:123 `.slice()` 紧跟 `.filter()`,filter 已返新数组,slice 冗余拷贝。无功能影响,建议删。 + 2. OBS2(sort 稳定性):updated_at 相等返回 0 依赖 V8 ES2019+ stable sort。会话同秒 updated_at 概率低,无可见问题。 + 3. OBS3(范围声明不符):登记称「UI 接线留 batch43」,实际 batch41 已完整接线。建议主会话核对 batch43 是否剩 i18n 微调。 +- **待修项回流 todo**: **无**(三观察项均 low 非阻断)。 + +### CR-260616-13 batch40 CR-06 审查观察项测试补强(②-3/②-4 回调测试 + ignored 确认) — ✅ 已审(PASS) + +- **审查时间**: 2026-06-16(独立审查 agent · 亲跑 cargo test + grep/read 核验) +- **总体结论**: ✅ **PASS** — 🔴0 🟡1⚪1(in_progress 失败→todo 状态机拦截产品决策点 med 非阻断 + 测试数登记 +9 vs +10 low) +- **亲跑 `cargo test -p df-nodes -p df-workflow`** ✅ — df-nodes **67 passed + 1 ignored**(57→67,+10 测试)/ df-workflow 23 passed,双 EXIT 0。 +- **独立核验**: + - **ignored 确认**(ai_node.rs:298-300)✅ — glm_live_complete 真调 GLM 集成测试,合理 #[ignore] 保留。 + - **9 新测试覆盖** ✅ — 3 templates callback(task_workflow_templates:callback 边界 + reject 可达性)+ 6 advance callback(task_advance_node:regression_target 镜像契约 + 三段 completed→target/failed→regression/非终态跳过 + 落库验证);镜像契约机制防 workflow.rs:44-51 私有函数回归漂移。 +- **🟡 观察项 O1(med,产品决策点,非本批引入)**:**in_progress 模板失败→regression_target 给 todo,但状态机 `backward_to_todo_rejected` 全拒**(task_state_machine.rs:188-193)→ 回调 advance_task_atomic 被 InvalidState 拦截(task_advance_node.rs:72-77),**任务原地保留 in_progress**。三选项待定:a 接受现状补 UI 提示 / b 改 `regression_target("in_progress")=None` 对齐状态机 / c 放开 todo 回退(破坏闸门)。本批测试显式锁定此行为(防回归)。 +- **⚪ 观察项 O2(low)**:测试数登记 +9 vs 实测 +10(差 1 来自既有测试重构,非问题)。 +- **待修项回流 todo**: **CR-13-O1 产品决策点**(非代码 bug,回流 todo 作产品决策 a/b/c)。 + +### CR-260616-12 batch38 settings 域(F-01 agentic max 可配) — ✅ 已审(PASS) + +- **审查时间**: 2026-06-16(独立审查 agent · 亲跑 cargo check + vue-tsc 双 EXIT 0 + grep/read 核验 15 件) +- **总体结论**: ✅ **PASS** — 🔴0 🟡0 ⚪3(前端默认值硬编码 + clamp 回写闪现 + batch45 演进关系,均 low 非阻塞) +- **亲跑 `cargo check --workspace` + `npx vue-tsc --noEmit`** ✅ — 双 EXIT 0(cargo 7 warning 全既存 dead_code;并发 batch45 改动共存编译通过)。注:审查中一次中间态 grep 抓到 batch45 改 mod.rs 时的 E0599,最终完整重跑 EXIT 0 确认 batch38 本身无错。 +- **独立核验**(15 件):1:1 严格复刻 llm_concurrency 范式(AppState 字段 `agent_max_iterations: Arc` + init 引用 `DEFAULT_MAX_AGENT_ITERATIONS=10`(agentic.rs:33)/ `run_agentic_loop(max_iterations)` 形参(agentic.rs:101)+ `0..max_iterations` 循环(agentic.rs:159)+ 达上限 warn+emit(agentic.rs:324-336)+ 3 调用点 ai_chat_send(commands.rs:103-106)/ai_regenerate(commands.rs:197-200)/try_continue_agent_loop(agentic.rs:477-487)load 快照透传 / `ai_set_agent_max_iterations` clamp 1-50 store(commands.rs:812-821)/ lib.rs:130 注册 / api/ai.ts:87-89 / Settings.vue input min1 max50(:275-277)+ watch 持久化 SQLite(:663-665)+ syncAgentMaxIterations Math clamp+debounce 300ms(:694-705)+ onMounted 同步防刷新(:766-775)/ onUnmounted 清 _agentIterTimer(:778-783)/ i18n 双语(settings.ts:79-80))。grep 确认无第 4 调用点遗漏。 +- **⚪ 观察项**(均 low): + 1. OBS1(前端默认值硬编码):Settings.vue:628/696 前端 `appSettings.get(..., 10)` 默认 10 与后端 const 巧合一致但非引用同一常量。范式层面观察(llm_concurrency 亦如此)。 + 2. OBS2(clamp 回写闪现):clamp 前瞬时值短暂显示后修正,视觉闪现功能无损。 + 3. OBS3(batch45 演进):lib.rs:111 注释 F-260616-03 标注 max_iterations 是 batch45 暂停态续跑前置基础,两批共存编译通过,演进链路清晰。 +- **待修项回流 todo**: **无**。 + +### CR-260616-11 batch39 ai chat 性能域(AR-8 节流评估 + B-53 snap 优化) — ✅ 已审(PASS) + +- **审查时间**: 2026-06-16(独立审查 agent · 亲跑 cargo check + vue-tsc 双 EXIT 0 + grep/read 核验 6 件) +- **总体结论**: ✅ **PASS** — 🔴0 🟡0 ⚪2(增量缓存 quirk + deep watch 每帧检测,均 low 非本批) +- **亲跑 `cargo check --workspace` + `npx vue-tsc --noEmit`** ✅ — 双 EXIT 0(cargo 5 warning 全既存 dead_code;首跑增量缓存陈旧报 AiMaxRoundsReached not found E0599,清缓存重跑 EXIT 0 确认)。 +- **独立核验**(6 件): + - **AR-8 前端滚动跟随已存在**(AiChat.vue:1110-1114 isNearBottom 80px + 234 onMessagesScroll + 412 showBackToBottom + 1139-1147 onContentChange)✅ — 完整存在,非本批新增。 + - **AR-8 后端 50ms 合批未改**(stream_recv.rs diff 纯 B-42 error_type 映射,零 batch39 改动)✅ — 负 ROI 论证成立(rAF ARC-08 吸收重 parse + heartbeat 语义冲突 + flush 复杂度)。 + - **B-53 snap 优化正确性**(AiChat.vue:1207-1210)✅ — 新 snap `{n: msgs.length, tc: msgs.map(m=>(m.toolCalls||[]).map(t=>({id,s})))}` 剔除冗余 id/role/len;body 逻辑(AiChat.vue:1218-1226)仅读 toolCalls 状态从不读被删字段 → 行为安全;流式 delta 不再触发 body 重算(toolCall 状态翻转/splice 淘汰仍触发)。 + - **B-53 短路 + deep + MESSAGE_CAP 全保留**(AiChat.vue:1213 snap===lastMsgSnapshot return + 1233 deep:true + ai.ts:36/94-105 MESSAGE_CAP=200 length watch 独立)✅。 + - **B-53 自包含**(lastMsgSnapshot 模块局部无外部消费 + collapseInactive 唯一调用点)✅。 + - **注释清晰度**(AiChat.vue:1197-1202)✅ — 与代码完全吻合无误导。 +- **⚪ 观察项**(均 low): + 1. OBS1(缓存 quirk):增量缓存陈旧致首跑 E0599,清缓存重跑过。建议 CI 走 cargo check 不带增量。 + 2. OBS2(deep watch 每帧检测):B-53 仅短路 body,deep watch 本身仍每帧遍历 messages 含 content,极长流式消息 O(n) 成本。rAF 节流已吸收,非阻塞。 +- **待修项回流 todo**: **无**(纯优化批次,零回归)。 + +### CR-260616-10 batch36 ai chat UX 域聚合(UX-02 消息操作栏 + UX-03 错误气泡操作入口) — ✅ 已审(PASS) + +- **审查时间**: 2026-06-16(独立审查 agent · 亲跑 cargo check + vue-tsc 双 EXIT 0 + 独立重跑双 check 佐证) +- **总体结论**: ✅ **PASS** — 🔴0 🟡2⚪3(ai_regenerate 并发竞态 med + commands.rs 时序倒置 med 非阻断 + 3 low) +- **亲跑 `cargo check --workspace` + `npx vue-tsc --noEmit`** ✅ — 双 EXIT 0(vue-tsc 首跑报 useAiEvents.ts 缺 import 已补后过;agent 独立重跑双 check 佐证)。 +- **独立核验**: + - **UX-02 操作栏**(AiChat.vue hover `.ai-msg-actions` 仅末条 复制+重新生成)✅。 + - **ai_regenerate IPC**(commands.rs)✅ — generating 占用校验 + pop_last_assistant_round + save + spawn run_agentic_loop 复用 + active_conversation_id 一致性。 + - **pop_last_assistant_round**(context.rs)✅ — 空 false / 末尾 user popped_any=false / 跨多轮 tool 三元组一并弹不留半截 / history_tokens saturating_sub。 + - **UX-03 错误气泡**(useAiEvents.ts AiError push 附 errorType `as AiMessage` cast B-42 零改 + AiChat.vue 错误气泡重试复用 regenerate + canOpenSettings 判 auth|provider_config)✅。 + - **只读消费 B-42**(ErrorType/AiErrorType/AiError 零改)✅。 +- **🟡 观察项(待修项,细节见 agent transcript)**: + 1. **CR-10-A(med 时序倒置)**:commands.rs:56/59 save 与 emit 时序。回流 todo。 + 2. **CR-10-B(med 竞态)**:ai_regenerate 并发竞态窗口。回流 todo。 +- **⚪ 观察项**:代码块复制按钮(v-html 子元素无法绑事件)遗留单独立项;AI 气泡内嵌复制与新操作栏复制共存最小改动;context.rs 跨 crate(df-ai)改动非 batch35/37 锁。 +- **待修项回流 todo**: **CR-10-A**(commands.rs:56/59 时序倒置 med)+ **CR-10-B**(ai_regenerate 竞态 med)。 + +### CR-260616-09 batch37 knowledge 域(F-260616-02 pending_review 归位·决策 a) — ✅ 已审(PASS) + +- **commit**: 工作区未 commit(攒批) +- **审查时间**: 2026-06-16(独立审查 agent · 亲跑 cargo check + cargo test df-storage 双 EXIT 0 + grep/read 核验) +- **总体结论**: ✅ **PASS** — 🔴0 🟡0 ⚪2(注释遗留 + list_non_archived dead_code,均 low 非阻塞) +- **亲跑 `cargo check --workspace` + `cargo test -p df-storage`** ✅ — **双 EXIT 0**;df-storage 11 passed(含 `list_by_status_orders_by_confidence_semantics` / `list_by_status_filters_other_status` / `test_knowledge_cols_matches_record` 三个 knowledge 测试)。 +- **独立核验**(5 件): + - **knowledge_list 默认收窄**(knowledge.rs:78-88)✅ — L86 `None => list_by_status("published")` 收窄纯 published;L84 `Some(s) => list_by_status(&s)` 显式透传(含 archived)向后兼容。 + - **调用方零破坏** ✅ — grep list_non_archived src-tauri/ **零 Rust 调用方**;前端 src/api/knowledge.ts:15 + stores/knowledge.ts:61-66 loadList 默认 undefined→null→后端默认 published;Knowledge.vue:275/522 loadList() 无参走默认。**无调用方依赖旧「非 archived 全部」语义**。 + - **merge_by_confidence 排序等价**(knowledge.rs:238-273)✅ — rank high=3/med=2/low=1/_=0 + created_at DESC,与 SQL(crud.rs:1363-1368 `CASE...DESC, created_at DESC`)权重+方向一致;双指针合并前 a/b 各 sort_by(cmp) 重排保证有序(L257-258);相等时 candidate 优先(L262)结果确定。 + - **向后兼容** ✅ — published 仍在 library(默认)/ archived 显式可查 `knowledge_list(status='archived')` / candidate+pending_review 仍在 inbox(knowledge_list_candidates L226-231)。 + - **i18n 文案**(zh/en knowledge.ts)✅ — inbox「待处理」/「Pending」+ 注释说明 F-260616-02 决策 a。 +- **⚪ 观察项**: + 1. **OBS1(low 注释遗留)**:Knowledge.vue:17/250 注释仍写「审核收件箱」,i18n 已改「待处理」;注释与新语义不符,建议清理(不影响功能)。 + 2. **OBS2(low dead_code)**:crud.rs:1478 `list_non_archived()` 收窄后全仓零调用方,成孤儿方法(编译器未报,宏展开 pub method 豁免);建议删除或 `#[allow(dead_code)]`。 +- **文档遗留(非代码,仅记录)**:df-knowledge 模块文档 L52/58/174-175 + 经验记录 L78 + df-storage 存储层文档 L114 多处 knowledge_list 默认语义/list_non_archived 描述过时,建议后续清理批次更新。 +- **待修项回流 todo**: **无**(代码层面 PASS;两 low 观察项 + 文档遗留均非阻塞)。 + +### CR-260616-08 batch35 TaskDetail 域聚合(①-1 工作流推进按钮 + B-41 进度内嵌 + ①-3 i18n) — ✅ 已审(PASS) + +- **commit**: 工作区未 commit(攒批) +- **审查时间**: 2026-06-16(独立审查 agent · 亲跑 cargo check + vue-tsc 双 EXIT 0 + grep/read 核验) +- **总体结论**: ✅ **PASS** — 🔴0 🟡1⚪1(blocked 态推进按钮语义偏差 med 非阻断 + lib.rs 混入非本批 low) +- **亲跑 `cargo check --workspace` + `npx vue-tsc --noEmit`** ✅ — **双 EXIT 0**(4 warning 均既有 dead_code 非本批)。 +- **独立核验**(11 件,grep + read 亲验): + - **workflow.rs 空dag选模板补丁**(L81-89)✅ — 三分支覆盖(nodes 非空→原 dag / 空+target Some→template_for 失败 Err「无对应工作流模板」 / 空+target None→原 build_dag);**与 CR-06 审的 ①-1 同代码,描述差异源于措辞非冲突**;dag shadowing 后 build_dag + dag_json 序列化落库实际拓扑(L92/L96)。 + - **②-3/②-4 回调**(workflow.rs:232-233/267-305)✅ — cb_task_id/cb_target_status clone move;completed→Some(target)/failed→regression_target/任一 None→跳过;回调失败 warn 不回滚;advance_task_atomic 签名匹配(task_advance_node.rs:43-47)。 + - **regression_target**(workflow.rs:44-51)✅ — testing→in_review/in_review→in_progress/in_progress→todo/其他→None。 + - **TaskDetail.vue 推进按钮**(L71 v-if wfAdvanceAction / L260-267 computed / L77 disabled 互含 / L288-310 handleWorkflowAdvance 空 dag+taskId+target 调 run)✅。 + - **B-41 进度内嵌 + exec_id 过滤**(L174-184 状态 / L313-314 过滤 / L411 onMounted + L419 onBeforeUnmount 生命周期对称)✅。 + - **多 listen 无冲突**(workflow.ts:37-41 独立 unlisten / store 收全量 vs TaskDetail 按 exec_id 过滤,职责隔离)✅。 + - **进度轻量近似**(L318-323 total 用已启动+已完成,前端无法预知后端模板节点数)✅。 + - **template_for 对齐 ADVANCE_MAP**(三模板 in_progress/testing/done;in_progress 态 primary=in_review 无模板不显示)✅。 + - **与手动 advance 并存**(L54-64 手动保留 / L66-91 工作流独立区块)+ 不碰 router/lib.rs listen ✅。 + - **i18n 8 key 对称**(zh/en taskDetail.ts:41-49)✅。 +- **🟡 观察项 O1(med,非阻断)**:**blocked 态推进按钮语义偏差** — blocked→in_progress primary(TaskDetail.vue:241-243)WF_SUPPORTED_TARGETS.has('in_progress')=true 显示按钮,但 template_for('in_progress') 是「todo→in_progress 从头执行」拓扑,blocked 恢复语义不该重跑 AiNode。UX 需确认(blocked 态应排除工作流推进或用恢复语义)。**非阻断**(后端回调逻辑正确,仅前端按钮显示语义)。 +- **⚪ 观察项 O2(low)**:lib.rs +3 行(ai_regenerate L107 / list_tool_executions L127-128)非 batch35 范畴,属其他批混入工作区,不影响本批正确性。 +- **待修项回流 todo**: **无**(均观察项非阻断;O1 blocked 语义建议主会话评估是否前端排除)。 + +### CR-260616-07 batch34 并行(B-42 错误码统一 + AE-08 审批历史面板) — ✅ 已审(PASS) + +- **commit**: 工作区未 commit(攒批) +- **审查时间**: 2026-06-16(独立审查 agent · 亲跑 cargo check + vue-tsc 双 EXIT 0 + grep/read 核验源码) +- **总体结论**: ✅ **PASS** — 🔴0 🟡0 ⚪2(既存死代码 warning + B-42 前端消费未改留 batch36) +- **亲跑 `cargo check --workspace` + `npx vue-tsc --noEmit`** ✅ — **双 EXIT 0**(output L68/L69);cargo check Finished 48.33s(L67);6 warning 全既存死代码(df-ai finish_reason/call_type/SessionState/session_state + devflow risk_level/releases/node_executions never read),非本批引入。 +- **独立核验**(B-42 + AE-08,grep 定位 + read 亲验源码): + - **B-42 ErrorType 枚举**(mod.rs:73-86)✅ — `#[serde(rename_all = "snake_case")]` + 5 variant(Auth/Network/Timeout/ProviderConfig/Unknown),每个文档注释说明分类标准;ProviderConfig→provider_config 与 types.ts:188 联合对齐。 + - **B-42 AiError variant**(mod.rs:107-112)✅ — `error_type: Option`,None 表示未分类(向后兼容旧 emit 点,L106 注释)。 + - **B-42 8 emit 点映射**(agentic.rs×3 + stream_recv.rs×5)✅ — Auth(agentic:128)/ ProviderConfig(agentic:452)/ Unknown(agentic:333 + stream_recv:207)/ Timeout(stream_recv:163)/ Network(stream_recv:237/278)/ None(stream_recv:308 建连失败混合源难精确归类)。映射合理。 + - **AE-08 list_recent SQL**(crud.rs:1246-1275)✅ — 专用 SELECT `ORDER BY requested_at DESC LIMIT ?1 OFFSET ?2`(L1259),绕过通用 query 宏(后者硬编码 ORDER BY created_at,本表无该列);limit.min(200) 钳制防滥用(L1253)。 + - **AE-08 list_tool_executions IPC**(audit.rs:85-113)✅ — limit.unwrap_or(50)/offset.unwrap_or(0) → list_recent → DTO map;**敏感字段截断**(arguments_brief 120 / result_brief 160,L104-105),完整原值留库。 + - **AE-08 truncate_chars**(audit.rs:71-78)✅ — `chars().count()<=max` 早返回 / 否则 `chars().take(max)` + push '…';**按 char 边界切,中文/emoji 安全**。 + - **AE-08 lib.rs 全路径注册**(lib.rs:128)✅ — `commands::ai::audit::list_tool_executions`,避碰 mod.rs glob,零行为破坏。 + - **AE-08 AuditLog.vue**(src/views/AuditLog.vue:1-311)✅ — 7 列表格 + 分页(prevPage/nextPage + hasMore=records.length===PAGE_SIZE + 空页回退 L105-108)+ 风险/状态/决策者标签映射;**类型内联**(ToolExecutionRecord interface L69-83,注释「不进 api/types.ts 避撞 B-42」)— 无 types.ts 交叉污染(关注点② ✅)。 + - **B-42 前端消费**(关注点③)✅ — grep `error_type` src/ 仅 types.ts:204 定义,**零消费逻辑**(AiChat.vue 等不读 error_type);UX-03 留 batch36 符合任务约定。 +- **⚪ 观察项**: + 1. **既存死代码 warning**(cargo check 6 个,df-ai/devflow never read)— 非本批引入,既存 lint 级;建议后续清理(SessionState/risk_level 可能阶段2 未接入死代码)。 + 2. **B-42 前端消费未改**(关注点③已知)— error_type 字段已加但前端暂不读,UX-03 留 batch36;字段先落地不阻塞,合理。 +- **待修项回流 todo**: **无**(B-42 + AE-08 全过,零缺陷;两观察项均已知/既存非回归)。 + +### CR-260616-06 batch33 阶段2 续(②-2/②-3/②-4 run_workflow task_id+回调 + ②-6 DAG模板) — ✅ 已审(PASS) + +- **commit**: 工作区未 commit(攒批,与 batch32 + 决策记录 + todo/待审查 混合) +- **审查时间**: 2026-06-16(独立审查 agent · 亲跑 cargo test 全量无 tail + grep/read 核验源码) +- **总体结论**: ✅ **PASS** — 🔴0 🟡0 ⚪2(df-nodes 1 ignored + ②-3/②-4 回调无集成测试) +- **亲跑 `cargo test -p df-workflow -p df-nodes 2>&1`(无 tail 全量)** ✅ — **exit 0**;df-nodes **58 running → 57 passed; 0 failed; 1 ignored**(output L30/L90,比 CR-05 的 53 多 4 模板测试);df-workflow **23 passed; 0 failed**(L119);无 FAILED 无 error[。**去 tail 修正 CR-05 截断问题**。 +- **独立核验**(四件 + ①-1,grep 定位 + read 亲验源码): + - **②-2 run_workflow 参数 + 前端透传**(workflow.rs:64-73 + api/workflow.ts:16-24 + stores/project/workflow.ts:23-26)✅ — 签名 `task_id: Option, target_status: Option`(L71-72)向后兼容;WorkflowRecord.task_id 填入(L104);前端 api `taskId?/targetStatus?`(L16-17)snake_case 透传(L23-24);stores runWorkflow 透传(L26)。旧调用方不传→None 零行为破坏。 + - **②-3 完成回调**(workflow.rs:267-273)✅ — `match (cb_task_id.as_ref(), cb_target_status.as_deref())` 都 Some → completed 分支 `advance_target=Some(target)`(L270)→ advance_task_atomic 推进到 target_status。 + - **②-4 失败回调退回**(workflow.rs:44-51 + 271)✅ — failed 分支 `advance_target=regression_target(target)`;映射 testing→in_review / in_review→in_progress / in_progress→todo / _(done/blocked/cancelled)→None;in_progress→todo 回起点符合「执行未达预期重排」直觉(L42-43 注释);起点 todo 无可退态→None。 + - **回调解耦语义**(workflow.rs:274-294)✅ — advance_task_atomic Err 仅 `tracing::warn!` 不回滚(工作流 status 已写库 L251,回调失败不撤销);`TaskRepo::new(&db)`(L275)复用 db Arc(clone 廉价)。 + - **②-6 DAG 模板**(task_workflow_templates.rs:1-152)✅ — `template_for(target)` match in_progress/testing/done→Some,else None(L28-35)不 panic;三模板拓扑正确(in_progress 单 ai / testing ai→human 串行 / done 单 human);testing+done 模板 human options 含「拒绝」(L67/L85)触发 ②-5 reject→Err→failed→②-4 退回闭环;config 留空由 run_workflow 全局注入+deep_merge(衔接 ④-1 L14-17);4 测试断言拓扑(unknown_none/single_ai/ai_to_human_serial 含「拒绝」断言 L141/single_human)。 + - **①-1 DagDef 来源选模板**(workflow.rs:81-89)✅ — dag.nodes 空+target Some→`template_for`(L83).ok_or_else(Err「无对应工作流模板」)(L84);空+None→原路径 build_dag 校验(L85);非空→用传入 dag(L88)。三分支零行为破坏。 +- **⚪ 观察项**: + 1. df-nodes **1 ignored**(L90,running 58 / passed 57 / ignored 1)— 登记关注点提「57 passed」未提 ignored;ignored 非 failed,建议确认哪个测试 `#[ignore]`(可能慢/集成测试留阶段2收尾)。 + 2. **②-3/②-4 回调无集成测试**(登记关注点②已知)— 已亲核 diff 逻辑正确(match 分支 + 解耦语义),但缺自动化回归保护;回调失败/退回路径仅靠代码审查覆盖。建议阶段2 收尾补集成测试(workflow 跑通→advance 验证)。 +- **待修项回流 todo**: **无**(四件+①-1 全过,零缺陷;两观察项均已知/非回归)。 + +### CR-260616-05 batch32 阶段2 首批(④-1 config 下沉 + ②-1 注册 TaskAdvanceNode + ②-5 HumanNode reject) — ✅ 已审(PASS) + +- **commit**: 工作区未 commit(攒批,与 CR-03/04 + 决策记录 + todo/待审查 混合) +- **审查时间**: 2026-06-16(独立审查 agent · 亲跑 cargo test + grep/read 核验源码,不信 agent 自报"53 passed") +- **总体结论**: ✅ **PASS** — 🔴0 🟡0 ⚪1(df-nodes 测试数字 tail 截断未单独显示,exit 0 佐证通过) +- **亲跑 `timeout 240 cargo test -p df-nodes -p df-workflow`** ✅ — **exit 0**;df-workflow **23 passed**(deep_merge_tests 6 + executor `node_config_overrides_global_in_node_context` 1 + cancelled 3 + state/conditions 13);df-nodes human_node reject 测试 exit 0 保证通过(`tail -40` 截断未单独显示数字,exit code 0 佐证)。**不信 agent 自报「53 passed」**。 +- **独立核验**(三件,grep 定位 + read 亲验源码): + - **④-1 deep_merge config 下沉**(dag.rs:169 + executor.rs:105 + registry.rs:52)✅ — + - `deep_merge(global,node)`(dag.rs:169-188):两端 Object 递归合并(同 key 节点级覆盖 L177-181 / 新 key 保留 L179);非两端 Object 节点级直接覆盖 L186(含 Null 显式空);**Null 语义=显式置空覆盖(非跳过),跳过=不写 node_configs key**(L164-165 注释)符合登记关注点。 + - `NodeContext.config`(executor.rs:105-108):`match node_configs.get(id) → Some(deep_merge(global,node_cfg)) / None(initial_config.clone())`;**缺失 key 走 None 分支 == 旧 initial_config.clone() 零行为破坏**。 + - `build_dag`(registry.rs:52):`add_node_with_config(id,node,node_def.config.clone())` 下沉 NodeDef.config。 + - **5 维度全测过**:同 key 胜(`node_overrides_global_scalar`)/ 新 key 留(`node_adds_new_key`)/ 嵌套递归(`nested_object_recursive_merge`)/ 非 Object 覆盖(`array_replaces_not_concat`)/ 空 no-op(`empty_node_config_is_noop`+`missing_node_config_treated_as_noop`)。 + - **零行为破坏**:HumanNode/AiNode/TaskAdvanceNode 节点 NodeDef.config 空/缺失 → node_configs 无 key → None 分支 == 旧行为。 + - **②-1 TaskAdvanceNode 注册**(state.rs:235-250)✅ — `build_registry(db: Arc)` 改签名;`register("task_advance", move |_| Box::new(TaskAdvanceNode::new(db.clone())))`(state.rs:248-250)move 闭包捕获 Arc,clone 满足 Send+Sync+'static 满足 NodeFactory 签名;init `build_registry(db.clone())`(state.rs:194)db move 前 clone 避 E0382。D-03 走 df-nodes Node trait 对齐。 + - **②-5 HumanNode reject 语义化**(human_node.rs:19-132)✅ — + - `REJECT_KEYWORDS`(L19-22):中「拒绝/驳回/退回/否决」+ 英「reject/decline/declined/deny/denied/no/block」。 + - `is_reject_decision`(L25-28):trim+to_lowercase 归一化 + **整词精确匹配(== 非 contains)** → 防「拒绝啦」误伤(测 L846 `!is_reject_decision("拒绝啦")` ✅)。 + - `contains_reject`(L31-33):picked 任一命中即拒(多选场景)。 + - execute(L120-132):`count_ok && each_valid` 后 `contains_reject` → Err「人工审批被拒绝(用户选择: X);意见: Y」。 + - **边界全过**:整词防误伤(L846)/ 大小写空白归一(L836-838)/ 多选任一即拒(L848)/ options 空自由文本仅拒绝词 Err 其余 Ok(L13-15 注释)/ 行为变更 Ok→Err executor set_failed → 工作流 failed(L113-119 注释)。 +- **⚪ 观察项**:亲跑命令带 `tail -40` 截断 df-nodes 测试数字(仅见 df-workflow 23 passed);**exit code 0 佐证 df-nodes+df-workflow 全过**,非缺陷。后续审查亲跑命令去 tail 或用 `2>&1 | tee` 保全量。 +- **待修项回流 todo**: **无**(三件全过,零缺陷)。 + +### CR-260616-03 batch30 ①类修复批次 — ✅ 已审(PASS) + +- **commit**: 工作区未 commit(攒批,与 CR-04 混合;CR-03 范围文件干净,无阶段2混入) +- **审查时间**: 2026-06-16(独立审查 agent · 亲核 diff + grep 核验,不信主代理"46 passed"声明) +- **总体结论**: ✅ **PASS** — 🔴0 🟡0 ⚪1(set_cancelled TOCTOU 低频,条目已知) +- **独立核验**(CR-03 登记范围 8 文件,范围干净): + - **CR-01-D 错误区分**(task_advance_node.rs)✅ — 同态(from==to)→`Validation("相同状态,无需推进")`;非法转换→`InvalidState{current:"X→Y(非法状态转换)"}`;NotFound(任务不存在)。三类拒绝错误类型+消息区分,前端 e.toString() 可分辨。3 测试 assert 含 from→to 上下文(illegal_skip/terminal_done/same_status)。 + - **CR-03-b openai source 链** ✅ — `Error::from(e).context("SSE 流错误: {e}")`,Display=ctx 逐字节等价旧 `anyhow!`,source 链保留。同 CR-04 anthropic 模式。 + - **CR-03-a resolve_project_label 三臂**(audit.rs)✅ — Ok(Some)→项目名 / Ok(None)→"项目已不存在" / Err→裸 id+warn(DB 故障不误报"已不存在"误导用户)。 + - **CR-03-c ToolCard find**(ToolCard.vue)✅ — computed Map(O(N)建表)→function find(O(N)提前退出);2 调用点 `.value[id]`→`(id)` 改对。 + - **CR-01-G i18n 分级**(TaskDetail.vue + i18n×2)✅ — loadFailed/advanceFailed 加 `{msg}` 插值;catch 走 `t(key,{msg:e.toString() ?? common.unknownError})`;common.unknownError zh/en 均有(common.ts:16)。**关注点② 前端兜底可显示区分消息** ✅。 + - **复核-新④ 终态守卫**(workflow.rs cancel_workflow_node)✅ — get→match:Pending/Running/Waiting→set_cancelled+Ok;终态→Err"已终态,无法取消"。行为变更正确(避免静默覆盖终态)。 +- **⚪ 观察项(关注点①,条目已知"低频忽略")**:`set_cancelled`(state.rs:95-100)**无终态守卫**,直接 `insert(Cancelled)`。IPC 层 get→match→set_cancelled 两步非原子:理论 TOCTOU 窗口(get 时 Pending 通过守卫,set 前被 executor 并发改终态)→ set_cancelled 强制覆盖终态(如 Completed→Cancelled 语义错位)。**实际低频**(人工取消+executor 竞争窗口极小)+ 最坏仅终态语义错位不崩溃 → med 观察项,IPC 守卫是主防线。彻底修需 set_cancelled 加守卫或 CAS,低频场景成本不值。 +- **待修项回流 todo**: 无明确缺陷;TOCTOU 属条目已知低频观察,非 CR-03 引入回归。 + +### CR-260616-04 batch31 NodeCancelled 事件语义+anthropic source链 — ✅ 已审(PASS) + +- **commit**: 工作区未 commit(攒批,与 CR-03 + 阶段2 混合) +- **审查时间**: 2026-06-16(独立审查 agent · 亲跑 test + grep 核验,不信主代理"16 passed"声明) +- **总体结论**: ✅ **PASS** — 🔴0 🟡0 ⚪1(前端消费缺失,既存非回归)+ ⚠ 范围混淆 flag +- **独立核验**(CR-04 登记范围 4 文件): + - **events.rs NodeCancelled variant** ✅ — `NodeCancelled { node_id }`(events.rs:64)新增,语义注释清晰(取消=用户主动 vs NodeFailed=失败)。 + - **executor.rs is_cancelled 二分** ✅ — L148-166:取消节点→emit NodeCancelled(不 set_failed,Cancelled 已终态避免非法转换崩溃);失败节点→set_failed+NodeFailed。零行为破坏(工作流仍 Err 中止,first_err 逻辑保留)。 + - **types.ts node_cancelled** ✅ — L128 手对齐后端 serde(rename_all snake_case)。 + - **anthropic_compat.rs:533 Display 不变** ✅ — `Error::from(e).context(format!("Anthropic SSE 错误: {}", e))`,Display = context = "Anthropic SSE 错误: {e}" 逐字节等价旧 `anyhow!`;e 进 `.source()` 链可追溯。 + - **亲跑 `cargo test -p df-workflow`** ✅ — **23 passed**(主代理称 16,实际 23 含 deep_merge/node_config 新测试),关键 `test_cancelled_node_emits_node_cancelled_event` ✅ 验证取消节点 emit NodeCancelled 非 NodeFailed。 + - **claim③ 无穷举 match** ✅ — 亲 grep:后端无 `match WorkflowEvent` 穷尽(全 matches!/构造点),前端 src/ node_failed/node_cancelled **仅 types.ts 定义无消费**。 +- **⚠ 范围混淆 flag**(重要):executor.rs 工作区 diff 混入**阶段2 config 下沉**(L99-108 deep_merge 全局+节点 config + EchoConfigNode 测试 + node_config_overrides 测试),**非 CR-04 范围**(未登记待审查)。该部分 test 过但不属本批结论覆盖。阶段2 改动(`dag.rs`+102 / `human_node.rs`+46 / `state.rs`+21 / executor.rs config 部分)需主会话另立 CR-05 登记后审。 +- **⚪ 观察项(既存非回归,建议评估)**:NodeCancelled 事件**前端无消费逻辑**(src/ 仅 types.ts 类型定义,无 switch/render 处理)。取消节点用户暂无"已取消"区分 UI 反馈。**非 CR-04 引入**(CR-04 前 node_failed 前端亦不处理,属既存渐进式类型对齐),NodeCancelled 用户价值依赖后续补前端事件处理。 +- **待修项回流 todo**: 无明确代码缺陷;前端事件消费缺失属既存功能不完整,建议主会话评估是否立项(非 CR-04 待修项)。 + +### CR-260616-02 batch29+R-PD-10 代码改动批次 — ✅ 已审(PASS) + +- **commit**: `fc767e1` 优化: R-PD-10 storage_err DRY统一 + FR-D3 migrations if链转数组循环 +- **审查时间**: 2026-06-16(独立审查 agent · 亲跑 test 核验,不信主代理"全过"声明) +- **总体结论**: ✅ **PASS** — 🔴0 🟡0 ⚪0 +- **独立核验**: + - **亲跑 `cargo test -p df-storage`** ✅ — crud 单元 **22** passed + project_soft_delete 集成 **11** passed,0 failed(覆盖 storage_err 替换后 CRUD / advance_status_atomic CAS / soft_delete / update_field 白名单 / migrations run() 全量路径)。主代理"11集成全过"声明属实,额外验证 22 单元。 + - **R-PD-10 storage_err DRY** ✅ — helper `fn storage_err(e: E) -> Error { Error::Storage(e.to_string()) }`(crud.rs:19)泛型等价旧闭包;残留旧模式 grep **=0**(无遗漏);test 全过证 110 处替换无语义偏差。 + - **FR-D3 migrations if→数组循环** ✅ — `steps: [(i32, fn(&Connection)->Result<()>); 15]`(migrations.rs:43)+ `for (version, migrate_fn) in steps { if current_version < version { migrate_fn(conn)?; } }`,与原 15 个 `if current_version Result<()>`,与 steps 数组 fn 指针类型精确匹配。 + - **迁移数据安全三层保留** ✅ — ①版本门控 run() 开头 `MAX(version)`(migrations.rs:29)②各 migrate_vN 末尾 `INSERT INTO schema_version`(L67/75/.../274)③column_exists PRAGMA 幂等探测(L7-9,v4-v15 各处)。 +- **待修项回流 todo**: **无**(全过,无新缺陷)。 + +### CR-260616-01-fixes 审查待修项修复批次 — ✅ 已审(PASS) + +- **commit**: `4dc5219` 修复: CR-01审查待修项①类7项(CSS补 testing/blocked 徽章 + CAS防回收站推进 + DRY + 注释 + 测试改名 + advancing 接文案) +- **审查时间**: 2026-06-16(独立审查 agent · 防"主代理已核查双绿"代入,亲核源码) +- **总体结论**: ✅ **PASS** — 🔴0 🟡0 ⚪2(范围外观察项) +- **独立核验**(不信主代理核查声明,grep/read 亲验 7 项①类修复): + - ① **CR-01-B CAS deleted_at 语义** ✅ — `soft_delete`(crud.rs:800)设 `deleted_at` 非空;`advance_status_atomic` 两分支(bump_rounds + 普通,crud.rs:855/859)均加 `AND deleted_at IS NULL`,回收站任务 `affected=0 → None` 必败。D-02 软删收口正确,一处关闭。 + - ② **CR-01-A CSS 配色跨视图一致** ✅ — TaskDetail.vue:344-345 与 Tasks.vue:413-414 **逐字镜像**(testing 橙 `#ff9800` / blocked 红danger 0.5px 边框),跨视图统一。 + - ③ **CR-01-C DRY 文案零变更** ✅ — `ALL_STATES`(task_state_machine.rs:48-56)= `["todo","in_progress","in_review","testing","done","blocked","cancelled"]`,`join("/")` = 原硬编码串,顺序与值全等,无运行时文案变化。 + - **CR-01-H 测试改名** ✅ — `cas_returns_none_when_status_mismatch`(task_advance_node.rs:313)真存在,改名后注释指向正确的 CAS 失败测试。 + - **CR-01-I advancing i18n** ✅ — zh/en 均有(taskDetail.ts:38 `推进中.../Advancing...`),TaskDetail.vue:63 接 advancing 态。 + - **CR-01-F 白名单注释** ✅ — `allowed_columns_for("tasks")`(crud.rs:331)数组无 `review_rounds` + 注释说明专用路径(仅 advance_status_atomic),收口防误补。 + - **CR-01-E state.rs 注释** ✅ — build_registry(state.rs:240)注释准确,说明 TaskAdvanceNode 未注册原因(阶段2 DAG 联动时加)。 +- **范围外(记录不审,非本批缺陷)**: + - ⚪ **工作区 crud.rs 226行未提交** = `R-PD-10` `storage_err` DRY 重构(提取包装函数替换散落 `.map_err(|e| Error::Storage(e.to_string()))`),非 4dc5219 范围,独立审查留待其 commit。 + - ⚪ **`status-abandoned` CSS class ↔ `cancelled` 后端态** = 历史命名别名(`constants/project.ts:83` `cancelled: 'status-abandoned'` 映射闭环,功能正确),非本批引入。命名语义瑕疵(abandoned 弃用 ≠ cancelled 取消),low,可选优化。 +- **待修项回流 todo**: **无**(7 项全过,无新缺陷)。 + +### CR-260616-01 任务推进链(batch28) — ✅ 已审 + +- **commit**: `d2cb38c` 新增: 任务推进链(7态状态机+advance_task CAS原子写)+软删除+前后端7态对齐 +- **审查时间**: 2026-06-16(5 路并行多角度·防代入独立核验) +- **决策依据**: D-260616-01(前端7态对齐) / D-02(任务软删除) / D-03(advance_task 走 df-nodes Node trait) / D-04(阶段1先行) +- **总体结论**: 🔴0 🟡6 ⚪4 — **质量优** +- **8 点 checklist 全过**: + - 状态机矩阵 ✅(7态15转换,前后端单一真相源) + - CAS 原子 ✅(`WHERE id AND status=expected` + affected=0 报错) + - 退回累加原子 ✅(同 UPDATE) + - V15 迁移幂等 ✅(版本门控 + column_exists 双层,八版零事故) + - IPC 真 thin ✅(D-03 守住,task.rs 仅 3 行转发) + - i18n 成对零遗漏 ✅(28 key) + - create_task 不暴露 rounds ✅ + - 测试 25 个 ✅ +- **F-03 status 双写 = 已知延后**(阶段3 三件同落地,非新缺陷) +- **范围外澄清**: df-ai/lib.rs(`mod retry`) + script_node.rs(`shell_type`) = 同 commit 无关连带改,零风险误报 +- **待修项回流 todo**(CR-01-A~I,见 [todo.md](./todo.md) "审查发现待修项"区): + - [P1] **CR-01-A** TaskDetail.vue scoped style 补 `.status-testing`/`.status-blocked`(D-01 前端7态遗漏,testing/blocked 徽章裸渲染丢样式;同查 Tasks.vue/Dashboard) + - [P1] **CR-01-B** crud.rs advance_status_atomic CAS SQL 加 `AND deleted_at IS NULL`(get_by_id 宏无软删过滤→回收站任务可被 advance,D-02 软删对齐遗漏,语义错乱) + - [P2] **CR-01-C** task_advance_node.rs:46 错误7态硬编码改 `ALL_STATES.join("/")`(DRY 单一事实源) + - [P2] **CR-01-D** task_advance_node.rs:62 同态拒绝(from==to)/非法转换区分(现合并 InvalidState,前端无法分辨) + - [P2] **CR-01-E** state.rs build_registry 注释补「task_advance 未注册(阶段2联动时加)」 + - [P2] **CR-01-F** crud.rs:325 白名单注释补 review_rounds 专用路径 + - [P3] **CR-01-G** TaskDetail.vue:218 advance 失败错误走 i18n 分级(en locale 也中文) + - [P3] **CR-01-H** task_advance_node.rs:297 测试改名(实际未测 CAS 失败) + - [P3] **CR-01-I** i18n `taskDetail.advancing` 冗余 key 处理 diff --git a/docs/clipboard_20260618_125114.png b/docs/clipboard_20260618_125114.png new file mode 100644 index 0000000..7ded639 Binary files /dev/null and b/docs/clipboard_20260618_125114.png differ diff --git a/docs/todo.md b/docs/todo.md index bc93206..d54debe 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -3,6 +3,8 @@ > 来源:`docs/02-架构设计/功能决策记录-2026-06-14.md`「需求与待办」+ `PROGRESS.md` 各 Sprint 遗留,2026-06-14 汇总去重 + 代码核对修正。 > 互操作:执行走 mission-control,回写 mission_id;审查走 cr;发布走 publish-*。 > 核对说明:2026-06-14 经代码勘察后修正——detached 卡死已部分修复降 P2、Sprint 19 遗留 3 项补入、依赖关系标注。 +> +> **2026-06-18 归档**: 已完成 `[x]` 与历史分析段已迁至 [07-项目管理/todo归档/2026-06-18.md](./07-项目管理/todo归档/2026-06-18.md)。 --- @@ -24,14 +26,14 @@ ## 待办 -### 📋 编排推进总览(2026-06-16 汇总) +### 📋 编排推进总览(2026-06-18 更新) 未完成待办按可执行性分 8 组(详细条目见下方各分类,勿重复记录): | 组 | 说明 | 代表项 | |---|---|---| -| **A todo 卫生** | 矛盾清理/被取代退役标注 | ✅ 本批:AR-1 退役 / AR-8 重评 | -| **B 零风险减法** | 死链清/空壳合并 | ✅ ARC-02 死链已删 / ARC-03 前提失效转重评 | +| **A todo 卫生** | 矛盾清理/被取代退役标注 | ✅ 本批已完成 | +| **B 零风险减法** | 死链清/空壳合并 | ✅ 本批已完成 | | **C 需用户输入** | 阻塞,无法代办 | S-260615-01 curl 测 / S-260614-01 多开澄清 / S-260614-02 实测重评 | | **D 功能增强** | 设计清晰可推进(P1) | F-15-01 审批选项 / F-15-02 task 详情 / F-15-03 分页⚠️breaking / F-15-04 卡片折叠 | | **E 架构重投入** | 需设计,非小改 | F-14-01 模型能力 / F-14-07 trait 下沉 / ARC-05·06 store 拆·循环依赖 | @@ -43,98 +45,36 @@ **③④C类已迁**(2026-06-16):需人工决策项(③产品取舍 7 / ④设计方向 7 / 重投入排期 9 / C用户实测 5)见 [待决策.md](./待决策.md),从本文件抽离职责分离。**batch54 可自主批进展(2026-06-16·workflow wsfz7zl8i + 主代核查)**:复核-新⑪ 看门狗幽灵 ✅ 销账(清主窗口 state 视觉残留;**核验更正**:原 watchdog 幽灵已被 CR-260615-24 stopListener:371 clearStreamWatchdog 兜底);AE-03 diff预览 ⚠️ 降级阻塞(后端 write_file 未返 diff,待后端补,见 line 538);UX-04 断线保文 待 DEC-260616-03 授权(见待决策.md 🟡,本批不含)。 -### P0 — 阻断性 bug -- [x] ✅(主代修06-17·撤销误判) **🚨 BUG-260617-01 [P0] 已修** — **`|| true` 致 400/参数错误无效重试 4 次**【已修:stream_recv.rs:433 删 `|| true` + 注释 + 单测 classify_unknown_retryable→classify_unknown_not_retryable 断言改 !;cargo test 20 passed】 — 用户实测证实(2026-06-17)。Anthropic `[1214][messages 参数非法]` 无 HTTP 前缀 → 落入 `stream_recv.rs:433` `\|\| true` → retryable=true → **空耗 4 次重试浪费 token**。前次审查误判为"有意设计"。**一行改动:删 `|| true` + 改单测断言**。— src-tauri/src/commands/ai/stream_recv.rs:433 -- [x] ✅(wf wf_b0fa5730-9e9·主代适配+commit 74003c) **🚨 BUG-260617-12 [P0] 已修** — **DeepSeek reasoning_content 缺失致必 400**【全链路透传:df-ai-core(provider.rs CompletionRequest/ChatMessage/CompletionResponse/StreamChunk 加字段+5单测) → df-ai(openai_compat.rs OpenAiRequest/OpenAiMessage/OpenAiMessageResp/OpenAiStreamDelta 映射+流式解析) → anthropic_compat.rs 11处 StreamChunk/CompletionResponse 兼底) → agentic.rs(last_reasoning_content 初始化→stream_one_provider 注入请求→outcome 提取→push 时回填 ChatMessage) → stream_recv.rs(reasoning_content 累积+StreamResult Complete/Partial 携带) → title.rs 字面量补齐 + df-nodes/ai_node.rs/compress/knowledge_inject 非agentic 路径 reasoning_content: None。cargo check --workspace EXIT 0 / df-ai-core 24 test pass / df-ai+df-nodes 0 test(无单测)】。调研确认 DeepSeek API 规范:reasoning_content 在 choices[].message.reasoning_content(响应)+多轮 assistant 消息必须回传(有 tool_calls 时),否则 400 "must be passed back"。】 — 14 文件 / +255 行 (commit 74003c) +> **📦 已完成项归档**: [07-项目管理/todo归档/2026-06.md](./07-项目管理/todo归档/2026-06.md) — 2026-06-18 拆分, 已完成 `[x]` 与历史分析段迁此。另有 [2026-06-18.md](./07-项目管理/todo归档/2026-06-18.md) — 本次归档。 -> **⚠ 2026-06-17 走查独立核验**:闭环逻辑逐行验明**正确**(流式累积→StreamResult→StreamOutcome→agentic loop `last_reasoning_content` 跨轮缓存→写回 ChatMessage 三路径→openai_compat 透传回 API;前端 src/ 无 reasoning_content 引用,落库不展示无 UX 泄露)。**B-260617-14 提交遗漏已修**(commit f6e4e82):补提 adversarial.rs + project.rs 的 `reasoning_content: None`,HEAD 干净 checkout 编译通过。 -- [x] B-260614-01 — ~~待审批持久化根治(重启恢复)未生效~~ ✅ mission:T-260614-01 已修复(commands.rs:444 clear→retain 保其他对话 pending;ai_approve 两处 if !recovered 守卫移除;cargo check 0 err / 19 test pass)(06-14) -- [x] B-260614-02 — ~~df-workflow ConditionEngine 默认 true~~ ✅ mission:T-260614-02 已修复(conditions.rs:31 `Ok(true)`→`Ok(false)` 保守拒绝;5 个原断言错误行为的测试同步改断言;df-workflow 7 test pass)(06-14) -- [x] B-260614-04 — ~~df-workflow NodeRegistry::default() script 工厂 unimplemented!~~ ✅ mission:T-260614-03 已修复(删除整个 Default impl——零调用方 + 违反铁律;state.rs build_registry 已用 new() + 手动注册真实 ScriptNode)(06-14) -- [x] B-260616-20 [P0] — **df-nodes 测试回归:`advance_succeeds_after_prior_status_change` 红(CR-260616-41 status 收口副作用)** ✅ **已修**(主代 task_advance_node.rs:331-332 改 `advance_status_atomic("t1","todo","in_progress",false)` 走 CAS 合法路径模拟并发推进,亲跑 `cargo test -p df-nodes` **67 passed/0 failed/1 ignored**)。原委:commit b94e74a 把 status 移出 tasks 白名单(D-260616-04 收口),`crates/df-nodes/src/task_advance_node.rs:332` 测试用 `repo.update_field("t1","status","in_progress")` 模拟「旁路并发改 status」前置,现被白名单拒 → unwrap panic。生产代码无回归,仅测试 fixture 同步。— crates/df-nodes/src/task_advance_node.rs:331(审查 CR-260616-41 复现) +### 🔴 2026-06-18 Agentic 最大轮次设置不生效(设 30 仍按 10 截断·session-role-diagnose-only·未实施) -### 🔧 2026-06-16 排查会话新报 bug(4 项·仅分析未实施) +> 用户报:Settings 里 Agentic 最大循环轮次设 30,但实际跑到 10 就提醒「达到最大」。走查全链路定位根因 + 记 todo,不改代码。 -> 排查性质会话(session-role-diagnose-only),仅走查定位根因 + 记录待办,未改代码。 +**现象**:设置项显示 30(前端持久值),实际 loop 跑 10 轮即触发 `AiMaxRoundsReached`(达 max 提醒)。 -- [x] ✅(第㉖批 2026-06-16·主代理直修) B-260616-09 [P1] — **任务详情点击打开空白(路由漏注册)**。**router/index.ts 补 /tasks/:id → TaskDetail**(L50-54 新增块,6 行,参照 /projects/:id L39-43 + /ideas/:id L27-31 模式)。name 'TaskDetail' 无冲突(现有 name 集无此项),复用 icon-thunder(对齐 /ideas/:id 复用 icon-lightbulb、/projects/:id 复用 icon-apps 一致模式)。TaskDetail.vue 现挂载,Tasks.vue:57 router.push(`/tasks/${task.id}`) 不再跳未注册路由,B-18 联动刷新等功能激活(死代码转活)。改动极简确定(6 行路由表项),主代理直修省 workflow 单 agent 编排开销。vue-tsc exit 0。— src/router/index.ts -- [x] ✅(第㉖批 2026-06-16) B-260616-10 [P1] — **provider 配置后 AI 面板仍提示「未配置」**。**Settings.vue loadProviders 同步刷全局 store**。L376 import useAiStore + L383 const aiStore = useAiStore()(跟随 L382 appSettings 风格)+ L420-424 loadProviders 内本地 aiProviders.value 刷新成功后 L424 await aiStore.loadProviders() 刷全局 state.providers(模块级单例 ai.ts:45,L20/L44 注释明确全应用共享同一份,AiChat 共享)。三处调用点(saveProvider L464/deleteProvider L479/setDefaultProvider L490)+ onMounted L731 均经 loadProviders 自动同步全局。无循环(useAiPanel.ts:91 loadProviders 只刷 state.providers 不回调 Settings)。无 i18n 改动(纯数据同步),try/catch + showToast 保留。vue-tsc exit 0。— src/views/Settings.vue -- [x] ✅(ca9b318·wrapNakedDiff落地+第㉖批diff语言注册) B-260616-11 [P2] — **AI 消息 `-`/`+` 列表/diff 无视觉效果**。两项子项均已落地:①**裸 diff 预处理** `wrapNakedDiff`(ca9b318 useMarkdown.ts:+96 守卫4:纯`-`列表不触发/≥2行连续±/已在围栏不动/段落级处理+短路优化+缓存key用wrapped) ②**diff 围栏高亮**(第㉖批 useMarkdown.ts import diff+registerLanguage('diff')+highlightCode getLanguage('diff')→hljs addition/deletion token+github-dark.css 红绿)。— src/composables/useMarkdown.ts(:183-282 renderMd+wrapNakedDiff 全函数) -- [x] ✅(第㉗批 2026-06-16·workflow) B-260616-19 [P2] — **知识库详情窄屏标题挤压成竖线(一行一字)**。三因子叠加(Knowledge.vue):①`.kn-layout` grid `grid-template-columns: 360px 1fr`(L567)——左列表死钉 360px、右详情 `1fr` 拿剩余,**无响应式断点**;窗口窄(如总宽 500px,减 padding 40+gap)详情列塌缩到 ~80px 甚至更小。②`.detail-title`(L625)`font-size:18px` + `word-break:break-word` —— 窄列里中文每字宽度 > 列宽,逐字换行 → 竖线化。③`.detail-actions`(L626)`flex-shrink:0` —— 编辑/发布/拒绝 3 按钮不缩,优先占位,把 `.detail-title-row`(`flex:1`) 挤到零头。`.kn-detail-panel`(L570-576) 无 `min-width` 兜底。改动(待定):①grid 列宽改 `minmax(240px,360px) 1fr` + `@media (max-width:760px)` 切单列(`grid-template-columns:1fr`,列表详情上下堆叠);②详情面板加 `min-width:0` 配合标题行 `min-width` 防塌缩;③可选标题字号响应式降级(18px→15px)。— src/views/Knowledge.vue。**低风险纯 CSS**,无逻辑/i18n/数据变动。 +**根因(后端内存态 + 恢复同步绑错生命周期)**: +后端 `agent_max_iterations` 是 `Arc` 纯内存(`state.rs:242`),**重启回默认 10(`DEFAULT_MAX_AGENT_ITERATIONS`),自身不落 DB**。持久化只在前端 `appSettings`(SQLite),后端靠前端启动时调 `ai_set_agent_max_iterations` IPC 同步恢复。而该同步 `syncAgentMaxIterations()` 只绑在 **`GeneralPanel.onMounted`**(`GeneralPanel.vue:267`)—— GeneralPanel 是 Settings 页子组件,**只在用户打开 Settings 时才挂载**。两失效场景(任一即中): +1. **重启后直接用 AI Chat(没先开 Settings 页)** → GeneralPanel 未挂载 → onMounted 不跑 → 后端停留默认 10 → loop 10 轮提醒;前端 Settings 显示 30(持久值)与后端实际 10 不一致。 +2. **即便开了 Settings,`appSettings` 缓存异步未就绪时** → `settings.agentMaxIterations` 初值读到默认 10(`:153`)→ onMounted 把 10 sync 给后端(onMounted 只跑一次,cache 就绪后不重跑)。 -### 🔧 2026-06-17 实测发现 bug(审批流程三连) +`agent_max_retries` 同病(同款 onMounted sync `:269`,后端 `agent_max_retries` 同款内存态 `state.rs:285-287`)。 -> 用户实测:AI 对话中让 AI 创建任务并推进状态 → AI 调 `run_workflow(task_id, target_status)` → 审批卡片渲染 → 用户点批准 → handler 返回 Err → LLM 重试循环。 +**排除项**: +- ✅ 后端 clamp 1-50(`commands.rs:1680`)——30 不会被压成 10。 +- ✅ `@change→IPC` 链路(`GeneralPanel.vue:226-237`)——用户改值当时会同步,**当前会话生效**;失效只在重启/未挂载场景。 +- ✅ loop 入口 load 快照(`commands.rs:138/264/739/879`)——热改后当前 loop 不受影响是设计(注释 :1670),但用户是发**新消息**仍 10,非当前 loop 热改问题。 -- [x] ✅(波9·e23b316+主代核查,cargo0err+devflow79pass,方案a2后端分支) B-260617-01 [P1] — **run_workflow 作为 AI 工具无法执行 + 无意义重试 + 审批混淆**。三个关联问题: - - **问题 1**:`run_workflow` handler(tool_registry.rs:530-546)仅持有 `db: Arc`,无法访问 `AppHandle/State`(registry/event_bus/workflows Repo),返回 `Err("run_workflow 工具需经 Tauri IPC 执行...")`。审批通过后 `ai_approve`(commands.rs:329)调 `ai_tools.execute()` → handler 必然 Err → `audit_status="failed"` → 错误信息作为 tool_result 回传 LLM。工作流实际未执行,任务状态未推进。 - - **问题 2**:handler Err → tool_result=错误提示 → LLM 认为失败 → 重试(1/4) → 同结果循环。工具定义未标 `no_retry`,浪费 token + 用户反复审批。 - - **问题 3**:前端渲染的是 AI 工具审批(tool_call pending_approval),非工作流 HumanNode 审批。用户批准后工作流引擎从未启动,HumanNode 审批节点从未到达。 - - **修复方向**:**方案 A(推荐)**:前端拦截 — `ai_approve` 中识别 `run_workflow` tool_call → 不走 `ai_tools.execute` → 直接 `invoke('run_workflow', {task_id, target_status})` → 结果回填 tool_result。**方案 B**:后端注入 State — `build_ai_tool_registry` 传入 `AppHandle` 使 handler 可直接执行。问题 2/3 随 A 自动消解。 - - **代码定位**:tool_registry.rs:530-546(handler 返 Err)/ audit.rs:590-630(High risk 进 pending)/ commands.rs:329-334(ai_approve execute→Err→failed)/ agentic.rs:503-508(pending_count>0 暂停循环) - — src-tauri/src/commands/ai/{tool_registry,audit,commands,agentic}.rs + src/composables/ai/useAiSend.ts +**证据链**:`state.rs:282-284`(默认 DEFAULT) · `agentic.rs:45`(`DEFAULT_MAX_AGENT_ITERATIONS=10`) · `commands.rs:1674-1683`(set 只 store AtomicUsize 不写 DB) · `GeneralPanel.vue:262-270`(恢复 sync 在 onMounted,非根组件) · `agentic.rs:891-909`(`!converged` emit AiMaxRoundsReached)。 -### 🔧 2026-06-16 aichat 工具卡片重复渲染排查(仅分析·未实施) +**修法(二选一)**: +- **B 推荐(前端小改)**:`syncAgentMaxIterations()/syncAgentMaxRetries()` 从 GeneralPanel.onMounted **上提到 App.vue 根 onMounted**(应用启动必挂载)+ 确保 `appSettings` loadAll 就绪后再调(读持久真值)。利用前端已有 SQLite 持久化,几行改动,立即生效。 +- **C 彻底(后端持久)**:`AppState::init` 从 DB settings 表读 `agent_max_iterations/retries`,`ai_set_*` IPC 同时写 DB。后端自持不依赖前端同步时机,改动较大但根治。 -> 排查性质(session-role-diagnose-only),走查定位根因 + 记录待办,未改代码。 +**待用户确认**:触发时是否「重启应用后直接发消息(没先开 Settings 页)」——若是,根因 1 坐实。 -- [x] ✅(247dc5d后端audit按id去重治本+useAiEvents.ts:205前端findToolCall幂等守卫双保险·候选A根因闭环) B-260616-21 [P1] — **aichat 工具卡片「重复读取同一文件」表象(read_file 出现两条卡:一 0 行·7.1KB running、一 183 行·7.1KB completed)**。用户报对话记录里 `读取 .../api/ai.ts` 出现两次。**根因分析**(已走查前端流转 + 后端 emit 点):①前端 `useAiEvents.ts:202-206` `AiToolCallStarted` 分支直接 `lastMsg.toolCalls.push(info)`,**无 id 幂等守卫**(对比同文件 `startToolSlowTimer:58` 有 `_toolTimers.has(callId)` 守卫,Started 漏了同款判重)②`findToolCall`(`aiShared.ts:41-49`) 按 `id` 命中**首个**返回,`AiToolCallCompleted`(`:213`) 只 update 第一张卡 → 同 id 若被 push 两次,第二张卡永远 running,`ToolCard.vue:72` `parsed?.lines||0` 无 result 时显示 `0 行` ③后端 `audit.rs:538-549` `process_tool_calls` 对 tc_list 每 draft emit 一次 Started;read_file=Low risk(`:561`) **无去重**(去重仅 High,`:553-556` 注释明确 Low 不进)。**两候选根因**:**(A 最贴合现象)** 同一 `tool_call_id` 被**重复 emit Started**(后端 `process_tool_calls` 重入 / `stream_recv.rs` 生成重复 id / `agentic.rs` loop 重 process 同轮)→ push 两张卡 → Completed 只更首张(183 行)→ 次张残留 running(0 行);**(B)** agent loop 多轮 LLM 真读两次(不同 id)→ 但应两卡皆 183 行,与「0 行 + 183 行」现象不符,**排除为主因**。**待查**:后端同 id 重复 emit 的确切路径(`audit.rs:532` tc_list 来源 `tool_calls_acc` + `stream_recv.rs` id 生成 + `agentic.rs` 是否对同轮 tool_calls 重复 process)。**修复方向**(待定):①前端 Started push 前加 `findToolCall(event.id)` 幂等守卫(确定性低风险,对齐 startToolSlowTimer 守卫,治标兜底) ②后端查重复 emit 源治本(定位 stream_recv/process/loop 哪环生重复)。**关联** F-260616-05(agentic loop 工具去重缺失,batch53 仅 High 去重,read_file/search_files 等 Low 未去重;本现象若实为候选 A 则与 F-05 不同维度——F-05 是不同 id 重复调用,本条是同 id 重复 emit)。— src/composables/ai/useAiEvents.ts(:195-209 Started 分支) + src/composables/ai/aiShared.ts(:41 findToolCall) + src/components/ToolCard.vue(:72 lines 展示) + src-tauri/src/commands/ai/audit.rs(:538-549 emit Started)。 - -### 🔧 2026-06-17 定期全量走查(第1轮·仅走查·未实施) - -> 周期性自检(session-role-diagnose-only:不碰代码,文档不对改文档 + 代码问题记 todo)。本轮范围:secret.rs 全文 + 近 6 提交 vs todo 状态核对 + 工作区未提交改动(tauri.conf.json)。 - -**文档状态校正(治本/实现已落地,原 [ ] 更新为 [x])**: -- B-260616-21 → ✅:后端 247dc5d(audit.rs:534-540 `seen_ids: HashSet` 按 id 去重保首个)+ 前端 useAiEvents.ts:205 `findToolCall` 幂等守卫双保险,候选 A 根因(LLM 复用同 tool_use.id 致 emit 两次)闭环。 -- F-260614-04 → ✅ 后端三段(79b6a43 数据层+池选择器 / b3684f4 池 IPC+set_caps / 80c0955 stream_one_provider+候选链 fallback),前端 api/ai.ts:142 IPC 封装已接;UI 配置面板待核验。 - -**新登记代码问题**: -- [x] ✅(小修批·2026-06-17·wdqxhw4x1) B-260617-06 [P3] — **secret.rs:104 注释与实现相反**。`resolve_provider_secret` 注释写「keyring 优先,fallback DB.api_key」,但代码(:105-110 `if !record.api_key.is_empty() { return DB }` + `get_provider_secret().unwrap_or_default()`)实际「**DB 优先,fallback keyring**」。与顶层设计(line 15「DB 优先,fallback keyring」)矛盾,line 104 注释为笔误。**实际无功能影响**(迁移态 DB 与 keyring 不会同时有值,set keyring→clear DB 原子序列),纯文档错误。**修法**:line 104 注释改为「DB 优先,fallback keyring」。— crates/df-storage/src/secret.rs:104 - -**工作区未提交改动观察(非 bug,记录供判断)**: -- src-tauri/tauri.conf.json: `bundle.targets` `"all"` → `["nsis"]`(打包目标从全平台收窄到仅 NSIS Windows 安装包)。正式收窄(devflow 仅 Win 桌面)则合理;若 dev 调试临时收窄忘还原,后续 mac/linux 打包会漏。**待用户确认意图**。 - -### 🔧 2026-06-17 定期全量走查(第2轮·仅走查·未实施) - -> 增量范围:F-05 多模态三提交(Phase2a 后端 e3cd448 / Phase2b 前端 e1d396d / Phase2c 闭环 4317d02)+ 核对 `src/components/settings/`(未跟踪)。UX-19 虚拟滚动已被 CR-62 复审 PASS(待审查.md 已回填),本轮不重审。 - -**F-05 多模态审查结论:质量优** -- 设计周全:`content:String` 主载荷不变 + `parts:Option>` 挂载,老 JSON 无 parts → None,向后兼容零回归(audit/title/commands 读 content 当字符串全不受影响)。ContentPart `#[serde(tag="type",rename_all="snake_case")]` ↔ 前端 TS discriminated union `type:'text'|'image'`,wire 格式严格对齐。 -- 转换层协议正确:Anthropic image `source.type=base64` + media_type + data;OpenAI `image_url` data URI / http url 双形态;OpenAI `content:String→serde_json::Value` 双形态,String 简写保纯文本端点兼容。 -- 所有权处理到位:openai_compat 注释「须在 move m.tool_calls 之前算,借用 m」——Rust 借用易错点作者注意到了。 -- truncate_parts 防撑爆:Image base64 → 占位 Text 片,url 模式原样;落库副本不污染内存真相源(ContextManager 保留原图,重发仍带图)。 -- 测试覆盖:ContentPart round-trip / legacy JSON / truncate / 转换层双形态各 2+ 测试。 -- FR-S1 安全边界注释到位:commands.rs 明确 ContentPart Image base64 是图片数据非 api_key,不入敏感面。 - -**新登记代码问题(均 P3,无 P0/P1/P2)**: -- [x] ✅(小修批·2026-06-17·wdqxhw4x1) B-260617-07 [P3] — **F-05 变量名误导 + 兜底注释矛盾**。①conversation.rs:109 `bytes_approx = b.len()` 实为 base64 **字符数**(文案「约 N 字符」对,变量名 `bytes_approx` 错,注释「base64 字符数 ≈ 字节数*4/3」也混淆)。②openai_compat.rs Image 缺数据兜底分支 `_ => String::new()` 注释「避免发空 url 触发 400」自相矛盾——空串本身即空 url,仍触发 400。当前不可达(`image_base64` 构造器强制 `media_type:Some`),纯注释/命名瑕疵。**修法**:变量改名 `base64_len`/文案统一;兜底分支改为明确报错或移除误导注释。— crates/df-ai/src/openai_compat.rs:336 / src-tauri/src/commands/ai/conversation.rs:109 -- [x] ✅(小修批·2026-06-17·wdqxhw4x1) B-260617-08 [P3] — **F-05 Anthropic url 模式发空 data 预留路径缺口**。anthropic_compat.rs:355 Image(url,base64:None) 时兜底发空 data + warn「commands 层应预拉 base64」,但 Phase2c commands.rs:177 透传 parts 时**无预拉 url 字节逻辑**。当前前端 Phase2b 只产 base64 模式(AiChat.vue:999 `FileReader.readAsDataURL`),路径不可达;但注释误导(声称的预拉不存在),未来加 URL 图片输入 / 历史 url 图重发会静默 Anthropic 400。**修法**:commands 层补 url→base64 预拉,或 provider 层 url 模式明确报错而非发空 data。— crates/df-ai/src/anthropic_compat.rs:355 - -**文档状态校正**: -- **F-260614-04 UI 面板核验通过**:第1轮标 [x] 时备注「UI 面板待核验」,本轮确认 `src/components/settings/ProviderPanel.vue`(enabled toggle + weight 0-100 input + `onPoolToggle` 即时调 IPC,落库后后端 `reload_provider_caps` 重建)已实现 = F-04c 负载均衡池 UI。**注**:该文件当前**未跟踪未提交**(git status `?? src/components/settings/`),含 GeneralPanel/KnowledgePanel/ConnectionPanel/ProviderPanel 四组件(设置面板拆分重构),待提交纳入版本控制。 - -**已审文件清单(本轮)**:crates/df-ai-core/src/provider.rs · crates/df-ai/src/anthropic_compat.rs · crates/df-ai/src/openai_compat.rs · src-tauri/src/commands/ai/conversation.rs · src-tauri/src/commands/ai/commands.rs · src/api/ai.ts · src/api/types.ts · src/composables/ai/useAiSend.ts · src/composables/ai/useAiConversations.ts · src/stores/ai.ts · src/components/AiChat.vue(图片构建+渲染) · src/components/settings/ProviderPanel.vue -**新登记 todo**:B-260617-07 · B-260617-08 - -### 🔧 2026-06-17 Tasks.vue i18n 缺失(仅分析·未实施) - -> Tasks.vue:72 报 `Not found 'completed' key in 'zh' locale messages`。session-role-diagnose-only,仅分析+记todo。 - -**现象**:Tasks.vue:72 `$t(statusLabel(task.status))` → `taskStatusLabel()` → `TASK_STATUS_LABELS[status]` 返回 `'tasks.status.done'` → `$t('tasks.status.done')` 在 en/zh-CN 均未定义 → intlify fallback 到 en 仍缺失。 - -**根因**:`src/constants/project.ts:61-68` 定义了 `TASK_STATUS_LABELS`(值如 `'tasks.status.done'`/`'tasks.status.todo'` 等 7 个 status + `'tasks.statusFilter.done'` 等 5 个 filter + priority 相关),但 **en/zh-CN 的 tasks 相关 i18n 文件完全不存在**。6b67214 大量新增功能时未同步补 tasks i18n key。 - -**影响范围**(需补全的 key): -| 组件 | 用到的 key 前缀 | 缺失的 key | -|---|---|---| -| Tasks.vue:72 | `tasks.status.*` (7个) | 全部 | -| Tasks.vue:148-155 | `tasks.statusFilter.*` (5个) | 全部 | -| Tasks.vue:96-103 | `tasks.priority.*` / `P0`/`P1`/`P2`/`P3` (5+4=9) | 全部 | - -- [x] ✅(误判销账·核验 tasks.ts 齐全) B-260617-10 [P2] — **Tasks.vue i18n 缺失**【独立核验:tasks.ts status 7 key(todo/in_progress/in_review/testing/done/blocked/cancelled)+ statusFilter 8 key 全有(zh/en 对称)。真实根因=老 DB status='completed' 残留 + taskStatusLabel `?? status` 兜底裸字符串 → $t() not found,已 commit 1cd7652 LEGACY_STATUS_MAP 防御修复】 - -> **2026-06-17 进展注记(1cd7652)**:提交 1cd7652 已做**数据层映射兜底**(project.ts 新增 `LEGACY_STATUS_MAP`:completed→done / review_ready→in_review / merged→done / abandoned→cancelled),`taskStatusLabel/taskStatusClass` 经映射后老 DB 脏态不再直接走 `$t()` 报 not found。**但 i18n key 补全(显示层)仍未做**——映射后 `$t('tasks.status.done')` 仍 not found(tasks.ts 不存在),用户从看到 "completed" 变成看到 "tasks.status.done" 原始 key。**B-260617-10 主问题(补全 21 个 i18n key)仍在**,1cd7652 是其前置的数据归一,待补 key 后老态自动正确显示。映射目标态(done/in_review/cancelled)均已核验在 TASK_STATUS_LABELS 7 态内 ✓。 +- [x] ✅(workflow wuirgcxoy·App.vue:215-224 onMounted loadAll+migrate 后加 setAgentMaxIterations/setAgentMaxRetries 恢复同步(clamp 对齐 GeneralPanel:228/245)·GeneralPanel:266 onMounted sync 保留双保险(IPC 幂等)·修bug正向重启后正确恢复持久值·主代 vue-tsc EXIT 0) **B-260618-23 [P1]** — **Agentic 最大轮次设置不生效(设 30 仍按 10 截断)**。后端 `agent_max_iterations`(`state.rs:242`) 纯内存 AtomicUsize 重启回默认 10、不落 DB;恢复同步 `syncAgentMaxIterations` 只绑 `GeneralPanel.onMounted`(:267),GeneralPanel 仅 Settings 页挂载 → 重启后直接用 AI Chat 不同步 → 后端用默认 10 与前端显示 30 不一致 → 达 10 提醒。`agent_max_retries` 同病。**修法 B(推荐)**:sync 上提到 App.vue 根 onMounted + 等 appSettings 就绪;**修法 C**:后端 AppState::init 从 DB 读持久化 + set IPC 写 DB。— `src/components/settings/GeneralPanel.vue`(:262-270) + `src/App.vue`(根 onMounted) 或 `src-tauri/src/state.rs`(:282-287) + `commands.rs`(:1674-1683) ### 🔧 2026-06-17 走查·tauri 打包目标收窄 + 状态映射 DRY(仅分析·未实施) @@ -142,16 +82,10 @@ - [ ] B-260617-11 [P2] — **tauri.conf.json 打包目标收窄未提交,若误入库锁死非 Windows 构建**。工作区改动 `bundle.targets: "all" → ["nsis"]`(src-tauri/tauri.conf.json:28),收窄到仅 Windows NSIS 安装包。若意图为本地只打 Windows 包,合理;**但若随其他改动一并提交**,macOS(dmg/app)、Linux(deb/appimage)构建将不可用,影响其他开发者/CI。**确认点**:该改动是临时本地构建还是有意入库?临时则建议提交前 revert 此行;有意则建议改为按平台条件配置而非硬编码单一 target。—— src-tauri/tauri.conf.json(:28) -- [x] ✅(commit 645f73d) B-260617-12 [P3·可选] — **LEGACY_STATUS_MAP 映射 DRY 重复**。抽 `mapLegacyStatus(status)` 函数收敛两处 `LEGACY_STATUS_MAP[status] ?? status` 一行重复。vue-tsc EXIT 0。—— src/constants/project.ts(:96-98) - ### 🔧 2026-06-17 走查·DeepSeek reasoning_content 实施审查(P1 提交不完整) > 核验 HEAD 74003bc(BUG-260617-12 修复提交)+ 工作区残留。session-role-diagnose-only,逐行核验非信声明。**闭环逻辑正确(见 :49 注记),但发现提交不完整 P1**。 -- [x] ✅(commit f6e4e82) **🚨 B-260617-14 [P1] 已修** — **74003bc 漏提交 adversarial.rs + project.rs**【补提两文件 reasoning_content: None,HEAD 干净 checkout 编译通过】 - -- [x] ✅(不做·优化方向错误) B-260617-15 [P2·可选] — **reasoning_content 落库存储膨胀**【核验 DeepSeek API 规范:有 tool_calls 的 assistant 消息 reasoning_content **必须**在后续请求回传(否则 400 "must be passed back")。verify agent "只走顶层"分析错误——实际需 messages 数组回传。落库剥离会导致重新加载对话后历史消息无 reasoning_content → DeepSeek 400(重新引入 BUG-12)。reasoning_content 必须保留在内存+落库+请求全链路,不可优化】 - - [ ] B-260617-16 [P3·可选] — **Partial(MidStream 保文)回填半截 reasoning_content 语义待评**。agentic.rs MidStream 保文路径用 `round_reasoning_content`(本轮部分累积)写回 ChatMessage,回传下一轮 DeepSeek 会看到不完整推理。属异常路径(网络中断保文),保文后已加"响应不完整"系统提示,用户大概率重发。影响小,登记观察。— src-tauri/src/commands/ai/agentic.rs(:765) ### 🔧 2026-06-17 对话标题不更新(还叫"新对话"·待运行时验证) @@ -172,311 +106,13 @@ **修法方向(待确诊后)**:① ensure 改同步(loop 内 await,完成后再 emit Completed)② :95 update_field 失败加 error 日志 ③ 排查信号量阻塞。 -- [ ] B-260617-17 [P2] — **对话标题不更新(刷新/重启后仍"新对话")**。save 不覆盖 title(已排除),title 初始 None 应触发生成。主嫌疑:ensure 后台 LLM 生成未完成即刷新/重启(网络耗时)→ 标题未落库。待运行时日志确诊 ensure 是否执行 + update 是否成功。— src-tauri/src/commands/ai/title.rs(:27,:90,:95) · agentic.rs(:924-931 spawn 后台) · conversation.rs(:166-194 save 不碰 title) - -### 🔧 2026-06-17 知识库列表首次空(确定性 bug·根因确诊) - -> 用户报:打开系统进知识库列表数据空,加载完成后仍不显示;需点"待处理"再回"知识库"才显示。session-role-diagnose-only,排查链路根因确诊。 - -**根因(确定性)**:`stores/knowledge.ts:31` `_itemsReqSeq` 序号被 **loadList(:62)/loadCandidates(:80)/search(:92) 三者共用**(都 `++_itemsReqSeq`),设计为"丢弃旧响应防竞态",但漏了并发覆盖场景: - -**onMounted(Knowledge.vue:521-523)并发调用** `loadList()` + `loadCandidates()`: -1. loadList 发起(seq=1, await list) -2. loadCandidates 发起(`++_itemsReqSeq`→2,**覆盖**) -3. loadList 响应回来 → knowledge.ts:67 `if (seq !== _itemsReqSeq)` → 1≠2 → **return 丢弃** → `state.items` 永空 -4. loadCandidates 响应 → seq=2 匹配 → 写 `state.candidates` - -→ 首次进入 `listItems` computed(Knowledge.vue:297-298 读 `store.items`)空。点"待处理"(inbox)→ switchTab loadCandidates;再回"知识库"(library)→ switchTab **单独 loadList(无并发)**→ seq 匹配→写 items→**显示**。完美解释"切走再回才显示"。 - -注释 knowledge.ts:29-30「items 序号覆盖 loadList/loadCandidates/search;candidates 与 items 共用同一队列」=有意共用序号,但没料到 loadList 被并发 loadCandidates 覆盖丢弃。 - -**修法(确定性·唯一正确解·低风险)**:分序号——loadList/search(都写 `state.items`)共用 `_itemsSeq`;loadCandidates(写 `state.candidates`)用独立 `_candidatesSeq`。各自防竞态,互不丢弃。改 knowledge.ts:31 拆两序号 + loadList/search/loadCandidates 各用对应序号。 - -- [ ] **🚨 B-260617-18 [P1]** — **知识库列表首次进入必空**。`_itemsReqSeq` 序号 loadList/loadCandidates/search 共用(knowledge.ts:31/:62/:80/:92),onMounted 并发 loadList+loadCandidates 时 loadList 的 seq 被 loadCandidates 的 `++` 覆盖 → loadList 响应 :67 `seq !== _itemsReqSeq` 丢弃 → state.items 永空 → 列表空。切 tab 再回(switchTab 单独 loadList 无并发)才显示。**确定性 bug**,修法:拆 `_itemsSeq`(loadList/search) + `_candidatesSeq`(loadCandidates) 分队列防互相丢弃。— src/stores/knowledge.ts(:31 序号共用·:62/:80/:92·:67 丢弃逻辑) · src/views/Knowledge.vue(:521-523 onMounted 并发) - -### 🔧 2026-06-17 aichat 消息全量重叠(5角度深入分析·未实施) - -> 用户报 aichat 对话「大量重叠」「不单间距问题」。session-role-diagnose-only,5 角度并行论证后记录。 - -> **🔥 2026-06-17 补充触发场景(展开全部·同根因)**:用户报"展开全部"折叠段(archived/compressed)后,**展开内容未把后续消息往下挤压,而是与未展开消息重叠**。机制同主根因:展开段 → `renderItems` computed 新增 msg item(key=`'e-'+seg.key+'-'+m.id`,AiChat.vue:2586)→ 新 item 注册 sentinel,`itemMeta` height 初始 0(useAiVirtualScroll.ts:104)→ 高度竞态 → slot 塌 0 → 内容 overflow 显示 + 后续上移 = 重叠。**与"第二次发送"触发的重叠同根(虚拟滚动 slot 高度塌陷),B-260617-09 方案 A(卸载 minHeight fallback)/ C(禁虚拟滚动验证)可一并解决**。 - -> **🔥 2026-06-17 用户精确场景收敛(根因定位·待 DevTools 终验)**:用户给出精确触发模式——①user 发 msg1→AI 回 reply1(一或多卡片) ②user **再次发送** msg2 时,**msg2 重叠在 reply1 之上** ③reply2 紧跟重叠 msg2 后 ④后续连续重叠。此场景锁定触发时机=**第二次发送(reply1 从 pinned 移出的瞬间)**,根因链收敛: -> - **CSS 兜底缺失(确定)**:`.ai-msg-slot`(AiChat.vue:3174)仅 `display:flex;flex-direction:column`,**无 min-height/height/position**。slot 高度完全靠内容撑;内容卸载时高度**唯一依赖**虚拟滚动 IO 回调设的 inline `minHeight`(useAiVirtualScroll.ts:74),无 CSS 静态兜底。 -> - **minHeight 守卫跳过(核心嫌疑)**:useAiVirtualScroll.ts:73 卸载分支 `if (meta && meta.height > 0 && meta.el.isConnected)`——**height=0 时跳过设 minHeight** → slot 塌 0 → 后续 flex 项上移 → 叠在 reply1 内容(overflow 显示)上 = 用户所见"msg2 重叠在 reply1 上"+后续连续重叠。 -> - **height=0 高发窗口=第二次发送**:reply2 push → `lastStreamingRenderKey`(:2596)切到 reply2 → watch(:2604)`setPinnedMsgs` reply1 移出 pinned;同时 `isLastAi(reply1)`(:1561)变 false → bubble key(:463)从 `_mdRenderKey` 变 `0` → **Vue 重建 reply1 bubble**(流式 streamingBlocks→renderContent 切换)。pinned 移出 + bubble 重建叠加,RO/IO 在此窗口易捕获 height=0(首次挂载初始化 0 :104 / 重建中间态 / RO 异步未跟上最终值)。 -> - **确诊待 DevTools**:第二次发送后查 ①reply1 的 `itemMeta.height`(useAiVirtualScroll itemMeta Map)②reply1 `.ai-msg-slot` 的 inline `minHeight` + `offsetHeight` ③`shouldRenderMsg(reply1)` 是否变 false。若 height=0 且 minHeight 未设 → 确诊。 -> - **修法选项**: -> - **A 治本**:useAiVirtualScroll.ts:73 卸载分支去掉 `height>0` 跳过,height=0 时 fallback 测 `meta.el.offsetHeight`(卸载前内容还在,offsetHeight 即真实高度)作 minHeight。 -> - **B 兜底**:`.ai-msg-slot` 加静态 `min-height`(如 60px),防塌 0(治标,虚拟滚动 height=0 隐患仍在)。 -> - **C 隔离验证(推荐先做)**:临时禁用虚拟滚动(`shouldRenderMsg` 恒 true),立判重叠是否消失 → 确诊是否虚拟滚动所致(5 分钟验证)。 - -**现象确认(基于截图)**: -| 现象 | 判定 | -|---|---| -| 顶部出现 `` 等模板源码文本 | ✅ 正常 — AI 回复中 read_file 读取 AiChat.vue 源码后 v-html 展示,非渲染泄漏 | -| 消息/工具卡大量重叠堆叠 | 🔴 异常 — 布局层叠问题 | -| 整体挤压、间距消失 | 🔴 异常 | - -**已排除项**: - -| 检查点 | 结论 | -|---|---| -| CSS 声明链路 | ✅ `.ai-messages` 有 `display:flex;flex-direction:column;gap:14px`(:3109-3116),无外部覆盖 | -| scoped 样式冲突 | ✅ 单 ` diff --git a/src/components/ToolCard.vue b/src/components/ToolCard.vue index 686885f..e761773 100644 --- a/src/components/ToolCard.vue +++ b/src/components/ToolCard.vue @@ -3,7 +3,7 @@ class="ai-tool-card" :class="[ 'ai-tool-card--' + tc.status, - { 'ai-tool-card--collapsed': !isExpanded && !shouldKeepOpen(tc), 'ai-tool-card--failed': isToolFailure(tc) }, + { 'ai-tool-card--collapsed': !isExpanded && !shouldKeepOpen(tc), 'ai-tool-card--failed': isFailed }, ]" > @@ -14,10 +14,10 @@
{{ toolDisplayName(tc) }} {{ $t('aiTool.running') }} - {{ $t('aiTool.executionFailed') }} + {{ $t('aiTool.executionFailed') }} {{ toolResultSummary(tc) }}
- +
@@ -73,9 +73,11 @@
{{ parsed?.path }} - {{ parsed?.lines || 0 }} {{ $t('aiTool.lines') }} · {{ formatBytes(parsed?.size) }} + {{ parsed?.has_more + ? $t('aiTool.linesTruncated', { shown: parsed?.returned_lines ?? 0, total: parsed?.lines ?? 0 }) + : `${parsed?.lines || 0} ${$t('aiTool.lines')}` }} · {{ formatBytes(parsed?.size) }}
{{ parsed?.content }}
@@ -87,6 +89,7 @@ {{ parsed?.path }} {{ parsed?.entries?.length || 0 }} {{ $t('aiTool.items') }} + {{ $t('aiTool.dirTruncated', { n: 1000 }) }}
+ +
+
+ + {{ parsed?.path }} + 「{{ parsed?.pattern }}」 + {{ $t('aiTool.foundN', { n: typeof parsed?.total === 'number' ? parsed.total : (parsed?.results?.length || 0) }) }} +
+
+
+ + {{ entry.path }} + {{ formatBytes(entry.size) }} +
+
+
+
{{ parsed?.path }} {{ formatBytes(parsed?.bytes_written) }} {{ $t('aiTool.written') }} + {{ $t('aiTool.writeOverwrite', { old: formatBytes(parsed.old_size), neww: formatBytes(parsed?.bytes_written) }) }} + {{ $t('aiTool.encodingLabel', { enc: parsed.encoding }) }} +
+ + +
+
+ + {{ parsed?.path }} + {{ formatSizeDiff(parsed?.size_diff) }} {{ $t('aiTool.bytesUnit') }} + {{ $t('aiTool.patchedMatches', { n: parsed.matches_found }) }} +
+
⚠ {{ parsed.warning }}
+
+
{{ ln.text }}{{ '\n' }}
+
+
+ + +
+
+ $ + {{ argString(tc.args, 'command') }} + +
+
{{ cmdOutput }}
-
⚠ {{ $t('aiTool.executionFailedHint') }}
+
⚠ {{ $t('aiTool.executionFailedHint') }}
{{ formatToolResult(tc) }}
@@ -146,6 +201,7 @@ const t = ((i18n as any).global.t as (k: string, p?: Record) => /** 工具结果 JSON 已知字段(全可选;list_* 运行时返回数组,靠 Array.isArray 区分) */ interface ToolResult { path?: string + pattern?: string from?: string to?: string content?: string @@ -184,6 +240,20 @@ interface ToolResult { has_more?: boolean warning?: string review_rounds?: number + // UX-260618-05~12:补全后端返回前端消费的字段(review_rounds/truncated/bytes_moved/has_more/total/entries 已存在) + task_id?: string + execution_id?: string + target_status?: string + note?: string + diff?: string + matches_found?: number + backup_path?: string + returned_lines?: number + items?: Array> + modified?: number | null + old_size?: number | null + encoding?: string + cross_volume?: boolean } /** 内联 SVG 图标字符串(经 v-html 注入,常量安全;图标库改造见决策记录) */ @@ -239,6 +309,12 @@ function formatToolName(name: string): string { return name.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase()) } +/** run_command 命令首行截断(头部展示;多行命令取首行,超长省略) */ +function shortCmd(cmd: string, max = 48): string { + const firstLine = cmd.split('\n')[0].replace(/\s+/g, ' ').trim() + return firstLine.length > max ? firstLine.slice(0, max) + '…' : firstLine +} + function formatJson(data: unknown): string { try { return JSON.stringify(data, null, 2) @@ -365,6 +441,30 @@ function formatToolResult(tc: AiToolCallInfo): string { return raw } switch (tc.name) { + case 'list_projects': + case 'list_tasks': + case 'list_ideas': + case 'list_trash': { + // UX-260618-12: list_* 后端返 {items,total,has_more} 对象(非数组),渲染结构化列表替代裸 JSON + const items = Array.isArray(r) ? r : (Array.isArray(r.items) ? r.items : []) + const total = typeof r.total === 'number' ? r.total : items.length + const head = tc.name === 'list_projects' ? t('aiTool.projectCount', { n: total }) + : tc.name === 'list_tasks' ? t('aiTool.taskCount', { n: total }) + : tc.name === 'list_ideas' ? t('aiTool.ideaCount', { n: total }) + : t('aiTool.trashCount', { n: total }) + const lines = items.slice(0, 10).map((it: Record) => { + const label = typeof it.name === 'string' ? it.name + : typeof it.title === 'string' ? it.title + : typeof it.id === 'string' ? it.id + : '' + const status = typeof it.status === 'string' ? ` [${it.status}]` : '' + const stack = typeof it.stack === 'string' ? ` (${it.stack})` : '' + return label ? `- ${label}${status}${stack}` : '' + }).filter(Boolean) + const parts = [head, ...lines] + if (r.has_more) parts.push(t('aiTool.listHasMore', { n: items.length })) + return parts.join('\n') + } case 'write_file': return `${r.path} (${formatBytes(r.bytes_written)})` case 'update_task': @@ -372,13 +472,16 @@ function formatToolResult(tc: AiToolCallInfo): string { ? t('aiTool.updatedTaskField', { id: r.id || '', field: r.field || '' }) : t('aiTool.updated') case 'advance_task': - return t('aiTool.advancedTask', { id: r.id || '', status: r.status || '' }) + // UX-260618-09: review_rounds>0 显退回累加轮数,=0 回退原文案(首推无噪音) + return typeof r.review_rounds === 'number' && r.review_rounds > 0 + ? t('aiTool.advancedTaskWithRounds', { id: r.id || '', status: r.status || '', n: r.review_rounds }) + : t('aiTool.advancedTask', { id: r.id || '', status: r.status || '' }) case 'delete_task': return r.deleted ? t('aiTool.deleted') : t('aiTool.deleteIneffective') case 'delete_file': return r.permanent ? t('aiTool.deleteFilePermanent', { path: r.path || '' }) - : t('aiTool.deleteFileSoft', { path: r.path || '' }) + : t('aiTool.deleteFileSoft', { path: r.path || '', backup_path: r.backup_path || '' }) case 'run_command': return formatCommandResult(r) case 'patch_file': @@ -389,7 +492,9 @@ function formatToolResult(tc: AiToolCallInfo): string { return t('aiTool.appendedTo', { path: r.path || '', bytes: formatBytes(r.bytes_written) }) case 'rename_file': return r.renamed - ? t('aiTool.renamedFile', { from: r.from || '', to: r.to || '' }) + ? (r.cross_volume + ? t('aiTool.renamedCrossVolume', { from: r.from || '', to: r.to || '', bytes: formatBytes(r.bytes_moved) }) + : t('aiTool.renamedWithBytes', { from: r.from || '', to: r.to || '', bytes: formatBytes(r.bytes_moved) })) : formatJson(r) case 'search_files': { const n = typeof r.total === 'number' ? r.total : (Array.isArray(r.results) ? r.results.length : 0) @@ -401,6 +506,14 @@ function formatToolResult(tc: AiToolCallInfo): string { return r.bound ? t('aiTool.boundDir', { path: r.path || '', stack: r.stack || '' }) : formatJson(r) + case 'run_workflow': { + // UX-260618-05: 后端返 {task_id,target_status,execution_id,status,note},显 execution_id 对应执行实例 + const execId = typeof r.execution_id === 'string' && r.execution_id + ? r.execution_id + : (typeof r.id === 'string' ? r.id : '') + if (!execId) return formatJson(r) + return t('aiTool.workflowTriggered', { id: r.task_id || '', status: r.target_status || '', exec: execId }) + } default: return formatJson(r) } @@ -435,13 +548,18 @@ function formatCommandResult(r: ToolResult): string { return parts.join('\n') } -/** 合并 stdout/stderr 取前 N 行,返回截断后文本 + 是否截断 + 总行数 */ -function combineAndTruncateLines(stdout: string | undefined, stderr: string | undefined, maxLines: number): { text: string; truncated: boolean; total: number } { +/** SW-260618-19: 合并 stdout/stderr(空串跳过),供 cmdOutput 展开 + combineAndTruncateLines 截断共享 DRY */ +function combineOutputs(stdout: string | undefined, stderr: string | undefined): string { const parts: string[] = [] if (stdout) parts.push(stdout) if (stderr) parts.push(stderr) - if (!parts.length) return { text: '', truncated: false, total: 0 } - const combined = parts.join('\n') + return parts.join('\n') +} + +/** 合并 stdout/stderr 取前 N 行,返回截断后文本 + 是否截断 + 总行数(合并复用 combineOutputs) */ +function combineAndTruncateLines(stdout: string | undefined, stderr: string | undefined, maxLines: number): { text: string; truncated: boolean; total: number } { + const combined = combineOutputs(stdout, stderr) + if (!combined) return { text: '', truncated: false, total: 0 } const allLines = combined.split('\n') const total = allLines.length if (total <= maxLines) { @@ -458,6 +576,10 @@ function formatFileInfoResult(r: ToolResult): string { } let base = t('aiTool.fileInfoSize', { path: r.path || '', size: formatBytes(typeof r.size === 'number' ? r.size : 0) }) const extras: string[] = [] + // UX-260618-12: file_info modified 时间(epoch_ms → 客户端 toLocaleString) + if (typeof r.modified === 'number' && r.modified > 0) { + extras.push(t('aiTool.fileInfoModified', { time: new Date(r.modified).toLocaleString() })) + } if (r.is_dir) { extras.push(t('aiTool.fileInfoDir')) } else { @@ -505,6 +627,12 @@ const emit = defineEmits<{ approve: [{ id: string; approved: boolean }] }>() +// run_command 输出折叠态:失败命令默认展开(便于看 stderr),成功默认折叠 +// 注:此处仅状态翻转时调一次,不进模板高频路径,沿用模块级 isToolFailure(props.tc) 无性能问题。 +const cmdOutputExpanded = ref(false) +// SW-260618-06: cmdOutputExpanded 的 status watch 合并到下方 approving 复位 watch +// (approving/approvingTimer 在下方定义,此处 immediate 会 TDZ,合并 watch 放三 ref 都已定义后)。 + /** * 审批按钮 loading 态(B-260616-08)。 * 点击后置 true,禁用两按钮防重复点击并显示 spinner。后端回事件 useAiEvents 把 tc.status @@ -570,13 +698,16 @@ async function onApprove(approved: boolean) { }, APPROVE_LOADING_TIMEOUT_MS) emit('approve', { id: props.tc.id, approved }) } -// status 离开 pending_approval → 后端已回事件 → 复位 loading 并清计时器 +// SW-260618-06: 合并双 watch(原 cmdOutputExpanded watch + approving 复位 watch,同源 props.tc.status)。 +// immediate 时 s=初始 status:cmdOutputExpanded 仅 completed 初始化(初始非 completed 无副作用); +// approving 初始 false + approvingTimer 初始 null(if null 跳过 clearTimeout),immediate 安全。 watch(() => props.tc.status, (s) => { + if (s === 'completed') cmdOutputExpanded.value = isToolFailure(props.tc) if (s !== 'pending_approval') { approving.value = false if (approvingTimer) { clearTimeout(approvingTimer); approvingTimer = null } } -}) +}, { immediate: true }) // 组件卸载清计时器防泄漏 onBeforeUnmount(() => { if (approvingTimer) clearTimeout(approvingTimer) @@ -586,6 +717,34 @@ onBeforeUnmount(() => { /** 一次解析结果供模板多次读(模板引用 6 次,computed 避免每次 patch 重 parse) */ const parsed = computed(() => parseResult(props.tc.result)) +/** + * 是否工具失败(completed 语义)。模板多处复用(.ai-tool-card--failed / 状态点 / 失败横幅等, + * 6+ 引用)。原每处直调 isToolFailure(tc) → 每次 patch 重新 parseResult + 正则, + * 现下沉为 computed 复用 parsed,patch 间不重算。逻辑与模块级 isToolFailure 同源。 + */ +const isFailed = computed(() => { + const tc = props.tc + if (tc.status !== 'completed') return false + const r = parsed.value + if (tc.name === 'run_command') { + if (r && typeof r.exit_code === 'number') return r.exit_code !== 0 + if (r) return true + } + if (!r) { + const raw = typeof tc.result === 'string' ? tc.result : '' + if (!raw) return false + return /^(执行失败|Error:|Failed:)/m.test(raw) + } + return false +}) + +/** run_command 展开态全量输出(stdout+stderr 合并);空串隐藏 toggle。复用 parsed 不重 parse */ +const cmdOutput = computed(() => { + const r = parsed.value + if (!r) return '' + return combineOutputs(r.stdout, r.stderr) +}) + /** * AE-2025-03: write_file 审批 diff 行解析。 * 后端 generate_diff 输出 '+新行/-旧行/ 上下文行',逐行拆 → {kind, text} 供模板按 kind 着色。 @@ -603,6 +762,29 @@ const diffLines = computed(() => { }).filter((x): x is { kind: string; text: string } => x !== null) }) +/** + * UX-260618-06: patch_file 结果 diff 行拆解(复用 diffLines 同款 +add/-del/ ctx 着色)。 + * 数据源不同:diffLines 取 props.tc.diff(审批事件注入,仅 write_file);此 computed 取 + * parsed.diff(patch_file 工具执行结果字段)。两条路径互不干扰。 + * 长 diff 截断到前 120 行防撑爆卡片(unified diff 含上下文行,大改可能很长)。 + */ +const resultDiffLines = computed(() => { + const d = parsed.value?.diff + if (!d) return [] + const MAX = 120 + const lines = d.split('\n').map(line => { + if (line === '') return null + if (line.startsWith('+')) return { kind: 'add', text: line } + if (line.startsWith('-')) return { kind: 'del', text: line } + return { kind: 'ctx', text: line } + }).filter((x): x is { kind: string; text: string } => x !== null) + if (lines.length > MAX) { + lines.splice(MAX) + lines.push({ kind: 'ctx', text: '…' }) + } + return lines +}) + /** * 按 id 查项目名(覆盖活跃 + 回收站):审批 delete/restore/purge 各阶段都可能引用项目, * 故先查 projects 再查 deletedProjects,找到即返回(提前退出),无则返回 undefined(调用方走 fallback)。 @@ -665,6 +847,11 @@ function toolDisplayName(tc: AiToolCallInfo): string { } case 'create_task': return t('aiTool.createTask') case 'create_project': return t('aiTool.createProject') + case 'run_command': { + // 折叠态头部也显命令(取首行+截断),无 command 回退通用名 + const cmd = argString(tc.args, 'command') + return cmd ? `$ ${shortCmd(cmd)}` : formatToolName(tc.name) + } default: return formatToolName(tc.name) } } @@ -679,9 +866,21 @@ function toolResultSummary(tc: AiToolCallInfo): string { const r = parseResult(tc.result) if (!r) return '' switch (tc.name) { - case 'list_tasks': return Array.isArray(r) ? t('aiTool.taskCount', { n: r.length }) : '' - case 'list_projects': return Array.isArray(r) ? t('aiTool.projectCount', { n: r.length }) : '' - case 'list_ideas': return Array.isArray(r) ? t('aiTool.ideaCount', { n: r.length }) : '' + case 'list_tasks': + case 'list_projects': + case 'list_ideas': + case 'list_trash': { + // UX-260618-10/12: 后端返 {items,total,has_more} 对象(非数组),原 Array.isArray(r) 恒 false→折叠态无计数 + const n = Array.isArray(r) ? r.length + : (typeof r.total === 'number' ? r.total + : (Array.isArray(r.items) ? r.items.length : 0)) + if (!n) return '' + const key = tc.name === 'list_projects' ? 'projectCount' + : tc.name === 'list_tasks' ? 'taskCount' + : tc.name === 'list_ideas' ? 'ideaCount' + : 'trashCount' + return t(`aiTool.${key}`, { n }) + } case 'create_project': return r.name ? t('aiTool.createdWithName', { name: r.name }) : t('aiTool.created') case 'create_idea': case 'create_task': return r.title ? t('aiTool.createdWithName', { name: r.title }) : t('aiTool.created') @@ -689,12 +888,14 @@ function toolResultSummary(tc: AiToolCallInfo): string { case 'update_task': return r.updated !== false ? t('aiTool.updatedTaskField', { id: r.id || '', field: r.field || '' }) : t('aiTool.updated') - case 'advance_task': return t('aiTool.advancedTask', { id: r.id || '', status: r.status || '' }) + case 'advance_task': return typeof r.review_rounds === 'number' && r.review_rounds > 0 + ? t('aiTool.advancedTaskWithRounds', { id: r.id || '', status: r.status || '', n: r.review_rounds }) + : t('aiTool.advancedTask', { id: r.id || '', status: r.status || '' }) case 'delete_project': case 'delete_task': return r.deleted ? t('aiTool.deleted') : t('aiTool.deleteIneffective') case 'delete_file': return r.permanent ? t('aiTool.deleteFilePermanent', { path: r.path || '' }) - : t('aiTool.deleteFileSoft', { path: r.path || '' }) + : t('aiTool.deleteFileSoft', { path: r.path || '', backup_path: r.backup_path || '' }) case 'restore_project': return r.restored ? t('aiTool.restored') : t('aiTool.restoreIneffective') case 'purge_project': return r.purged ? t('aiTool.purged') : t('aiTool.purgeIneffective') case 'run_command': { @@ -707,7 +908,9 @@ function toolResultSummary(tc: AiToolCallInfo): string { : t('aiTool.patchedFile', { path: r.path || '', diff: formatSizeDiff(r.size_diff) }) case 'append_file': return t('aiTool.appendedTo', { path: r.path || '', bytes: formatBytes(r.bytes_written) }) case 'rename_file': return r.renamed - ? t('aiTool.renamedFile', { from: r.from || '', to: r.to || '' }) + ? (r.cross_volume + ? t('aiTool.renamedCrossVolume', { from: r.from || '', to: r.to || '', bytes: formatBytes(r.bytes_moved) }) + : t('aiTool.renamedWithBytes', { from: r.from || '', to: r.to || '', bytes: formatBytes(r.bytes_moved) })) : '' case 'search_files': { const n = typeof r.total === 'number' ? r.total : (Array.isArray(r.results) ? r.results.length : 0) @@ -719,7 +922,13 @@ function toolResultSummary(tc: AiToolCallInfo): string { case 'bind_directory': return r.bound ? t('aiTool.boundDir', { path: r.path || '', stack: r.stack || '' }) : '' - case 'run_workflow': return t('aiTool.workflowHint') + case 'run_workflow': { + // UX-260618-05: header 副标题显 execution_id 概要(原 workflowHint「请到工作流页面运行」误导——工作流已触发) + const execId = typeof r.execution_id === 'string' && r.execution_id + ? r.execution_id + : (typeof r.id === 'string' ? r.id : '') + return execId ? t('aiTool.workflowTriggeredShort', { exec: execId }) : '' + } default: return '' } } @@ -1204,6 +1413,64 @@ function toolResultSummary(tc: AiToolCallInfo): string { word-break: break-all; } +/* -- run_command 结果:命令行(常显) + 可折叠输出 -- */ +.ai-tool-cmd-result { + margin: 0; + border-top: 0.5px solid var(--df-border); + font-family: var(--df-font-mono); + font-size: 11px; +} +.ai-tool-cmd-line { + display: flex; + align-items: center; + gap: 6px; + padding: 6px 10px; + background: rgba(61,219,160,0.04); +} +.ai-tool-cmd-line--failed { + background: var(--df-danger-bg); +} +.ai-tool-cmd-prompt { + flex-shrink: 0; + font-weight: 600; + color: var(--df-success); +} +.ai-tool-cmd-line--failed .ai-tool-cmd-prompt { + color: var(--df-danger); +} +.ai-tool-cmd-text { + flex: 1; + min-width: 0; + white-space: pre-wrap; + word-break: break-all; + color: var(--df-text); +} +.ai-tool-cmd-toggle { + flex-shrink: 0; + padding: 2px 4px; + border: none; + border-radius: var(--df-radius-sm); + background: transparent; + color: var(--df-text-dim); + font-size: 10.5px; + cursor: pointer; +} +.ai-tool-cmd-toggle:hover { + color: var(--df-text); + background: var(--df-bg-hover, rgba(255,255,255,0.06)); +} +.ai-tool-cmd-output { + margin: 0; + padding: 8px 10px; + max-height: 160px; + overflow-y: auto; + background: rgba(0,0,0,0.18); + color: var(--df-text-dim); + white-space: pre-wrap; + word-break: break-all; + border-top: 0.5px solid var(--df-border); +} + /* -- UX-260617-14: 审批 loading 超时兜底 toast(复用 AiChat 同款视觉 token) -- */ .ai-tool-toast { position: absolute; diff --git a/src/components/ToolCardList.vue b/src/components/ToolCardList.vue index d3d6aab..ec523ec 100644 --- a/src/components/ToolCardList.vue +++ b/src/components/ToolCardList.vue @@ -1,9 +1,9 @@