diff --git a/docs/todo.md b/docs/todo.md index f8d22a8..7ef8ef6 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -46,7 +46,9 @@ ### 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 -- [ ] **🚨 BUG-260617-12 [P1]→P0 联动** — **DeepSeek reasoning_content 缺失致必 400** — 全代码库零处理 thinking/reasoning_content 字段(CompletionRequest+ChatMessage 均无)。DeepSeek thinking 模式每次请求 400,被 BUG-01 放大至 **16 次无效请求**(4重试×可能多 provider)。需补字段+透传+openai_compat 映射。— crates/df-ai-core/src/provider.rs:22-39,92-99 +- [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) @@ -140,12 +142,32 @@ - [ ] 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) -- [ ] B-260617-12 [P3·可选] — **LEGACY_STATUS_MAP 映射 DRY 重复**。1cd7652 中 `taskStatusLabel`(:96) 与 `taskStatusClass`(:101) 各写一遍 `const mapped = LEGACY_STATUS_MAP[status] ?? status`。两处一行重复,可抽 `mapLegacyStatus(status)` 收敛。极简优先可不动(仅 2 处 + 单行),登记备查。—— src/constants/project.ts(:96,:101) +- [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**。 + +- [ ] **🚨 B-260617-14 [P1]** — **74003bc 漏提交 adversarial.rs + project.rs,HEAD 编译失败**。74003bc stat 仅 11 文件,**未含** `crates/df-ideas/src/adversarial.rs`(:177) 与 `src-tauri/src/commands/project.rs`(:547,:642),二者字面量 `CompletionRequest{...}` 构造仍缺 `reasoning_content` 字段(补丁在工作区 `+reasoning_content: None` 但未入库)。该构造无 `..Default::default()` → **E0063 missing field** → HEAD 74003bc 干净 checkout 编译失败。todo.md:49 记的 cargo check EXIT 0 系工作区含此俩补丁的态误报。**铁证**:`git status` 显示此俩文件仍 `M`(未提交),`git diff` 确认补丁内容为 `+reasoning_content: None`。**修法**:`git add` 此俩文件补提交(确定性唯一解——ai_node.rs/compress/knowledge_inject/title 等同类补丁均已入库,独漏此俩)。— crates/df-ideas/src/adversarial.rs(:177) · src-tauri/src/commands/project.rs(:547,:642) + +- [ ] 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 分钟验证)。 + **现象确认(基于截图)**: | 现象 | 判定 | |---|---|