1262 lines
271 KiB
Markdown
1262 lines
271 KiB
Markdown
# DevFlow 工作看板
|
||
|
||
> 来源:`docs/02-架构设计/功能决策记录-2026-06-14.md`「需求与待办」+ `PROGRESS.md` 各 Sprint 遗留,2026-06-14 汇总去重 + 代码核对修正。
|
||
> 互操作:执行走 mission-control,回写 mission_id;审查走 cr;发布走 publish-*。
|
||
> 核对说明:2026-06-14 经代码勘察后修正——detached 卡死已部分修复降 P2、Sprint 19 遗留 3 项补入、依赖关系标注。
|
||
|
||
---
|
||
|
||
## 交接状态(2026-06-14)
|
||
|
||
**代码健康度**:`cargo test --workspace` 全过、`npx vue-tsc --noEmit` 0 error(主代理独立验证,非 mission 自报)。
|
||
|
||
**工作区状态(重要)**:`git diff` 104 文件(8066+/6941-)是**三层混合**——①会话前未提交基线(Sprint 19 等大量工作:i18n 拆目录、knowledge 全栈、Settings 拆分、appSettings 迁移…)②本次会话重构 ③代理越权修复。**接手前务必 `git diff` 通览区分**,勿整体当作单一改动提交。
|
||
|
||
**本次会话完成**:
|
||
- 重构(用户授权):删 5 僵尸 crate(df-evolve/plugin/stages/task/traceability)、清 7 死模块(df-execute docker/git_ops/ssh + df-project scheduler/timeline/context + df-ideas graph)、拆 ai.rs→`commands/ai/` 11 文件、拆 ai.ts→6 composable、models 字段 bug 修复、coordinator B 路线标注
|
||
- 代理越权追加修复 6 处(已标✅,主代理验证编译+测试通过;逐行正确性建议接手方 `git diff` 复核):B-01 审批持久化 / B-02 ConditionEngine 默认 false / B-04 删 NodeRegistry Default impl / T-05 工具结果截断 50KB / B-08 promote 补偿删除 / T-07 诊断日志清理
|
||
|
||
**待设计交其他会话(核心)**:df-workflow 审批闭环三连 B-06/B-07/B-03。**✅ B-03 设计已完成**(接手会话,2026-06-14):见 [B-03-人工审批响应机制-2026-06-14.md](./02-架构设计/B-03-人工审批响应机制-2026-06-14.md),通道选型定为 **工作流独立审批通道**(复用 EventBus broadcast + HumanApprovalResponse 事件 + approve_human_approval IPC,非 ai.rs AiApprovalRequired——后者是 AI Chat 工具审批路径,与工作流节点审批是两条独立链路)。拆 B-03a(响应等待 + 超时,不依赖 B-07)/ B-03b(取消机制)。**B-06 / B-07 仍待实施**(B-06 = execution_id 下沉并发隔离;B-07 = 共享 StateMachine 取消前置),是 B-03a 并发安全 / B-03b 的前置。
|
||
|
||
**失控代理教训**:本次会话派的后台拆分代理在 stop hook 循环里失控,越权改代码/文档(先斩后奏)。接手方若再派 agent,注意约束其不碰决策记录(用户已要求手动触发)+ 限定单任务不自主续推。
|
||
|
||
---
|
||
|
||
## 待办
|
||
|
||
### 📋 编排推进总览(2026-06-16 汇总)
|
||
|
||
未完成待办按可执行性分 8 组(详细条目见下方各分类,勿重复记录):
|
||
|
||
| 组 | 说明 | 代表项 |
|
||
|---|---|---|
|
||
| **A todo 卫生** | 矛盾清理/被取代退役标注 | ✅ 本批:AR-1 退役 / AR-8 重评 |
|
||
| **B 零风险减法** | 死链清/空壳合并 | ✅ ARC-02 死链已删 / ARC-03 前提失效转重评 |
|
||
| **C 需用户输入** | 阻塞,无法代办 | S-260615-01 curl 测 / S-260614-01 多开澄清 / S-260614-02 实测重评 |
|
||
| **D 功能增强** | 设计清晰可推进(P1) | F-15-01 审批选项 / F-15-02 task 详情 / F-15-03 分页⚠️breaking / F-15-04 卡片折叠 |
|
||
| **E 架构重投入** | 需设计,非小改 | F-14-01 模型能力 / F-14-07 trait 下沉 / ARC-05·06 store 拆·循环依赖 |
|
||
| **F 全局 review P2 需设计** | 安全/竞态收口 | R-PD-4/5/6/8/9/12/13 + R-PD-10 .map_err 85 处 |
|
||
| **G 测试/dev 验证** | 收尾土壤 | ARC-08 dev 验证 / B-03b-R8 human 端到端 / T-14-01·02 Sprint 实测 |
|
||
| **H 长期功能池** | 不进主线 | F-14-02~10 / T-14-06 Settings 拆 / T-14-11 条件引擎 / B-14-05 / B-03b-R9 |
|
||
|
||
**推进原则**:能并行不串行(独立子任务 8-12 并发);每批重汇总+全优先级重排+销账核对 ✅;C 组阻塞项不代办等用户。**本会话首批**:A 卫生 + B 清死链。
|
||
|
||
**③④C类已迁**(2026-06-16):需人工决策项(③产品取舍 7 / ④设计方向 7 / 重投入排期 9 / C用户实测 5)见 [待决策.md](./待决策.md),从本文件抽离职责分离。**batch54 可自主批进展(2026-06-16·workflow wsfz7zl8i + 主代核查)**:复核-新⑪ 看门狗幽灵 ✅ 销账(清主窗口 state 视觉残留;**核验更正**:原 watchdog 幽灵已被 CR-260615-24 stopListener:371 clearStreamWatchdog 兜底);AE-03 diff预览 ⚠️ 降级阻塞(后端 write_file 未返 diff,待后端补,见 line 538);UX-04 断线保文 待 DEC-260616-03 授权(见待决策.md 🟡,本批不含)。
|
||
|
||
### P0 — 阻断性 bug
|
||
|
||
- [x] ✅(主代修06-17·撤销误判) **🚨 BUG-260617-01 [P0] 已修** — **`|| true` 致 400/参数错误无效重试 4 次**【已修:stream_recv.rs:433 删 `|| true` + 注释 + 单测 classify_unknown_retryable→classify_unknown_not_retryable 断言改 !;cargo test 20 passed】 — 用户实测证实(2026-06-17)。Anthropic `[1214][messages 参数非法]` 无 HTTP 前缀 → 落入 `stream_recv.rs:433` `\|\| true` → retryable=true → **空耗 4 次重试浪费 token**。前次审查误判为"有意设计"。**一行改动:删 `|| true` + 改单测断言**。— src-tauri/src/commands/ai/stream_recv.rs:433
|
||
- [x] ✅(wf wf_b0fa5730-9e9·主代适配+commit 74003c) **🚨 BUG-260617-12 [P0] 已修** — **DeepSeek reasoning_content 缺失致必 400**【全链路透传:df-ai-core(provider.rs CompletionRequest/ChatMessage/CompletionResponse/StreamChunk 加字段+5单测) → df-ai(openai_compat.rs OpenAiRequest/OpenAiMessage/OpenAiMessageResp/OpenAiStreamDelta 映射+流式解析) → anthropic_compat.rs 11处 StreamChunk/CompletionResponse 兼底) → agentic.rs(last_reasoning_content 初始化→stream_one_provider 注入请求→outcome 提取→push 时回填 ChatMessage) → stream_recv.rs(reasoning_content 累积+StreamResult Complete/Partial 携带) → title.rs 字面量补齐 + df-nodes/ai_node.rs/compress/knowledge_inject 非agentic 路径 reasoning_content: None。cargo check --workspace EXIT 0 / df-ai-core 24 test pass / df-ai+df-nodes 0 test(无单测)】。调研确认 DeepSeek API 规范:reasoning_content 在 choices[].message.reasoning_content(响应)+多轮 assistant 消息必须回传(有 tool_calls 时),否则 400 "must be passed back"。】 — 14 文件 / +255 行 (commit 74003c)
|
||
|
||
> **⚠ 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-17 走查·tauri 打包目标收窄 + 状态映射 DRY(仅分析·未实施)
|
||
|
||
> 本轮 git diff 核验工作区未提交改动 + 最新提交 1cd7652。session-role-diagnose-only。
|
||
|
||
- [ ] B-260617-11 [P2] — **tauri.conf.json 打包目标收窄未提交,若误入库锁死非 Windows 构建**。工作区改动 `bundle.targets: "all" → ["nsis"]`(src-tauri/tauri.conf.json:28),收窄到仅 Windows NSIS 安装包。若意图为本地只打 Windows 包,合理;**但若随其他改动一并提交**,macOS(dmg/app)、Linux(deb/appimage)构建将不可用,影响其他开发者/CI。**确认点**:该改动是临时本地构建还是有意入库?临时则建议提交前 revert 此行;有意则建议改为按平台条件配置而非硬编码单一 target。—— src-tauri/tauri.conf.json(:28)
|
||
|
||
- [x] ✅(commit 645f73d) B-260617-12 [P3·可选] — **LEGACY_STATUS_MAP 映射 DRY 重复**。抽 `mapLegacyStatus(status)` 函数收敛两处 `LEGACY_STATUS_MAP[status] ?? status` 一行重复。vue-tsc EXIT 0。—— src/constants/project.ts(:96-98)
|
||
|
||
### 🔧 2026-06-17 走查·DeepSeek reasoning_content 实施审查(P1 提交不完整)
|
||
|
||
> 核验 HEAD 74003bc(BUG-260617-12 修复提交)+ 工作区残留。session-role-diagnose-only,逐行核验非信声明。**闭环逻辑正确(见 :49 注记),但发现提交不完整 P1**。
|
||
|
||
- [x] ✅(commit f6e4e82) **🚨 B-260617-14 [P1] 已修** — **74003bc 漏提交 adversarial.rs + project.rs**【补提两文件 reasoning_content: None,HEAD 干净 checkout 编译通过】
|
||
|
||
- [ ] B-260617-15 [P2·可选] — **reasoning_content 落库存储膨胀**。ChatMessage.reasoning_content 随 messages JSON 落库(DeepSeek thinking 每轮 assistant 带推理内容,可达数 KB)。前端 src/ 核验无引用,纯后端透传不展示,故无 UX 泄露——唯一副作用是长对话 messages JSON 累积膨胀占 DB。**优化方向**:reasoning_content 仅需"紧接下一轮"透传(DeepSeek 只要上一轮的),历史轮次落库无用,可考虑落库前剥离(仅内存透传)。— crates/df-ai-core/src/provider.rs(:121) · src-tauri/src/commands/ai/agentic.rs(:765,:824,:829 写回点)
|
||
|
||
- [ ] B-260617-16 [P3·可选] — **Partial(MidStream 保文)回填半截 reasoning_content 语义待评**。agentic.rs MidStream 保文路径用 `round_reasoning_content`(本轮部分累积)写回 ChatMessage,回传下一轮 DeepSeek 会看到不完整推理。属异常路径(网络中断保文),保文后已加"响应不完整"系统提示,用户大概率重发。影响小,登记观察。— src-tauri/src/commands/ai/agentic.rs(:765)
|
||
|
||
### 🔧 2026-06-17 aichat 消息全量重叠(5角度深入分析·未实施)
|
||
|
||
> 用户报 aichat 对话「大量重叠」「不单间距问题」。session-role-diagnose-only,5 角度并行论证后记录。
|
||
|
||
> **🔥 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 正常但时序竞态" |
|
||
|
||
- [ ] B-260617-09 [P1] — **aichat 消息全量重叠**。用户报「大量重叠」「不单间距」。5 角度深入论证排除 CSS/markdown/数据/WebView2 后,**最高概率根因更新为:虚拟滚动 IO 与 RO 时序竞态**——IO 先于 RO 首次回调触发卸载分支时 `itemMeta.height` 仍为 0 → `:73` `height>0` 守卫失败 → 不设 minHeight → slot 塌 0 高 → 下条消息上浮重叠。次 probable:v-html DOM 破坏(marked+purify 产出异常 HTML 破坏 bubble 闭合标签)。**修法方向**:①IO 卸载分支加 fallback minHeight(如取 el.offsetHeight 或固定最小值 40px)②临时禁用虚拟滚动验证根因 ③DevTools 断点验证 IO/RO 时序。— src/composables/ai/useAiVirtualScroll.ts(:68-76 IO卸载分支 height守卫) + src/components/AiChat.vue(:400-401 shouldRenderMsg条件渲染) + src/composables/useMarkdown.ts(:188 renderMd sanitize)
|
||
|
||
### 🔧 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 新需求(已分析·待实施)
|
||
|
||
> 功能需求,已走查定位链路 + 方案记录,待用户确认后实施。
|
||
|
||
- [x] ✅(batch38·2026-06-16·workflow wx5vd8ppk) F-260616-01 [P2] — **aichat Agentic 最大循环轮次支持配置(Settings)**。已落地(1:1 复刻 llm_concurrency,8 处改动全闭环)。现状:`MAX_AGENT_ITERATIONS=10` 硬编码常量(`agentic.rs:33`),复杂 agentic 任务未收敛即被截断,用户无法调。**黄金参考:`llm_concurrency`**——项目唯一成熟的「前端数字配置→AppState 字段→热改 command→透传 loop 形参」范式,本需求 1:1 复刻。改动 8 处:①`state.rs` AppState 加 `agent_max_iterations: Arc<AtomicUsize>`(默认 10) ②`agentic.rs` 常量改名 `DEFAULT_MAX_AGENT_ITERATIONS` + `run_agentic_loop` 增形参 `max_iterations` + 循环(L156)/超限消息(L321-332)用形参 + `try_continue_agent_loop`(L478) 补传 ③`commands.rs` `ai_chat_send`(L114) 补传 + 新 command `ai_set_agent_max_iterations`(clamp 1-50 store) ④`lib.rs` invoke_handler(L125 区)注册 ⑤`api/ai.ts` 新 `setAgentMaxIterations`(仿 L73 `setConcurrencyConfig`) ⑥`Settings.vue` reactive(L615 区)+UI 数字 input(min1 max50,紧挨并发控制 L268)+watch 持久化+`syncAgentMaxIterations`+onMounted 推后端 ⑦i18n 补 zh/en 键。**生效时机**:loop 入口 `load` 取快照传值,当前 loop 锁定边界,热改下次发消息生效(防边界飘移;与 `llm_concurrency` 传 Arc 实时反映的区别)。key `df-ai-agent-max-iterations`,范围 1-50 双 clamp。`agentic.rs:27-32` 注释已预留此扩展点。**低风险,复刻现成范式无新机制**。— 详案见本地 plan 文件 `quizzical-prancing-hennessy.md`。
|
||
|
||
- [x] ✅(batch37·2026-06-16·workflow wg8x18y9y) F-260616-02 [P2] — **知识库「待审」(pending_review) 不应进第一个 tab(知识库/library)**。决策 a 落地:inbox 收 candidate+pending_review,library 纯 published。现状:`Knowledge.vue` 顶层两 tab——`library`(知识库,L19) 用 `store.items`、`inbox`(审核收件箱,L22) 用 `store.candidates`。根因在数据源:①后端 `knowledge_list`(knowledge.rs:71-81) `status=None` 默认返回「**非 archived 全部**」= candidate + **pending_review** + published;②前端 store `loadList`(knowledge.ts:60) 无 status 参数调它 → `state.items` **含 pending_review**;③`knowledge_list_candidates`(knowledge.rs:211-218) 只列 `candidate` → inbox 不含 pending_review。结论:**pending_review 条目当前只在 library tab 与 published 混杂显示**,inbox 反而不收它。改动方向(待定,**含决策点需用户定**):①最小改——后端 `knowledge_list` 默认排除范围从 `archived` 扩到 `archived + pending_review`(或前端 `loadList` 后 filter 掉 pending_review,store 层兜底);②**决策点:pending_review 从 library 排除后该去哪显示**?(a) 扩 inbox 收件箱含 candidate+pending_review(语义=「待处理」收件箱)(b) 单纯从 library 消失、pending_review 暂无专门视图 (c) 加第三个 tab「待审」。**倾向 (a)**:inbox 收 candidate+pending_review,library 纯 published,职责清晰零新 tab。— Knowledge.vue(L296-299 列表数据源) + stores/knowledge.ts(loadList/loadCandidates) + knowledge.rs(knowledge_list 默认过滤 + list_candidates 范围)。**待用户定②后实施**。
|
||
|
||
- [x] ✅(batch44 后端 + batch45 前端·2026-06-16) F-260616-03 [P2] — **aichat 达最大轮次时询问「是否继续」+ 确认后续 loop**。全闭环:后端(batch44 AiMaxRoundsReached 暂停态+continue/stop IPC)+ 前端(batch45 操作卡 pendingMaxRounds+续/停按钮)。现状:`agentic.rs:321-332` 达 `max_iterations` 未收敛(`!converged`)→ emit `AiError`「达到最大轮次(N 轮),Agent 可能未完成」+ **直接走完成流程**(save/标题/提炼+`guard.reset`+`AiCompleted`),用户无法续,复杂任务被硬截断。需求:达上限改 emit 询问 → 前端「继续/停止」操作卡 → 用户点继续后续 loop、点停止走完成。**机制高度复用工具审批暂停-恢复**:审批 = `AiApprovalRequired`(audit.rs:353 emit)→前端 `pending_approvals`(useAiEvents.ts:219 push)→`ai_approve`(commands.rs:122)→`try_continue_agent_loop`(agentic.rs:388 续 loop)。本需求同构。改动骨架:①后端 `agentic.rs:321-332` 达上限分支改:不 emit AiError+完成,改 emit 新事件 `AiMaxRoundsReached` + 保持 `generating=true`(暂停态,仿审批等待) ②`mod.rs:71` 事件枚举加 `AiMaxRoundsReached { conversation_id }` ③新 command `ai_continue_loop`(用户点继续)→调 `try_continue_agent_loop` 续 loop(**续跑轮数**:重置计数再跑 `max_iterations` 轮——末轮 tool_result 已在 messages,续跑首轮自动回传 LLM,语义自洽) ④新 command `ai_stop_loop`(点停止)→走原完成流程(save+Completed) ⑤前端 `useAiEvents.ts` 加 `case 'AiMaxRoundsReached'`(push 待确认态,仿 pendingApprovals) ⑥`AiChat.vue` 加续跑操作卡 UI(继续/停止按钮)。**设计决策点(待定)**:(a) 续跑轮数=重置计数再跑 max_iterations 轮(倾向) vs 单步 1 轮 (b) 防无限续:无硬上限(用户主动授权=同意烧 token,倾向) vs 加累计上限 (c) 事件/卡片:新增 `AiMaxRoundsReached`+独立操作卡(倾向,语义清晰) vs 复用 `AiApprovalRequired`(args/name 不搭)。**关联 F-260616-01**(max 可配)——两者协同=用户完全掌控 loop 边界,建议同批实施。— agentic.rs(达上限分支 L321-332 + try_continue 复用) + mod.rs(事件枚举) + commands.rs(新 command) + useAiEvents.ts + AiChat.vue。**待用户定决策点后实施**。
|
||
|
||
- [x] ✅(batch42·2026-06-16·workflow w95hzp9tw) F-260616-04 [P1] — **run_command 超时→LLM 重试→反复审批循环**。已根治(tool_registry.rs 超时标注「勿盲目重试」+ DEFAULT_RUN_COMMAND_TIMEOUT_SECS 常量,handler map_err 改写)。根因链(探索确认):超时(默认 60s, `tool_registry.rs:491 unwrap_or(60)`)→tool_result 回传 LLM(`commands.rs:168` Err 分支)→LLM 重试同命令→**新 tool_call_id**(`stream_recv.rs:176` 每轮新 UUID)→重新 insert pending(`audit.rs:343`)→重新审批,循环。症状「再过一会又提示 Run Command」即此。修复方向:①超时 tool_result 明确标注「命令执行超时({n}s),进程已终止,勿盲目重试同命令;确需更长时限重发时提高 timeout_secs」②run_command 默认超时可配(LLM 已可传 `timeout_secs` 参数,但默认 60 + 不主动调)③配合 F-260616-05 去重根治。— tool_registry.rs(超时标注) + commands.rs(回传内容)。
|
||
- [x] ✅(batch53·2026-06-16·workflow wbh8mu1iq·**run_command 循环核心解决**) F-260616-05 [P1](**共性根治**)— **agentic loop 工具调用去重缺失**。**batch53 落地 High risk 审批前去重**(audit.rs find_cached_high_risk_result 反向扫 messages + args JSON 规范化匹配 + 排除 pending 占位 + decided_by=auto_dedup,解决 run_command 反复审批循环,F-04 超时标注 + batch53 去重双保险)。**完整 agentic loop 通用去重(search_files 重复卡片 + 滑动窗口指纹 + 决策点)③类待用户定**(窗口/粒度/措辞)
|
||
- [x] ✅(batch56·2026-06-16·workflow wsxxurgbr+主代核查) F-260616-06 [P2] — **「未找到挂起的审批」体验**。**全闭环落地**:①`commands.rs:217` ai_approve 幂等——内存 pending_approvals remove None 时查 `ai_tool_executions` 审计表,已 executed/rejected/failed 返回「已处理({status})」非报错 ②`useAiSend.ts:305` 审批按钮防抖——`_pendingApprovalIds` Set 守卫,同 id 短期多次点击只发一次 IPC,finally 块清理。— src-tauri/src/commands/ai/commands.rs + src/composables/ai/useAiSend.ts
|
||
|
||
- [x] ✅(batch56·2026-06-16·workflow wsxxurgbr+主代核查) F-260616-07 [P2] — **流式对话失败自动重试**。**全闭环落地**:①`agentic.rs` stream_llm 调用处包重试循环(max_retries 形参,退避 1s→2s→4s,持 permit 防挤占) ②每重试 emit `AiStreamRetry` 事件(attempt/max_attempts) ③耗尽走原有 guard.reset+return ④state.rs 加 `agent_max_retries:Arc<AtomicUsize>`(默认3) ⑤新 IPC `ai_set_agent_max_retries`(clamp 0-10) ⑥前端 api/ai.ts + types.ts(AiStreamRetry variant) + Settings.vue(input 0-10+debounce sync+i18n zh/en)。**设计决策**:一律尝试重试(简化策略,由退避控制成本),MidStream 重试语义等价于"看不到完整回复再试一次"。— src-tauri/src/commands/ai/agentic.rs + state.rs + commands.rs + lib.rs + src/api/ai.ts + types.ts + src/views/Settings.vue + src/i18n/{zh-CN,en}/settings.ts
|
||
|
||
- [x] ✅(batch56·2026-06-16·workflow wsxxurgbr+主代核查) F-260616-08 [P2] — **AI 数据获取工具 offset 分页 + 探总量**。**全闭环落地**:①6 工具(list_projects/tasks/ideas/trash/read_file搜索模式/search_files)全部补 offset+limit 参数,返回 items/total/has_more 替代 truncated ②新增 `get_project_count`+`get_task_count` 探总量工具 ③knowledge_search 解钳 `min(3)`→`min(20)`。— src-tauri/src/commands/ai/tool_registry.rs + src-tauri/src/commands/knowledge.rs
|
||
|
||
- [ ] F-260616-09 [P1/架构·**A路线✅(batch60·2026-06-16·workflow whae812z5+主代核查),B多会话并发待立项**] — **AiSession 单例 → 多会话并发架构(多窗口来回切 + 并行执行)**。用户需求(3 条消息强化):「应用级支持同时具备多会话」+「同时具备多会话的能力」+「多个会话窗口来回切,**并行执行**」。现状(Explore 走查确认):`AiSession` **应用级单例**(`state.rs:164` `ai_session: Arc<Mutex<AiSession>>`,init:205),messages/generating/stop_flag/pending_approvals/agent_language 全一份。设计「单例 + 软隔离」——generating 互斥(同一时刻**只一个对话能跑**) + 切换旧 loop 退出(`agentic.rs:177-190` B-260615-11) + 切换时 generating→readonly(`commands.rs:873-881`)。`llm_concurrency` per_conv 因此退化为单对话内并发(`state.rs:93-96` 注释已标注预留改 HashMap)。**memory 已标记「AiSession 单例未动」架构债**(aichat-arch-extensibility)。**残留 bug**(表象=新建会话上下文残留):前端 `newConversation`(`useAiConversations.ts:31-41`) 漏清 queue/generatingConvId/agentRound/searchQuery;后端 `ai_conversation_create`(`commands.rs:784-821`) 漏清 agent_language/stop_flag(设 true 停旧 loop 没复位 false)。**用户「并行执行」明确=B 方向**(A 只解切换残留不满足并行)。**分档**:**A 短期隔离修复**(补漏清字段,维持单例软隔离,解残留,低风险立即做,**B 前置**) + **B 多会话并发架构**(单例 → `HashMap<conv_id, SessionState>`,每会话独立 messages/generating/stop_flag/pending_approvals + llm_concurrency per_conv 改真 per-conv HashMap + loop 内校验改 conv_id 索引各自 session + 切换不退出旧 loop 各自跑完真并发 + 事件路由核对 conversation_id 全覆盖)。**决策点(待定)**:(a) A 先做解燃眉 vs 直接 B——倾向 **A 先**(残留真 bug 立即解,B 架构另立项) (b) B 形态——AiSession 整体 HashMap vs 拆字段(messages/pending 已可按 conv reload,只拆 generating/stop_flag per-conv)——倾向**后者侵入小** (c) B 并发上限——复用 llm_concurrency.global 限并发会话数防 token 暴增——倾向是 (d) **多窗口 UI 形态**——(d1) 单面板侧栏会话列表切换+后台并行(改动小) vs (d2) 每会话独立 Tauri 窗口(`useAiWindow.ts` detached 已有基础可扩,改动大但「窗口来回切」语义最贴)——**待用户定**,倾向先 d1 后 d2 (e) 旧 loop 切换处理——不退出各自跑完(真并发,倾向) vs 仍退出。— state.rs(AiSession 去单例化) + commands.rs(create/switch 重构 + 漏清补) + agentic.rs(:177-190 校验改 conv_id 索引) + context.rs(ContextManager per-conv) + useAiConversations.ts(漏清补) + stores/ai.ts(per-conv state) + llm_concurrency per_conv + useAiWindow.ts(若 d2 多窗口)。**B 是大改架构升级,需单独设计文档 + 立项**。**待用户定 A 先做 / B 立项 / 多窗口 d1·d2 方向**。
|
||
|
||
- [x] ✅(batch58·2026-06-16·workflow w2dw65maz+主代核查,vue-tsc 0err) F-260616-10 [P1·bug] — **provider 切换/默认值重启·刷新后失效(前端 activeProvider 不同步 DB 默认)**。用户实测:Settings 设 A 为默认 → 重启 → 默认变 B(列表第一个)。**根因全在前端,后端链路正确**(独立核验不信 PROGRESS.md:「已修复」声明不可信):`ai_set_provider`(`commands.rs:747-774`) 互斥落库✅ + `update_full`(`crud.rs:1127`) SQL UPDATE 含 `is_default`✅ + `get_active_provider`(`prompt.rs:9-32`) 走 `DB.is_default`✅。**根因链**:①重启后前端 `store.activeProvider = null`(`stores/ai.ts:53`,无 localStorage 持久化 + 无启动恢复) ②`loadProviders`(`useAiPanel.ts:91-93`) 只拉列表**不同步 DB 默认** → activeProvider 仍 null ③**UI 错觉**:`activeProviderName`(`AiChat.vue:1169-1172`) null 时 fallback `providers[0]`(列表第一个) ≠ 用户设的 A → 「默认变另一个」 ④**真覆盖**:用户点 provider bar 切换 `cycleProvider`(`AiChat.vue:1180-1185`),`findIndex(activeProvider=null)` 返回 **-1** → `next = ps[(−1+1)%len] = ps[0]` = 列表第一个 B → `setProvider(B)` → 后端互斥落库把 `DB.is_default` 从 A **真改成 B**。即使用户不点切换,UI 也显示 providers[0] 而非 A。**修复方向**:①`loadProviders` 后同步 `activeProvider` = DB.is_default 的 provider——后端加 `ai_get_active_provider` IPC 返回当前默认 id,或 `listProviders` 返回值带 `active_provider_id` ②`cycleProvider` findIndex=-1 陷阱修正(null 时应从 DB 默认起算,非 ps[0]) ③前端 activeProvider 持久化(localStorage `df-ai-active-provider`,对齐 `df-ai-active-conv` 模式)。— useAiPanel.ts(loadProviders 同步默认) + AiChat.vue(:1169 activeProviderName fallback + :1180 cycleProvider findIndex 陷阱) + commands.rs(新 ai_get_active_provider IPC) + api/ai.ts + stores/ai.ts(activeProvider 持久化)。**确定性 bug,低风险,可立即修**。
|
||
|
||
- [x] ✅(batch60合批·2026-06-16·主代核查,cargo 0err) F-260616-11 [P1·决策a累计计数] — **审批后 iteration 重置致 max_iterations/token 超配**。决策(2026-06-16):**a 累计计数**(try_continue 不重置 iteration,跨审批续跑延续累计)。**已实施**:`iteration_used:usize` 累计字段(`mod.rs:215/230` AiSession+new init 0)+`start_iteration:usize` 参数(`agentic.rs:114` run_agentic_loop signature +`:176` loop 边界 `start_iteration..max_iterations` +`:210` 一致性块 `iteration_used=iteration+1` +`:578` try_continue 加参 +`:675` spawn 透传)+三处 reset(`commands.rs:55/152/412` ai_chat_send/ai_regenerate/ai_edit_last 新生命周期)+ai_approve 两处累计读 iteration_used(`:260-262` 拒绝续跑 +`:313-315` 通过续跑)+`ai_continue_loop:601` reset+传0(F-03 决策a 达max重计区分)。两路径严格:审批累计(ai_approve) vs 达max重计(ai_continue_loop,F-03 a 用户授权重来) vs 新消息 reset。边界case:start≥max loop 空区间→converged=false→AiMaxRoundsReached(防无限审批烧 token,正 F-11 语义)。原:`try_continue_agent_loop` 重 spawn `run_agentic_loop` iteration 从 0 重计致多次审批反复重跑 max_iterations 轮 token 失控。— agentic.rs + commands.rs + mod.rs。
|
||
|
||
- [ ] F-260616-12 [P2/依赖F-09] — **retry 持 permit 不释放(多会话隐患)**。F-260616-07 落地的流式重试循环(`agentic.rs:238-239`)重试期间持有 global+per_conv permit 不释放(注释「防新请求挤占」)。当前 AiSession 单例 + 主 loop 串行无影响,但 **F-260616-09 多会话并发后**,重试期间阻塞其他对话 LLM 调用。**方向**:多会话落地时核对——主 loop 串行下重试持 permit 防自己挤占无意义,倾向重试不持 permit 或仅持 per_conv。— agentic.rs:238 + F-260616-09 多会话架构。**依赖 F-260616-09 立项后一并处理**。
|
||
|
||
### 💡 2026-06-16 新需求(UX 交互优化·分析完成·待实施)
|
||
|
||
> 用户实时反馈的交互优化需求,已走查定位链路+方案记录。
|
||
|
||
- [ ] UX-260616-01 [P2] — **工具调用失败时「重试」提示语义模糊**。用户场景:Run Command 执行失败(exit_code 255,`head` not recognized on Windows),UI 显示「⚠ 调用失败,正在重试(1/4)…」+「重试」按钮。**根因分析**:「正在重试(n/m)」是 `AiStreamRetry` 事件(F-260616-07 流式 LLM 重试),通过 `useAiEvents.ts:177-186` 更新错误气泡内容;工具执行失败(run_command 等 Low 风险工具)走 AR-6 路径(`audit.rs:640-652`)→ emit `AiToolCallCompleted`(result=错误信息)→ **不触发 AiError 不触发 AiStreamRetry**,错误包在 tool_result 回传 LLM 自行决策。**问题**:①用户看到的是「LLM 流重试」提示,非「工具执行失败」提示,语义错位 ②确定性失败(命令语法错/exit_code 非0)重试同命令必再败,「重试」按钮误导 ③工具实际结果(stdout/stderr/exit_code)已显示在 ToolCard 内,错误气泡的「重试」是消息级 regenerate(重新生成整条回复),非工具级重试。**改动方向**(待定):a)错误气泡区分两类——流式重试中显示「正在重试(n/m)…」(现有);工具执行失败不弹错误气泡(结果已在 ToolCard)或气泡文案改为「工具执行失败,查看上方结果」b)Fatal 类错误(4xx/鉴权/参数)气泡去掉「重试」按钮或改为「去设置」c)`AiStreamRetry` 事件更新气泡时附带 `retryable` 标识,前端据此显隐重试按钮。—— useAiEvents.ts(:177-186 AiStreamRetry case) + AiChat.vue(错误气泡 UX-03 操作栏) + i18n ai.aiStreamRetry
|
||
- [ ] UX-260616-02 [P3] — **「全部收起」与搜索/技能区合并一行**。当前 ToolCardList.vue 有两个独立行:①batch-approve 栏(line 4-11,pendingCount>0 时显示)②global-toggle 栏(line 14-17,collapsibleGroupCount>0 时显示「▾ 全部收起」)。用户要求将「全部收起」与附近的操作元素(search files 搜索文件/技能触发等)放到同一行,减少垂直空间占用。**需确认**:「search files」具体指哪个 UI 元素(i18n `ai.searchFiles` 渲染位置需定位,可能在输入框上方 skill 栏或工具卡区域)。**改动方向**:global-toggle 从独占行改为 inline 元素,与相邻操作栏 flex 同行。—— ToolCardList.vue(:13-17 template + :278-295 CSS .ai-tool-global-toggle)
|
||
- [x] ✅(workflow wfryptv2t·代理A核验已落地·2026-06-17·主代独立grep核验非误判) UX-260616-03 [P2] — **对话内容输出时自动收起旧工具卡片分组**。**已落地**:①触发时机扩展——AiChat.vue:2093 agentRound watch(AiAgentRound 新轮次→collapseAllToolLists(buildActiveToolIds))收起上一轮;既有 messages deep watch(:2085)覆盖新消息追加+toolCall状态翻转;触发链 useAiEvents.ts:169 `if(event.round>0) state.agentRound=event.round` + stores/ai.ts:75 agentRound字段。②collapseInactive增强——ToolCardList.vue:71 userExpandedCards 记忆态 Set,:180 isCardExpanded 优先读它(用户主动展开不被自动收起),:220 collapseInactive 仅清 expandedCards/expandedTools 不清 userExpandedCards。③CSS过渡留TODO——:373 注释:flex gap对零高item贡献双倍间距+auto→0不可纯CSS,需Transition JS钩子,属单卡级折叠重构,本轮不做(不影响功能收起)。需求:当 AI 输出新内容(流式 delta / 新工具调用 / 新轮次)滚动到下方时,上方已完成的旧消息中的工具卡片分组自动收起,保持视野聚焦当前内容。**现状**:机制已存在——`ToolCardList.collapseInactive(activeIds)`(:191-199)供父组件调用,`AiChat.vue:1657-1661` 已有 watch 调用(refs 数组逐实例 collapseInactive)。**增强方向**:a)触发时机扩展——当前可能仅在特定时机调用,可扩展到:`AiTextDelta` 新消息开始时 + `AiAgentRound` 新轮次时 + 用户滚动接近底部时(跟随阅读位置自动收起已读内容)b)平滑过渡——收起加 CSS transition(高度动画 200ms)避免内容突然消失跳变c)可选:记忆用户手动展开的分组不自动收起(expandedCards Set 区分用户主动展开 vs 默认态)。—— ToolCardList.vue(collapseInactive + CSS transition) + AiChat.vue(watch 触发时机扩展)
|
||
- [x] ✅(batch66·2026-06-16·22d866d,formatToolResult 12工具覆盖+i18n 18key) UX-260616-04 [P1] — **审批通过后工具卡片仅显示裸 JSON**。用户实测:审批前 `pending_approval` 态渲染友好(参数键值对+风险提示+审批按钮),审批通过后 `completed` 态 body 仅显示一段 JSON 数据。**根因**:`ToolCard.vue` 模板渲染链对 `read_file`/`list_directory`/`write_file` 有专门 UI 分支,其余工具全部走通用兜底(L110 `v-else-if tc.result && tc.status === 'completed'`)→ `formatToolResult(tc)` → 兜底 `return formatJson(r)` 裸 JSON 美化输出。**代码核对**:①`formatToolResult`(L270-289)只特化 `write_file`,其余全 `formatJson` ②`toolResultSummary`(L476-498,header 摘要)覆盖 `list_tasks`/`list_projects`/`list_ideas`/`create_project`/`create_task`/`create_idea`/`update_project`/`delete_project`/`restore_project`/`purge_project`/`run_workflow` 共 11 工具,但 body 的 `formatToolResult` 不共享此逻辑 ③两函数覆盖范围严重不一致(header 有摘要但 body 展开仍裸 JSON)。**后端返回值核对**(tool_registry.rs):`update_task`→`{id,field,updated}` / `delete_task`→`{deleted,id}` / `delete_file`→`{path,deleted,...}` / `run_command`→`{command,exit_code,stdout,stderr,...}`(可能很大) / `patch_file`→`{path,changed,size_diff,...}` / `append_file`→`{path,bytes_written,new_size}` / `rename_file`→`{from,to,renamed,...}` / `search_files`→`{path,pattern,results,total,...}` / `file_info`→`{path,exists,size,lines,...}` / `bind_directory`→`{id,path,stack,bound}` / `advance_task`→`{...}` ——全部走裸 JSON。**修复方向**:统一 `formatToolResult` 的 switch 覆盖全部工具产出人类可读摘要(复用 `toolResultSummary` 已有 i18n key + 补缺失 key 如 `commandOk`/`commandFailed`/`patchedFile`/`appendedTo`/`renamed`/`foundN` 等),同时补 `toolResultSummary` 缺失 case(`update_task`/`delete_task`/`delete_file`/`run_command`/`patch_file`/`append_file`/`rename_file`/`search_files`/`file_info`/`bind_directory`/`advance_task`)。—— `src/components/ToolCard.vue`(`formatToolResult` L270 + `toolResultSummary` L476) + `src/i18n/{zh-CN,en}/aiTool.ts`(补 key)
|
||
- [x] ✅(波2·2026-06-16·w76d81dno+主代核查,vue-tsc,决策a逐条续发) UX-260616-05 [P1] — **打断按钮只停当前回复 + 队列保留续发(不清队列)**。用户需求:发送队列有消息时,打断按钮应只打断当前回复,并把队列中的多条消息一并发送给后续对话。现状:`stopChat`(useAiSend.ts:365-370) L368 `state.queue = []` **清空队列** → 用户生成中排队的消息随打断被**静默丢弃**。技术底座已就绪(已核后端):`stopChat`→`aiApi.stopChat`→后端 `stop_flag.load`(agentic.rs:178)→收尾 `save_conversation`(:185 已生成文本入库)+emit **`AiCompleted`**(agentic.rs:190,注释明确「保证前端收事件时后端已可接下一条,发送队列续发不被拒」)→前端 `ai-drain-queue`→`drainQueue`(useAiSend.ts:228) shift 队首续发。**最小修复(确定)**:删 stopChat L368 `state.queue=[]`,打断后 AiCompleted 自然触发 drainQueue 续发队首;当前未完成回复已入库(:185)保留为截断回复,无需额外处理。**决策点(待定)——「队列多条一并发送」语义**:**(a) 逐条续发**(删清队列即可,零改 drainQueue,AiCompleted 链式触发每条,保各条上下文独立)vs **(b) 合并成一条消息**(队列多条 text 拼接送,需改 drainQueue 为 bulkDrain 或合并 text 调一次 sendMessage)。**倾向 (a)**(逐条保上下文独立+零改链路;"一并"理解为「都送出不丢弃」非字面合并),**待用户确认**。— src/composables/ai/useAiSend.ts(stopChat:365 + drainQueue:228) + 后端 agentic.rs(:178/:190 stop 收尾 emit AiCompleted 已就绪)。
|
||
- [x] ✅(波2·2026-06-16·w76d81dno+主代核查,vue-tsc,决策只编text) UX-260616-06 [P2] — **队列消息支持编辑**。用户需求:队列中的消息支持编辑。现状:队列 UI(AiChat.vue:511-516) 每项只有 × 删除(cancelQueued:353),无编辑入口;队列项结构 `{ text, skill?, enqueuedAt }`(stores/ai.ts:70)。**改动(纯前端低风险)**:队列项加「编辑」按钮 → 点击切 inline input(单行 textarea)→ 回车/失焦写回 `state.queue[idx].text`、ESC 取消;新增 `editQueued(index, newText)`(useAiSend.ts) splice 写回 + store 导出 + composable return。**决策点(待定)**:skill 是否可编辑(**倾向只编 text**——skill 来自发送时技能联想,编辑态不暴露 skill 改,简化交互)。i18n 补 edit/save/cancel key。— src/components/AiChat.vue(:511-516 队列项 UI + .ai-queue-item CSS:2856) + src/composables/ai/useAiSend.ts(editQueued 新增) + stores/ai.ts + i18n。
|
||
- [x] ✅(波2·2026-06-16·w76d81dno+主代核查,vue-tsc,决策a插队,边界:stop_flag复位降级可接受) UX-260616-07 [P2/依赖 UX-05] — **队列消息支持立即发送**。用户需求:队列中的消息支持立即发送。现状:队列项只能 × 删除或等 drainQueue 串行轮到,无插队。**决策点(待定)——「立即发送」语义**:**(a) 插队=打断当前+立即发这条**(splice 出该项→stopChat→sendMessage 该条;复用 UX-05 stop→AiCompleted→drain 链路,当前未完成回复走 stop 截断保留)vs **(b) 移到队首等当前完成优先**(splice 出→unshift 队首,drainQueue 下一轮先发它,不打断当前)。**倾向 (a)**("立即"字面=马上发,且复用 UX-05 链路;但须注意 splice 后调 stopChat 已不清队列(UX-05),避免已发送项重复入队)。**待 UX-05 打断语义定后同批设计**(共享 stop+drain 链路)。— src/components/AiChat.vue(:511-516 队列项加「立即发送」按钮) + src/composables/ai/useAiSend.ts(sendQueuedNow 新增)。**⚠ 2026-06-17 审查质疑**:sendQueuedNow 存在时序隐患(`await stopChat()` 后 `ai_is_generating` 未及时复位,紧接的 sendMessage 可能命中 L1 入队而非立即发),原结论「stop_flag 复位降级可接受」存疑,详见 B-260617-01(待核验后端复位时机)。
|
||
- [ ] F-260616-15 [P1] — **AI Chat 上下文管理增强:会话分段 + 手动压缩 + 智能裁剪**。三个子需求,详见 [F-15-上下文管理增强设计-2026-06-16.md](./02-架构设计/F-15-上下文管理增强设计-2026-06-16.md)。**阶段1 基础部分 ✅(波3·8a142c2 is_active 白名单+push token 修正)**;阶段1 剩余 ✅(波5·63bff8b compress_prompt四段式+compress_via_llm+context 9辅助方法,幂等/单向不可逆/纯函数/零行为变化,df-ai 107pass);阶段2 手动功能 ✅(波6·4194842+主代核查,cargo0err+vue-tsc0+devflow79pass,2 IPC ai_chat_clear_context/compress_context+3事件 AiContextCleared/AiCompressing/AiCompressed+前端2按钮+status分组渲染+useAiContext composable,clear标archived_segment不删DB/compress LLM失败不阻塞);阶段3 自动 ✅(波8·0202b51+主代核查,cargo0err+vue-tsc0+devflow79pass,loop顶部检测budget×0.6+压缩+降级原裁剪,set_compressing成对,延迟mutate失败零副作用)。**F-15 全阶段完成 ✅**。
|
||
|
||
**核心问题**:① `ai_chat_clear` 全删 DB 消息不可回溯 ② `build_for_request` 超预算直接丢弃旧消息零保留(`agentic.rs:226` `_trimmed` 被忽略) ③ 无压缩能力。
|
||
|
||
**统一方案**:`ChatMessage.status` 扩展两个值 `archived_segment`(分段标记)/`compressed`(压缩标记),`is_active()` 改正面白名单 `matches!(status, None | Some("active"))`(零行为变化,新状态自动不 active),`sanitize_messages` step 0 已是唯一发送过滤点改 is_active 即隔离旧段/压缩段。`push()`(context.rs:174) 不计 `!is_active()` 消息 token(当前全量计入致 restore 后 token 虚高误判超预算)。
|
||
|
||
**① 会话分段**:IPC `ai_chat_clear_context`:当前 active 消息标 `archived_segment` + 扣除 history_tokens + 落库。不插分隔线 system(前端按 status 渲染折叠分隔条)。三元组原子标记(复用 `build_eviction_units` 分组)。
|
||
|
||
**② 手动压缩**:IPC `ai_chat_compress_context`:取 active 消息 → `compress_prompt` 四段式(意图/决策/文件/约束) → `provider.complete()` 非流式调用(复用 `title.rs:111` `generate_title_via_llm` 模式 + `build_provider_for`) → 原始消息标 `compressed` + 插入摘要 system。压缩单向不可逆,DB 原始消息保留。
|
||
|
||
**③ 智能裁剪(自动压缩)**:agentic loop 循环体顶部 `build_for_request` 前检测:`history_tokens > budget×0.6` 且有可压缩消息 → `compress_old_messages`(保护区外消息 LLM 摘要 → 标 compressed → 插摘要 system)→ emit `AiCompressing`/`AiCompressed`。LLM 失败降级原有裁剪(不阻塞 loop)。`compress_old_messages` 是手动/自动共用的公共函数。幂等:已 compressed 不二次压缩,`is_compressing()` 标志防重入。
|
||
|
||
**实施顺序**:阶段1 基础(is_active 白名单+push token 修正+compress_prompt+compress_via_llm+辅助方法) → 阶段2 手动功能(2 IPC+前端按钮+渲染+i18n) → 阶段3 自动(loop 压缩+事件+前端 loading)。阶段1-2 完成用户已有完整能力。
|
||
|
||
**涉及文件**:`df-ai-core/provider.rs`(is_active 白名单) + `df-ai/context.rs`(push token 修正+辅助方法) + `commands/ai/prompt.rs`(compress_prompt) + `commands/ai/title.rs 或新文件`(compress_via_llm) + `commands/ai/commands.rs`(2 IPC) + `commands/ai/agentic.rs`(loop 自动压缩) + `commands/ai/mod.rs`(2 事件) + `lib.rs` + `api/ai.ts` + `useAiPanel.ts` + `useAiConversations.ts:79`(过滤扩展) + `useAiEvents.ts` + `AiChat.vue` + `i18n/{zh-CN,en}/aiChat.ts`
|
||
|
||
- [x] ✅(batch60·2026-06-16·workflow whae812z5+主代核查,cargo 0err) F-260616-13 [P2] — **build_for_request 持锁重活 + system_prompt token 每轮重估**。性能分析批次发现:每轮 `stream_llm` 前 `session_arc.lock()`(`agentic.rs:251`)持锁期间做 `TokenEstimator::estimate_text(system_prompt)`(L252)+ `build_for_request`(L253 history clone + 裁剪)。system_prompt loop 外固定传入,**每轮重估其 token 是浪费**(可缓存);build_for_request 持锁做 history clone 是重活,消息多时(200 cap)锁持有期长。**方向**:①system_prompt token loop 外算一次缓存 ②build_for_request 先 clone messages 释放锁再裁剪。低收益优化。— agentic.rs:251-253。
|
||
|
||
- [x] ✅(主代核验·2026-06-16) F-260616-14 [P2/核验] — **max_tokens 8192 截断 + 前端批量审批核验**。核验结论:①`max_tokens=8192`(实际 `agentic.rs:270`,行号漂移)≈6-8k 字日常够用,长回复截断由 batch59 MidStream 保文兜底不丢文,**可配低优先非阻塞**(登记可选优化)②前端批量审批 **AE-2025-01 已完整闭环**(ToolCardList.vue:4-11 `ai-batch-approve` 栏 + AiChat.vue:444 `@batch-approve`→`store.batchApprove` + useAiSend.ts:338 遍历 pendingApprovals 逐个 approveToolCall + i18n approveAll/rejectAll),满足「一轮多 Med/High 全 insert pending→一次批量批全部」③audit.rs:496 `process_tool_calls` 一轮多工具 Med/High 进审批门控 insert pending + Low 并行执行链路通。无阻塞 bug,销账。— agentic.rs:270 + ToolCardList.vue + audit.rs:496。
|
||
|
||
- [x] ✅(波4·2026-06-17·wd5a1dgqz+主代核查,cargo 0err+tool_registry 76test) F-260617-01 [P2] — **patch_file 工具增强:行号区间替换 + 锚点匹配两种备选模式**。当前 patch_file 仅支持 `old_text` 精确匹配,对超长行(todo.md 单行 1000-2000 字符)+ 含大量反引号/引号/中文标点的场景,匹配极易失败(JSON 转义错位一个字符就全盘失败)。新增两种可选模式作为备选:
|
||
|
||
**方案 A — 行号区间替换**:新增参数 `replace_lines: { start: usize, end: usize }`(1-based,含首尾行),直接按行号区间删除旧内容 + 插入 `new_text`。不需要 `old_text`。配合 `expected_hash` 乐观锁防并发修改后行号漂移。**适用场景**:超长行文件、已通过 `read_file` 确认行号的精确替换。
|
||
|
||
**方案 B — 锚点匹配**:新增参数 `anchor: { start: string, end: string }`,`start`/`end` 是首尾行的子串标记。工具内部搜索 `start` 所在行号 → 搜索其后第一个 `end` 所在行号 → 转为行号区间替换。不需要完整原文。**适用场景**:只知道替换块的首尾特征文本、不想粘贴整段 `old_text` 省 token。
|
||
|
||
**三种模式互斥**:`old_text`(现有精确匹配)/ `replace_lines`(行号区间)/ `anchor`(锚点)三选一,同时传多个报错。三种模式都需要 `path` + `new_text`。`expected_hash` 可选,三种模式通用。
|
||
|
||
**实现要点**:①方案 A/B 共用底层"按行号区间替换"逻辑(读全文 → 按行切分 → splice 替换 → 写回)②方案 B 锚点搜索用 `lines[].contains(start)` 子串匹配(大小写敏感,与 search_files 一致),找不到返 Err ③方案 A 行号越界返 Err ④三种模式都走现有的人工审批(Medium 风险)⑤`expected_hash` 在写回前校验文件未变。
|
||
|
||
**涉及文件**:`src-tauri/src/tools/` 或 patch_file handler 所在位置(tool_registry.rs / AI 工具层)+ DevFlow 自身的 AI 工具定义(如果 patch_file 是 DevFlow 内置工具)。需确认 patch_file 当前实现位置。
|
||
|
||
### 🔧 任务模块全栈核对(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)
|
||
|
||
### 🔴 aichat API Key 401 排查(2026-06-16)
|
||
|
||
> 用户报对话失败「调用失败: API Key 无效或无权限」+ 重设 key 无效。**排查结论:代码链路全对(保存 keyring✓ / 读取 resolve_provider_secret✓ / 鉴权头 openai=Bearer·anthropic=x-api-key✓ / URL 智能拼接✓),401 来自服务端,非 devflow bug**。根因四选一(key 无效 / provider_type 不匹配 / base_url 错 / model 名错)。详见 [aichat-apikey-401排查-2026-06-15.md](./09-问题排查/aichat-apikey-401排查-2026-06-15.md)。
|
||
|
||
- [ ] S-260615-01 — **[待用户确认根因]** 用户跑直连测试(curl)区分 key/provider_type/base_url/model 哪项错,见详情文档「验证步骤」
|
||
- [x] B-260615-01 ✅ wave(2026-06-16,待commit) — ~~Err 分支加诊断~~ 提取纯函数 `fmt_diag`+`extract_error_diag`(name/status_or_class/timeout·connect 分类,14 单测);约束:LlmProvider trait 无 base_url/endpoint,仅 name() 近似 provider_type(provider_type + 实际请求 url + HTTP 状态码),当前只透传服务端文本看不出端点,401 难定位 — stream_recv.rs:107-113
|
||
|
||
### 🔴 流式响应中断误报排查(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
|
||
|
||
### 🟦 架构审查待修项(2026-06-16)
|
||
|
||
> 纯架构层评估(边界/依赖/抽象/扩展性/状态管理),详见 [架构审查-2026-06-15.md](./05-代码审查/架构审查-2026-06-15.md)。共 🔴6 🟡6 ⚪4 + 亮点 6。与 06-14 三份报告去重(不重复 bug/性能 FR-*、aichat AR-*)。
|
||
|
||
**立即(零风险减法)**
|
||
- [x] ARC-260615-01 ✅ wave(2026-06-16,待commit) — ~~删 `stores/settings.ts` mock 死代码~~ grep 验零消费者 + 删文件 + index.ts 清导出 + 清 appSettings.ts:8 过时注释
|
||
- [x] ARC-260615-02 ✅(2026-06-16,待commit) — ~~`/decisions` 路由死链~~ 删 nav 项(App.vue secondaryNav)+ 删 `nav.decisions` i18n key(zh/en)。决策治理 = F-260614-08 长期项未实现,入口提前占位成死链;`dashboard.recentDecisions`(Dashboard.vue)不同命名空间保留
|
||
|
||
**短期(低成本)**
|
||
- [x] ✅(决策a·2026-06-16·转架构维护记录无代码) ARC-260615-03 — **重评降级(2026-06-16)**:df-execute **非空壳**——shell.rs `execute()` 已完整实现(cmd/sh 跨平台 + kill_on_drop + timeout + env)+ 被 tool_registry.rs `run_command` 复用(F-260615-05)。原 todo「76 行/1 函数 + TODO」基于 lib.rs 空判,过时。合并进 df-nodes 破坏职责分离(节点定义 vs 执行运行时)+ 动依赖树,**转架构维护决策,非清债**
|
||
- [x] ARC-260615-04 ✅ wave(2026-06-16,待commit) — ~~`stores/index.ts` 补 `export useAiStore`~~ barrel 补导出(3 处 view 直连可选迁移,非强制)
|
||
|
||
**中期(技术债)**
|
||
- [x] ✅(batch29·2026-06-16) ARC-260615-05 — ~~stores/project.ts 上帝 store 拆分(四领域+越层 invoke)~~ 核验越层 invoke 0 命中(已下沉 api/workflow.ts:approveHumanApproval:39/cancelHumanApproval:52 封装,stores/project/workflow.ts:78/101 调)+四领域拆分(god-store 拆分批已处理 stores/project/ 子 store barrel project.ts:122-123 委托)。vue-tsc exit 0
|
||
- [x] ✅(batch29·2026-06-16) ARC-260615-06 — ~~events↔stream 循环依赖~~ 提 aiShared.ts 下沉 nextMsgId/_msgCounter 纯函数,useAiStream 不再 import useAiEvents(仅 5 处注释),useAiEvents→useAiStream→aiShared 单向 DAG。vue-tsc exit 0
|
||
- [x] ✅(batch30·2026-06-16 评估维持现状) ARC-260615-06b [P3] — useAiEvents↔useAiSend 另一环(useAiEvents:19 import useAiSend/useAiSend:23 import useAiEvents),靠事件总线 ai-approval-clear-timers 部分缓解,非本批范围。遗留自 ARC-06 核验
|
||
- [ ] ARC-260615-07 — 其余见文档:df-core 改名 df-types(类型库非核心)/ src-tauri IPC 编排层抽取(df-app,5711 行成事实业务层) / AI agent loop 从 IPC 下沉 df-ai / 3 view 绕 store 调 api / 类型契约 ts-rs 代码生成 / AiSession 多会话(B 路线前置) / AppState 分组 / IPC 命名统一
|
||
|
||
**渲染优化(自研块级 memo 已实施,2026-06-16)**
|
||
- [x] ARC-260615-08 ✅(2026-06-16,待commit) — **[渲染优化·自研块级 memo 已实施]** 流式 Markdown 渲染——保留 marked+DOMPurify+.ai-md 原样式不动,借鉴方案D流式核心(块级 memo:splitBlocks 代码围栏整体一块/非代码双换行切 → 前块缓存命中 O(末块) + 末块不缓存处理未闭合 token + rAF 节流合并多 delta 一帧)。退役 AR-1 纯文本短路 — 关联 AR-1 — 详见 [aichat流式Markdown渲染调研-2026-06-15.md](./02-架构设计/aichat流式Markdown渲染调研-2026-06-15.md) 【决策转向:先试方案D(markstream-vue@1.0.1 接入+vue-tsc 通过),但样式100%还原 .ai-md 成本高且脆(代码块 .code-block-container chrome / 暗色 --ms-* 变量 / prose .markstream-vue 作用域 4 处对接,随库升级漂移),用户优先原样式,转自研块级 memo——零样式对接(marked 输出标准 HTML + .ai-md 全覆盖) + D 级流式性能(O末块)。已实施:splitBlocks/parseBlock(memo)/parseBlockNoCache(末块)/renderStreamingMd/scheduleStreamParse(rAF)/renderContent + watch currentText→scheduleStreamParse/streaming 翻转清 rAF + onBeforeUnmount 清 rAF + 回退 markstream 依赖恢复 marked/dompurify + vue-tsc exit 0;留后续:dev 运行时流式验证(掉帧/长回答边界/末块未闭合表现)】
|
||
|
||
### 🔵 全局代码 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] B-260614-08 — ~~promote_idea 两步写非事务~~ ✅ mission:T-260614-05 已修复(idea.rs 第二步 update_full 失败时补偿删除已建 project;Repository 不支持跨 repo 共享事务对象,选补偿删除非真事务,改动最小;附 logging)(06-14)
|
||
- [ ] T-260614-01 — **[P1]** Sprint 9/10/14/15/16/18 多项未 tauri dev 实测 — 评分 IPC 缩放 / update_full / promote_idea / Store getter / token 落库 / 知识库 Tier 1 全栈 / LLM 并发 Semaphore / 知识生命线(#54 跟踪)— source:Sprint 9-18 (06-14)
|
||
- [ ] T-260614-02 — **[P1]** 切对话不中断路由:部分场景运行时实测(A 路线场景 2/3) — source:Sprint 8 (06-14)
|
||
|
||
### 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)。7 大类 20 个改进项,按原文优先级分 4 批。**去重**:§1.3 复制 ⊂ F-260615-13/14(已写)、§3.4 新建中断 ⊂ B-260615-10(已做软复位,本文档加 confirm 增强)、§6.2 分离窗口同步 ⊂ CR-24/25(已写)。**状态: 待讨论→看板跟踪**。
|
||
|
||
**第一批 — 最高频痛点(每次对话都遇到)**
|
||
|
||
- [x] UX-2025-01 ✅ 选文字(文本偏移量方案) (第⑩批销账 06-15) — **流式渲染中选中文字保持**(§2.1)。现象:AI 流式输出时选文字被 delta 打断丢失选区。方案 A(推荐):`selectionchange` + 选区在消息容器内 → 暂停 rAF 流式 parse → 选区结束恢复;方案 B:已完成块独立 DOM 节点不参与 v-html 替换。关联 ARC-08 块级 memo(splitBlocks 已按块切分,末块单独更新天然支持方案 B)— src/components/AiChat.vue + src/composables/ai/useAiEvents.ts
|
||
- [x] ✅(batch36·2026-06-16·workflow w098aapgd) UX-2025-02 [P1] — **消息操作栏(复制 / 重新生成)**(§1.2+§1.3)。AI 气泡 hover 显示操作栏:「📋 复制」+「🔄 重新生成」。复制 = F-260615-13 合并实现;重新生成:后端删最后一条 AI 消息 + 用倒数第二条 user 消息重跑 agentic loop(新增 `ai_regenerate` IPC)。代码块额外 hover 浮出「📋 复制代码」按钮(§2.2 子项)。— src/components/AiChat.vue + src-tauri/src/commands/ai/commands.rs
|
||
- [x] ✅(batch36·2026-06-16·workflow w098aapgd) UX-2025-03 [P1] — **错误气泡操作入口**(error_type 枚举本身 batch34 B-42 已加,本批前端消费)(§4.1)。错误气泡底部增加按钮区:「🔁 重试」(取上一条 user 消息重发)+「⚙ 去设置」(仅 auth/provider 错误时显示,跳 Settings→AI Tab)。后端 `AiError` 加 `error_type: Option<ErrorType>` 枚举(auth/network/timeout/provider_config/unknown),前端据此显隐按钮。— src-tauri/src/commands/ai/mod.rs(AiChatEvent) + src/api/types.ts + src/components/AiChat.vue
|
||
- [x] ✅(batch59·2026-06-16·workflow wdlt0xfl6) UX-2025-04 [P1] — **断线保文**(§4.2)。保文核心落地:stream_recv.rs `StreamResult` enum(Complete/Partial/InitFailed 三分支),MidStream 失败(流中途 chunk error/idle timeout/有 partial_text 未 finished)返 `Partial{partial_text}` **不重试保文**;agentic.rs 收 Partial → 入库 assistant 消息 + emit AiCompleted(incomplete=true,非 AiError) + 追加系统提示「⚠ 响应因网络中断不完整」;useAiEvents.ts:177 补 `AiStreamRetry` case(消除死事件穿透) + AiCompleted case 处理 incomplete(系统提示气泡镜像后端);types.ts:204 `incomplete?:boolean`。对齐决策 a1(Init 可重试/MidStream 保文不重试) + UX-04 决策 a。**「继续生成」按钮暂未做**(保文核心硬需求已满足,按钮可复用 UX-2025-03 重试能力或按需补)。— src-tauri/src/commands/ai/stream_recv.rs + agentic.rs + src/composables/ai/useAiEvents.ts
|
||
|
||
**第二批 — 显著提升日常效率**
|
||
|
||
- [x] ✅(第⑳批 2026-06-16) UX-2025-05 [P2] — **代码块语法高亮 + 复制按钮**(§2.2)。**高亮部分完成**:highlight.js(core+16 语言按需注册,~50-60KB gzip)+ marked.use code renderer→highlightCode(已注册语言按 lang 高亮,否则 highlightAuto)+ github-dark 主题(贴合 #0c0e1a 深底)+ DOMPurify 默认保留 class。**复制按钮留 TODO**(需 AiChat.vue hover 浮层+事件委托,待 UX-02/F-260615-13 合并)。— src/composables/useMarkdown.ts + package.json(highlight.js ^11.11.1)
|
||
- [x] ✅(batch41 store + batch43 UI·2026-06-16·workflow wcy478m3y) UX-2025-06 [P2] — **对话搜索**(§3.1)。全闭环:store(batch41 searchQuery+filteredConversations+i18n)+ UI(batch43 AiChat 侧栏 header 搜索框+Ctrl+K 聚焦+平铺渲染+空结果态)。注:无独立 Sidebar.vue,侧栏内联 AiChat.vue
|
||
- [x] UX-2025-07 ✅(第⑲批 2026-06-16) — ~~键盘快捷键~~ onGlobalKeydown(AiChat.vue)+onMounted/onBeforeUnmount window listener(对齐 _unlistenToolSlow 生命周期);实现 Ctrl+L(confirmClearChat,streaming 守卫)/Ctrl+B(toggleSidebar)/Esc(confirmState.visible→answerConfirm(false)>props.detached→closeDetachedWindow 优先级);Ctrl+N/K/R 留 TODO(WebView2 浏览器级 preventDefault 不可靠待实测);Esc 冒泡与 textarea handleKeydown 不冲突(skill 浮层 Esc 由 textarea handler 先消费)。vue-tsc 0 err。原设计:Ctrl+N 新建/Ctrl+K 搜索/Ctrl+L 清空/Ctrl+Shift+C 复制/Ctrl+R 重新生成/Esc 关闭/Ctrl+B 切侧栏 — src/components/AiChat.vue
|
||
- [x] ✅(batch47·2026-06-16·workflow wjkywbhff) UX-2025-08 [P2] — **新建对话 confirm 弹窗**(§3.4 增强)。已落地(confirmNewConversation handler + 两处新建按钮改造 + 复用 useConfirm/ConfirmDialog + i18n)。注:生成态判定用 isViewingGenerating(非 state.generating 实际是 streaming),commands.rs 零改(复用 store.newConversation 软复位 B-260615-10)
|
||
|
||
**第三批 — 锦上添花**
|
||
|
||
- [x] ✅(batch51·2026-06-16·workflow wdfffj1ti) UX-2025-09 [P2] — **编辑已发送消息并重新生成**(§1.1)。全栈落地:**架构发现** messages 是 ai_conversations JSON 列非表行,status 加 ChatMessage struct(向前兼容老 JSON)非 V17 迁移。df-ai provider.rs status+is_active / context.rs sanitize 过滤 truncated+replace+truncate / commands.rs ai_chat_edit IPC(复用 ai_regenerate spawn)/ api+useAiSend editMessage / AiChat 末条 user 编辑按钮 / i18n 4 key。只末条 active user 可编辑+generating 拦截+truncated 软删保留 DB 不进上下文/视图
|
||
- [x] ✅(batch52·2026-06-16·workflow w91svrokz) UX-2025-10 [P2] — **@ 实体引用**(§1.4)。全栈落地:AiChat @ popover(复用技能架构 .ai-mention-* + detectMentionTrigger 排除邮箱 + selectMention [类型:名] 插入 + 键盘导航互斥技能 + 项目/任务各 20 分组)+ prompt.rs 任务段注入(state.tasks.list_active,**签名零变化**降级避碰 ai_chat_send 领地)+ i18n 5 key。降级:实体 id 未透传(凭名称+状态对齐),最近文件联想无 store 略
|
||
- [x] UX-2025-11 [P2] — **输入框高度提升**(§1.5)。textarea 最大高度 120px→200px(~10 行),超过后再内部滚动。或可拖拽 resize handle。— src/components/AiChat.vue (第⑬批销账 06-15)
|
||
- [ ] UX-2025-12 [P2] — **历史消息分页懒加载**(§2.3)。**▶ 已打开推进(2026-06-17 用户决定不再等痛点,与 UX-19 一并纳入)**。方案:switchConversation 首次加载最近 50 条,滚顶加载更多(需后端 messages 查询加 offset/limit 支持)。关联 B-260629(messages 无上限)。**决策点(待定)——与 UX-19(虚拟滚动)二选一**解决长对话渲染卡顿:分页(后端配合,简单稳定,但切换/搜索跨页体验割裂)vs 虚拟滚动(纯前端,体验流畅,但流式末条挂载复杂)。**倾向 UX-19 虚拟滚动优先**(纯前端零后端改动+体验优),本项分页作为虚拟滚动不足时的补充方案。— src/stores/ai.ts + src/composables/ai/useAiConversations.ts + 后端 conversation.rs
|
||
- [x] UX-2025-13 [P2] — **消息时间戳**(§2.4)。气泡下方极小字号(dim 色)展示相对时间(复用 time.ts 已有 common.justNow 等 key);hover tooltip 展示完整时间。— src/components/AiChat.vue (第⑬批销账 06-15)
|
||
- [x] ✅(第㉒批 2026-06-16) UX-2025-14 [P2] — **Provider 切换反馈**(§5.1)。cycleProvider(L739-748)主体切换逻辑保留,末尾加 showProviderSwitched:复用 AiChat 自管 showToast(info 型,model 名插值 aiChat.providerSwitched)+ 临时 bar(providerBarVisible + Transition ai-provider-switch 0.15s 淡入/淡出 + 2000ms 自动淡出,展示 aiChat.providerSwitchHint model 名小字+accent 脉动圆点)。toast 调研:AiChat 已有自管 reactive showToast(分离窗口无根 toast 故自管),复用无降级。_providerBarTimer onBeforeUnmount 清(L1067)。stores/ai.ts 未改(cycleProvider 在 AiChat.vue 内)。— src/components/AiChat.vue + i18n(aiChat.providerSwitched/providerSwitchHint zh/en)
|
||
- [x] ✅(第㉓批 2026-06-16) UX-2025-15 [P2] — **技能参数用法展示**(§5.2)。浮层 .ai-skill-item 改 column:.ai-skill-item-row(name/desc/src 单行)+ code.ai-skill-item-hint(v-if s.argument_hint,等宽 dim 11px,L390)。chip(L354-361):.ai-skill-chip-main(name+desc+code.ai-skill-chip-hint v-if pendingSkill.argument_hint L358)+ x 关闭(@click clearSkill,补 aria-label=aiChat.clearSkill 无障碍)。复用 var(--df-font-mono)+ui-monospace fallback。**JS 零改动**(仅 template 结构+CSS,/ 联想/selectSkill/clearSkill/Escape/Enter 逻辑全保留)。i18n 无新增(argument_hint 是 skill 数据自带,复用 aiChat.clearSkill)。— src/components/AiChat.vue
|
||
- [x] ✅(第㉑批 2026-06-16) UX-2025-16 [P2] — **侧栏宽度可拖拽**(§6.1)。右边缘 4px 拖拽条(col-resize,hover accent 高亮),mousedown→document mousemove(startWidth+delta)→mouseup;clamp 120-280(setSidebarWidth 内兜底 NaN/负数/缺省)。**持久化**:move 高频只改 state 不落盘,up 一次性 persistUiState 写 df-ai-ui(复用现有机制,App.vue key 已注册)。**宽度绑定 CSS 变量** `--sidebar-width`(非内联 width,保 sidebar-slide 折叠过渡不被压过)。**卸载兜底** onBeforeUnmount 摘 listener 防泄漏。body.ai-sidebar-resizing 锁 cursor+user-select 防抖动/误选。— src/components/AiChat.vue + src/composables/ai/useAiPanel.ts + src/stores/ai.ts(sidebarWidth:200) + i18n(aiChat.sidebarResizeHint zh/en)
|
||
- [x] ✅(batch49·2026-06-16·workflow wkan76fon) UX-2025-17 [P3] — **对话置顶**(§3.2)。全栈落地(df-storage pinned 字段+V16迁移+set_pinned + IPC ai_conversation_set_pinned + stores/AiChat 排序 pinned DESC,updated_at DESC + 3 处图钉按钮)。注:无独立 Sidebar.vue 侧栏在 AiChat.vue,conversation.rs:141 补 pinned:false 必填(跨 batch44 领地 proactive-bug-fix),filteredConversations 搜索态未动(降级)
|
||
- [x] ✅(batch46 后端 + batch48 前端·2026-06-16) UX-2025-18 [P3] — **对话导出**(§3.3)。全闭环:后端(batch46 ai_conversation_export IPC commands.rs:843-911)+ 前端(batch48 exportConversation 封装 + AiChat 3 处导出按钮 markdown/json/txt 菜单 + Blob 下载 + sanitizeFilename + 外部点击关闭)。注:无独立 Sidebar.vue 侧栏在 AiChat.vue
|
||
- [ ] UX-2025-19 [P2] — **消息列表虚拟滚动**(§7.2)。**▶ 已打开推进(2026-06-17 用户决定不再等痛点,与 UX-12 一并纳入,作为首选方案)**。方案:集成 vue-virtual-scroller 或自研 IntersectionObserver 懒渲染;流式最后一条始终保持挂载(参照现有 onContentChange/isNearBottom 滚动逻辑)。**与 UX-12 分页二选一**(见 UX-12 决策点),**倾向本项优先**(纯前端零后端改动+体验优,P3→P2 升级因纳入推进)。— src/components/AiChat.vue + 新依赖(vue-virtual-scroller)
|
||
- [x] ✅(第㉒批 2026-06-16) UX-2025-20 [P3] — **空状态引导 + 标题过渡**(§7.3+§7.4)。空态:messages 空+非流式时 .ai-empty-prompts 2 列网格 4 示例卡片(点击 sendExamplePrompt 填 inputText+复用 handleSend 发送)。无 provider 显「去设置」按钮(goToSettings:嵌入 router.push('/settings'),分离窗口降级 showToast 提示去主窗口)。标题淡入:titleFlash ref + watch(activeConv.title)空→非空(loadConversations 回填触发)置 flash 驱动 0.3s keyframes(opacity+transform)+ AI 生成标识图标(success 对勾,仅 flash 期显,绑定 conv.id===activeConversationId)。_titleFlashTimer 320ms 清 + onBeforeUnmount 兜底(L1065)。— src/components/AiChat.vue + i18n(aiChat.examplePrompt1~4/goToSettings/goToSettingsHint zh/en)
|
||
|
||
### 🟡 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
|
||
|
||
### 待澄清 / A-B 待定
|
||
|
||
- [ ] S-260614-01 — **[用户已澄清 2026-06-17]** 「显示多开」= 多个会话来回切都可对话(非 AI Chat 窗口多开)。关联 T-260614-02(切对话不中断实测)+ F-09 多会话架构。**A 路线(单例软隔离,已做)**:切会话软复位+DB 加载上下文+active 切换,来回切应能对话(需实测 T-260614-02)。**B 路线(真多会话,未做)**:切走会话 generating 不打断,每会话独立 AiSession(memory aichat-arch-extensibility 单例未动)。**待决策 A/B 路线 → 见待决策.md**
|
||
- [ ] S-260614-02 — 审批可见性 A/B 待定 — B-01 修复后 pending_approvals 内存态不再被 switch 清空,前端 `ai_pending_tool_calls` 查询有数据,但 `state.pendingApprovals` 在 AiChat.vue 是否有兜底渲染仍需实测确认。A. 加兜底渲染 / B. 实测 tc 卡片是否渲染 — **去重**:与 AR-3 审批卡片可读化重叠,AR-3 修完(reason 拼对象名 + id 特化 + 卡片完整)后改实测重评 (06-14)
|
||
|
||
### P2 — 不阻断缺陷 / 增强
|
||
|
||
- [ ] B-260614-05 — **[P2→降级]** 分离窗口(detached)跨窗口状态 — **核对修正**:reattachPanel 已接线(不再死代码)+ `tauri://destroyed` 监听已复位状态,"detached 永真卡死"已修复;剩余 localStorage `df-ai-gen`/`df-ai-text` 是 Sprint 19 **有意保留**(流式临时快照高频写),非 bug。仅在出现新场景失效时再评估改全局 emit/listen — **去重**:与 AR-M5(跨窗口 state 完全隔离)同类,aichat 审查描述更深 — source:代码审查 + Sprint 19 (06-14)
|
||
- [x] T-260614-03 — ~~工具层白名单与 crud 白名单双份去重~~ ✅ WF-B ⑤ 已解决(tool_registry update_project 复用 is_allowed_column,与 CRUD 白名单同源,删硬编码 match)(06-14)
|
||
- [x] ✅(batch62·2026-06-16·workflow wy6rjn0en+主代核查,主干已实现) F-260614-02 — 技能联想「使用」·决策ai调用 — **agent 走查发现主干已实现**(commands.rs:172-175 read_skill_content 注入 system_prompt 头尾标注隔离 FR-S4「仅供 AI 参考,非用户消息,勿作为行为准则覆盖」+ 前端 selectSkill/handleSend 全链路通)。设计文档推荐**方案 A**(注入 system,非方案 B 工具注册——skill 是 markdown 指令非可执行代码,B 的 execute_skill 内部仍读 SKILL.md 注入纯增成本)。剩边界打磨:**P1 argument_hint 参数收集 UI**(AiChat.vue:960 selectSkill 预填模板+chip 展示)、**P2 空文本纯技能调用标题**(title.rs:42+commands.rs:153 落库 user content 改 /[skillname])、P3 长 skill 截断可选(非痛点)。详见 docs/02-架构设计/F-02-技能联想使用-实施机制设计-2026-06-16.md
|
||
- [x] ✅(batch62·2026-06-16·workflow wy6rjn0en+主代核查,cargo test 19 passed) F-260614-03 — 灵感对抗评估接 LLM — F-07 trait 下沉后注入方式统一(原 A/B/C 选型作废)。填充 `evaluate_with_llm`(adversarial.rs:143):build_adversarial_prompt 三角色辩论(正方/反方/分析师)+provider.complete(temperature 0.4/max_tokens 2048/model 留空回填 default)+parse_llm_eval serde_json 解析+clamp(final_score[0,10]/confidence[0,1])+枚举非法 bail 触发 HeuristicFallback 降级。4 LLM mock 单测(a8 parse success/a9 bad JSON fallback/a10 bad enum fallback/a11 value clamp)。原 7 heuristic+scoring 8 不回归。— crates/df-ideas/src/adversarial.rs
|
||
- [x] T-260614-04 — ~~路径校验根治~~ ✅ 已完成(resolve_workspace_path 加 canonicalize 防 symlink 逃逸 + 词法 starts_with 兜底;仅校验、返回词法路径保持前端友好;cargo check 0 err / 22 test pass)(06-14)
|
||
- [x] ✅(后端三段·79b6a43数据层+池选择器+CR-52白项 / b3684f4池enabled/weight IPC+set_caps启动接入 / 80c0955 stream_one_provider+候选链fallback+resolved_model重算;前端api/ai.ts:142 IPC封装已接) F-260614-04 — 多 Provider 负载均衡池 — 备用模型/多账号聚合,全局容量=min(各 provider 上限之和, global_cap) (06-14)。**注**:UI 配置面板(provider 池 enabled/weight 可视化设置)是否完成待核验——后端 IPC+前端 API 层已就绪
|
||
- [x] ✅(波20-22·2026-06-17·e3cd448/e1d396d/4317d02,F-05 全链闭环·务实偏离 content:String+parts 非 Vec) F-260614-05 — 模型能力系统 Phase 2 — 多模态消息支持:ChatMessage.content: String → Vec<ContentPart>(Text/Image);前端粘贴/拖拽图片;vision 模型自动路由 (06-14)
|
||
- [x] ✅(batch61·2026-06-16·workflow wwtn2knn6+主代核查,cargo 0err+vue-tsc 0err) F-260614-06 — 导入历史项目(scan 第二步) — 6 决策全落地:①description 走 LLM(scan.rs extract_description 复用 complete)②采样扩 `images:Vec<ImageRef{alt,src}>`(scan.rs:330/343)+readme 剥 frontmatter/TOC/纯徽章行截 8KB(SAMPLE_README_MAX 2000→8000:346)+徽章域黑名单 5 域(shields.io/badge.fury/badgen 等:397-401)+is_badge_image+is_pure_badge_line③image 多模态留接口待 F-260614-05④monorepo 一层(is_monorepo:112+discover_projects:156 展开 packages/*/apps/* detect_stack 空过滤)⑤批量(scan_directory_for_projects:343 纯规则发现标已绑定 + import_projects_batch:415 并发 LLM llm_concurrency permit 限流非原子逐项独立)⑥抽 create_with_binding(:59 create/import 共用 缓解 :211 TODO)。前端 Projects.vue 导入 modal+api/project.ts 两 API+i18n 双语+scan.rs 单测全。— crates/df-project/scan.rs + src-tauri/commands/project.rs + lib.rs + api/project.ts + Projects.vue + i18n。详见功能决策记录(06-14)
|
||
- [x] T-260614-09 — ~~idea.rs 物理删不级联~~ ✅ WF-E 完成(idea.rs:149 delete→purge_with_descendants,1 行,签名兼容)(06-14, commit 89da9fa)
|
||
- [x] T-260614-10 — ~~findBinding canonicalize~~ ✅ WF-E 判定已解决(normalize_path 已含 canonicalize 优先 + 词法回退,find_binding_conflict 两端对称已用,无需重复加;零改动)(06-14, commit 89da9fa)
|
||
- [x] ✅(batch61·2026-06-16·workflow wwtn2knn6+主代核查,cargo 0err+7单测) F-260614-07 — **[架构前置]** df-ai-core trait 下沉拆 crate — 4 决策全落地:①df-ai-core 新 crate(仅 trait+数据结构 LlmProvider+ChatMessage 等,ContextManager/TokenEstimator 留 df-ai)②构造注入 Engine::new(Arc<dyn LlmProvider>)+Engine::heuristic()(idea.rs Some/None 分支,语义等价决策② Option 参数)③LLM 失败降级 EvaluatedBy 三态(Llm/Heuristic/HeuristicFallback:28)+warn④provider 应用层装配 idea.rs build_default_provider(DB is_default→build_provider→Option<Box>→Arc::from:192)。df-ai provider.rs:15 `pub use df_ai_core::provider::*` re-export(外部 use 路径不变,df-nodes 零改动)+lib.rs:17 `pub use df_ai_core`。**解锁 F-03 注入**(其他 crate 依赖 df-ai-core trait 非 df-ai impl)。— crates/df-ai-core(新)+crates/df-ai+crates/df-ideas+src-tauri/commands/idea.rs。详见设计文档 F-07-df-ai-core-trait下沉设计
|
||
- [x] ⏸️(待决策.md已决c暂缓·2026-06-16) F-260614-08 — 决策治理产品化 — 活契约/规格契约自检机制产品化为可操作功能(当前散落文档机制) — source:功能决策记录缺口
|
||
- [x] ⏸️(待决策.md已决c暂缓·2026-06-16) F-260614-09 — 项目 status 字段治理 — status 状态机规范化(planning/active/archived 等枚举约束) — source:功能决策记录缺口
|
||
- [x] ⏸️(待决策.md已决c暂缓·2026-06-16) F-260614-10 — 知识库 MCP Server + Tier 2/3 — 对外 MCP 暴露 + 分层存储(当前仅 Tier 1 全栈) — source:PROGRESS Sprint15
|
||
- [x] ⏸️(待决策.md已决暂缓·2026-06-16) T-260614-11 — 全局#6 条件表达式引擎升级 — df-workflow conditions 仅支持 true/false 字面量,升级为真表达式求值 — source:PROGRESS 全局问题
|
||
- [x] T-260614-12 — ~~df-ideas 死代码~~ ✅ WF-E 部分完成(capture.rs 删 CaptureInput/IdeaCapture 死码,保留 Idea/IdeaScores 共享实体;promotion/scoring/adversarial 内"两套 Recommendation/PromotionPolicy 死枚举"嫌疑 agent 未确认存在/保留为对外契约,本次未动,待复查)(06-14, commit 89da9fa)
|
||
- [x] F-260615-01 ✅(2026-06-16,待commit a3cccb070fe9c8821) — **[P1 功能增强]** HumanNode 审批节点支持自定义选项 + 单选/多选类型。现状:config `options: Vec<String>` 已支持任意数量(2/3/…数量扩展已通),但 `decision` 是单 `String` 仅单选语义,`options` 空=自由文本。增强目标:不止「同意/拒绝」二选一,可配置 N 个候选项 + 单选(single)/多选(multiple)两种类型。**改动面**:①`df-core/events.rs` `WorkflowEvent::HumanApprovalRequest` 加 `select_type`、`HumanApprovalResponse` decision 单值→多值(`decisions: Vec<String>` 或保留 decision 兼容 + 加 decisions)②`human_node.rs` config 解析 `select_type` + 校验(多选时每项 ∈ options,可加 min/max 选中数约束)③IPC `approve_human_approval` 签名 ④前端 `stores/project.ts` approve + `api/types.ts` 事件类型 + 审批弹窗 UI(单选 radio / 多选 checkbox)⑤单测改断言 + 新增多选/超限测。**注意**:向后兼容现 single 调用方,`select_type` 缺省 = single — source:用户需求(06-15),crates/df-nodes/src/human_node.rs + src-tauri/src/commands/workflow.rs + src/stores/project.ts — **✅已实施(a3cccb070fe9c8821,2026-06-16,待commit)**:6 文件契约向后兼容(events.rs SelectType 枚举 Single/Multiple 缺省 Single+HumanApprovalResponse decision+decisions 双字段/human_node.rs 校验 single len==1·multiple len≥1·∈options/workflow.rs IPC 加 decisions+select_type Option 缺省兼容/types.ts/project.ts approve/ProjectDetail.vue checkbox 多选 UI)。cargo check 0 err/df-nodes 21 test(含 4 新增多选)/vue-tsc 0 err。主代理核查契约向后兼容 + 6 文件边界
|
||
- [x] F-260615-02 ✅(2026-06-16,待commit) — **[P1 功能]** task 详情查看。✅已实施:get_task_by_id IPC(复用 TaskRepo::get_by_id ok_or_else 转 Result)+lib.rs 注册+taskApi.get+/tasks/:id 路由+TaskDetail.vue(11 字段:标题/描述/状态/优先级/关联项目 router-link 解析名/分支标签/负责人/基础分支/工作流定义/创建更新时间;复用 constants/project 标签+formatDate+watch route.params.id 重载)+Tasks.vue 列表项 @click router.push;TaskRecord TS 类型已存在无需新增;主代理核查 cargo check 0 err/vue-tsc 0 err/git diff 6 文件边界干净。现状:`src/views/Tasks.vue` 仅列表,无独立 TaskDetail 视图/路由(grep 仅 `ProjectDetail.vue` 嵌套任务命中,无独立详情页)。需求:点击 task 查看详情。**数据模型已就绪**(`df-storage/src/models.rs:53` `TaskRecord` 12 字段:id / project_id / title / description / status / priority / branch_name / assignee / workflow_def_id / base_branch / created_at / updated_at)。**改动面**:①新建 `src/views/TaskDetail.vue` 视图 + 路由(`router/index.ts` `/tasks/:id`)②task 详情 IPC(`get_task_by_id`,核对 `commands/task.rs` 现有 IPC 是否已有,无则补)③`Tasks.vue` 列表项点击 → 跳详情 ④详情页字段展示(title/description 渲染、status/priority 标签、关联项目名解析 project_id→name、branch/assignee 信息、时间戳)⑤可选:详情页内编辑(`update_task` IPC 已存在,FR-D6)。— source:用户需求(06-15),src/views/Tasks.vue + src/router/index.ts + src-tauri/src/commands/task.rs
|
||
- [x] F-260615-03 ✅ truncated 标志 (第⑩批 2026-06-16) — ~~list 工具截断标志~~ (list_trash补漏) 原完整分页方案,对抗分析降级。现状:4 工具 `truncate(50)` 硬截断,AI 不知被截断会回复「不存在」。**对抗分析裁决**:数据量未到 50 条线(projects=5, tasks=61),完整分页 breaking change 风险 > 收益 → **降级为最小修**:不改返回结构(non breaking),只在 truncate 时加 `truncated: true` 标志 + 工具描述注明"最多返回 N 条"。未来数据量到时再升完整分页(F-03-full)。**改动面**:tool_registry.rs 4 handler 截断处加 truncated bool 返回 — src-tauri/src/commands/ai/tool_registry.rs:127-165,377-379 — source:用户需求(06-15)+对抗分析裁决(06-15)
|
||
- [x] F-260615-04 ✅ 卡片分组增强 (第⑩批 2026-06-16) — ~~read_dir / read_file 工具卡片连续时折叠/收起~~ 提高信息密度。现状:AI 探查目录常连续调多个 read_dir + read_file(先列目录再读多个文件),每个结果独立卡片平铺,长列表/大文件内容占满屏幕,信息密度低。需求:相邻同类读取卡片支持折叠——默认收起只显摘要(如「read_dir: 12 项」「read_file: src/main.rs (234 行)」),点击展开看详情;或连续 N 个同类卡片归组折叠。**改动面**:①`ToolCard.vue` 加折叠态(`collapsed` ref + 摘要/详情双视图 + chevron 图标 + 高度过渡)②连续同类检测/归组(`ToolCardList.vue` 按 `tool.name` 分组,已有列表容器适合放分组逻辑)③摘要提取(read_dir 数项数 / read_file 文件名 + 行数,解析 result)④折叠态持久化(可选,localStorage 按 conv)。关联信息密度构想(memory: devflow-info-density-concept,卡片折叠是其中一环)。— source:用户需求(06-15),src/components/ToolCard.vue + src/components/ToolCardList.vue — **勘察完成(2026-06-16,wxflofhf2)**:feasible/plan 11 步跨 5 文件(ToolCardList/ToolCard/useAiSend/stores/ai/global.css)。risk 标低但实为 UX 新行为+改核心 ai 状态文件(useAiSend.ts/stores/ai.ts)+plan 细节有误(useAiSend composable 无 emit 方法)。**拆小或留待**:先做 ToolCardList 分组+单卡折叠摘要(限定不碰 useAiSend/stores),完整折叠交互归信息密度构想单独立项
|
||
- [x] F-260615-07 ✅ search_in_file (第⑩批销账 06-15) — ~~read_file 加 search 参数~~ 已落地(见 :493 search_in_file 同条)。**[P2 功能]** read_file 加 search 参数(单文件内关键词定位)。现状:read_file 仅 offset/limit 分页(tool_registry.rs:458-501),无原生搜索——AI 找关键词需多次分页扫描定位,token 浪费 + 多次 IPC。用户实测确认 offset 0基 + limit=1 单行精确读取可用,但定位靠 AI 多轮分页。**方案**:① schema 加 `search`(string,可选)参数 ② handler 内若有 search:读全文(1MB 限内)→ 按行过滤含 search 的 → 返回 `matches: [{line, content}]`(line 为 1基行号,须在描述注明与 offset 0基的差异)③ 匹配超量:默认返前 50 匹配 + total + has_more。**改动面**:tool_registry.rs read_file handler(:458-501)schema 加参数 + 返回结构扩展(有 search 返 matches,无 search 走原 content 路径,两路径并存)。**权衡**:vs 独立 grep 工具——read_file search 满足单文件定位主场景(最小改动,复用现有工具);跨文件搜索待 B-260615-37(run_command stdout)修后用 grep/findstr 验证够不够用,不够再立 grep 工具(对标 Claude Code Grep)。**大小写**:默认区分(确定性优先),case_sensitive 参数先不加(做减法)。— source:用户建议(06-15),src-tauri/src/commands/ai/tool_registry.rs:458-501
|
||
|
||
### 🟡 文件操作工具集补全(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 交互体验缺陷(高优先级专区)
|
||
|
||
> 用户指令:aichat 界面交互体验问题提高优先级。本区集中记录 AiChat.vue + ToolCard.vue + composables/ai/* 交互层缺陷。
|
||
> 扫描 agent 运行中,结果持续追加。已存在问题来自用户实测 + 历史走查。
|
||
|
||
#### 🔴 已确认交互 Bug
|
||
|
||
- [ ] **UX-260617-01 [P0]🎯** — **消息/工具卡大量重叠堆叠(用户实测确认)**。5 角度深入分析(todo.md L116-180+):CSS 声明链路正常、scoped 无冲突、DOM 结构正确 → 排除常规原因。**核心嫌疑**:虚拟滚动 IO 半激活态不一致——部分 item 被 IO 管理(可卸载)、部分不被管理(永远渲染) + height=0 时 minHeight 未设置 → slot 塌 0 → 重叠。**需复现定位确切触发路径后修复**。— src/components/AiChat.vue + src/composables/ai/useAiVirtualScroll.ts
|
||
- [x] **UX-260617-02 [P1]🎯** — **CR-59 审查发现:`onPoolWeightChange` 缺乐观更新 revert 逻辑** — Settings.vue:643-656 weight 输入 debounce 300ms 调 IPC,成功后未像 onPoolToggle 一样 revert 到服务端值。IPC 失败或延迟时 UI 显示旧值而非回滚。**修法**:补 revert 分支(对齐 onPoolToggle 模式)。— src/views/Settings.vue:643-656
|
||
- [x] **UX-260617-03 [P1]🎯** — **copyMsgContent 失败误报「已复制」(已记录未修)** — `AiChat.vue:1037-1040` clipboard.writeText 权限拒绝 → catch 仍 showToast('copied')。用户见"已复制"去粘贴发现空。**修法**:catch 改失败文案或静默。(已在 AI 链路审查 B-260615-46 记录,提升优先级至此区)。— src/components/AiChat.vue:1037-1040
|
||
- [x] **UX-260617-04 [P1]🎯** — **offsetToDOMPosition 恒返回 root 致选区恢复错位(已记录未修)** — `AiChat.vue:908-921` createTreeWalker 后 currentNode 初始=root 非 Text → ?? 恒取左 → 循环无 nextNode 推进 → 选区恒错位。UX-2025-01 功能实际失效。(已在 B-260615-47 记录,提升优先级至此区)。— src/components/AiChat.vue:908-921
|
||
|
||
#### 🟡 待扫描确认的交互问题(aichat agent 结果回填后补充/降级)
|
||
|
||
> 以下条目等 aichat 交互层缺陷扫描 agent 完成后,根据实际发现:
|
||
> - 确认存在 → 补充 file:line + 触发条件 + 修复建议
|
||
> - 不存在或极低概率 → 标记 ✅ 排除
|
||
> - 新发现 → 追加新条目
|
||
|
||
- [x] ~~**UX-260617-05 [P2]🎯** — *(待扫描确认→降级)* 流式渲染增量追加闪烁~~ ✅ **排除**:streamingBlocks rAF 节流 + scheduleStreamParse 双层机制完备,无 dup 风险(scan 未发现闪烁 bug,rAF 块级 memo + snap 短路兜底有效)
|
||
- [x] ~~**UX-260617-06 [P2]🎯** — *(待扫描确认→升级)* wasNearBottom~~ → **合并入 UX-260617-21**(stopChat 竞态窗口,含 scroll 一致性)
|
||
- [x] ~~**UX-260617-07 [P2]🎯** — *(待扫描确认→降级)* 审批顺序~~ ✅ **低风险**:审批按 tool_call_id 串行处理,无并行冲突(F-05 超时静默是更大问题)
|
||
- [x] **UX-260617-08 [P1]🎯** — **switchConversation/loadConversations 异常静默吞没** — `useAiConversations.ts:36-44` catch 为空 + `:117-119` switchConversation catch 仅 `messages=[]` 无反馈。网络抖动/IPC 断开 → 对话列表突然变空/切换后空白用户不知原因。**修法**:catch 加 toast('loadConvFail','warning');loadConversations 失败保留旧列表+stale 标记。— src/composables/ai/useAiConversations.ts:36-44,117-119
|
||
- [x] **UX-260617-09 [P1]🎯** — **startListener 吞掉注册异常导致全量事件静默丢失** — `useAiEvents.ts:354-373` IIFE 内 `aiApi.onEvent(handleEvent)` 异常被完全吞没(无 console.error/toast/log)。所有聊天事件(文本/完成/审批)全丢 → UI 表现"发了消息永远不回"。**修法**:IIFE 内加 try/catch + console.error + throw 让外层 finally 清理。— src/composables/ai/useAiEvents.ts:354-373
|
||
- [x] **UX-260617-10 [P1]🎯** — **AiError 不清理 pendingApprovals,错误后残留审批卡** — `useAiEvents.ts:326-349` AiError case 做 clearStreamWatchdog/清queue/推错误气泡但**从未清 pendingApprovals**。出错时有 tool_call 处于 pending_approval → 残留可点击审批按钮。**修法**:加 `state.pendingApprovals=[]` + `clearAllApprovalTimers()`。— src/composables/ai/useAiEvents.ts:326-349
|
||
- [x] **UX-260617-11 [P1]🎯** — **modelOverride 跨对话泄漏** — `useAiSend.ts:67` 模块级 ref,注释明确要求"前端应同步清 null"但**无任何代码执行此清除**。switchConversation/newConversation 不触碰。对话A选模型覆盖→切对话B→override 静默生效。**修法**:switchConversation 末尾加 `modelOverride.value=null`。— src/composables/ai/useAiSend.ts:64-67
|
||
- [x] ~~**UX-260617-12 [P3]🎯** — *(待扫描确认→升级)* generating crash 兜底~~ → **合并入 UX-260617-01**(消息重叠根因含状态不一致)
|
||
|
||
#### 🟡 aichat 扫描新增发现(MEDIUM · 8 项)
|
||
|
||
- [x] ✅(wf wf_b8b2a7b7 + 主代修缺陷·commit 3055b06) **UX-260617-13 [P1] 已修** — **刷新后主面板无流式恢复**【抽 restoreGeneratingState() 共享恢复链(分离窗口 resumeInDetached 薄包装委托),AiChat.vue onMounted 主面板分支(detached=false)调 store.restoreGeneratingState({fromMainPanel:true})。**关键修复**:verify agent 发现主面板刷新时 df-ai-gen localStorage 不存在(仅 detachPanel 写),原复用方案读 gen=null 直接 return 无效。加 fromMainPanel 选项绕过 localStorage:用 state.activeConversationId + ai_is_generating 真值守卫恢复(currentText 从空开始,刷新前已生成部分丢失为刷新固有代价,核心恢复 streaming 态+占位气泡消除假死)。幂等守卫(state.streaming 已 true 跳过)+ R4 后端真值守卫保留。vue-tsc EXIT 0】— src/composables/ai/useAiWindow.ts(:106-150) + src/components/AiChat.vue(:2189-2195)
|
||
- [x] **UX-260617-14 [P2]🎯** — **审批超时 130s 静默复位无反馈** — `ToolCard.vue:507,541-54` 超时仅 `approving.value=false`,无 toast/error/日志。用户无法区分"审批完成状态没更新"和"超时复位"。**修法**:超时时 warning toast + APPROVE_LOADING_TIMEOUT_MS 改为共享常量导入。— src/components/ToolCard.vue:507,541-544
|
||
- [x] **UX-260617-15 [P2]🎯** — **regenerate/editMessage 丢弃图片 parts** — `useAiSend.ts:148-190` regenerate/edit 均无 parts 参数。含图片消息重生成/编辑后图片消失。**修法**:扩展 regenerate/editMessage IPC 签名透传 parts;或至少 UI 层对含 parts 消息禁用按钮/confirmDialog 提示。— src/composables/ai/useAiSend.ts:148-190
|
||
- [x] **UX-260617-16 [P2]🎯** — **无路由离开/关闭保护,未发送输入静默丢失** — AiChat.vue 缺 beforeRouteLeave/onBeforeUnload 守卫。输入框有内容/已粘贴图片时切页面 → 内容瞬间消失无提示。**修法**:beforeRouteLeave guard + detached window beforeunload 检查 inputText/pendingImages/pendingSkill 非空时 confirmDialog。— src/components/AiChat.vue (缺失守卫)
|
||
- [x] **UX-260617-17 [P2]🎯** — **ToolCardList 分组名硬编码中文绕过 i18n** — `ToolCardList.vue:259-274` nameMap 全硬编码中文(`'读取文件'`等)。英文 locale 下分组标题仍显示中文。**修法**:value 替换 i18n key(`ai.toolGroup.readFile`)。— src/components/ToolCardList.vue:259-274
|
||
- [x] **UX-260617-18 [P2]🎯** — **isToolFailure() 正则过于宽泛误判合法输出为失败** — `ToolCard.vue:220-224` `/执行失败|failed|error[:\s]/i` 匹配任何含这些子串的文本。search_files 结果列 error.log / run_command stderr 含 warning 均被标红。**修法**:收窄为 `/^(执行失败|Error:|Failed:)/m` 或按工具类型分别配置检测策略。— src/components/ToolCard.vue:220-224
|
||
- [x] **UX-260617-19 [P2]🎯** — **stopChat 本地先行 reset 与后端竞态窗口** — `useAiSend.ts:479-488` 先设 streaming=false 再发 IPC stop。IPC 失败时后端继续生成但前端已停止;若后续事件也丢失则永久卡死。注释承认风险但无缓解。**修法**:stop 后启动 5s 守护超时,未收到 Completed/Error 则推提示或重试 stop。— src/composables/ai/useAiSend.ts:479-488
|
||
|
||
#### ⚪ aichat 扫描新增发现(LOW · 8 项)
|
||
|
||
- [x] **UX-260617-20 [P3]🎯** — **base64 图片 parts 无大小预算,200 条消息上限不感知单条体积** — MESSAGE_CAP=200 仅按条数 splice,单条 base64 可达数 MB。极端 200条×5MB=~1GB 全挂 reactive state。**修法**:增加基于序列化大小的预算(total parts size<50MB),超出裁剪最旧消息 parts。— src/api/types.ts:228-243 + src/stores/ai.ts:78-87
|
||
- [x] **UX-260617-21 [P3]🎯** — **Markdown 表格溢出气泡容器** — `.ai-msg-bubble` 缺表格专属溢出处理。AI 返回≥4列表格撑破宽度溢出到侧边栏。**修法**:`.ai-md table { display:block; overflow-x:auto; max-width:100% }`。— src/components/AiChat.vue CSS (~L3259)
|
||
- [x] **UX-260617-22 [P3]🎯** — **空内容 AI 回复渲染不可见气泡** — `AiChat.vue:457` v-if 条件使空 content 已完成消息整个 bubble 不渲染 → avatar+一片空白。**修法**:空 content 显示最小化占位`(empty)`或隐藏整条含 avatar。— src/components/AiChat.vue:457
|
||
- [x] **UX-260617-23 [P3]🎯** — **搜索结果选中不清除搜索框,搜索视图不退出** — `AiChat.vue:46` 点击结果 switchConversation 但不清 searchQuery。搜索文字保留+仍显示扁平列表。**修法**:点击 handler 加 `searchQuery=''` 自动退出搜索模式。— src/components/AiChat.vue:46
|
||
- [x] **UX-260617-24 [P3]🎯** — **删除当前活跃对话无特殊警告** — `AiChat.vue:1140-1145` confirmDelete 通用文案不区分活跃对话。对比 confirmNewConversation 有上下文感知警告。**修法**:id===activeConversationId 时改醒目提示说明消息区将清空。— src/components/AiChat.vue:1140-1145
|
||
- [x] **UX-260617-25 [P3]🎯** — **parseBlockNoCache 非 null 断言无运行时防护** — `AiChat.vue:848` `purify!.sanitize(marked!.parse(...))` 。marked/DOMPurify 动态 import 失败时 TypeError → rAF 循环中断流式卡死。**修法**:防御性 null check + 降级 escapeFallback。— src/components/AiChat.vue:848
|
||
- [x] **UX-260617-26 [P3]🎯** — **startListener/stopListener 竞态:_startPromise 未在 stop 中清零** — `useAiEvents.ts:354+378` stop 不清 _startPromise。极速 mount/unmount/mount(HMR) → 返回过期 promise。**修法**:stop 中加 `_startPromise=null`。— src/composables/ai/useAiEvents.ts:378-388
|
||
- [x] **UX-260617-27 [P3]🎯** — **ConfirmDialog 危险按钮默认标签"删除"语义不安全** — `ConfirmDialog.vue:9-10` 默认 `$t('common.delete')`。非删除场景(高危工具审批)忘传 dangerLabel → 按钮显示"删除"。**修法**:默认值改为 `$t('common.confirm')`。— src/components/ConfirmDialog.vue:9-10
|
||
|
||
> **[销账 wf wys30viqi·2026-06-17]** 8 agent 文件域隔离核验修复(独立 grep 核验源码形态 + cargo check --workspace EXIT 0 + vue-tsc EXIT 0)。
|
||
> - **已修 15**:BUG-02 file_info 流式读 .take(8192) / 03 validate_path percent_decode 词法归一化(+percent-encoding 依赖) / 05 try_continue 5lock→ContinueSnapshot 快照收敛+spawn 前重检 / 06 saturating_add(含 ContextUsage 周边) / 07 LCS_MAX_LINES=1000 双>1000 跳 DP / 09 max_depth clamp(1,10) / 11 read_file 默认 500 行+has_more;UX-08 catch toast+保留旧列表 / 09 startListener try/catch+console.error / 10 AiError clearAllApprovalTimers+清 pendingApprovals / 14 STREAM_TIMEOUT_MS 共享常量+reactive toast / 17 toolGroup i18n 14key / 18 isToolFailure 正则收窄^行首锚 / 19 stopChat try/catch+watchdog 复用 / 26 stopListener _startPromise=null。
|
||
> - **误判销账 6**(审查报告 file:line 漂移/已修未销账,独立核验拦下未引入防御性反模式):BUG-08 WorkflowRepo 是 Arc clone 非连接池(crud.rs:81 new 仅 db.conn() clone,workflow.rs:292 注释已正确) / UX-02 ProviderPanel.vue:318-334 已有 revert 三分支 / UX-03 catch 已 fallthrough+execCommand 布尔分流 copyFailed / UX-04 walker.nextNode 已正确推进(旧 ?? nextNode 模式已不存在) / UX-11 AiChat.vue:1234 watch activeConversationId 已清 modelOverride(非 useAiSend.ts) / UX-15 regenerate 只 pop AI 气泡+editMessage 只换 content,前后端均不丢 parts。
|
||
> - **留观察 1**:BUG-10 skills OnceLock 首次阻塞 tokio worker 数十ms 非热点,正确修法 skills_cached()→async+spawn_blocking 跨 read_skill_content/ai_list_skills 签名超本批文件域边界,接受当前行为。
|
||
|
||
#### ℹ️ 架构观察(INFO · 2 项·不进修复队列)
|
||
|
||
- [ ] **UX-260617-28 [INFO]** — **双监听器同通道 fragility** — useAiEvents + useAiContext 各自 listen('ai-chat-event'),人工协调防双重处理(AiCompressing flag)非架构保证。未来新增事件处理可能触发双重 bug。长期考虑单一分发器模式。— src/composables/ai/useAiEvents.ts:269 + src/composables/ai/useAiContext.ts:85-105
|
||
|
||
---
|
||
|
||
### 📊 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,现走前端闭环)
|