# todo 已完成项归档（2026-06）

> 从 docs/todo.md 拆出: 已完成 `[x]` 条目段 + 纯历史分析段。
> 维护: 随开发追加; 主看板只留未完成 `[ ]` 与导航/总览。
> 拆分日期: 2026-06-18
> 来源: docs/todo.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)

> **⚠ 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-16 排查会话新报 bug（4 项·仅分析未实施）

> 排查性质会话（session-role-diagnose-only），仅走查定位根因 + 记录待办，未改代码。

- [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/数据变动。

### 🔧 2026-06-17 实测发现 bug（审批流程三连）

> 用户实测：AI 对话中让 AI 创建任务并推进状态 → AI 调 `run_workflow(task_id, target_status)` → 审批卡片渲染 → 用户点批准 → handler 返回 Err → LLM 重试循环。

- [x] ✅(波9·e23b316+主代核查,cargo0err+devflow79pass,方案a2后端分支) B-260617-01 [P1] — **run_workflow 作为 AI 工具无法执行 + 无意义重试 + 审批混淆**。三个关联问题：
  - **问题 1**：`run_workflow` handler（tool_registry.rs:530-546）仅持有 `db: Arc<Database>`，无法访问 `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

### 🔧 2026-06-16 aichat 工具卡片重复渲染排查（仅分析·未实施）

> 排查性质（session-role-diagnose-only），走查定位根因 + 记录待办，未改代码。

- [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<Vec<ContentPart>>` 挂载，老 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 态内 ✓。

### 🔧 2026-06-18 走查·虚拟滚动修复链（B-260617-09 销账核对）

> 核验 8abcd56/0ca5d98/02bee38 三 commit（B-260617-09 重叠修复链）。session-role-diagnose-only，独立核验代码形态非信 commit msg。

**8abcd56（方案 A·治本）✓**：卸载分支去 `height>0` 守卫，height>0 用实测 / 否则 offsetHeight / 再否则 40px 兜底（useAiVirtualScroll.ts:72-76）；额外修 registerSentinel 首帧占位（:107-109 h≤0 设 40px 防 list 首渲染塌 0）。实施正确，比我原建议完善。

**0ca5d98（🔴 临时禁用未回退·P1 遗留）**：`shouldRender` 恒 `return true`（useAiVirtualScroll.ts:138），虚拟滚动裁剪**完全关闭**。注释标"临时禁用·定位后回退"但**仍在 HEAD**。三重问题：①8abcd56 已修根因，禁用多余；②裁剪关闭致 8abcd56 卸载 fallback **被绕过**（不走卸载分支）；③长对话所有消息恒渲染，虚拟滚动形同关闭性能下降。**应回退**：恢复 `return renderedKeys.value.has(key) || pinnedKeys.value.has(key)`。

**02bee38 销账 B-260617-09**：根因 8abcd56 修了，0ca5d98 诊断未回退销账略早。**→ 2026-06-18 最终决策：彻底移除虚拟滚动**（见下工作区进展），非回退 0ca5d98 恢复裁剪。B-260617-09 真闭环。

- [x] ✅(2026-06-18·转向彻底移除·工作区待提交) **B-260618-01** — 原"回退 0ca5d98 恢复虚拟滚动裁剪"**→ 转向彻底移除虚拟滚动**（删 useAiVirtualScroll.ts + AiChat.vue 移除全链路，见下进展段）。理由：IO/RO 时序竞态致重叠（shouldRender 恒 true 时 IO 仍设/清 sentinel minHeight 竞态源未除）+ 消息量级不需要。B-260617-09 真闭环。

> **🔥 2026-06-18 工作区进展（方案变更·虚拟滚动整体删除）**：wf agent 未回退 0ca5d98，而是**彻底删除 useAiVirtualScroll.ts**（工作区 `D`，-175 行）+ AiChat.vue 移除全部 6 处引用（模板 ref / import / 解构 / onMounted setupVirtualScroll / lastStreamingRenderKey+watch），`grep` 确认**无残留引用**（编译安全）。重叠由此根治（移除 IO/RO 时序复杂度源）。**B-260618-01 以"删除文件"方式解决**（非回退临时禁用）。**代价**：长对话所有消息恒渲染（无虚拟化），性能下降——正确性优先权衡。**均工作区未提交**（HEAD 仍 0ca5d98），待提交入库。同期改动：ToolCard run_command 增强 + ToolCardList userCollapsedCards 修复首卡折叠 bug（见 B-260618-03）+ **U-260618 global-toggle（全部展开/收起）从 topbar 下沉到首个可折叠分组标题行尾巴**（firstCollapsibleGi computed，与 run_command 分组标题"run command N"同行，chevron 移除 margin-left:auto）+ **分组标题行加 max-width:var(--df-msg-max-width)(90%) 对齐气泡/卡片宽度**（原无 max-width 拉满 100% > 气泡/卡片 90%）+ global-toggle 移除独立按钮 padding/背景高度=文字行高不撑高标题行（见 B-260618-04）。

- [x] ✅(2026-06-18·工作区待提交·vue-tsc EXIT 0) **B-260618-03** — **ToolCardList 首卡折叠 bug**。`isCardExpanded`（ToolCardList.vue）`ci===0` 兜底 `return true` 覆盖用户折叠——多卡分组首卡默认展开，用户点折叠后 ci===0 仍 true 重新展开。**修复**：加 `userCollapsedCards` 记忆态，`isCardExpanded` 最优先判断用户折叠（覆盖 ci===0 兜底 + userExpandedCards 记忆）；`toggleCardExpand` 改用 isCardExpanded 判当前真实态（避免首卡 toggle 误判反向展开）；template `@toggle` 传 tc/gi/ci。— src/components/ToolCardList.vue(isCardExpanded + toggleCardExpand + userCollapsedCards)

- [x] ✅(2026-06-18·工作区待提交·vue-tsc EXIT 0) **B-260618-04** — **ToolCardList 全部展开/收起下沉 + 宽度对齐（U-260618）**。①global-toggle 从 topbar 下沉到首个可折叠分组标题行尾巴（firstCollapsibleGi computed，与 run_command 分组标题同行，chevron 移除 margin-left:auto）；②分组标题行加 max-width:var(--df-msg-max-width)(90%) 对齐气泡/卡片宽度（原无 max-width 拉满 100%）；③global-toggle 移除独立按钮 padding/背景高度=文字行高不撑高标题行。— src/components/ToolCardList.vue(firstCollapsibleGi + template global-toggle 下沉 + CSS)

- [x] ✅(2026-06-18·工作区待提交·vue-tsc EXIT 0) **B-260618-05** — **AI 独立窗口吸附挤出屏幕 + 宽度偏窄**。①吸附挤出:`syncToMain` 把 AI 窗口放主窗口右(pos.x+size.width+GAP),主窗口最大化时 AI x=屏幕宽+4 挤出屏幕。**修复**:syncToMain 增强——主窗口 isMaximized 时 unmaximize + 主+AI 宽度超屏幕时缩小主窗口(newMainWidth=screenWidth-GAP-AI_WIDTH-pos.x,最小 400px 保护)+ _isSyncing flag 防 syncToMain 改主窗口 size 触发 onResized 递归;屏幕宽度用顶层函数 `currentMonitor()`(@tauri-apps/api/window,非 Window 实例方法)。②独立窗口窄:detachPanel width 520→600 对齐吸附态 AI_DOCK_WIDTH。— src/composables/ai/useAiWindow.ts(syncToMain + detachPanel)

### 🔧 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 分钟验证）。

**现象确认（基于截图）**：
| 现象 | 判定 |
|---|---|
| 顶部出现 `<transition name="sidebar-slide">` 等模板源码文本 | ✅ 正常 — AI 回复中 read_file 读取 AiChat.vue 源码后 v-html 展示，非渲染泄漏 |
| 消息/工具卡大量重叠堆叠 | 🔴 异常 — 布局层叠问题 |
| 整体挤压、间距消失 | 🔴 异常 |

**已排除项**：

| 检查点 | 结论 |
|---|---|
| CSS 声明链路 | ✅ `.ai-messages` 有 `display:flex;flex-direction:column;gap:14px`（:3109-3116），无外部覆盖 |
| scoped 样式冲突 | ✅ 单 `<style scoped>` 块，global.css 无 ai-messages 覆盖 |
| markdown 渲染安全 | ✅ DOMPurify sanitize + escapeFallback 兜底（useMarkdown.ts:181-192） |
| DOM 结构正确性 | ✅ `.ai-msg-slot` 是 `.ai-messages` 直接子元素，gap 应生效 |

---

## 角度①：虚拟滚动 IntersectionObserver 时序与渲染逻辑

**Vue 3 ref 时序修正**：`.ai-messages`(ref=messagesContainer) 是**父元素**，其 ref 先于 v-for 子元素 `.ai-msg-slot` 的 ref 回调赋值。所以 `registerSentinel` 执行时 `options.root.value` **理论上已就绪**，IO/RO 应能正常创建和 observe。**假设「root 未就绪导致漏 observe」概率下调但仍不能排除——因为 Vue 3 的 ref 赋值时机是微任务队列中的异步操作，v-for 子元素的 ref 回调可能在同一次微任务中但顺序不确定。

**关键执行路径核验**：

```
registerSentinel(key, el) 执行流程:
  ① el instanceof HTMLElement? ✓ (DOM 已创建)
  ② el.dataset.vscrollKey = key
  ③ h = prev?.height ?? 0 → 首次 h=0
  ④ h>0? → 否,跳过 minHeight 设置
  ⑤ itemMeta.set(key, {el, height:0})
  ⑥ ensureObserver() → rootEl=options.root.value
     - 若 rootEl 存在且 io 为空 → 创建 IO+RO → io.observe(el)+ro.observe(el) ✓
     - 若 rootEl 为 null → 静默返回,io/ro 仍为 null → observe 全部跳过 ✗
  ⑦ renderedKeys 不含 key → add(key)
```

**如果路径 ⑥ 走了 ✓ 分支（IO 正常创建）**：
- IO 以 rootMargin:600px 观察 sentinel → 首屏所有 item 都在可见区 → 全部 add 到 renderedKeys → **所有消息渲染** → 此时不应有重叠
- RO 测量每个 sentinel 的真实高度 → itemMeta.height 被回填
- 滚动后不可见 item → IO 回调触发删除 → 设置 minHeight 锁高度 → 内容卸载(v-else-if=false) → slot 占位

**如果路径 ⑥ 走了 ✗ 分支（rootEl 为 null）**：
- 所有 sentinel 的 io/ro observe 被跳过
- onMounted → setupVirtualScroll() → ensureObserver() → **此时 rootEl 就绪** → 创建 IO+RO
- **但已注册的元素未被重新 observe！** setupOnMount 注释明确写"不主动全量 observe"
- IO 永远不触发回调 → renderedKeys 只增不减 → **所有消息始终渲染** → 单纯"虚拟化失效"不应导致重叠
- **除非后续操作**（消息新增/对话切换）触发 registerSentinel 重跑 → 此时 IO 已存在 → 新 item 被 observe → IO 开始工作 → 但旧 item 仍从未被 observe

**角度①结论**：IO 漏 observe 导致**纯虚拟化失效**（全部渲染）不会直接造成重叠。但如果 IO 在某个时刻开始部分工作（如新增消息触发的 registerSentinel 让 IO 活跃起来），可能出现**不一致状态**：部分 item 被 IO 管理（可卸载）、部分不被管理（永远渲染）。这种半激活态下若发生卸载而 height=0（RO 从未测过）→ minHeight 不设置 → slot 塌 0 → **可能重叠**。**概率：中（需特定时序条件触发）**。

---

## 角度②：CSS 布局嵌套链路分析

**完整 flex 嵌套层级（6 层）**：

```
.ai-panel          display:flex; flex-direction:row; height:100%
  └─ .ai-chat-area   position:relative; flex:1; display:flex; flex-direction:column; height:100%; min-width:0
       ├─ .ai-header   (固定高度 ~44px)
       ├─ .provider-bar (~32px)
       └─ .ai-messages  flex:1; overflow-y:auto; display:flex; flex-direction:column; gap:14px; padding:14px
            └─ .ai-msg-slot  display:flex; flex-direction:column  ← UX-19 新增 wrapper
                 └─ .ai-msg  (role-based class)
                      ├─ .ai-msg-user  display:flex; gap:8px; align-items:flex-start; flex-direction:row-reverse
                      └─ .ai-msg-ai    display:flex; gap:8px; align-items:flex-start
                           └─ .ai-msg-content  flex:1; min-width:0; display:flex; flex-direction:column; gap:8px
                                ├─ .ai-msg-bubble  (内容)
                                ├─ .ai-msg-actions (按钮组)
                                ├─ ToolCardList    (工具卡列表)
                                └─ .ai-msg-time    (时间戳)
```

**全局规则影响**：
- `* { margin:0; padding:0; box-sizing:border-box }`（global.css:100）→ 消除浏览器默认 margin/padding，对 flex gap 计算无负面影响
- `html,body,#app { overflow:hidden }`（:105）→ 根级不滚动，`.ai-messages` 的 `overflow-y:auto` 独立滚动，**这是正确的**
- **无冲突点**

**UX-19 改前 vs 改后对比**：
| | 改前 | 改后 |
|---|---|---|
| `.ai-messages` 直接子元素 | `.ai-msg` / `.ai-msg-segment` | `.ai-msg-slot`（新 wrapper） |
| gap 作用对象 | 消息/分隔条本身 | slot 容器 |
| slot 内部布局 | N/A | `display:flex;flex-direction:column` |

**slot 作为额外 flex 容器的影响**：`.ai-msg-slot { display:flex;flex-direction:column }` 将 `.ai-msg` 变为 flex item。由于 `.ai-msg` 自身也是 flex 容器（user/ai 方向），这增加了一层 flex 格式化上下文。在标准浏览器中这不应导致问题，但增加了嵌套深度到 **7 层**。

**角度②结论**：CSS 声明链路干净，无全局覆盖。UX-19 多一层 slot wrapper 增加嵌套深度但在标准 Chromium 中不导致重叠。**概率：低（非根因）**。

---

## 角度③：消息数据→渲染一致性分析

**renderItems 生成逻辑**（:2511-2527）：
```typescript
// messageSegments computed → 按 status 分段(normal/archived/compressed)
// renderItems computed → 扁平化:
//   normal 段 → { kind:'msg', key:'m-'+msg.id, msg }
//   折叠段   → { kind:'sep', key:seg.key, seg } + 展开时追加 { kind:'msg', key:'e-'+seg.key+'-'+msg.id, msg }
```

**key 唯一性**：
- normal 段 key = `'m-' + msg.id` → msg.id 全局唯一 → key 唯一 ✅
- 展开折叠段 key = `'e-' + seg.key + '-' + msg.id` → seg.key 含 msg.id 前缀 → 组合唯一 ✅
- sep key = `'seg-' + startMsgId → 按 status 连续分组 → key 唯一 ✅
- **无重复 key 风险** ✅

**切换对话时的清理**：
- `switchConversation` 替换 `store.state.messages` → messageSegments 重新计算 → renderItems 全部重建
- Vue 的 v-for + :key 机制：旧 key 对应的 DOM 元素被销毁 → 触发旧 sentinel 的 `registerSentinel(key, null)` 卸载分支 → io.unobserve + ro.unobserve + itemMeta 保留 height
- **清理链路完整** ✅

**流式生成中新增消息**：
- 新消息 push 到 store.state.messages → messageSegments 更新 → renderItems 追加新 item → 模板 v-for 挂载新 `.ai-msg-slot` → registerSentinel(newKey, newEl)
- 此时 IO/RO 已存在（onMounted 早已执行）→ ensureObserver 直接返回（io 非 null）→ io.observe(newEl) + ro.observe(newEl) **✅ 正常**
- **新增消息路径无问题** ✅

**角度③结论**：数据→渲染链路 key 唯一、切换清理完整、流式新增正常。**概率：极低（非根因）**。

---

## 角度④：ToolCard 内部布局对父级影响

**ToolCard 绝对定位元素清单**：

| 选择器 | 位置 | 用途 | 影响 |
|---|---|---|---|
| `.ai-tool-file-pre--collapsed::after` | :1080-1089 | 文件预览渐变遮罩(48px高) | `position:absolute; bottom:0` — 伪元素脱离文档流 |
| `.ai-sidebar-resize-handle` | AiChat.vue:2641-2649 | 侧栏拖拽条 | `position:absolute; height:100%` — 在 sidebar 上，不影响 messages |

**关键分析：`.ai-tool-file-pre--collapsed::after`**：
- 父元素 `.ai-tool-file-pre--collapsed` 设了 `position:relative`（:1078）→ absolute 伪元素相对于它定位
- 伪元素 `height:48px` 覆盖底部，**但不撑高父元素**（absolute 元素不参与父级高度计算）
- **然而**：`.ai-tool-file-pre--collapsed` 本身有 `max-height:180px; overflow-y:auto`（:1074-1077）→ 内容超出 180px 时内部滚动 → 父元素高度锁定 180px → **不受 absolute 伪元素影响**
- **结论：此 absolute 伪元素不会导致父级高度塌陷** ✅

**ToolCardList 布局**（ToolCardList.vue:282-284）：
```css
/* ToolCardList 容器 */
display: flex;
flex-direction: column;
gap: 8px;
```
- 每个 ToolCard 是 flex item，高度由内容决定
- 折叠态 ToolCard 用 `v-show`（非 v-if）→ `display:none` 彻底移出流 → 不占空间 ✅
- running/pending_approval 强制展开 → ToolCard 可见 → 占据实际高度

**极端场景模拟**：单条 AI 消息含 **N 个 ToolCard**（截图场景看起来有 10+ 个工具调用）：
- 每个 ToolCard 含 header(~36px) + body(文件预览/参数/结果, 可能 200-500px) + footer
- 单条 AI 消息总高度可能达 **2000-5000px**
- `.ai-msg-content`(flex-direction:column) 正确包含全部高度 ✅
- `.ai-msg-ai`(flex) 正确包含 content ✅
- `.ai-msg-slot`(flex-direction:column) 正确包含 `.ai-msg` ✅

**角度④结论**：ToolCard 内部 absolute 定位有 `position:relative` 父容器约束，不会导致高度塌陷。工具卡密集导致的单条消息极高在 flex 嵌套中能正确传播高度。**概率：低（非根因）**。

---

## 角度⑤：Tauri/WebView2 特有问题

**WebView2 版本**：Tauri v2 使用系统安装的 WebView2 Runtime（非捆绑）。Windows 11 默认 WebView2 版本 ≥ 91，flex gap 在 84+ 完整支持。

**已知 WebView2 与 flex 相关问题**：
1. **Chromium bug #1143747**：深层嵌套 flex + gap 在某些情况下 gap 不生效 → **但我们的 gap:14px 在 `.ai-messages` 上仅 1 层嵌套，不太可能命中**
2. **DPI 缩放**：Windows 125%/150% 缩放下 flex 布局的像素计算可能有亚像素舍入误差 → 导致 1px 级别的偏移，**不足以解释大规模重叠**
3. **IntersectionObserver rootMargin**：WebView2 的 IO 实现与标准 Chrome 一致（共享 Blink 引擎），rootMargin:600px 应正常工作
4. **ResizeObserver + IO 同时观测同一元素**：规范允许，Chromium 正确处理，无竞态问题

**Tauri 特殊配置**：检查 tauri.conf.json 中是否有 webview 相关配置可能影响渲染。

**角度⑤结论**：WebView2 在 Windows 11 上与标准 Chrome 行为一致，无已知 bug 能解释此现象。**概率：极低（非根因）**。

---

## 综合判定与新假设

**5 角度逐一排除后，原假设（IO 漏 observe / ToolCard absolute / CSS 嵌套 / 数据一致 / WebView2）均无法充分解释「全量重叠」现象。**

**需要补充排查的方向**：

**🔴 新假设 A [最高概率]：虚拟滚动 `shouldRenderMsg` 返回 false 导致内容卸载，但 slot 的 minHeight 未正确设置 → 0 高 slot 堆叠**

具体路径：
1. IO 正常工作（角度①修正：rootEl 就绪时 IO 成功创建）
2. 某些 sentinel 在 IO 首次回调时被判为**不可见**（如页面尚未完成 layout，或 rootMargin 计算偏差）
3. IO 回调进入卸载分支（:68-76）：`meta.height > 0` 检查
4. **此时 RO 尚未触发**（RO 的首次回调通常比 IO 晚，因为 RO 需要等内容渲染完成后才测得尺寸）→ `meta.height` 仍为初始值 **0**
5. `meta.height > 0` 为 false → **不设置 minHeight** → renderedKeys 中删除该 key
6. `shouldRenderMsg(key)` 返回 false → `v-else-if` 不渲染内容
7. `.ai-msg-slot` 内部无内容 → **高度塌为 0**（padding/margin 均为 0，仅靠 gap 撑开）
8. 下一条消息的 slot "坐落" 在上一条的 0 高 slot 上 → **视觉重叠**

**关键证据支持**：
- useAiVirtualScroll.ts:73 `if (meta && meta.height > 0 && meta.el.isConnected)` — **height=0 时跳过 minHeight 保护**，这正是漏洞
- RO 首次回调 timing 不保证在 IO 之前 → 存在 **IO 先于 RO 触发** 的竞态窗口
- 一旦某个 item 的 height 被错误地记为 0（或从未被 RO 测量），后续即使重新可见（:62-66 清 minHeight），如果再次不可见而 height 仍为 0 → 再次不设 minHeight → **持续塌陷**

**🟡 新假设 B [中等概率]：`v-else-if` 条件渲染导致 Vue 复用 DOM 节点错乱**

模板结构：
```html
<div class="ai-msg-slot">
  <div v-if="item.kind === 'sep'" ...>        <!-- 分隔条 -->
  <div v-else-if="shouldRenderMsg(item.key)" ...> <!-- 消息内容 -->
  <!-- 无 v-else → 两者都不满足时 slot 内为空 -->
</div>
```
当 `shouldRenderMsg` 在 true/false 间快速切换（IO 回调频繁触发时），Vue 的 v-if/v-else-if DOM 复用机制可能在极端情况下产生**短暂的 DOM 结构不一致**（如旧节点未完全销毁即被复用）。但这通常是瞬态的，不应导致持久重叠。

**🟡 新假设 C [中等概率]：markdown v-html 输出含未闭合标签破坏 DOM 结构**

虽然 DOMPurify sanitize 应防止此问题，但如果 AI 回复的内容（经 marked 解析后）产生了异常 HTML（如 `<div>` 未闭合被 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::<i32>() ∈ 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<Database> + 注册 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<String>` + V17 迁移(幂等补列) + crud 白名单+SELECT/INSERT/UPDATE 全链路
    - [x] ✅(c10adaf) ②df-nodes ai_node.rs: AiNode 持 db(Arc<Database>)+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<<retry_attempt` 纯指数无 jitter/无总预算/一律重试(含 MidStream),三处注释(agentic.rs:37/commands.rs:1106/state.rs:185)误称「对齐 retry.rs(1s→2s→4s+jitter)」。retry.rs:67-78 实有 ±20% jitter+30s 预算+4xx 分类(:102-104 Fatal 立即放弃)。修法:agentic 加 jitter+总预算+错误分类真对齐,或删注释改「独立简化策略」— CR-260616-30 归档
  - [x] ✅(CR-260616-35 复审 PASS·agent cargo 6+20 passed) **CR-30-2** [med] AiStreamRetry 死事件 + MidStream 重试文本混乱 — 后端 emit(agentic.rs:310)+types.ts:214 variant 有,但 useAiEvents.ts switch 无 case 穿透忽略;MidStream 失败重试时已 emit AiTextDelta 累积 currentText,重试新 delta 追加→文本重复。修法:useAiEvents 补 case+重试前清 currentText;或后端 MidStream 不重试只重试 Init(对齐决策a)— CR-260616-30 归档

### ⏸️ 待用户授权/确认（巡检悬挂项·2026-06-16）

> 巡检推进中浮现的、需用户拍板才能继续的操作授权/范围确认项(非架构方向决策,见上节 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<String>` 无校验；`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<String> 参数(前端从 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<String>`,前后端契约对齐。**纠正上轮过时判断**(上轮压缩前快照仍 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` 或 `<AiMarkdown>` 组件。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<Mutex> 共享 + 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,PendingApproval> 按 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` 或 `<AiMarkdown>` 组件。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 返回后追加 `<pre>` 红删绿增渲染。原诉求: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>`（内存态，随会话销毁）；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<id,timer>+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<Mutex> 共享 + 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<String>` + 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<WorkflowRepo>。— 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：`<span class="filter-btn-icon">{{s.icon}}</span><span class="filter-btn-label">{{s.label}}</span>`（图标文字独立元素，便于 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<Set<string>>(new Set(['earlier']))`（默认 earlier 收起）。
2. 标题加交互（:83）：`<div class="ai-conv-group-title" @click="toggleGroup(group.key)"><span>{{ collapsedGroups.has(group.key) ? '▸' : '▾' }}</span>{{ group.label }}<span class="ai-conv-group-count">{{ group.items.length }}</span></div>`。
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 处)

