204 KiB
待审查队列
职责分离:
- 主会话(编码合并后 / 巡检)→ 【写入】"当前队列"区,登记待审查项(范围 + 维度 + commit)。
- 审查 agent(独立会话 / workflow)→ 【读取】"当前队列",执行审查,完成回填结论 + 移到"已审归档"。
这里只放审查登记,不放推进待办(推进待办见 todo.md)。 主会话不在这里做审查,只登记 + 读 todo 推进。审查发现的【待修代码项】回流 todo 作推进待办。
审查规则(审查 agent 遵守)
- 只读审查,不改代码。
- 独立 grep/read 核验源码,不信文档 / 会话描述 / 设计声明的结论(防上下文污染)。
- 每条 finding 须
file:line源码佐证 + evidence。 - 严重度:high(破坏性 / 数据错 / 回归) / med(逻辑瑕疵 / 边界漏) / low(风格 / 可读)。
- 完成回填:总体 verdict(PASS / ISSUES / WATCH)+ high/med/low 清单 + 待修项回流 todo。
状态图例
🟡 待审 / 🔵 审查中 / ✅ 已审(结论) / 🔴 待修(有 high 问题)
当前队列
CR-260616-58 F-260616-07 阶段3 AiNode 自审闸门(verdict=fail 作 DAG 节点闸门阻断下游)(e16d038) — ✅ 已审(PASS)
- 复审结论(2026-06-17·审查 agent 亲跑 cargo+test+独立 grep/read 核验): ✅ PASS — 🔴0 🟡0 ⚪1
- 验证:
cargo check --workspaceEXIT 0(6 pre-existing dead_code warnings 无关本 CR)/cargo test -p df-nodes --lib82 passed; 0 failed; 1 ignored (+3 gate 单测:1076-1103)。 - ①gate_should_block 决策矩阵完备 PASS:ai_node.rs:426-428
gate && verdict == "fail"简洁正确,gate=false 全放行(阶段2 兼容),gate=true+仅 fail 阻断,unknown/非标 verdict 保守不阻断(单测:1096-1103 覆盖)。 - ②execute 末尾 Err 路径正确 PASS:ai_node.rs:574-582 update_field 落库在前(审查痕迹不丢) + :615-629 闸门检查后置,verdict=fail 返回 Err 正确。
- ③复用 executor first_err + ②-4 回调语义 PASS:df-workflow/src/executor.rs:128-178 first_err 捕获首个 Err 后立即中止后续层(下游 human_review 不跑) + workflow.rs:292-310 failed 触发 regression_target 退回 in_review(review_rounds+1,task_advance_node.rs:515-547 验证)。
- ④gate 默认 false 向后兼容 PASS:ai_node.rs:619
.unwrap_or(false)阶段2 行为不变,AiNode schema(:331-346)无 gate 字段,仅 AiSelfReviewNode 有(:644)。 - ⑤testing 模板 gate:true 启用 PASS:task_workflow_templates.rs:65-69
"gate": true正确注入 + 单测:139-144 断言验证。 - ⑥schema 契约正确 PASS:ai_node.rs:644 gate 布尔字段,非 required,description 完整。
- ⑦文件锁独立性 PASS:ai_node.rs + task_workflow_templates.rs 独占,df-workflow 零改动(git show
e16d038确认)。 - ⑧选型论证 PASS:ai_node.rs:605-613 注释完整(方案 A 最简 vs B 暂缓条件引擎 vs C executor 改核心),选 A 语义贴近「自审结果作为闸门」且可开关。
- ⚪ CR-58-1(low,注释冗余):ai_node.rs:605-613 选型注释 9 行,但提供设计决策历史,对后续维护有价值,可保留。
- 待修项回流 todo: 无(8 维度全 PASS,1 low 注释冗余非阻断)。
- 验证:
CR-260616-59 波16 F-04c前端Settings池编辑(enabled toggle+weight输入+IPC绑定)(3f90aad) — ✅ 已审(PASS)
- 复审结论(2026-06-17·审查 agent 亲跑 vue-tsc + cargo check + 独立 grep/read 核验): ✅ PASS — 🔴0 🟡1 ⚪0
- 验证:
npx vue-tsc --noEmitEXIT 0 /cargo check --workspaceEXIT 0(6 warning 全 pre-existing dead_code)。 - ①updateProviderPool wrapper 对齐现有 invoke 风格 PASS:ai.ts:146-148
invoke('ai_update_provider_pool', { providerId, enabled, weight })与现有 aiApi.setProvider/deleteProvider 风格一致,thin wrapper 无额外逻辑。 - ②AiProviderConfig enabled?/weight? 可选 PASS:types.ts:191-193 enabled?/weight? 可选字段(snake_case 对齐 serde),注释完整("老库默认 true"/"老库默认 50")。Settings.vue:61
p.enabled !== false正确处理 undefined 视为 true。 - ③即时调 IPC 决策合理 PASS:即时调理由(Settings.vue:616-618 注释)充分——池调度参数 reload_provider_caps 变更即重建,下条消息即生效;避 saveProvider 密钥迁移分支(R-PD-1)。对齐 syncConcurrencyConfig 即时调模式(Settings.vue:822-839 debounce 300ms 证明)。
- ④debounce 300ms+clamp+NaN 兜底 PASS:Settings.vue:647
clearTimeout(_poolTimer)+ :648setTimeout(..., 300)debounce 正确。:645Math.min(100, Math.max(0, Number.isFinite(raw) ? Math.trunc(raw) : 50))双 clamp+NaN 兜底(回退 50)防 serde u32 失败。 - ⑤乐观更新 revert 逻辑 部分PASS:onPoolToggle(:626-637)
const prev = p.enabled+p.enabled = prevrevert 完整。🟡 onPoolWeightChange(:643-656) 缺 revert—— debounce 异步路径,失败后p.weight已更新为 clamped 值但 IPC 未成功落库,用户视角显示已改但实际未生效,需补const prev = p.weight; p.weight = clamped; catch { p.weight = prev }(前端乐观更新失败应 revert 状态)。 - ⑥i18n 双语 PASS:zh-CN/settings.ts:17-23 + en/settings.ts:17-23 7 key 对称完整(detailPoolEnabled/detailPoolWeight/badgePoolDisabled/descPoolEnabled/descPoolWeight/toastPoolUpdateOk/toastPoolUpdateFail)。特殊字符无转义需求(值均为静态枚举字面量)。
- ⑦onUnmounted 清理 PASS:Settings.vue:954
if (_poolTimer) clearTimeout(_poolTimer)清理完整,与 _concurrencyTimer/_agentIterTimer 等同级处理。 - ⑧Tauri 2 参数命名转换 PASS:后端 commands.rs:1023-1028
provider_id/enabled/weight(snake_case),前端 ai.ts:147 传providerId/enabled/weight(camelCase)。Tauri 2 自动转换 snake_case→camelCase 为设计行为(官方文档+StackOverflow)。 - 待修项回流 todo: 🟡 onPoolWeightChange 补 revert 逻辑(非阻断,用户可刷新重试,但应补
const prev = p.weight+ catch 内p.weight = prev)。
- 验证:
CR-260616-60 波17 B-03b-R10前端审批清理(终态清pending+互斥锁防重复IPC)(0289d8c) — ✅ 已审(PASS)
-
复审结论(2026-06-17·审查 agent 亲跑 vue-tsc + 独立 grep/read 核验源码): ✅ PASS — 🔴0 🟡0 ⚪0
- 验证:
npx vue-tsc --noEmitEXIT 0 ✅ - ①⑤ 终态清 execution_id 限定防误清 PASS:workflow.ts:62-70 三条件判断(
pendingApproval存在+execution_id匹配+终态事件类型) + 并发工作流场景(execution_id不等不误清他人pending) + 后端事件类型契约确认(WorkflowCompleted/Failed/NodeCancelled→snake_case) + PendingApproval.execution_id字段契约一致(state.ts:6 ↔ events.rs:86)。 - ②⑦ 互斥锁 _approvalInFlight 单例防重复 IPC PASS:模块级单例(workflow.ts:11) + approve/cancel双入口守卫(L99/L135
if(_approvalInFlight)return) + finally块全释放(L127/L150 成功/失败均释放) + 共享单例跨调用方统一(所有入口经workflow.ts统一函数无旁路)。 - ③ 失败重试语义正确 PASS:approve/cancel catch块只设置state.error不清空pendingApproval(L122-124/L145-147),用户可重试 + 成功后才清空(L121/L144)。
- ④ 互斥锁释放路径完备 PASS:approve/cancel try-catch-finally三路径全覆盖,无early return跳过finally(async/await保证finally执行)。
- ⑤ 事件类型覆盖完整 PASS:终态三类型(workflow_completed/workflow_failed/node_cancelled)全覆盖,覆盖工作流完成/失败/节点取消三路径。
- ⑥ 单槽设计边界清晰 PASS:当前设计pendingApproval单槽(state.ts:23),审查报告P2「改Map」推迟正确,本CR范围只实施⑤⑦未改动架构。
- ⑦ 调用方入口统一 PASS:ProjectDetail.vue三入口(409/420/427)经workflow.ts统一函数,stores/project.ts转发无旁路。
- ⑧ type safety 验证 PASS:WorkflowEventPayload.execution_id/event.type契约(types.ts:140-142) + WorkflowEventType联合类型含三终态(types.ts:135-136)。
- 待修项回流 todo: 无(8维度全PASS,零问题)。
- 验证:
-
范围: workflow w9xypd8ug agent B。src/stores/project/workflow.ts ⑤终态不清(startEventListener 加 else-if,workflow_completed/failed/node_cancelled 且 execution_id 匹配清 pendingApproval,按 exec_id 限定防并发误清)+ ⑦互斥(模块级 _approvalInFlight 单例标志,approve/cancel 入口检查+try/finally 释放,防双击重复 IPC)。④单槽→Map 推迟(state 形态变更牵动多消费者,后端串行闸门保证单执行不并发,现实触发需并发工作流)。波17 B-03b 后端 workflow.rs 0 实施(⑧⑩⑪ 报告失效/已修复/已有兜底,③ 真实潜伏跨 crate df-core 缺 execution_id 留观察)。
-
维度: ⑤终态清 execution_id 限定(防并发误清)+ ⑦互斥 _approvalInFlight 单例(跨调用方统一)+ ④推迟理由(state 形态变更+后端串行)。
-
commit: 0289d8c。
-
主代核查: vue-tsc EXIT 0。
-
审查 agent 待复审重点: ⑤终态清 execution_id 匹配逻辑(防误清)+ ⑦互斥 _approvalInFlight try/finally 释放(异常路径不泄漏)。
-
待修项回流 todo: 无(⑤⑦ 实施,④ 推迟有理由)。
CR-260616-61 波18 ARC df-core改名df-types(机械全workspace 54处)(4be1591) — ✅ 已审(PASS)
- 复审结论(2026-06-17·审查 agent 亲跑 cargo+test+独立 grep/read 核验): ✅ PASS — 🔴0 🟡0 ⚪0
- 验证:
cargo check --workspaceEXIT 0(6 pre-existing dead_code warnings 无关本 CR)/cargo test -p df-nodes82 passed; 0 failed; 1 ignored /cargo test -p df-workflow23 passed /cargo test -p df-ideas20 passed /cargo test -p df-storage29 passed。 - ①grep df_core/df-core 代码侧归零 PASS:
grep -r "df_core" --include="*.rs" --include="*.toml" crates/ src-tauri/零输出 /grep -r "df-core" crates/ src-tauri/零输出 /grep -r "df_types" crates/ src-tauri/54 处(与 commit 统计一致)。 - ②import 路径全更新 PASS:git show 核验 task_advance_node.rs 12 处
df_core::error::→df_types::error::/ executor.rs 8 处df_core::events/types→df_types::/ task.rs 1 处df_core::types→df_types::types/ 全部编译期通过(cargo check EXIT 0)。 - ③注释连字符全更新 PASS:git show 核验 lib.rs 文档注释
df-types: 核心类型定义+ task_advance_node.rs/human_node.rs/manager.rs/crud.rs 共 8 处注释df-core→df-types或df-types::。 - ④Cargo.lock 自动迁移 PASS:git show 核验
df-core包定义已删除(name + dependencies 块移除) + 所有依赖方(df-ai/df-execute/df-nodes/df-project/df-storage/df-workflow/src-tauri)依赖更新为df-types。 - ⑤glob members 自动适应 PASS:workspace Cargo.toml
members = ["crates/*"]glob 自动覆盖新 df-types 目录 /ls crates/确认 df-core 已删除仅剩 df-types。 - ⑥目录结构完整 PASS:ls -la crates/df-types/src/ 确认 error.rs/events.rs/lib.rs/types.rs 四文件完整 / git mv R100/R092 确认文件移动无丢失。
- ⑦宏自引用零遗漏 PASS:git show '*.rs' grep
macro零新增(本 CR 无宏定义改动,仅 import 路径替换) / 波15 走查核验通过。 - ⑧字符串字面量零遗漏 PASS:git show '*.rs' grep
^\+.*"df-core"零命中 / 注释外零字符串字面量残留。 - 待修项回流 todo: 无(8 维度全 PASS,零问题。文档 .md/project.ts 注释 deferred 非:blocking,按原始登记不回流 todo)。
- 验证:
CR-260616-62 波19 UX-19消息列表虚拟滚动(自研IntersectionObserver+sentinel+末条保活)(e38474b) — ✅ 已审(PASS)
- 复审结论(2026-06-17·审查 agent 亲跑 vue-tsc + cargo check + 独立 grep/read 核验): ✅ PASS — 🔴0 🟡0 ⚪0
- 验证:
npx vue-tsc --noEmitEXIT 0 ✅ /cargo check --workspaceEXIT 0(6 pre-existing dead_code warnings 无关本 CR)。 - ①pinnedKeys 末条保活 PASS:AiChat.vue:2363-2373
lastStreamingRenderKeycomputed 取末条 AI 消息 key(m-${id}) + watch 立即传setPinnedMsgs(new Set([key]))+ useAiVirtualScroll.ts:130-132shouldRender双路返回(renderedKeys U pinnedKeys) + IO 回调(:68) 跳过pinnedKeys.value.has(key)项。三重保证流式末条恒渲染。 - ②sentinel 占位高度不塌 PASS:useAiVirtualScroll.ts:72-75 卸载前锁 minHeight=
${height}px+ :88 ResizeObserver 实测e.contentRect.height持续更新 + :102-103 挂载时复用上次 height 占位 + :65 重新可见清 minHeight 让内容自然撑高。AiChat.vue:2941.ai-msg-slot { display: flex; flex-direction: column; }承担 gap:14px。scrollHeight 计算不受虚拟化影响。 - ③零新依赖 PASS:git show 核验仅新增 useAiVirtualScroll.ts 162 行 + AiChat.vue 44 行改动。
grep vue-virtual-scroller package.json零命中。自研 IO/RO 方案无外部依赖。 - ④现有逻辑全保留 PASS:grep 核验 streamingBlocks(:777-783 rAF 块级 memo)/scheduleStreamParse(:863-873)/wasNearBottom(:1765)/collapseAllToolLists(:1889-1893)/isNearBottom(:1754-1760)/scrollToBottom(:1782-1788)全在。AiChat.vue 模板 :400
shouldRenderMsg(item.key)仅裁内容不裁滚动逻辑。 - ⑤IO/RO disconnect 无泄漏 PASS:useAiVirtualScroll.ts:146-152
onBeforeUnmount双 disconnect + io=null/ro=null/itemMeta.clear()。:114-120 卸载分支io?.unobserve(prev.el)+ro?.unobserve(prev.el)。三路径完整(组件卸载/单项卸载/观察器重建)。 - ⑥registerSentinel 生命周期正确 PASS:useAiVirtualScroll.ts:97-121 挂载时
io?.observe(el)+ro?.observe(el)(:106-107) + 卸载时unobserve(:116-117) + itemMeta 保留 height 复用(:119)。el.dataset.vscrollKey = key(:100) 绑定唯一标识。 - ⑦rootMargin 预加载 600px PASS:useAiVirtualScroll.ts:81
rootMargin: '${rootMargin}px 0px ${rootMargin}px 0px'上下各 600px + :38 默认 600px。滚动时内容提前就位,防白屏闪烁。 - ⑧setPinned 强制进渲染集 PASS:useAiVirtualScroll.ts:135-144
setPinned更新pinnedKeys.value+ 强制合并进renderedKeys。pinned 项即使 IO 不可见也渲染。 - 待修项回流 todo: 无(8 维度全 PASS,零问题。ToolCard 嵌套/超大历史 sentinel 二段留观察,非当前 CR 范围)。
- 验证:
CR-260616-63 波20 F-05多模态Phase2a后端(ContentPart+parts+provider图片块+truncate)(e3cd448) — ✅ 已审(PASS)
- 复审结论(2026-06-17·审查 agent 亲跑 cargo+test+独立 grep/read 核验): ✅ PASS — 🔴0 🟡0 ⚪1
- 验证:
cargo check --workspaceEXIT 0(6 pre-existing dead_code warnings 无关本 CR)/cargo test -p df-ai-core19 passed /cargo test -p df-ai111 passed。 - ①ContentPart serde tag=type 兼容老 JSON PASS:provider.rs:48-50
#[serde(tag = "type", rename_all = "snake_case")]正确区分 Text/Image + 单测:370-377contentpart_legacy_json_no_parts老 JSON 反序列化 parts=None 验证 + :381-402contentpart_with_image_roundtrip新 JSON round-trip 验证。 - ②parts Option 向后兼容 PASS:provider.rs:102-103
#[serde(default, skip_serializing_if = "Option::is_none")]+ 构造器 user/user_parts/assistant 均设 parts=None(:122/:125/:128) + 单测:442-455contentpart_struct_literal_compat字面量构造合法。 - ③openai content Value+image_url PASS:openai_compat.rs:320-348 m.has_image() 判定 + flattened_parts() 拍平 + content 数组 [{type,text},{type,image_url,image_url:{url}}] + base64→data URI 转换(:330-333
format!("data:{};base64,{}", mt, b))。 - ④anthropic image source base64 PASS:anthropic_compat.rs:342-375 含图时 content blocks 数组 + Image 片→{type:image,source:{type:base64,media_type,data}} + url 模式 warn 提示预留 2b/2c 预拉(:356-362)。
- ⑤truncate_parts 防 DB 撑爆 PASS:conversation.rs:87-127 truncate_parts_for_persist Image base64 替换占位 Text(:100-106
<image: base64 已省略, 共约 {} 字符>) + Text 片复用 truncate_for_persist 头尾截断 + url 模式原样保留。 - ⑥务实偏离合理性 PASS:grep 核验 4 forbidden 消费点(audit.rs:461/commands.rs:87+678/knowledge_inject.rs:314+316+338)均读 m.content 字符串零改动,title.rs:50-58 结构体字面量 parts:None 补字段唯一越界但零变化(单测:442-455 证字面量合法)。
- ⚪ CR-63-1(low,Anthropic URL 预拉留 2b/2c):anthropic_compat.rs:356-362 url 模式 warn 但未实现预拉(当前 base64 为主流,url 预拉留后续联调),非阻断当前 Phase 2a 就绪。
- 待修项回流 todo: 无(6 重点全 PASS,1 low 预拉非阻断,Phase 2a 就绪)。
- 验证:
CR-260616-64 波21 F-05多模态Phase2b前端(图片粘贴/拖拽+渲染+parts透传)(e1d396d) — ✅ 已审(PASS)
- 复审结论(2026-06-17·审查 agent 亲跑 vue-tsc + cargo check + 独立 grep/read 核验): ✅ PASS — 🔴0 🟡0 ⚪0
- 验证:
npx vue-tsc --noEmitEXIT 0 ✅ /cargo check --workspaceEXIT 0(6 pre-existing dead_code warnings 无关本 CR)。 - ①@paste 图片检测+base64 PASS:AiChat.vue:1037-1052 onPaste 遍历 clipboardData.items,
it.kind==='file'&&it.type.startsWith('image/')正确检测 + :1050 e.preventDefault() 阻止默认粘贴(避免二进制塞 textarea) + :1051 逐个 addImageFile。readImageFile:997-1003 FileReader Promise 正确。 - ②@dragover/@drop 拖拽检测 PASS:onDragOver:1055-1063
Array.from(e.dataTransfer.types).includes('Files')判断含文件 + preventDefault + dropEffect='copy' 正确。onDrop:1066-1074 filterf.type.startsWith('image/')仅取图片 + preventDefault + 逐个 addImageFile。 - ③img 渲染随虚拟滚动无冲突 PASS:模板:415
v-if="msgImageParts(item.msg).length"+ :413-419 注释明确「虚拟滚动裁剪整条消息,气泡内随 sentinel 卸载无冲突」。msgImageParts:1083-1086 filter type==='image' 正确。imageSrc:1089-1096 base64 模式拼 data URI(
data:${mt};base64,${img.base64}) + url 模式兜底空串。 - ④parts 全链透传 PASS:handleSend:1830 snapshotImgs 快照 + :1831-1849 ContentPart[] 构造(Text 首+Image 序) + :1863
store.sendMessage(text,skill?.name,false,parts)传参。useAiSend.ts doSend:80 接 parts 参数 + :87 仅非空挂 parts + :114/:117 aiApi.forceSend/sendMessage 透传(Phase 2c 后端接入即生效)。drainQueue:261sendMessage(next.text,next.skill,false,next.parts)续发正确。queue 扩展:ai.ts:72-73 注释明确 + useAiSend.ts:298-302 入队挂 parts。 - ⑤虚拟滚动保留 PASS:AiChat.vue:1106-1111 useAiVirtualScroll 未改动(git show
e1d396d确认) + 模板:401shouldRenderMsg(item.key)裁剪整条消息,图片在气泡内随消息卸载,无独立冲突。 - ⑥i18n 双语 PASS:zh-CN:103-107 + en:102-106 五 key 对称(removeImage/imagePreviewAlt/imageTooLarge/imageLimit/imageReadFailed)。特殊字符无转义需求。
- ⑦约束正确 PASS:MAX_PENDING_IMAGES=6 + MAX_IMAGE_BYTES=10MB + addImageFile:1007-1029 size/type/数量三重校验 + toast 提示完整。
- ⑧发送失败回填 PASS:handleSend:1869-1871
if(parts) pendingImages.value=snapshotImgs回填正确,用户可重试。 - 待修项回流 todo: 无(8 维度全 PASS,零问题。Phase 2c IPC 接入即全链闭环)。
- 验证:
CR-260616-65 波22 F-05多模态Phase2c闭环(IPC接parts+api透传+doSend传参)(4317d02) — ✅ 已审(PASS)
- 复审结论(2026-06-17·审查 agent 亲跑 cargo+vue-tsc+独立 git show/grep/read 核验 2 轮): ✅ PASS — 🔴0 🟡0 ⚪0
- 验证:
cargo check --workspaceEXIT 0(6 pre-existing dead_code warnings 无关本 CR)/npx vue-tsc --noEmitEXIT 0。 - ①IPC serde 反序列化 PASS:commands.rs:8 import
ContentPart+ :149parts: Option<Vec<ContentPart>>参数 + :147-148 注释阐明 Tauri 2 自动 serde 反序列化(df-ai-core provider.rs:49#[serde(tag = "type", rename_all = "snake_case")]与前端 types.ts:288-298 契约严格对齐)。 - ②user_parts 空兼容 PASS:commands.rs:182-186
if let Some(ps) = parts.as_ref().filter(|p| !p.is_empty())双重守卫(非 None 且非空)→ user_parts() + else 分支 user() 零回归。provider.rs:139-141 user_parts 实现(parts 挂 ChatMessage.parts 字段,provider.rs:144-145 has_image 判定走 vision 端点)。 - ③api parts 透传 PASS:ai.ts:13+24 两函数加 parts 参数 + :19/:30
parts && parts.length > 0 ? parts : null空过滤(与 commands.rs:182 守卫对称)+ :5 importContentPart类型。invoke 参数对象正确传。 - ④doSend 传参 PASS:useAiSend.ts:113+125 force/普通双分支加 parts 传参(
aiApi.forceSend/sendMessage(..., parts)) + :93 函数签名接 parts + :100 本地 state 挂parts && parts.length > 0 ? parts : undefined。Phase 2c 全链生效(CR-64 前端已挂 parts→本 CR IPC 接入→2a 后端处理闭环)。 - ⑤FR-S1 Image base64 安全 PASS:commands.rs:146-148 注释阐明「ContentPart Image base64 是图片数据非 api_key,不入敏感面」。核验:grep
NodeContext.config/NodeOutput/tool_callschema 三点零 parts 字段(conversation.rs:153 truncate_parts 仅落 session.messages+provider 转发,audit/title/tool_calls 不读 parts)。base64 仅经 ai_chat_send→session.messages→provider HTTP 出站,不进 IPC 返回/日志/错误/工作流节点上下文。 - ⑥forceSend 透传 PASS:commands.rs:740 ai_chat_force_send 加 parts 参数 + :763 透传给 ai_chat_send(
ai_chat_send(..., parts).await)。注释 :739 澄明「强制发送同款多模态支持」。 - ⑦truncate_parts 防撑爆 PASS:conversation.rs:87-127 truncate_parts_for_persist(Image base64 替换占位+Text 头尾截断)+ :153 落库前调用(git show
4317d02前已有此 Phase 2a 代码,本 CR 零改动但依赖存在)。DB 撑爆防护已就绪。 - ⑧类型契约对齐 PASS:types.ts:288-298 ContentPart 联合类型(
type:'text'|'image'discriminator)与 df-ai-core provider.rs:49-65 serde tag 严格对齐(snake_case ↔ 字面量 'text'/'image')。前端 4 处parts&&parts.length>0守卫(ai.ts:19/:30/useAiSend.ts:100/:331/useAiConversations.ts:90)保证空/undefined 不传。 - 待修项回流 todo: 无(8 维度全 PASS,零问题。F-05 多模态全链闭环:2a 后端 ContentPart+parts+truncate+ 2b 前端粘贴/拖拽/渲染+ 2c IPC 透传+ doSend 传参,完整落地)。
- 第 2 轮独立核验补充(2026-06-17·git show
4317d02快照态): ①三文件 diff 全吻合登记声明。②行号微偏(useAiSend.ts 签名实为 :80 非 :93/state 挂 parts 实为 :87 非 :100/forceSend 实为 :114 非 :113/sendMessage 实为 :116 非 :125),代码逻辑无误,仅引用行号需校正。③FR-S1 深度核验追加:ContentPart/base64 grep 仅命中 df-ai + df-ai-core 两 crate,df-nodes/df-workflow 零命中(NodeContext.config 是 serde_json::Value 节点配置非消息,无 parts 字段);all_messages_clone()两 IPC 调用点(commands.rs:84/672)均仅读.content做知识检索,不读.parts;持久化路径 conversation.rs:153save_conversation对每条 msg 调 truncate_parts_for_persist(Image base64→占位 Text)→ DB 落盘的 parts 已是占位;commands.rs:1444 ai_conversation_export "json" 格式serde_json::to_string_pretty(&messages)读的是 DB 持久化副本(record.messages),非内存全量,故导出 JSON 不含原始 base64。FR-S1 全链零泄漏(不进 IPC 返回/日志/NodeContext/tool_call/导出)。④cargo check EXIT 0(6 pre-existing dead_code 全无关)/ vue-tsc EXIT 0,亲跑复现。
- 验证:
CR-260616-67 波24 B-03b-R10③工作流事件补execution_id治本并发串扰(bac3c76) — ✅ 已审(PASS)
- 复审结论(2026-06-17·审查 agent 亲跑 cargo+test+独立 grep/read 核验): ✅ PASS — 🔴0 🟡0 ⚪0
- 验证:
cargo check --workspaceEXIT 0(6 warning 全 pre-existing dead_code)/cargo test -p df-types7 passed /cargo test -p df-workflow23 passed /cargo test -p devflow --lib92 passed。 - ①字段 #[serde(default)] 向后兼容 PASS:events.rs:83-84 WorkflowCompleted
#[serde(default)] execution_id: String+ :91-92 WorkflowFailed 同字段。老事件/老 DB 反序列化无 execution_id 时填空串不炸,serde(default) 自动处理。 - ②3 emit 点全覆盖 PASS:executor.rs:185-186 WorkflowCompleted 发
self.execution_id.clone()(DagExecutor 字段:20) + workflow.rs:208-210 Lagged 补发 completed 发forward_exec_id.clone()+ :212-217 Lagged 补发 failed 发forward_exec_id.clone()+ :218-222 Lagged 补发 cancelled 发forward_exec_id.clone()+ :364-365 执行失败补发 WorkflowFailed 发exec_id.clone()。5 emit 点全覆盖(executor 1 + Lagged 3 + 补发 1),全部携带正确 execution_id。 - ③消费 exec_id 守卫只 break 自己 PASS:workflow.rs:167-172 matches! 双守卫(
变体 Completed/Failed+if execution_id == &forward_exec_id)。他人终态事件(execution_id != forward_exec_id)finished=false 继续循环,不误 break。源码佐证: :171 条件守卫 + :180if finished { break }。 - ④空串匹配语义只漏不误 PASS:#[serde(default)] 老事件空串 + :171 条件
execution_id == &forward_exec_id,自家 forward_exec_id 永远非空(run_workflow_inner:73 生成 UUID),空串匹配永不触发 finished。语义正确:只可能漏(自家老事件空串),不会误 break(他人终态非空但不等)。 - ⑤前端契约零破坏 PASS:types.ts:140 WorkflowEventPayload.execution_id 已有字段 + workflow.ts:64-67 终态清空逻辑只看
payload.event?.type不变。execution_id 在事件 payload 内,类型不变,前端零改动。 - ⑥字段补齐对齐其他变体 PASS:grep 核验 HumanApprovalRequest/Response(:98/:109)已有 execution_id 字段,Completed/Failed 现在对齐,契约统一。
- ⑦注释完整 PASS:events.rs:77-81 + 89 注释阐明 B-03b-R10 ③ 根因 + 解决方案 + serde(default) 理由。workflow.rs:159-166 注释阐明并发串扰 + 双重匹配 + 空串向后兼容语义。executor.rs:182-183 注释阐明 emit 携 execution_id 理由。
- 待修项回流 todo: 无(7 维度全 PASS,零问题。跨 crate 治本完整,并发串扰根除)。
- 验证:
CR-260616-36 batch60 F-09A 隔离补清 + F-13 性能优化 + AE-03 write_file diff 预览(路径B) — ✅ 已审(PASS)
-
复审结论(2026-06-16·CR-36 agent 亲跑 cargo+vue-tsc+test,723s): ✅ PASS — 🔴0 🟡0 ⚪1
- 验证:
cargo check --workspaceEXIT 0(4 warning 全 pre-existing:mod.rs:282 PendingApproval.diff never read 误报 / state.rs:146 releases+node_executions dead_code)/cargo test -p df-ai37 passed /npx vue-tsc --noEmitEXIT 0。 - F-13 messages.clone() 等价性 PASS:stream_recv.rs:137-144 stream_llm 签名不接 session_arc + 重试块(agentic.rs:259-363)到 process_tool_calls(:476)间 0 个 session.messages.push(push 在 :391/395/446/450 之后)+ CR-260616-35 回归核验(保文/退避/permit RAII/guard.reset/request 重建全无回归)。
- F-13 sys_tokens 缓存 PASS:agentic.rs:172-174 system_prompt 是 run_agentic_loop owned String 参数,整 loop 只读(:174 estimate_text + :227 ChatMessage::system),无 mutate。
- AE-03 build_write_file_diff 安全 PASS:audit.rs:508-520 预读旧文件路径未校验,但桌面应用威胁模型(LLM 生路径非远程攻击者)+ read 仅展示不外泄 + handler validate_path 执行时兜底,可接受风险。
- AE-03 diff 双路径 PASS:实时(audit.rs:588-609 PendingApproval.diff clone + AiApprovalRequired.diff move 双注入)+ 恢复(audit.rs:282 diff=None 故意,DB ai_tool_executions 无 diff 列,前端 ToolCard v-if tc.diff 兜底)。types 对齐(mod.rs:101/282 Option + types.ts:198/244 diff? + useAiEvents.ts:239 传播)。
- F-09A 单例补清 PASS:commands.rs:860 stop_flag.store(false,SeqCst) 在生成中分支置 true(:840)后复位覆盖两路径 + :861 agent_language=None + 前端 useAiConversations.ts:42-45 补清 queue/generatingConvId/agentRound/searchQuery + 未动 AiSession 单例(B 路线边界)。
- ⚪ low:mod.rs:282 PendingApproval.diff "never read" warning — 误报,值经 clone 进 AiApprovalRequired event payload(audit.rs:609)前端消费,dead-code 分析不见直接 read。
- 待修项回流 todo: 无(5 重点全 PASS,1 low 误报)。
- 验证:
-
范围: 3 agent 合并批(workflow whae812z5,主代独立核查全过 + cargo check --workspace EXIT 0 + vue-tsc EXIT 0)。①F-09 A 路线(单例软隔离补漏清字段,不动 B 多会话架构):前端
useAiConversations.ts:42-45newConversation 补清queue=[]/generatingConvId=null/agentRound=0/searchQuery=''(防旧会话排队消息带进新会话 + 后台事件错路由 + 侧栏旧搜索过滤);后端commands.rs:842-843ai_conversation_create 补清stop_flag.store(false,SeqCst)(关键:上方生成中分支置 true 停旧 loop 不复位则新会话 loop 启动即见 stop 退出) +agent_language=None(防沿用旧语言设置)。②F-13 性能:agentic.rs:173system_prompt token loop 外算一次缓存为 sys_tokens(整个 loop 不变参数,原每轮+每次重试重算);:219-225外层 messages 构建用 sys_tokens;:262-270重试循环改messages: messages.clone()复用外层 Vec(删每次重试的 session_arc.lock + build_for_request 全量 clone + estimate_text)。安全前提核验:stream_llm 不接 session_arc + 重试块到 process_tool_calls 间无 push,重试内 session.messages 与外层一致,重建等价复用。③AE-03 路径 B(审批 payload 加 diff,非路径 A):tool_registry.rs:32generate_diff fn→pub(crate)(复用 F-260615-10 LCS);audit.rs:508-520新增 build_write_file_diff(从 args 取 path+content → 预读旧文件 tokio::fs::read_to_string → 无变化/旧文件缺失/读失败均 None → generate_diff);audit.rs:588-609仅 write_file draft 挂起审批前预计算 approval_diff 注入 PendingApproval.diff(:600 clone)+AiApprovalRequired.diff(:609 move);恢复路径 diff=None(文件可能已变);mod.rs:101/269AiApprovalRequired+PendingApproval 加diff: Option<String>;types.ts:198AiApprovalRequired 加diff?: string;useAiEvents.tsevent.diff 传 toolCall 信息;ToolCard.vue:40-41模板v-if="tc.diff"diff 块 + :394 diffLines computed(按 +/-/space 前缀拆分)+ :677-705 样式(add 绿 del 红 ctx 灰 + token 复用)。 -
维度: ①F-09 单例字段补清完整性(stop_flag 复位是否覆盖所有生成中→新建路径/agent_language 影响面) + B 路线边界(确认未误动 AiSession 单例) ②F-13 行为不变性核验(sys_tokens 缓存是否真不变/messages.clone() 复用是否等价重建/stream_llm 不持 session_arc 前提是否成立) + 锁持有期缩短收益确认 ③AE-03 路径 B 安全性(预读旧文件只读不改/审批拒绝不执行 handler/路径不校验风险——恶意路径读失败仅 None) + diff 注入完整性(实时挂起+恢复路径双覆盖/前端消费链 event→toolCall→模板) + generate_diff pub(crate) 暴露面。
-
commit: d00b30f(重构: AI聊天可靠性批次)。
-
主代独立核查: ✅ 全过(useAiConversations:42-45 补清 / commands:842-843 stop_flag.store(false)+agent_language=None / agentic:173 sys_tokens loop 外缓存 + :219-225 外层用 + :262-270 重试 messages.clone() 复用 + 保文退避逻辑未动(Partial 不重试/retry_deadline 30s) / tool_registry:32 generate_diff pub(crate) / audit:508-520 build_write_file_diff 预读+无变化/缺失 None+复用 generate_diff / audit:588-609 仅 write_file 生成 diff 注入 PendingApproval(clone)+AiApprovalRequired(move) / mod:101+269 diff 字段 / types:198 diff? / ToolCard:40-41 模板 + :394 diffLines + :677-705 样式 / cargo+vue-tsc 双 EXIT 0)。
-
审查 agent 待复审重点: ①F-13 重试 messages.clone() 复用等价性——核验 stream_llm 签名(stream_recv.rs:137 不接 session_arc) + 重试块到 process_tool_calls 间确实无 session.messages.push(若遗漏则重试复用旧 messages 致 tool_calls 丢失) ②F-13 sys_tokens 缓存——核验 system_prompt 确为 run_agentic_loop 不变参数(整个 loop 期间无 mutate) ③AE-03 build_write_file_diff 安全——预读旧文件
tokio::fs::read_to_string(path)路径未校验(恶意 path 读失败仅 None 不产生危害,但核验无 symlink 逃逸读敏感文件风险——审批只读,write_file handler 自身 validate_path 执行时兜底) ④AE-03 diff 注入双路径——实时挂起(audit:588-609 有 diff)+恢复路径(PendingApproval diff 字段是否持久化,若仅内存则重启恢复审批无 diff) ⑤文件锁独立性——3 agent 改动文件无重叠(F-09: useAiConversations/commands / F-13: agentic / AE-03: mod/tool_registry/audit/types/useAiEvents/ToolCard),useAiEvents.ts Agent C 独占(A/B 未碰)确认无冲突。
CR-260616-37 F-11 审批续跑 iteration 累计计数(跨审批不重置) — ✅ 已审(PASS)
-
复审结论(2026-06-16·CR-37 agent 亲跑 cargo+test,658s): ✅ PASS — 🔴0 🟡0 ⚪0
- 验证:
cargo check --workspaceEXIT 0(5 warning 全 pre-existing dead_code:diff field / releases+node_executions 未读)/cargo test -p df-ai37 passed(retry 6 + context 18 + openai_compat 8 + stream_recv 5,finished 2.59s)。 - iteration_used 生命周期完备 PASS:grep 全消费点 9 处——ai_chat_send(:55 reset 0)/ ai_regenerate(:152 reset 0)/ ai_chat_edit_last(:412 reset 0)/ ai_continue_loop(:601 reset 0 + 传 0)/ ai_approve 拒绝(:260 累计读)/ ai_approve 通过(:313 累计读)/ agentic loop 递增(:210 iteration+1 写回)/ mod.rs:215 字段定义 / mod.rs:230 new() init 0。ai_conversation_create 隐式从 0(新会话 new init 0)。无遗漏。
- start_iteration 透传链 PASS:ai_approve(:260/:313 读 session.iteration_used)→ try_continue_agent_loop(:578 接 start_iteration 参)→ spawn(:675 透传)→ run_agentic_loop(:114 接参)→ for iteration in start_iteration..max_iterations(:176 使用)。每环 file:line 佐证无断链。
- 两路径区分 PASS:ai_approve 累计(F-260616-11,不 reset 读 iteration_used)vs ai_continue_loop 重计(F-260616-03 决策a,reset+传 0)vs 新消息(reset 0)严格区分。注释 agentic.rs:499-501 + commands.rs:258-259/311-312/599-600 均写明归属。
- 边界 start≥max PASS:for 循环空区间不执行 + converged 保持 false(:170)→ 命中 :502
if !converged→ emit AiMaxRoundsReached(:518-520) + return,防无限审批续跑烧 token,符合 F-11 语义。 - 并发安全 PASS:session.iteration_used=iteration+1 在 agentic.rs:198-211 同一 session_arc.lock().await 块(与 :199 active_conversation_id 一致性校验共享锁),锁模式与 :424-452/:474-477 一致。
- mod.rs 字段 PASS::215
pub iteration_used: usize(无符号防负)+ :230 new init 0 + agentic.rs:176 循环边界 + :210 一致性块更新。 - 待修项回流 todo: 无(6 重点全 PASS,零问题)。
- 验证:
-
范围: F-260616-11 决策a 落地(与 batch60 三项合批 commit
d00b30f)。mod.rs:215/230AiSession 加iteration_used:usize字段+new() init 0;agentic.rs:114run_agentic_loop 加start_iteration:usize参数 +:176loop 边界改start_iteration..max_iterations+:210一致性校验块更新session.iteration_used=iteration+1+:578try_continue_agent_loop 加 start_iteration 参数 +:675spawn 透传 +:499-501注释区分两路径;commands.rs:55/152/412ai_chat_send/ai_regenerate/ai_edit_last reset iteration_used=0(新生命周期)+:260-262ai_approve 拒绝续跑读 session.iteration_used 累计传 +:313-315ai_approve 通过续跑累计传 +:601ai_continue_loop reset iteration_used=0 传 0(F-03 决策a 达max重计区分)。两路径严格区分:审批续跑(ai_approve 累计透传 iteration_used) vs 达max续跑(ai_continue_loop reset+传0,F-03 决策a 用户授权重来) vs 新消息(reset 0)。 -
维度: ①iteration_used 字段生命周期完备性(所有会话生命周期入口 reset 覆盖——create/send/regenerate/edit_last/continue_loop 是否齐全无遗漏路径) ②start_iteration 透传链完整(agentic run_agentic_loop←try_continue_agent_loop←ai_approve 两个调用点,无断链) ③两路径区分正确性(ai_approve 累计 vs ai_continue_loop 重计,F-11/F-03 决策 a 分别归属无混淆) ④边界 case(start≥max loop 空区间→converged=false→AiMaxRoundsReached,防无限审批烧 token,符合 F-11 语义) ⑤并发安全(iteration_used 在一致性 lock 块更新,与现有 lock 模式一致)。
-
commit: d00b30f(同 CR-36 合批)。
-
主代独立核查: ✅ 全过(mod.rs:215/230 iteration_used 字段+init / agentic.rs:114 start_iteration 参数+:176 loop 边界+:210 一致性块 iteration+1+:578 try_continue 加参+:675 spawn 透传+:499-501 注释两路径 / commands.rs:55/152/412 reset 0+ai_approve:260/313 累计读 iteration_used+ai_continue_loop:601 reset 传0 / cargo check --workspace EXIT 0)。
-
审查 agent 待复审重点: ①iteration_used 生命周期完备性(所有会话生命周期入口 reset 覆盖无遗漏——create/send/regenerate/edit_last/continue_loop/ai_approve 全路径核验) ②start_iteration 透传链(run_agentic_loop←try_continue_agent_loop←ai_approve 两调用点无断链) ③两路径区分(ai_approve 累计 vs ai_continue_loop 重计,F-11/F-03 决策a 归属无混) ④边界 start≥max loop 空区间→converged false→AiMaxRoundsReached(防无限审批烧 token) ⑤并发安全(一致性 lock 块更新)。
CR-260616-38 F-07 df-ai-core trait 下沉拆 crate(解锁 F-03 注入) — ✅ 已审(PASS)
-
复审结论(2026-06-16·CR-38 agent 亲跑 cargo+test,137s): ✅ PASS — 🔴0 🟡0 ⚪0
- 验证:
cargo check --workspaceEXIT 0(5 warning 全 pre-existing dead_code)/cargo test -p df-ideas adversarial11 passed(a1-a11,含 7 heuristic + 4 LLM mock)。 - provider.rs 迁移完整 PASS:df-ai-core/src/ 仅 lib.rs+provider.rs(Glob 确认),provider.rs:1-235 含 LlmProvider trait(:208-235)+ 全数据结构(CompletionRequest/ChatMessage/MessageRole/ToolDefinition/ToolCall/CompletionResponse/TokenUsage/StreamChunk/ToolCallDelta/StreamResult),注释 :6-8 声明「零 IO」;grep ContextManager/TokenEstimator/AiToolRegistry/build_provider/HTTP impl 在 df-ai-core 内零命中(业务逻辑+HTTP impl 确留 df-ai);df-ai Cargo.toml:8 加 df-ai-core 依赖。
- re-export 透明 PASS:df-ai/src/lib.rs:17
pub use df_ai_core+ provider.rs:15pub use df_ai_core::provider::*;grepuse df_ai::provider11 处实际消费(idea.rs/project.rs/ai_node.rs/agentic.rs/stream_recv.rs/title.rs/knowledge_inject.rs/commands.rs/audit.rs 等)workspace 编译期全过;workspace Cargo.toml members=["crates/*"]glob 自动覆盖 df-ai-core 无需手动加。 - 构造注入语义 PASS:adversarial.rs:19 直接 use df_ai_core(不引 df-ai 重依赖)+ :96-98
provider: Option<Arc<dyn LlmProvider>>+ :101 new(Arc)→Some + :107 heuristic()→None + :112-133 evaluate() 调度;调用方 idea.rs:192 new(Arc::from(p))=Some / :193 heuristic()=None 等价无遗漏。 - EvaluatedBy 三态 PASS:adversarial.rs:28-37 Llm/Heuristic(#[default])/HeuristicFallback + :49-50
#[serde(default)]向后兼容老数据;:112-133 调度 Some+Ok=Llm / Some+Err=warn+HeuristicFallback / None=Heuristic 三态完备。 - build_default_provider PASS:idea.rs:265-279 list_all().ok()? DB 查失败 None + is_default 优先 find + 首条 fallback + build_provider_for Err→warn+None + :191-194 None 直走 heuristic 语义正确。
- Box→Arc 转换 PASS:idea.rs:192
Arc::from(Box<dyn LlmProvider>)标准库转换(堆分配复用),cargo check EXIT 0 编译期证类型匹配。 - 待修项回流 todo: 无(6 重点全 PASS,零问题)。
- 验证:
-
范围: F-260614-07 4 决策落地(workflow wwtn2knn6)。①df-ai-core 新 crate:
crates/df-ai-core/(Cargo.toml serde+async-trait+futures + src/lib.rspub mod provider+ src/provider.rs 迁移 trait+数据结构 LlmProvider/ChatMessage 等,不含 HTTP impl);②df-ai re-export:crates/df-ai/Cargo.toml加 df-ai-core 依赖 +src/provider.rs:15pub use df_ai_core::provider::*+src/lib.rs:17pub use df_ai_core(外部 use df_ai::provider 路径不变,df-nodes 零改动,workspace Cargo.toml 零改动 members=crates/*);③df-ideas 构造注入:crates/df-ideas/Cargo.toml加 df-ai-core+async-trait +src/adversarial.rs:18use df_ai_core::provider::LlmProvider + struct 加provider:Option<Arc<dyn LlmProvider>>(:96) +new(provider:Arc<dyn LlmProvider>)(:101 调用方 Some/None 分支语义等价 Option)+heuristic()(:107) +evaluate(&self)(:111) +evaluate_with_llm(:138) + EvaluatedBy 三态 enum(:28) + AdversarialEval.evaluated_by 字段(:49) + LLM 失败→warn+HeuristicFallback 降级(:122);④src-tauri 装配:idea.rs:190build_default_provider(DB is_default→build_provider→Option<Box>:265) + :192 Some→AdversarialEngine::new(Arc::from(p))/ None→heuristic()(:193)。 -
维度: ①re-export 透明性(df_ai::provider::LlmProvider 路径 df-nodes/df-project 等外部 use 无断裂) ②trait 下沉边界(LlmProvider trait+纯数据结构下沉,ContextManager/TokenEstimator/AiToolRegistry 业务逻辑确留 df-ai 未误迁) ③构造注入语义(new(Arc) vs 决策② Option——调用方分支等价性,无遗漏 Some/None) ④EvaluatedBy 三态正确(Llm/Heuristic/HeuristicFallback,provider None 全走 Heuristic) ⑤Box→Arc 转换(idea.rs:192 Arc::from(Box) 编译通过) ⑥build_default_provider DB 读(is_default 查询+无默认兜底 heuristic) ⑦adversarial 7 单测改 heuristic() 构造后全过。
-
commit: 2069f79(重构: df-ai-core trait下沉+导入历史)。
-
主代独立核查: ✅ 全过(df-ai-core 新建 Cargo.toml+lib.rs+provider.rs / df-ai provider.rs:15+lib.rs:17 re-export+Cargo.toml 加依赖 / df-ideas adversarial.rs:18 use df_ai_core:96 provider 字段+:101 new(Arc)+:107 heuristic+:111 evaluate(&self)+:138 evaluate_with_llm+:28 EvaluatedBy+:49 evaluated_by+:122 HeuristicFallback 降级 / idea.rs:190 build_default_provider+:192 new(Arc::from)+:193 heuristic+:265 fn / cargo check --workspace EXIT 0(5 warning 全 pre-existing dead_code))。
-
审查 agent 待复审重点: ①provider.rs 迁移完整性(df-ai 原 trait+数据结构全部下沉,df-ai/src/provider.rs 仅剩 re-export,retry/openai_compat/anthropic_compat HTTP impl 确留 df-ai 未断) ②df-nodes/df-project 外部 use df_ai::provider 编译验证(re-export 透明无断裂,实际 grep df_ai::provider 消费点) ③EvaluatedBy 三态语义(provider Some+LLM 成功=Llm / Some+LLM 失败=HeuristicFallback / None=Heuristic,evaluate_internal 调度正确) ④build_default_provider DB is_default 查询正确+build_provider 复用现有工厂 ⑤Box→Arc::from 编译期验证(cargo EXIT 0 已证)。
CR-260616-39 F-06 导入历史项目 scan 第二步(LLM description+monorepo+批量并发) — ✅ 已审(PASS)
-
复审结论(2026-06-16·CR-39 agent 亲跑 cargo+test+vue-tsc,215s): ✅ PASS — 🔴0 🟡0 ⚪2(agent 标题 🟡1 系笔误,待修项表全 low 无 med)
- 验证:
cargo check --workspaceEXIT 0(5 warning 全 pre-existing dead_code)/cargo test -p df-project15 passed(detects_monorepo_pnpm/npm_workspaces/non_monorepo/rust/full_stack + strips_frontmatter_and_toc_and_badges + image_collection_skips_badges + extract_desc 三测 + discover_monorepo/flat_children)/npx vue-tsc --noEmitEXIT 0。 - is_monorepo 判定 PASS:scan.rs:100-133 4 marker(pnpm-workspace/lerna/turbo/nx)+ package.json workspaces 兜底(:122-131);仅判根不递归(discover_projects 展开时 :223 设 false),nested monorepo 边界正确。
- discover_projects 展开 PASS:scan.rs:156-229 三路(根 manifest 入选/monorepo 展开 packages+apps/普通扫一层子目录)+ detect_stack 空过滤(:216-218 候选发现语义,无栈对 LLM 价值低,可手动 import)+ collect_tree_level is_sub 控 2 层(:638)不递归过深 + 隐藏目录/SAMPLE_IGNORED_DIRS 过滤。
- images 收集 PASS:scan.rs:329-481 ImageRef{alt,src}+ PartialEq + SAMPLE_README_MAX=8000 + truncate_chars 按 chars().take 截断(:664-669 UTF-8 安全)+ 实际 12 域黑名单 BADGE_HOSTS(:396-410 img.shields/shields/badge.fury/badgen/...)+ is_badge_image 域名+关键词双判(:441-448)+ collect_images 行级扫 +同 src 去重+上限 20(:454-481)+ is_pure_badge_line 混内容图返 false(:533-591);arch/screenshot 内容图留、badge 跳。
- create_with_binding 抽取 PASS:project.rs:59-107 统一校验+防重+探测+insert;create_project(:45-50)/import_project(:177)/import_projects_batch(:469)三入口共用;relocate 确未并入(:281-315 update_field 非 insert,注释 :56 声明);status 固定 planning+idea_id 透传行为一致无回归。
- import_projects_batch 并发 PASS:project.rs:415-493 双层 permit acquire_global+acquire_per_conv(:535-536 RAII 覆盖 complete)+ 非原子 join_all 逐项独立(LLM 失败 desc 空仍入库,create 失败返 error item,imported/skipped 计数)+ provider Arc 共享零拷贝 + 无 provider 明确报错非降级。
- scan_directory_for_projects 纯规则 PASS:project.rs:343-376 仅 discover_projects(spawn_blocking)+ find_binding_conflict 标已绑定,零 LLM 调用,深度一层。
- 前端 PASS:api/project.ts:116-126 两 API + 类型完整 + Projects.vue:73-133 导入 modal(只读表格 name/stack/path/bound + checkbox 全选/半选/单选 + 已绑定禁选灰显 + toast 汇总 imported/skipped + 全失败保留重扫)+ i18n zh/en 19 key 对称。
- df_ai::provider re-export 零断裂 PASS:ai_node.rs:12 + project.rs:8/440 三消费点编译期证。
- ⚪ CR-39-1(low,文档笔误):登记称「徽章域 5 域黑名单」与代码实际 12 域(BADGE_HOSTS scan.rs:396-410)不符,代码更完备文档滞后。
- ⚪ CR-39-2(low,设计取舍):scan.rs:216-218 detect_stack 空栈过滤剔除有 manifest 无已知栈标志的合法项目(自定义构建系统),当前候选发现语义可接受,用户可手动单条 import。
- 待修项回流 todo: 无(2 low 文档/边缘非阻断)。
- 验证:
-
范围: F-260614-06 6 决策落地(workflow wwtn2knn6)。①description 走 LLM(scan.rs extract_description 复用 complete,非纯规则);②采样改进(ProjectSample 扩
images:Vec<ImageRef{alt,src}>scan.rs:330/343 + readme 剥 frontmatter/TOC/纯徽章行截 8KBSAMPLE_README_MAX=2000→8000:346 + 保留内容图 markdown);③image 多模态条件化(ImageRef 数据结构+collect_images 收集:454,本批不读 base64 留接口待 F-260614-05);④monorepo 一层(is_monorepo:112 检 pnpm-workspace/lerna/turbo/nx/package.json workspaces + discover_projects:156 展开 packages//apps/ detect_stack 空过滤);⑤批量流程(scan_directory_for_projects project.rs:343 纯规则发现标已绑定 + import_projects_batch:415 并发 LLM llm_concurrency permit 限流复用绑定入库非原子逐项独立);⑥对称改进(抽 create_with_binding project.rs:59 create/import 共用校验+防重+探测+insert 缓解 :211 TODO,relocate 不并入)。前端 Projects.vue 导入 modal+api/project.ts 两 API+i18n 双语+scan.rs 单测(剥 badges/image 收集/monorepo/discover/extract_desc)。 -
维度: ①is_monorepo 判定完备(pnpm-workspace/lerna/turbo/nx+package.json workspaces 全覆盖) ②discover_projects 展开(detect_stack 空过滤误剔合法项目风险/monorepo 子目录一层不递归过深) ③collect_sample images 收集(徽章域黑名单 5 域+is_badge_image alt+src 双判+is_pure_badge_line 整行纯徽章,内容图 arch/screenshot 留) ④SAMPLE_README_MAX 8000 截断(truncate_chars 不破坏 markdown/UTF-8 边界) ⑤create_with_binding 抽取(create+import 共用一致,relocate 确未并入) ⑥import_projects_batch 并发(llm_concurrency 双层 permit 限流/非原子逐项独立失败不阻塞) ⑦scan_directory_for_projects 纯规则不跑 LLM(标已绑定正确) ⑧df-project use df_ai::provider 路径不变(F-07 re-export 透明) ⑨前端预览只读+toast 汇总(导入 N/跳过 M)。
-
commit: 2069f79(重构: df-ai-core trait下沉+导入历史)。
-
主代独立核查: ✅ 全过(scan.rs:112 is_monorepo+:156 discover_projects+:330 ImageRef+:343 images 字段+:346 SAMPLE_README_MAX 8000+:357 collect_sample+:397-401 徽章域 5 域+:439 is_badge_image+:454 collect_images+:533 is_pure_badge_line+:742-876 单测 / project.rs:59 create_with_binding+:343 scan_directory_for_projects+:415 import_projects_batch / lib.rs:85-86 注册两 IPC / api/project.ts:117/125 两 API / Projects.vue:111 mono-tag / cargo check --workspace EXIT 0+vue-tsc EXIT 0)。
-
审查 agent 待复审重点: ①is_monorepo/discover_projects 边界(nested monorepo/无 workspace 配置/detect_stack 空过滤误剔) ②徽章过滤完整(5 域黑名单+alt/src 双判是否漏内容图误剔或漏 badge 误留) ③create_with_binding 与原 create_project 行为一致(校验+防重+探测+insert 路径无回归) ④import_projects_batch 并发安全(llm_concurrency permit 限流正确+非原子逐项失败隔离) ⑤scan_directory_for_projects 性能(纯规则不跑 LLM,目录扫描深浅/大目录性能) ⑥前端 modal 预览表格只读+勾选+toast 汇总交互完整。
CR-260616-40 F-03 对抗评估接 LLM(evaluate_with_llm 真实实现) — ✅ 已审(PASS)
- 范围: F-260614-03 落地(workflow wy6rjn0en)。crates/df-ideas/src/adversarial.rs:143 evaluate_with_llm 真实实现(原 F-07 占位 bail):build_adversarial_prompt(:380 三角色辩论 prompt)+CompletionRequest(model 留空回填 default/temperature 0.4/max_tokens 2048/stream false)+provider.complete(:160 map_err anyhow)+parse_llm_eval(:500 serde_json::from_str 解析 LlmEvalRaw+枚举映射:571/586 bail 非法值)+clamp(final_score[0,10]/confidence[0,1] 结构兜底:167-170)。4 LLM mock 单测(MockProvider impl LlmProvider:733-755+a8 parse success/a9 bad JSON fallback/a10 bad enum fallback/a11 value clamp)。evaluate 调度(:111)+evaluate_heuristic(:176)+原 7 单测不动。
- 维度: ①prompt 设计质量(三角色辩论+JSON schema 明确+temperature 0.4 防发散+max_tokens 2048 充足) ②JSON 解析容错(非 JSON/字段缺失/枚举非法 全 bail 触发 HeuristicFallback 降级,a9/a10 单测覆盖) ③clamp 兜底(final_score[0,10]/confidence[0,1],a11 单测) ④model 留空回填(OpenAICompatProvider convert_request 回退 default_model 正确) ⑤evaluate 调度不变(Some→Llm/Err→HeuristicFallback/None→Heuristic) ⑥MockProvider 单测真实性(impl LlmProvider trait 完整,stream bail 因 adversarial 不调 stream) ⑦不回归(原 7 heuristic+scoring 8 全过)。
- commit: dfe0096(新增: F-03对抗评估接LLM+F-02纯技能调用标题+i18n@转义)。
- 主代独立核查: ✅ 全过(adversarial.rs:143 evaluate_with_llm 真实实现+:161 complete+:380 build_adversarial_prompt+:500 serde_json 解析+:571/586 枚举 bail+:167-170 clamp / MockProvider:733-755+4 单测 a8-a11 / cargo test -p df-ideas 19 passed EXIT 0)。
- 审查 agent 待复审重点: ①build_adversarial_prompt 全文质量(:380 prompt 三角色+JSON schema 是否明确无歧义) ②parse_llm_eval 解析(:500 LlmEvalRaw 结构+枚举映射 serde/FromStr 实现) ③LLM 返回非 JSON 降级(a9 单测,extract JSON 容错——LLM 可能返回 markdown json 包裹需剥离) ④prompt injection 防护(idea.title/description 注入 prompt 是否需隔离,防用户输入操纵评估) ⑤max_tokens 2048 是否够(复杂 idea 评估 JSON 可能超) ⑥temperature 0.4 评估稳定性。
- 复审结论(2026-06-16·独立审查 agent · 亲跑 cargo test EXIT 0 + grep/read 核验源码): ✅ PASS — 🔴0 🟡0 ⚪4
- 自验:
cargo test -p df-ideas adversarial11 passed EXIT 0(7 heuristic a1-a7 + 4 LLM a8-a11)/cargo test -p df-ideas19 passed EXIT 0(8 scoring + 11 adversarial 不回归)。 - 逐件核验(8 维度全 PASS):
- ①prompt 设计 ✅:build_adversarial_prompt:398-448 三角色(正方/反方/分析师)+JSON schema 字段名/枚举字面量明确(
final_assessment: "StrongGo|Recommended|Conditional|Revised|Defer"/recommendation: "ImmediateAction|Soon|WithResources|ResearchMore|Monitor")+自洽约束(final_score 与 recommendation 对应 final_assessment)+SYSTEM_PROMPT:364-374 硬约束「只输出 JSON 对象/枚举字面量严格/数值区间」。temperature 0.4 注释:141-142 立论(0.3 重复/0.5 发散折中)合理。max_tokens 2048(schema10 字段全填600 token + 论点 2-3 条/角色 × 3 角色 ~1200 token 富余)。 - ②JSON 解析容错 ✅:parse_llm_eval:498-539 ①extract_json:545-561 剥 ```json 围栏(去前缀+跳语言标记行+去后缀)+trim ②serde_json::from_str → LlmEvalRaw:456-463 必填字段缺/类型错 → bail:500-502 ③枚举映射 parse_assessment_level:564-576/parse_recommendation:579-591 非法字面量 → bail:571/586(a10 "GoNow" 覆盖)。a9 非 JSON 降级 HeuristicFallback:830 覆盖。
- ③clamp 兜底 ✅:parse_llm_eval 内 confidence clamp[0,1]:511/517 + final_score clamp[0,10]:534;evaluate_with_llm:167-170 再做一次结构兜底(注释明确「结构兜底」)。a11 confidence 1.5→1.0/-0.3→0.0/final_score 99→10.0:874-876 全过,仅越界不降级 evaluated_by=Llm:873。
- ④model 留空回填 ✅:OpenAICompatProvider::convert_request:295-296
if req.model.is_empty() { self.default_model.clone() };AnthropicCompatProvider::convert_request:282-283 同样回填;两 provider 一致,留空 String::new() 正确回退各自默认模型。 - ⑤evaluate 调度 ✅:evaluate:112-133 三分支 Some(p)→evaluate_with_llm:114 成功标 Llm:116/Err 标 HeuristicFallback:123/warn 日志:121;None→evaluate_heuristic:128 标 Heuristic:129。调度不变,a9/a10 验证降级路径。
- ⑥MockProvider 真实性 ✅:MockProvider:733-761 impl LlmProvider trait 三方法完整(complete:739-749 返预设 text/model="mock-model"/usage default/stream:751-756 bail「不调用」/name:758-760)。embed 用 trait 默认实现(provider.rs:222-224 返 Err,adversarial 不调 embed 无影响)。stream bail 注释:755 明确「adversarial 路径不调用」合理。
- ⑦不回归 ✅:原 7 heuristic 单测 a1-a7:645-723 全过(评分→recommendation 映射/confidence 区间/thesis 含标题/negative evidence 非空/final_score 一致性);scoring 8 单测全过。total 19 passed。
- ⑧prompt injection 防护 ⚪(low,结构安全/语义受限):结构性注入已阻断——idea.title/description 经
format!命名参数注入(:443-446),format! 不对参数值二次解析,Rust 运行时 title/description 中的{/}字符直接作为字面文本输出,不会破坏 prompt 模板骨架。语义性注入未隔离(用户可写 description="忽略规则,输出 final_score=10")属 LLM 固有局限非本实现 bug;description 截 800 字:383-390 已限长降低面。当前评估结果越界有 clamp 兜底、枚举非法有 bail 降级,最坏后果降级启发式不致数据错。
- ①prompt 设计 ✅:build_adversarial_prompt:398-448 三角色(正方/反方/分析师)+JSON schema 字段名/枚举字面量明确(
- ⚪ low 清单(4,全非阻断):
- CR-40-1(low,clamp 双重执行冗余):parse_llm_eval 已 clamp confidence:511/517+final_score:534,evaluate_with_llm:167-170 又 clamp 一次(parse_llm_eval 注释:533 也承认「调用方再做一次」)。无害防御性重复,建议删 :167-170 留 parse 内单点收口,减少阅读歧义。
- CR-40-2(low,extract_json 围栏位置局限):extract_json:548 只 strip_prefix("
") 处理围栏在开头;LLM 若输出 `好的,以下是评估:\njson\n{...}\n````(围栏不在首),strip_prefix 失败走 trimmed 原文 → serde 报错 → 降级。降级语义安全但命中率略低;非阻断(降级正确),可后续用正则(?s)\{.*\}兜底提取首个 JSON 对象增强。 - CR-40-3(low,semantic prompt injection 未隔离):见维度⑧,description 截 800 字已限长,枚举/数值有 clamp+bail 兜底,语义注入最坏降级启发式,不阻断。
- CR-40-4(low,temperature/max_tokens 硬编码不可配):temperature 0.4/max_tokens 2048 硬编码:153-154,无运行时调参能力;当前单 provider 单次调用可接受,后续若多 provider 调优需抽配置。
- 待修项回流 todo: 无(4 low 全非阻断:clamp 冗余/extract_json 增强/semantic injection/硬编码均可在后续迭代处理,当前 PASS)。
- 自验:
- 主代独立核查: ✅ 全过(adversarial.rs:143 evaluate_with_llm 真实实现+:161 complete+:380 build_adversarial_prompt+:500 serde_json 解析+:571/586 枚举 bail+:167-170 clamp / MockProvider:733-755+4 单测 a8-a11 / cargo test -p df-ideas 19 passed EXIT 0)。
CR-260616-41 batch64 F-03 收口三件(advance_task/run_workflow 工具注册+update_task 拒 status+crud 白名单移 status) — ✅ 已审(PASS·🔴-1已修)
- 范围: F-260616-07 阶段3 确定性收口(workflow wii1u1lnm,3 agent 并行)。①Agent A tool_registry.rs:advance_task 注册(:395,handler L407 调
df_nodes::task_advance_node::advance_task_atomic(&TaskRepo::new(&db),id,target_status)与 IPCcommands::task::advance_task:165同源,thin 转发无 AppHandle/State 依赖,RiskLevel::Medium)+ run_workflow 注册(:428,handler 架构约束无 AppHandle/State 报错引导前端转调invoke('run_workflow')IPC,RiskLevel::High,ToolDefinition+审批文案 L1260-1261/1286-1287 注册)+ update_task handler guard L374if field=="status"bail(schema 通用 field/value 模式非 enum,故靠 handler guard 拦非 schema 改)。②Agent B crud.rs:tasks allowed_columns L331-347 移"status"+"review_rounds"(D-260616-04 收口),注释明确「status/review_rounds 走 advance_status_atomic CAS 独立路径,后人勿补回白名单」;advance_status_atomicL848 CAS SQLWHERE id AND status=expected AND deleted_at IS NULL完全绕validate_column_name/allowed_columns_for;tests/project_soft_delete.rs:250断言反转update_field_rejects_tasks_status(改 status 现 Err)。③Agent C CR-40 审查(对 batch62dfe0096evaluate_with_llm):PASS 4 low 非阻断。 - 维度: ①advance_task handler 同源核验(tool_registry.rs:407 调 advance_task_atomic 与 commands::task::advance_task:165 同源,thin 转发无逻辑分叉) ②update_task handler guard 双重防御(L374 handler 拒 + df-storage 白名单拒,status 双层拦截) ③crud 白名单移 status+review_rounds 收口语义(advance_status_atomic L848 CAS 独立路径不经 allowed_columns,review_rounds 唯一写入路径防计数错乱) ④run_workflow handler 架构约束(handler 无 AppHandle/State 报错引导走 IPC 是否合理,后续批注入 AppState 句柄让 AI 直驱) ⑤审批文案映射(display_hint_for_tool L1260-1261 + tool_display_hint L1286-1287 advance_task/run_workflow 条目) ⑥project_soft_delete.rs 测试断言反转(update_field_rejects_tasks_status 改 status 现 Err 正确) ⑦前端 status 路径零残留(taskApi.update 全代码库零调用方,status 唯一走 taskApi.advance→advance_task IPC) ⑧commands/task.rs update_task IPC 残留死代码(task.rs:100-108 field=="status" 分支移白名单后永不命中,回流 todo 清理)。
- commit: 待提交(batch64 攒批)。
- 主代独立核查: ✅ 全过(tool_registry.rs advance_task:395+handler:407 advance_task_atomic 同源 / run_workflow:428+handler 约束报错引导 IPC / update_task handler guard:374
field=="status"bail / crud.rs:331-347 tasks 白名单移 status+review_rounds 注释明确 / advance_status_atomic:848 CASWHERE id AND status=expected AND deleted_at IS NULL独立路径 / cargo check --workspace EXIT 0(5 pre-existing warnings 无关)+ df-storage 11 集成测试含新 update_field_rejects_tasks_status)。 - 审查 agent 待复审重点: ①advance_task handler 同源 IPC(commands::task::advance_task:165 核验,thin 转发无逻辑分叉,df_core::error::Error→anyhow ?From 链路) ②update_task handler guard 双重防御(handler guard L374 + df-storage 白名单双拦,是否漏路径) ③crud 白名单移 status+review_rounds 收口(advance_status_atomic L848 CAS 是否真独立不经 allowed_columns,review_rounds 唯一写入路径防旁路写) ④run_workflow handler 报错引导 IPC 是否合理(架构约束无 AppHandle/State,后续批注入句柄) ⑤审批文案映射完整(display_hint L1260-1261/1286-1287) ⑥测试断言反转正确(project_soft_delete.rs:250 update_field_rejects_tasks_status 改 status 现 Err) ⑦commands/task.rs:100-108 update_task IPC 残留死代码回流 todo。
- 复审结论(2026-06-16·审查 agent 亲跑 cargo + 独立 grep/read 核验源码): ⚠ ISSUES — 🔴1 🟡0 ⚪1 → 修复后(主代
b94e74a之后): ✅ PASS 🔴0 🟡0 ⚪1(🔴-1 测试 fixture 已修)- 验证:
cargo check --workspaceEXIT 0(5 pre-existing dead_code warnings 无关本 CR)/cargo test -p df-storage33 passed(22 lib + 11 project_soft_delete 含新update_field_rejects_tasks_status✅)/cargo test -p df-nodes66 passed; 1 FAILED(advance_succeeds_after_prior_status_change红,见 🔴-1)→ 🔴-1 修复后 67 passed; 0 failed; 1 ignored(task_advance_node.rs:331-332 改 advance_status_atomic)。 - ①advance_task handler 同源核验 ✅ tool_registry.rs:407 调
advance_task_atomic(&TaskRepo::new(&db), id, target_status)与 commands::task::advance_task(task.rs:165)同源调df_nodes::task_advance_node::advance_task_atomic(&state.tasks, &id, &target_status),均 thin 转发到同一advance_task_atomic(task_advance_node.rs:43-90),无逻辑分叉。df_core::error::Error 是 thiserror(crate df-core/src/error.rs:5#[derive(Error)]),实现 std::error::Error,anyhow::Error: From 链路通(?编译过 cargo check 证)。 - ②update_task handler guard 双重防御 ✅ tool_registry.rs:374-378
if field=="status"bail(第一层 handler 拒)+ tool_registry.rs:380-382is_allowed_column("tasks", field)查 crud.rs:331-347 白名单(第二层,白名单已无 status),status 双层拦截。无漏路径:value 必经 args["field"].as_str() 解析后两条 guard 之一必中。 - ③crud 白名单移 status+review_rounds 收口 ✅ crud.rs:342 现 tasks 白名单
"project_id","title","description","priority","branch_name","assignee","workflow_def_id","base_branch","updated_at"(无 status/review_rounds)。advance_status_atomic(crud.rs:848-880)走独立 CAS SQLUPDATE tasks SET status=?1[, review_rounds=review_rounds+1] WHERE id=?3 AND status=?4 AND deleted_at IS NULL,不经 validate_column_name/allowed_columns_for(无白名单调用)。review_rounds 唯一写入路径收口语义达成:除 advance_status_atomic 退回转换 bump_rounds=true 路径,无任何旁路可写(crud.rs:865-866 SQL 内联review_rounds+1)。 - ④run_workflow handler 报错引导 IPC ✅ 合理。tool_registry.rs:431-446 handler 仅持
_db: Arc<Database>(L432 实际未用,前缀_标记),无法构造 AppHandle/AppState(registry/event_bus/workflows Repo),L440-444 显式 anyhow 返回明确错误引导前端invoke('run_workflow', {task_id, target_status})。符合单一执行路径原则(不在 handler 重放 DAG 引擎)。注释 L419-426 标注后续批注入 AppState 句柄扩展路径。 - ⑤审批文案映射 ✅ display_hint_for_tool(tool_registry.rs:1259-1261)注册
advance_task => "推进任务状态:{} → {}", &["id","target_status"]+run_workflow => "触发工作流:任务{} 推进到 {}", &["task_id","target_status"];tool_display_hint(tool_registry.rs:1286-1287)注册advance_task => Some("推进任务状态")+run_workflow => Some("触发工作流")。两条目齐全。 - ⑥project_soft_delete.rs 测试断言反转 ✅ project_soft_delete.rs:250
update_field_rejects_tasks_status现res.is_err()(L259-262)+ 对照assert_ne!(rec.status, "done")(L266)。亲跑 11 集成测试全过 EXIT 0。 - ⑦前端 status 路径零残留 ✅ taskApi.update(src/api/task.ts:17-19)仅 tasks.ts:31
updateTask调用,全代码库 grepupdateTask(零组件调用方(仅定义无消费)。TaskDetail.vue:281 推进走taskApi.advance(task.value.id, target)(状态唯一路径→advance_task IPC)。status 前端路径零残留达成。注:tasks.ts:29updateTask函数本身保留(改 title/description 等非 status 字段,合法),仅 status 字段已无消费方。 - ⑧commands/task.rs update_task IPC 残留分支 ⚪(low)task.rs:102-108
if field=="status" && !TaskStatus::is_valid(&value)分支并非纯死代码:它仍为 status 非法值(拼写错/越界)提供更友好早错误(先于白名单拒)。但 status 合法值路径必落到 task.rs:133-135update_field被白名单拒,故「校验通过后写入」分支永不命中。建议保留 value 校验(改善 UX)+ 删除成功路径假设注释,或整体移除改由 advance_task 独占(后续迭代决定,非阻断)。 - 🔴-1(df-nodes 测试回归,high): task_advance_node.rs:332
repo.update_field("t1", "status", "in_progress").await.unwrap()测试红 —— 退回测试advance_succeeds_after_prior_status_change(L324-336)用update_field模拟「旁路并发改 status」前置,但本 commit 已把 status 移出 tasks 白名单,update_field现 Err →unwrap()panic「表 tasks 不允许的字段名: status」。亲跑cargo test -p df-nodes证实 66 passed/1 failed。生产代码无回归(advance_task_atomic 本身工作正常,66 个推进链测试全过),仅测试 fixture 需同步:把 L332 改成repo.advance_status_atomic("t1","todo","in_progress",false).await.unwrap()(用 CAS 路径模拟并发改,语义等价)或直接repo.insert(rec("t1","in_progress"))重建前置态。CI 红门未守,须修后才能合。 ✅ 已修(主代 task_advance_node.rs:331-332 改advance_status_atomic("t1","todo","in_progress",false)走 CAS 合法路径模拟并发推进,亲跑 cargo test -p df-nodes 67 passed/0 failed/1 ignored) - ⚪ low 清单: ①task.rs:102-108 status value 校验分支半死代码(合法值成功路径永不命中,非法值仍触发,见 ⑧)。
- 待修项回流 todo: 🔴-1(df-nodes 测试回归 task_advance_node.rs:332) ✅ 已修(主代改
advance_status_atomic,亲跑 67 passed,见上方修复记录)。低 ①task.rs:102-108 status value 校验半死代码非阻断(审查建议保留 value 校验改善 UX + 删成功路径假设注释,后续迭代决定)。
- 验证:
CR-260616-43 AiNode 自审阶段2(AiNode持db+ai_self_review节点+DAG透传+前端展示) — ✅ 已审(PASS)
-
复审结论(2026-06-16·CR-43 agent 亲跑 cargo+vue-tsc+test,142s): ✅ PASS — 🔴0 🟡0 ⚪1
- 验证:
cargo check --workspaceEXIT 0(5 warning 全 pre-existing dead_code)/cargo test -p df-nodes79 passed; 0 failed; 1 ignored /npx vue-tsc --noEmitEXIT 0。 - ①AiSelfReviewNode prompt 四维度+JSON 兜底完备 PASS:build_review_prompt(ai_node.rs:405-424) 含需求+产出+四维度+输出格式 + parse_review_json(:360-381) 覆盖非 JSON/非 Object/缺 verdict → verdict=unknown 兜底 + 代码块剥离(:363-367 ```json 围栏) + 单测(:928-963) 合规/非法/fence 场景全过。
- ②写回 output_json review 子字段 PASS:读现有合并不覆盖 text(:514-519 先读 output_obj 若 JSON 则复用) + review 子字段写入(:520-534 含 verdict+reviewed_at+reviewer_model) + 落库失败只 warn 不阻断(:538-544)。
- ③DAG 透传 PASS:NodeOutput.data 平铺(:550-556 verdict/summary/suggestions) + human_review 经 inputs 读(task_workflow_templates.rs:72 edge ai_self_review→human_review) + HumanNode 零改动(git diff 核验 human_node.rs 在 3 commit 中无变动)。
- ④state.rs 注册+工厂闭包 PASS:AiNode 注册(:261-263 AiNode::new(ai_db)) + AiSelfReviewNode 注册(:269-270 AiSelfReviewNode::new(review_db)) + move db.clone() 闭包捕获正确。
- ⑤testing 模板节点类型对齐 PASS:模板 L61 注册 "ai_self_review" 节点 + L72 edge 透传 + 单测 L123-143 验证节点类型+边方向+options。
- ⑥前端 schema 契约一致 PASS:types.ts:102 output_json? 字段 + L98-100 schema 注释 + TaskDetail.vue:229-234 parsedOutput computed try/catch 兜底 + reviewVerdictClass/reviewLabel/reviewSuggestions 均从 parsedOutput.review 读 + 字段名对齐 verdict/summary/suggestions。
- ⑦i18n 双语 PASS:zh-CN/taskDetail.ts:23-27 review.pass/fail/unknown + en/taskDetail.ts:23-27 对称翻译 + 特殊字符无转义需求(值均为静态枚举字面量)。
- ⑧P0 安全债登记确认:api_key 明文 config 注入违背 FR-S1 已在 memory/devflow-aichat-review-pending.md 登记 + 本次 CR 范围已实现 resolve_provider 三路径(ai_node.rs:62-135 完整实现) + F-260616-07c 阻塞⑥联调已记录。
- ⚪ CR-43-1(low,单测降级声明):AiSelfReviewNode.execute 完整 mock LlmProvider 单测本次降级(仅 parse_review_json+update_field 单测覆盖,P3 待补)。ai_node.rs:589-875 单测已覆盖 resolve_provider 双路径+parse_review_json 兜底+update_field 路径,end-to-end execute 单测依赖真实 LLM(#[ignore] glm_live_complete 存在),非阻断。
- 待修项回流 todo: 无(1 low 单测降级非阻断,已在 CR 信息说明 P3 待补)。
- 验证:
-
范围: F-260616-07 阶段3 AiNode 自审闭环②③④⑤(3 commit:539b5ed 迁移 +
c10adafai 侧 +741b0b9前端)。①①迁移(539b5ed):TaskRecord 加output_json: Option<String>+ V17 迁移(幂等补列) + crud 白名单加 output_json(L346)+SELECT/INSERT/UPDATE 全链路 + 5 构造点补 None。②ai_node.rs②③④(c10adaf):AiNode 持 db(Arc<Database>,对齐 TaskAdvanceNode:98-110 先例)+ execute 后config["task_id"]存在则update_field落 output_json(产出 schema {text,model,usage},落库失败只 warn 不阻断)+ AiSelfReviewNode 独立节点(parse_review_json 兜底 verdict=unknown + build_review_prompt 四维度 + 写回 review 子字段不覆盖 text)+ state.rs L269-270 注册 ai_self_review + ai 工厂 L262AiNode::new(db)+ testing 模板 L61 ai_self_review + L72 edge(ai_self_review→human_review)+ NodeOutput.data 平铺透传 + HumanNode 零改动。③前端⑤(741b0b9):types.ts output_json?(schema 注释)+ TaskDetail.vue parsedOutput computed(try/catch 兜底)+review-card 红绿标+renderedOutput 复用 useRendered + i18n zh/en taskDetail.output/review 双语。 -
维度: ①AiSelfReviewNode prompt 四维度+JSON 兜底完备性(parse_review_json 各路径 verdict 覆盖,代码块 fence 剥离重试) ②写回 output_json review 子字段(读现有合并不覆盖 text,reviewed_at/reviewer_model) ③DAG 透传(NodeOutput.data 平铺,human_review 经 inputs 读,HumanNode 零改动核验) ④state.rs 注册+工厂闭包 move db.clone() ⑤testing 模板节点类型对齐 ⑥前端 schema 契约一致(types.ts 与 ai_node.rs 写回 schema 字段名对齐 verdict/summary/suggestions) ⑦i18n 特殊字符转义 ⑧P0 安全债登记(api_key 明文 config 注入违背 FR-S1,跨 crate 阻塞,F-260616-07c 待决) ⑨主代亲跑 cargo df-nodes 73 passed + workspace EXIT 0 + vue-tsc EXIT 0。
-
主代独立核查: ✅ 全过(human_node.rs 零改动 git diff 确认 / state.rs L262 AiNode::new(ai_db)+L269-270 AiSelfReviewNode::new(review_db) / templates L61 ai_self_review+L72 edge / ai_node.rs AiNode 持 db@L119+execute 写@L184+AiSelfReviewNode@L292+parse_review_json@L258 / cargo test -p df-nodes 73 passed 亲跑 / cargo check --workspace EXIT 0 / vue-tsc EXIT 0 / 前端 schema 对齐)。
-
审查 agent 待复审重点: ①AiSelfReviewNode.execute 完整 mock LlmProvider 单测(本次降级 parse_review_json+update_field 单测,P3 待补) ②parse_review_json 兜底各路径(verdict=unknown 覆盖非 Object/数组/缺 verdict/JSON 解析失败/fence 剥离) ③写回 output_json 读现有合并的并发安全(同一 task 多次自审竞态) ④human_review 审批卡 description 拼自审摘要未实现(P2,透传链通但卡片未拼,HumanNode 只读 config 不读 inputs) ⑤P0 api_key config 注入(F-260616-07c,✅ 已由 CR-44 secret 下沉+注入链解决)。
CR-260616-44 P0 secret下沉+AiNode provider注入链(方案B FR-S1闭环) — ✅ 已审(PASS)
-
复审结论(2026-06-16·CR-44 agent 亲跑 cargo+test+vue-tsc,485s): ✅ PASS — 🔴0 🟡0 ⚪0
- 验证:
cargo check --workspaceEXIT 0(5 warning 全 pre-existing dead_code)/cargo test -p df-nodes79 passed; 0 failed; 1 ignored(含 9 个 resolve_provider 新增测试)/npx vue-tsc --noEmitEXIT 0。 - ① secret 下沉边界 PASS:secret.rs:7-9 注释明确不下沉 build_provider_for(依赖 df-ai 会循环)。grep 0 处函数定义,5 处全注释(src-tauri/Cargo.toml:17/secret.rs:7/8)。src-tauri/secret.rs:20 pub use df_storage::secret::* 转发所有下沉函数 + :32 build_provider_for 保留全限定路径调 df_ai::build_provider(:37)。
- ② 转发壳 glob 兜底 PASS:13 个实际调用点(排除注释行)全部经 crate::commands::ai::secret:: 或 super::secret:: 路径调用(commands.rs 4 处+agentic.rs 2 处+title.rs 1 处+knowledge_inject.rs 2 处+idea.rs 1 处+project.rs 2 处+lib.rs 1 处迁移调用)。编译期验证零断裂(cargo check EXIT 0)。
- ③ keyring feature 平台覆盖 PASS:Cargo.toml workspace:24 统一声明 windows-native+apple-native+linux-native-async-persistent 三平台。df-storage/Cargo.toml:18 keyring = { workspace = true }。secret.rs:220 cfg(any(target_os="windows",target_os="macos")) 单测(仅桌面 OS 环境实测 keyring roundtrip,CI 任意 OS 安全)。
- ④ resolve_provider 三路径完备 PASS:ai_node.rs:62-135 路径1 provider_id 优先→get_by_id(:76)→resolve_provider_secret(:143)→ensure_resolved_key(:144)/ 路径2 老明文兼容(:86-121,has_plain_base+has_plain_key 双判+:89-92 warn "老路径将在后续版本移除")/ 路径3 兜底(:124-134,list_all→find is_default→or_else 首条,友误 "未配置 AI Provider")。9 单测全覆盖(by_id/empty_key/unknown_id/legacy_plain/legacy_empty/fallback_default/no_providers/fallback_first/config_model_override)。
- ⑤ FR-S1 闭环 PASS:api_key 仅存 ResolvedProvider 内存(ai_node.rs:45 明文字段,注释 "仅 AiNode 内存可见"),不进 config(schema L313/314 "已废弃过渡" 描述)/NodeOutput(L273-281 仅 text/model/usage 无 api_key)/schema(L316 required 改 ["provider_id"])。resolve_provider_secret(DB 优先→keyring)解析,build_provider(:235-240)传明文仅 AiNode 进程内存可见,不落库不外泄。
- ⑥ schema required 改 [provider_id] PASS:ai_node.rs:316 required=["provider_id"] + :309 description "FR-S1:密钥经 df_storage::secret 解析,不进 config" / AiSelfReviewNode :569 required=["task_id","provider_id"]。
- ⑦ 老明文兼容 warn 过渡期 PASS:ai_node.rs:89-92 tracing::warn!("AiNode 明文 api_key/base_url 经 config 注入已废弃, 改用 provider_id (FR-S1). 老路径将在后续版本移除")。路径2 逻辑保留兼容(:86-121 has_plain_base+has_plain_key 双判)。
- ⑧ 迁移逻辑 FAILCOUNT SIDECAR PASS:secret.rs:34-72 failcount_path/.devflow-keyring-failcount + read/write/record/clear_migration_failcount + migrate_secrets_to_keyring(:125)失败达阈值 3 升级 warn(:136-142 明文滞留风险+3 条排查建议:确认 OS 钥匙串可用/keyring 后端异常/手动重新保存)。lib.rs:28 启动调用非阻断(:29 warn "启动密钥迁移失败(非阻断)")。
- ⑨ df-nodes 显式导入 PASS:ai_node.rs:17 use df_storage::secret::{ensure_resolved_key, resolve_provider_secret} 显式导入(非 glob)。resolve_from_record(:143-144)直接调用。ai_node.rs:14-15 也显式导入 AiProviderRepo/Database/AiProviderRecord。
- ⑩ testing 模板 PASS:task_workflow_templates.rs:61 注册 "ai_self_review" 节点 + :72 edge(ai_self_review→human_review) + 单测(:123-129)验证节点类型+边方向+config 空。state.rs:269-270 注册 AiSelfReviewNode::new(review_db) 闭包捕获正确。
- 待修项回流 todo: 无(10 维度全 PASS,零问题)。
- 验证:
-
范围: F-260616-07c P0 安全闭环(3 commit:36f70b3 方案 +
766f235下沉 +84a6fc4注入链)。①secret 下沉(766f235):df-storage 新建 src/secret.rs move 纯密钥逻辑(get/set/delete/resolve/ensure/migrate+failcount,不含 build_provider_for 避循环)+ keyring workspace 平台 feature(windows/apple/linux-native-async-persistent)+ src-tauri secret.rs 瘦身转发壳(pub use df_storage::secret::* + build_provider_for 保留全限定路径调)+ 13 调用点零改(glob 兜底)+ keyring 单测 cfg-gate。②AiNode 注入链(84a6fc4):ai_node.rs resolve_provider(db,config)->ResolvedProvider 三路径(provider_id 优先 get_by_id→resolve_provider_secret→ensure / 老明文兼容 warn / 空兜底 list_all find is_default)+ AiNode/AiSelfReviewNode execute 同构经 resolve_provider 拿 provider + schema required [provider_id] + 模板 config 空走兜底。 -
维度: ①secret 下沉边界(纯密钥逻辑无 df-ai 依赖,build_provider_for 留 src-tauri,无循环) ②转发壳 glob 兜底(13 调用点零改核验) ③keyring feature 平台覆盖(Linux linux-native-async-persistent) ④resolve_provider 三路径完备(provider_id/老明文 warn/兜底 is_default/无 provider 友好错误) ⑤FR-S1 闭环(api_key 仅 ResolvedProvider 内存,不进 config/NodeOutput/schema required) ⑥schema required 改 [provider_id] ⑦老明文兼容 warn 过渡期 ⑧主代亲跑 cargo df-nodes 79 passed(9 新增 resolve_provider 测试)+ workspace EXIT 0。
-
主代独立核查: ✅ 全过(df-storage secret.rs 无 build_provider 函数定义,grep 5 处全注释 / src-tauri 转发壳 pub use+build_provider_for 全限定路径 / keyring workspace feature 三平台 / resolve_provider 三路径 L74/L89/L123 / schema required L316+L569 / api_key 仅 ResolvedProvider L45/238/464 调 build_provider 不进 NodeOutput / cargo test df-nodes 79 passed 亲跑 / workspace check EXIT 0)。
-
审查 agent 待复审重点: ①secret 下沉后 src-tauri 13 调用点经转发壳路径完整性(lib.rs/commands.rs/agentic.rs + build_provider_for 6 处) ②keyring Linux CI(linux-native-async-persistent 后端依赖 zbus/secret-service) ③resolve_provider 老明文兼容路径后续移除时机 ④FR-S1 端到端验证(实测 tauri dev:testing 模板 ai_self_review 经 resolve_provider 拿 provider→LLM→写 output_json) ⑤⑥端到端联调实测。
CR-260616-35 batch59 CR-34-1 守卫 + CR-30 流式重试对齐决策a1 + UX-2025-04 断线保文 — ✅ 已审(PASS)
- 范围: 3 agent 合并批(workflow wdlt0xfl6,主代独立核查全过)。①CR-34-1 useAiPanel.ts:102 守卫加有效性检查
|| !list.some(p=>p.id===activeProvider)(deleted provider 残留回落 DB is_default) ②CR-30-1/CR-30-2 + UX-2025-04 合并:stream_recv.rsStreamResultenum(Complete/Partial/InitFailed 三分支) +classify_status_or_class(4xx Fatal/5xx+429+timeout Retryable 镜像 retry::is_status_retryable) + agentic.rs 重试对齐决策 a1(Init 失败重试复用 retry::backoff_delay 1s→2s→4s±20%jitter + 30s 总预算 retry_deadline + Fatal 分类 4xx 立即放弃; MidStream Partial 不重试保文→入库 + AiCompleted(incomplete=true) + 系统提示「响应因网络中断不完整」) + 删 3 处误导注释(agentic.rs:12/commands.rs/state.rs) + retry.rspub mod+backoff_delay/is_status_retryable改pub暴露供 src-tauri 复用 + useAiEvents.ts:177 补case 'AiStreamRetry'(消除死事件穿透) + AiCompleted case 处理 incomplete + types.ts:204/mod.rsincomplete?:boolean字段(所有 emit 点补 None 向后兼容) + i18n aiStreamRetry/responseIncomplete 双语。 - 维度: 流式核心中风险(重试循环正确性/保文语义/跨 crate pub 暴露影响面) + 前端事件消费完整性 + 决策 a1 对齐度(Init 可重试 ≤max_retries+jitter+30s+Fatal, MidStream 不重试) + B-260615-05 保留(空文本无 finished 仍 InitFailed 交重试)。
- commit: 待提交(累积 ~30 文件攒批)。
- 复审结论(2026-06-16·CR-30 agent 亲跑 cargo + 主线程 CR-34-1 预核验): ✅ PASS — 🔴0 🟡0 ⚪0
- 验证:
cargo check --workspaceEXIT 0(5 warning 全 pre-existing dead_code)/cargo test -p df-ai retry6 passed(backoff_is_monotonic_within_jitter/status_classification/fatal_aborts_immediately/first_attempt_ok/retry_once_then_ok/all_retryable_exhausts)/cargo test stream_recv20 passed(classify_429_retryable/classify_4xx_fatal 等)。 - CR-30-1(7 项全 PASS):agentic.rs:320 真用
retry::backoff_delay(retry_attempt+1)(非自写,旧1<<retry_attempt仅留注释)/ backoff 含 ±20% jitter(retry.rs:74-85 SystemTime nanos)/ 30s 总预算(agentic.rs:242 retry_deadline + :290 budget_exhausted + :321 min 截断)/ 4xx Fatal 立即放弃(agentic.rs:293!retryablereturn + stream_recv.rs:411-434 classify_status_or_class 镜像 is_status_retryable)/ 三处注释修正(commands.rs:1114 / state.rs:184 / agentic.rs:38)/ DRY 无重写(retry_with_backoff 仍 pub(crate) 流式不走)/ AttemptOutcome+is_reqwest_error_retryable 暴露合理。 - CR-30-2(7 项全 PASS):useAiEvents.ts:177-186
case 'AiStreamRetry'完整(取最后 isError 消息 + i18n t 更新「重试 n/m」,break 非穿透)/ 看门狗 reset 正确(AiStreamRetry 不在 NO_RESET_WATCHDOG Set)/ MidStream 决策 agentic.rs:274-285 Partial→incomplete=true + break 不重试,:286-342 InitFailed retryable 分支重试 / currentText 清空(useAiEvents.ts:316 AiError casestate.currentText=''再 AiStreamRetry 更新气泡,无累积重复)/ AiCompleted incomplete 系统提示(后端 agentic.rs:384 push session.messages 落库 + 前端 useAiEvents.ts:278-285 气泡)/ types.ts:215-217 variant + :202-204 incomplete?:boolean / i18n aiStreamRetry+responseIncomplete 双语齐。 - 回归(4 项无回归):permit RAII 释放(agentic.rs:405-406 显式 drop + loop 作用域 Drop)/ guard.reset 耗尽复位(8 exit 点全覆盖 :137/:300/:314/:352/:393/:457/:480/:543)/ request 每轮重建(:251-266 messages 从 session lock 重构)/ 无新死代码(AttemptOutcome/is_reqwest_error_retryable/is_status_retryable/backoff_delay/AiStreamRetry/classify_status_or_class 全有消费)。
- CR-34-1(主线程预核验 PASS):useAiPanel.ts:102 守卫
|| !list.some(p=>p.id===activeProvider)残留无效回落 DB is_default,cycleProvider 不受影响(findIndex 不再 -1 跳过 ps[0])。 - UX-2025-04 断线保文:核心 ✅(MidStream Partial 保文入库 + incomplete 系统提示);「继续生成」按钮未做(可复用 UX-2025-03 重试能力按需补,非阻断)。
- 待修项回流 todo: 无(CR-34-1/30-1/30-2 三条确认修复,todo 勾完成)。
- 验证:
- 主代独立核查: ✅ 全过(useAiPanel:102 守卫 / stream_recv:97 enum 三分支 + 注释 / agentic:242 retry_deadline + :274 Partial 不重试 + :286 InitFailed 重试 + :320 backoff_delay 复用 + :360 if incomplete 保文入库 + :400 emit incomplete:Some(true) / useAiEvents:177 AiStreamRetry case / types:204 incomplete / retry.rs:62+74 pub + agentic:14
use df_ai::retry跨 crate 通)。 - 审查 agent 待复审重点: ①stream_recv StreamResult 三分支边界(Partial vs InitFailed 空文本路径,B-260615-05 保留点) ②agentic 重试循环 permit RAII 释放/边界(max_retries=0/guard.reset) ③跨 crate retry pub 暴露是否过度(retry_with_backoff 仍 pub(crate)) ④AiCompleted incomplete 前端消费完整性(系统提示气泡镜像后端) ⑤UX-2025-04「继续生成」按钮未做(保文核心 ✅,按钮可复用 UX-2025-03 重试能力或按需补)。
CR-260616-45 波1 F-01阶段3厂商模型列表拉取 + UX-01错误气泡Fatal区分 + UX-03工具卡按轮次收起 — ✅ 已审(PASS)
- 复审结论(2026-06-17·CR-45 agent 亲跑 cargo+test+vue-tsc+独立 grep/read 核验): ✅ PASS — 🔴0 🟡0 ⚪2
- 验证:
cargo check --workspaceEXIT 0(5 warning 全 pre-existing dead_code)/cargo test -p df-ai model_fetch24 passed 全过/npx vue-tsc --noEmitEXIT 0。 - ①build_models_url 7 分支完备 PASS:model_fetch.rs:165-178 尾 trim/|v1|v4/|models/chat/completions/api/根 base 7 分支全覆盖;断言成立(无 /v1/v1/models 重复,trim 后单次 suffix 判断);测试 build_url_v1_suffix_appends_models:303
assert!(!...contains("/v1/v1"))编译期证。 - ②噪音过滤 13 类 + embedding 保留 PASS:is_non_chat_model:196-219 dall-e/midjourney/sd/imagen/tts/whisper/audio/speech/voice/realtime/transcribe/moderation/-search 13 类;embedding_kept_per_design:410-414 单测证保留;注释 :194 明确按 §5.4 设计。
- ③反序列化多 alias 兜底 PASS:ModelsList:248-253 serde(default) + data alias="models";ModelEntry:256-259 id alias="name";into_ids:263-269 filter_map 丢弃 None;parse_openai_format/parse_anthropic_format/parse_ollama_format/parse_missing_data_field_yields_empty/parse_entry_missing_id_skipped 5 单测全覆盖。
- ④协议分派 PASS:fetch_openai_compat:66-89 Bearer 鉴权 + /v1/models;fetch_anthropic_compat:92-119 x-api-key+anthropic-version 双 header + /v1/models;fetch_model_names:40-46 其他报 UnsupportedProvider;三路径完备。
- ⑤错误映射友好提示 PASS:map_network_error:132-138 timeout→超时提示/其他→网络错误;map_status_error:141-151 401/403→鉴权失败/404→端点不存在/5xx→厂商异常;全含 provider_type+url 上下文。
- ⑥fetch_and_probe probe 兜底 PASS:fetch_and_probe:52-59 探测为纯 CPU map collect,单模型探测失败不中断整体(probe 本身有兜底)。
- ⑦UX-01 canRetry Fatal 分类 PASS:AiChat.vue:1994-1999 auth/provider_config=Fatal 隐藏重试;notRetryable 提示 L413;v-if L415 守卫;边界说明正确(404/参数错仍可重试,待后端 retryable 字段精确化)。
- ⑧UX-03 agentRound watch 触发时机 + CSS 不动决策 PASS:watch agentRound:1738-1744 cur<=prev 早退,collapseAllToolLists(buildActiveToolIds(...));ToolCardList.vue:353-357 flex gap 原理说明明确,本轮不重构 JS 钩子决策合理。
- ⑨i18n 转义 PASS:ai.ts/aiChat.ts zh-CN/en 8 key 全纯文本,无 @/|/裸 {} 特殊字符;notRetryable/queueEdit/queueSave/queueCancel/queueSendNow 双语对称。
- ⚪ CR-45-1(low,
-search子串匹配可能误伤):is_non_chat_model:218id.contains("-search")可能误伤含-search后缀的合法 chat 模型名(如厂商未来发布gpt-next-search-assistant模型实际支持 chat);当前单测覆盖text-davinci-search-001旧 GPT 搜索变种合法剔除,但contains子串匹配设计粒度粗。已知影响面小(主流厂商暂无含-search的 chat 模型),非阻断;后续可按需收紧为正则r#"-(search|search-[a-z]+)$"#或加入白名单例外。 - ⚪ CR-45-2(low,fetch_and_probe 无分页):fetch_anthropic_compat:92-119 调
build_models_url通/v1/models,Anthropic 响应含has_more字段但当前 serde 反序列化 ModelsList:248 未取 has_more(serde 仅取 id);超大列表>500 可能截断。当前阶段3 聚焦拉取主流模型,截断影响可控(头部 500 模型足够覆盖);非阻断,留阶段5 IPC 实测后按需补分页。 - 待修项回流 todo: 无(2 low 非阻断:子串匹配可收紧/分页可后续补)。
- 验证:
CR-260616-46 波2 F-01阶段4路由器 + UX-05/06/07队列三件套 — ✅ 已审(PASS)
- 复审结论(2026-06-17·审查 agent 亲跑 cargo+vue-tsc,87s): ✅ PASS — 🔴0 🟡0 ⚪1
- 验证:
cargo check --workspaceEXIT 0(5 warning 全 pre-existing dead_code)/cargo test -p df-ai router::15 passed(13 原+2 新)/npx vue-tsc --noEmitEXIT 0。 - ①select 7 步过滤链 PASS:router.rs:57-66 严格对齐设计 §6.1 L470-478(enabled→模态全子集 L60→needs_tool_use 含 ToolUse L61→intelligence>=min L62→max_cost<= L63→context_window>= L64→max_by_key L65)。单测 full_dimensions_match_picks_best:L310-360 5 候选参差验证全链。
- ②Reverse(cost_tier) 取反正确性 PASS:router.rs:11
use std::cmp::Reverse+L65max_by_key(|m| (m.weight, Reverse(m.cost_tier))).CostTier Ord Free<Low<Medium<High,Reverse 让小值变"大"选便宜(设计原文 -(as i32) 等价但更 idiomatic)。单测 same_weight_picks_cheaper_cost_tier:L288-307 验证 Free 胜 High。 - ③select_model_id helper PASS:router.rs:79-81 纯函数
select_model_id(req,pool)->Option<String>调 select 映射 model_id。单测 select_model_id_*:L117-128 池空/命中全过。 - ④关联函数 vs &self 方法偏离 PASS:router.rs:9 注释注明「对齐任务规格,非设计文档的 &self 方法」。关联函数
ModelRouter::select(&req,&pool)(L57)适配阶段5 调用点ModelRouter::select(&req,&pool),零行为差异(无状态单元结构,无需 self)。 - ⑤pub use re-export PASS:router.rs:16
pub use df_ai_core::model::{Capability,CostTier,IntelligenceTier,Modality,ModelConfig}。阶段5 调用点经df_ai::router::{...}直接 import 维度枚举构造 TaskRequirements,零跨 crate 路径冗长。 - ⑥UX-05 决策 a 落地 PASS:useAiSend.ts:397-404 stopChat 删
state.queue=[]决策 a(逐条续发)。后端 agentic.rs:209 注释「保证前端收事件时后端已可接下一条,队列续发不被拒绝」+drainQueue(useAiSend.ts:228-232)由 AiCompleted 触发链式续发。 - ⑦UX-06 editQueued PASS:useAiSend.ts:370-375 边界(index 越界/空白 no-op)+trim+直接赋值 text。AiChat.vue:1567 editingQueueIdx/1569 editingQueueText ref+L519-531 inline input 三路保存一致(回车/blur/@mousedown.prevent 防 blur)。
- ⑧UX-07 sendQueuedNow 顺序 PASS:useAiSend.ts:387-392 splice 先→stopChat→sendMessage 顺序关键(splice 前置避重复发,stopChat UX-05 后已不清队列故 splice 必须前置)。复用 UX-05 stop 链路(agentic.rs:209 AiCompleted 就绪核验)。
- ⑨i18n 双语 PASS:queueEdit/Save/Cancel/SendNow 4 key 纯文本无特殊字符,zh-CN/en aiChat.ts:L68-71 对称。
- ⚪ low 清单:①router.rs:77 select_model_id 注释「池空/无匹配→None→兜底 default_model」。阶段5 调用点 unwrap_or(default_model) 确认行为不变,当前测试见 CR-48 调用点接入验证。
- 待修项回流 todo: 无(9 重点全 PASS,1 low 注释清晰非阻断)。
- 验证:
CR-260616-47 波3 F-15上下文基础(is_active白名单+push token) + F-01模型拉取IPC+Settings UI — ✅ 已审(PASS)
- 复审结论(2026-06-17·审查 agent 亲跑 cargo+test+vue-tsc,62s): ✅ PASS — 🔴0 🟡0 ⚪2
- 验证:
cargo check --workspaceEXIT 0(5 warning 全 pre-existing dead_code)/cargo test -p df-ai-core is_active1 passed /cargo test -p df-ai context::14 passed(零回归)/npx vue-tsc --noEmitEXIT 0。 - ①is_active 白名单零行为变化 PASS:provider.rs:85-86
matches!(self.status.as_deref(), None | Some("active"))与旧!matches!(self.status.as_deref(), Some("truncated"))等价(当前仅 None/active/truncated 三态)。核验 truncated 时序:context.rs:484-496 truncate_after_user_message 在 push 之后标记(:496t.message.status = Some("truncated")),push 时刻 is_active 恒 true,history_tokens 累加不变,零行为变化证成。 - ②push token 修正 PASS:context.rs:179-185
if message.is_active() { self.history_tokens += tokens }。!active 消息仍 push 到 self.messages 全量保留(:186self.messages.push(...)),仅不计 token。修复 restore_from_messages 后 token 虚高致 build_for_request 误判超预算。 - ③restore_from_messages 路径 PASS:context.rs:420-425 遍历调 push,token 同步仅 active。单测 push_token_only_active:860-910 验证直接 push+restore 双路径,active/archived/compressed 三条 restore 后 token 只含 active 一条(:902-910 断言通过)。
- ④ai_fetch_models FR-S1 闭环 PASS:commands.rs:872
df_storage::secret::resolve_provider_secret(&provider)内存解析,不进日志/返回值。ModelConfig(df-ai-core/model.rs:87-125)无 api_key 字段,:902 返回值Vec<ModelConfig>天然不含密钥。 - ⑤normalize_provider_type 归一 PASS:commands.rs:841-846 anthropic→anthropic_compat 归一(:873 调用)。对齐 model_fetch.rs:40-42 fetch_and_probe 协议分派,双命名收敛。
- ⑥fetch_and_probe + update_full 写回 PASS:commands.rs:875
fetch_and_probe调用 + :883-887update_full写回 model_configs + updated_at。 - ⑦ai_probe_model 纯 CPU probe PASS:commands.rs:897-902 调
df_ai::model_probe::probe(&model_id),无网络。 - ⑧types.ts 4 维度枚举 snake_case 对齐 PASS:types.ts:200-206 Modality/Capability/CostTier/IntelligenceTier 字面量(df-ai-core/model.rs:20-72 serde
rename_all = "snake_case"严格对齐)。 - ⑨Settings 拉取 UI PASS:Settings.vue:95 拉取按钮(:96 disabled 非新建态提示先保存)+:102-103 模型列表展示(4 维度标签+enabled toggle+weight input)+:519-536 fetchModels 函数错误分支 toast(超时/鉴权/404/厂商异常)。
- ⑩i18n 双语 PASS:zh-CN/en settings.ts:33-37 testConnection/fetchingModels/fetchHintSaveFirst/labelWeight/modelListTitle 对称,无特殊字符。
- ⚪ CR-47-1(low,Settings enabled/weight 本地态非持久化):Settings.vue:525
providerForm.models = models.map(...)仅本地会话态,未存回 model_configs。文案 "下次「测试连接」会以最新探测结果覆盖"(:516-518 注释)明示,阶段6 后续持久化另见 issues。非阻断(本批目标拉取展示 UI,本地编辑态可接受)。 - ⚪ CR-47-2(low,ai_probe_model UI 未接):无单模型探测按钮,留阶段6 AiChat 模型下拉。非阻断(probe 纯 CPU,按需补 UI)。
- 待修项回流 todo: 无(10 重点全 PASS,2 low UI 增强/持久化非阻断)。
- 验证:
CR-260616-48 波4 F-01阶段5调用点接入(select_model_id helper+6点) + F-260617-01 patch_file三模式编辑 — ✅ 已审(PASS)
- 复审结论(2026-06-17·CR-48 agent 亲跑 cargo+test+vue-tsc,342s): ✅ PASS — 🔴0 🟡0 ⚪2
- 验证:
cargo check --workspaceEXIT 0(5 warning 全 pre-existing dead_code)/cargo test -p df-ai router15 passed(2 新增:select_model_id_empty_pool_returns_none/select_model_id_hit_returns_model_id)/cargo test -p df-nodes --lib79 passed; 1 ignored/cargo test -p df-ideas adversarial12 passed(含 a12 新增 llm_mock_fenced_not_at_start)/cargo test -p devflow tool_registry24 passed(16 三模式单测)/npx vue-tsc --noEmitEXIT 0。 - ①select_model_id helper 纯函数正确性 PASS:router.rs:79-81 纯函数调用 ModelRouter::select + map model_id,单测:117-127 覆盖池空 None/命中 model_id 返回字符串,编译期证类型匹配。
- ②pub use re-export 可见性 PASS:router.rs:16
pub use df_ai_core::model::{Capability,CostTier,IntelligenceTier,Modality,ModelConfig};6 调用点全经df_ai::router::{...}全限定导入(agentic.rs:19/project.rs:11/title.rs:11/knowledge_inject.rs:12/ai_node.rs:17/adversarial.rs:165),编译期证零断裂。 - ③6 调用点兜底行为不变 PASS:agentic.rs:175-176
select_model_id(...).unwrap_or_else(|| provider_config.default_model.clone())池空/无匹配→None→兜底 default_model;title.rs:86-87/project.rs:539-540/knowledge_inject.rs:350-351/ai_node.rs:214/adversarial.rs:165 同模式;池空时 resolved_model==default_model 零差异。 - ④agentic resolved_model 审计字段统一 PASS:agentic.rs:288/410/414/419/465/469 原用 default_model 处统一改 resolved_model,语义更正确(记实际服务模型);池空时 resolved_model==default_model 无行为变更。
- ⑤knowledge_inject 嵌入降级 needs_tool_use=false PASS:knowledge_inject.rs:58-65 TaskRequirements(needs_tool_use=false,min_intelligence=Lite),宽松过滤允许嵌入模型入选;TaskRequirements 无 capabilities 字段设计偏离,embedding provider 池通常即嵌入模型实践中命中,可接受。
- ⑥df-ideas 新增 df-ai 重依赖 PASS:df-ideas/Cargo.toml:12 df-ai 依赖引入;注释:9-11 说明 router 模块仅依赖 df-ai-core::model,编译期 tree-shaking 零 HTTP 代码链入(仅引用符号),实测 cargo check EXIT 0 证无链接错误;后续若严格轻量可将 router 下沉 df-ai-core 非阻断。
- ⑦df-nodes ResolvedProvider.model_pool 字段 PASS:ai_node.rs:54 model_pool 字段+ :205 record 路径填 record.model_configs.clone + :143 老明文路径填空池;config.model 显式优先(:197-203 有则用 model_id 否则路由),单测覆盖。
- ⑧patch_file 三模式互斥运行时校验 PASS:tool_registry.rs:932-940 has_old_text/has_replace_lines/has_anchor 计数,0=缺定位 bail/:937 >1=冲突 bail/:939,两边界完备;schema 层 object_schema 无法静态互斥,运行时兜底合理。
- ⑨patch_file expected_hash mtime+size 非 sha256 PASS:tool_registry.rs:971-981
format!("{}_{}", modified.unwrap_or(0), file_meta.len())指纹(mtime+size),三模式通用,语义与 old_text 模式一致(read_file 返回 size 非 hash,注释:141 与 read_file 行计数一致),设计正确。 - ⑩apply_line_range trailing newline 语义 PASS:tool_registry.rs:133-152 单测:1722-1729 尾换行保留(
\n结尾文件替换后仍以\n结尾),splice 循环 lines() 后单独处理 trailing_newline,边界正确。 - ⑪resolve_anchor 子串行内命中 PASS:tool_registry.rs:182-206 子串大小写敏感匹配+start 后第一个含 end 行;单测:1783-1789 行内命中 / 1794-1798 start==end 同行 / 1803-1808 first-end-after-start,覆盖完备。
- ⑫16 单测覆盖 PASS:tool_registry.rs:1701-1853 单测 apply_line_range 8 + resolve_anchor_to_lines 8 + e2e 1,亲跑 cargo test 24 passed 全过。
- ⚪ CR-48-1(low,agentic 主对话含图 Vision 检测未实现):agentic.rs:169 modalities 仅
[Modality::Text],ChatMessage 纯文本无图像 Part,多模态类型接入后需检测 Part/Image 追加 Vision,当前非阻断(设计文档已标注:166-167)。 - ⚪ CR-48-2(low,build_provider/诊断日志仍用 default_model):provider trait 不暴露改 default_model,CompletionRequest.model 走路由 provider impl 适配(OpenAICompatProvider::convert_request:295-296 req.model.is_empty() 回退 default_model),诊断日志仍用 default_model 字段名,非阻断。
- 待修项回流 todo: 无(2 low 非阻断:Vision 检测待多模态接入/诊断日志命名,路由核心功能全 PASS)。
- 验证:
CR-260616-49 波5 F-01阶段6 AiChat模型下拉+override穿透(4c84fbc) / F-15阶段1剩余compress基础(63bff8b) — ✅ 已审(PASS)
- 复审结论(2026-06-17·CR-49 agent 亲跑 cargo+test+vue-tsc+独立 grep/read 核验): ✅ PASS — 🔴0 🟡0 ⚪1
- 验证:
cargo check --workspaceEXIT 0(6 warning 全 pre-existing dead_code,PendingApproval/releases/node_executions)/npx vue-tsc --noEmitEXIT 0/cargo test -p df-ai107 passed(含 context 9 新方法 + 8 新单测)/cargo test -p devflow --lib compress3 passed。 - ①F-01 override 兜底零行为变化 PASS:agentic.rs:181-188
match model_override.as_deref()— Some(id) 且 !id.is_empty() 且provider_config.model_configs.iter().any(|m| m.model_id == id)→ 用 id;_ => resolved_model(落回路由)。override=None(自动模式默认)完全等价改动前。非空但不在池→落回路由兜底正确,防止 override 导致无模型。 - ②F-01 清零路径完整 PASS:create(commands.rs:1147
session.model_override = None)+ switch(commands.rs:1225)两入口清零,前端 watch activeConversationId 同步清(AiChat.vue:1071)。防跨对话污染,零残留。 - ③F-01 续跑透传 PASS:try_continue_agent_loop(agentic.rs:699-702)读 session.model_override.clone() 透传,审批续跑/达 max 续跑保持同一主对话的 override 一致,语义正确。
- ④F-01 空池降级 PASS:AiChat.vue:237
v-if="enabledModels.length"隐藏下拉+ :231 toggle :disabled + :1323 setSpecifyMode 早返。空 provider 不 crash。 - ⑤F-01 i18n 特殊字符 PASS:aiChat.ts 新 6 key(autoMode/specifyMode/autoModeHint/specifyModeHint/selectModel/noModels)纯文本,无 @/|/裸{} 特殊字符。
- ⑥F-15 compress 幂等 PASS:context.rs:629-640 compress_old_messages 只处理 is_active,saturating_sub 扣 token,已 compressed 跳过;返回 newly_compressed 仅本次新标记。DB 原始消息保留,is_active 白名单隔离。单测 push_token_only_active:954 覆盖直接 push+restore 双路径。
- ⑦F-15 compress_via_llm 兜底 PASS:compress.rs:66-67 select_model_id 池空/无匹配→unwrap_or_else 兜底 default_model;:89 map_err + :92 空摘要 Err。阶段2 IPC 接调用点后降级。
- ⑧F-15 纯函数优先 PASS:compress_prompt(prompt.rs:148 无 IO)/ build_eviction_units/has_compressible_messages/compress_old_messages 无 IO;compress_via_llm 封装 IO(双层 Semaphore 限流复用 title 模式)。单测 3 passed。
- ⑨F-15 零行为变化 PASS:push/restore_from_messages/build_for_request/sanitize_messages 未改,仅新增方法 + is_compressing 字段(new/clear 初值 false)。
- ⚪ CR-49-1(low,前端 ref 单例设计说明):modelOverride 模块级 ref(useAiSend.ts:53)非 per-conversation,靠 watch 清理对齐后端 switch 清 session.model_override。当前单实例 AiChat 可接受,多窗口架构未来若需独立模型选择需改 per-conversation ref。非阻断(设计文档已标注)。
- 待修项回流 todo: 无(9 重点全 PASS,1 low 设计说明非阻断)。
- 验证:
CR-260616-50 波6 F-15阶段2 手动上下文管理(2 IPC+3事件+前端按钮+status渲染)(4194842) — ✅ 已审(🔴 RED 已修 PASS)
-
复审结论(2026-06-17·波7 ww68uamzu agent CR-50): ⚠ ISSUES → 🔴 RED 已修(主代) — 后端 IPC/事件/保护区/失败路径全 PASS,前端事件分发 RED(审查抓到真 bug,主代即时修)
- 验证:
cargo check --workspaceEXIT 0(6 warning 全 pre-existing dead_code)/npx vue-tsc --noEmitEXIT 0(零输出)/cargo test -p devflow --lib79 passed(无回归)。 - ① clear_context 不删 DB PASS:commands.rs:430-433 经 messages_mut() 标 status="archived_segment"(is_active 自动 false,sanitize 隔离),落库走 save_conversation 全量 upsert(:439),非 clear_messages 删库。
- ② compress LLM 失败不阻塞 PASS:commands.rs:543-554 先取 active 克隆(读不改 status)→ compress_via_llm 失败则 set_compressing(false)+ emit AiError(message 不含 api_key)+ 返 Err;消息状态不变(未调 compress_old_messages)。成功路径才 compress_old_messages 标 compressed + insert_at(0, summary system)。
- ③ 保护区最近 N 条 active 不动 PASS:PROTECT_COUNT=6(commands.rs:412/465 两处一致),protect_start = len.saturating_sub(6),clear/compress 只对 [0, protect_start) 操作。
- ④ 空会话/无 active 降级 PASS:protect_start==0 → emit 对应事件 + Ok(不报错,commands.rs:416-420/485-492);active_msgs 空 → AiCompressed(summary 空,commands.rs:511-515) + Ok。
- ⑤ 3 事件变体 serde — 🔴 RED(审查抓到真 bug):mod.rs AiChatEvent 仅
#[serde(tag="type")]无rename_all="snake_case",serde 默认序列化 PascalCase(useAiEvents.ts 10 case 铁证)。useAiContext.ts:77/80/84/87 switch case 用 snake_case('ai_compressing'等)→ 永不命中 → isCompressing 永不复位 → 压缩按钮用过一次永久禁用 + 清空后视图不刷新。主代修复(2026-06-17):case 4 处改 PascalCase(AiCompressing/AiCompressed/AiContextCleared/AiError)+ compressContext 成功兜底复位(后端同步 resolve 非异步)+ doc 注释一致性。vue-tsc EXIT 0 + grep 无残留 snake_case + useAiEvents 对照铁证。未改后端(rename_all 会破坏 useAiEvents 既有 10 case)。 - ⑥ useAiContext composable 闭环 PASS(RED 修后):isCompressing ref 防重入 + 第二 listener 消费 4 事件 PascalCase 分发 + compressContext 成功兜底复位(后端 IPC 同步等 LLM 完成才 resolve 非异步 spawn,成功即复位防事件丢失)。
- ⑦ status 分组渲染 PASS:AiChat.vue:2240 messageSegments computed 按 status 分组(archived_segment/compressed 连续合并),:2290 renderItems 扁平化供 v-for;折叠段点击 toggleSegment 展开,正常消息零改动。
- ⑧ i18n 13 key ×2 无特殊字符 PASS:aiChat.ts 新 key(clearContext/compressContext/clearContextConfirm/contextArchived/compressed/compressing/clearSuccess/compressSuccess/compressError/clearError/expand/collapse/compressedSummaryLabel)仅合法 {n}/{msg} 插值,无 @/|/裸 {}。
- ⑨ useAiConversations status 透传 PASS:useAiConversations.ts:86-90 switchConversation mapper 透传 status 字段(archived_segment/compressed 落 AiMessage 供渲染),types.ts 未含此字段经 as any 读写闭环。
- ⑩ 已知取舍(history_tokens 口径) PASS:commands.rs:423-429 注释明确标注口径——clear_context 改 status 后 history_tokens 不立即扣减(私有字段无 setter),后果短暂偏高触发更早裁剪,属保守方向不劣化安全性。
- 待修项回流 todo: 无(🔴 RED 已即时修,主代核验 case PascalCase + vue-tsc EXIT 0 + useAiEvents 10 case 铁证对照)。
- 验证:
-
范围: 2 agent 并行 workflow w6o61cbqn,文件域隔离(后端独占 commands.rs/mod.rs/lib.rs;前端独占 api/ai.ts/useAiContext.ts(新)/useAiConversations.ts/stores/ai.ts/AiChat.vue/i18n×2)。零重叠。
-
commit: 4194842(单 commit,功能完整:后端 IPC 无前端无意义,配套提交)。
-
主代核查(2026-06-17): cargo check --workspace EXIT 0(6 warning 全 pre-existing dead_code,PendingApproval/releases/node_executions)/ npx vue-tsc --noEmit EXIT 0(零输出)/ cargo test -p devflow --lib 79 passed(无回归)/ grep 核验形态(lib.rs:122-123 注册 2 IPC + mod.rs:145/147/149 三事件变体 + commands.rs:397/457 两 IPC fn + 6 处 emit + 前端 useAiContext composable + AiChat.vue messageSegments 分组渲染)。
-
审查 agent 待复审重点:
- ①clear_context 不删 DB:commands.rs:397 经 messages_mut() 标 status="archived_segment"(is_active 自动 false,sanitize step0 隔离),落库走 save_conversation(全量 upsert 非 clear_messages 删库)。
- ②compress LLM 失败不阻塞不 panic:commands.rs:457 先取 active 克隆(读不改 status)→ compress_via_llm;LLM 失败 set_compressing(false)+ emit AiError(message 不含 api_key)+ 返 Err;消息状态不变(未调 compress_old_messages)。成功路径才 compress_old_messages 标 compressed + insert_at(0, summary system)。
- ③保护区最近 N 条 active 不动:PROTECT_COUNT=6(commands.rs:413/465),protect_start = len.saturating_sub(6),clear/compress 只对 [0, protect_start) 操作。
- ④空会话/无 active 降级:protect_start==0 → emit 对应事件 + Ok(())不报错;active_msgs 空 → AiCompressed(summary 空) + Ok。
- ⑤3 事件变体 serde:mod.rs:145/147/149 AiContextCleared/AiCompressing/AiCompressed{summary},继承枚举级 #[serde(tag="type")] rename_all snake_case → 前端 type:"ai_context_cleared"/"ai_compressing"/"ai_compressed"。
- ⑥前端 useAiContext composable 闭环:isCompressing ref 防重入 + 第二 listener 消费 ai_compressing/compressed/context_cleared/error 生命周期;compressContext IPC 受理仅表请求受理,真正完成由 ai_compressed 事件复位 isCompressing(异步闭环,非 IPC 同步等)。
- ⑦status 分组渲染:AiChat.vue messageSegments computed 按 status 分组(archived_segment/compressed 连续合并折叠段),renderItems 扁平化供 v-for;折叠段点击 toggleSegment 展开,正常消息走 v-else 原渲染零改动。
- ⑧i18n 13 key ×2 禁字符:aiChat.ts 新 key(clearContext/compressContext/clearContextConfirm/contextArchived/compressed/compressing/clearSuccess/compressSuccess/compressError/clearError/expand/collapse/compressedSummaryLabel)message 值无字面
@///裸{}(仅合法{n}/{msg}插值)。注意 archived 与侧栏冲突 → 重命名 contextArchived(vue-tsc TS1117 已修)。 - ⑨useAiConversations status 透传:switchConversation mapper 透传 status 字段(archived_segment/compressed 落 AiMessage 供渲染折叠),types.ts 未含此字段经 cast 读写闭环。
- ⚠️ 已知设计取舍(backend 自报,记审查项):clear_context 经 messages_mut() 改 status 后,ContextManager.history_tokens(私有字段无 setter)不立即扣减 archived 段 token(与 compress_old_messages 路径不同,compress 内部扣 token)。后果:history_tokens 短暂偏高,build_for_request 超预算裁剪可能比实际更早触发,属保守方向(不超发)不劣化安全性。代码注释已标注口径。
CR-260616-51 波8 F-15阶段3 agentic loop自动压缩(0202b51) + AE-2025-04会话级授权(6ec191e) — ✅ 已审(PASS-WATCH·🟡1回波11修)
-
复审结论(2026-06-17·波10 wsm0tk95c agent CR-51): ⚠ PASS-WATCH — 🔴0 🟡1 ⚪2。阶段3(loop语义/触发/防重入成对/延迟mutate/降级/emit路由)+ AE-04(TrustKey/session_trust/audit查trust/ai_approve写trust/toast桥接/FR-S1)逻辑全 PASS,devflow 79 + df-ai 107 passed,vue-tsc EXIT 0。🟡 yellow 回流波11修:audit.rs:593-596 trust-hit 放行分支
.await tools_arc.execute持&mut session(注释 L593 声称锁外但代码持锁,与 Low risk spawn 模式不一致),run_command 慢命令阻塞该会话所有触 state.ai_session 的 IPC(30+)。修法:对齐 Low risk spawn+clone(trust_hits Vec 收集→循环外 join_all spawn execute 锁外→串行回填持锁)+ 修正注释。⚪2 非阻断(摘要 insert_at 二次压缩边缘/canonicalize 失败回退边缘)。 -
范围: 2 agent 并行 workflow wigdmq5s4,文件域零重叠(阶段3 独占 agentic.rs;AE-04 独占 mod.rs/audit.rs/commands.rs/tool_registry.rs/AiChat.vue/useAiEvents.ts/types.ts/i18n×2)。
-
commit: 0202b51(阶段3)+ 6ec191e(AE-04)。分 2 commit(语义清,功能不同)。
-
主代核查(2026-06-17): cargo check --workspace EXIT 0(6 warning 全 pre-existing dead_code)/ npx vue-tsc --noEmit EXIT 0(零输出)/ cargo test -p devflow --lib 79 passed(无回归)/ grep 核验形态(阶段3 loop 顶部压缩分支 + set_compressing 成对 + AE-04 TrustKey/session_trust/audit 查 trust/ai_approve 写 trust)。
-
审查 agent 待复审重点:
- ①阶段3 loop 语义不变:agentic.rs loop 顶部加压缩检测分支(L259-374),主 loop 迭代/stream/重试/tool_calls/converge 零改动。压缩是 build_for_request 前预处理。
- ②阶段3 触发条件:
budget*6/10 < history_tokens(0.6 阈值整数比避浮点)+ protect_start>0 + has_compressible_messages(protect_start)。 - ③阶段3 防重入 + 成对:prev_compressing 守卫(L280)+ set_compressing(true)(L303)↔ 三出口 false(L340 成功/L355 noop/L361 Err)。成对覆盖所有分支。
- ④阶段3 延迟 mutate(成功才改):先取 active 克隆 → LLM 出摘要 → 成功才 compress_old_messages(标 compressed+扣 token)+ insert_at(摘要)。失败零副作用(消息状态/token 完全不变)。理由:history_tokens 私有无 setter,失败回滚 token 不精确;延迟 mutate 语义最干净。
- ⑤阶段3 降级:LLM 失败/空摘要 → set_compressing(false)+ emit AiError(message 不含 api_key)+ 继续走原 build_for_request 裁剪(不阻塞 loop)。
- ⑥阶段3 emit conversation_id 路由:AiCompressing(L298)/AiCompressed(L348)/AiError(L367)三处均 Some(conv_id)。
- ⑦AE-04 TrustKey 设计:mod.rs:201 enum Write{dir}/Execute{dir}(目录级,derive Hash/Eq)+ trust_key_for(:215 write_file→Write 父目录/run_command→Execute working_dir)+ 首批仅 write_file/run_command 命中(其余 None 走原审批)。
- ⑧AE-04 session_trust 内存态:HashSet(mod.rs:346),new 初值空(:363),换会话(create/switch)clear(commands.rs:1161/1241)重审,不落库。
- ⑨AE-04 audit 查 trust:audit.rs:568 process_tool_calls 审批前(Med/High 进 pending 前)查 session_trust.contains 命中 → 直接执行(非复用缓存,与 F-05 去重不同)+ emit AiToolAutoApproved(:587)+ Completed + audit decided_by=auto_trust(:613)留痕 + continue 跳过 pending + 二次确认。
- ⑩AE-04 ai_approve 写 trust:commands.rs:294-295 用户批准时 trust_key_for 命中 → session_trust.insert(下次同类自动放行)。
- ⑪AE-04 前端 toast 桥接:AiToolAutoApproved 后端 emit → useAiEvents case(types.ts union)→ emit ai-tool-auto-approved-toast 事件总线 → AiChat.vue listen 弹 info toast(🔓 自动放行),非审批气泡避免干扰。
- ⑫AE-04 FR-S1:TrustKey 只含 tool+dir,不含 api_key/文件内容/命令串(mod.rs:189 注释)。
- 待修项回流 todo: 无(核心 grep+test+构建全 PASS;阶段3 延迟 mutate 口径 + AE-04 目录级粒度为设计取舍非 bug)。
CR-260616-52 波9 B-260617-01 run_workflow审批执行(方案a2后端分支)(e23b316) — ✅ 已审(PASS)
-
复审结论(2026-06-17·波10 wsm0tk95c agent CR-52): ✅ PASS — 🔴0 🟡0 ⚪3。a2 实施正确:run_workflow_inner 抽取语义零变化(命令层瘦转发)/ ai_approve 分支(commands.rs:317)仅隔离 run_workflow 其余不变 / execute_run_workflow_for_tool 回填 Ok json(execution_id)让 LLM 收成功 / AE-04 trust_key_for(:294)在分支(:317)前且 run_workflow 返 None 不写 trust / audit trust 查询 + create/switch clear 未动 / High 去重缓存断重试双层 / handler 防御文案正常不可达 / FR-S1 tool_result 仅 task_id+target_status+execution_id。devflow 79 passed。⚪3 非阻断:①workflow.rs:124 triggered_by 注释声称 ai 实际 manual(已知待办)②mod.rs:465 空 config 既有行为(对齐前端)③tool_registry.rs:547 _db 死代码冗余。
-
范围: 单 agent workflow wf8r5vlno,走查+实施。改 workflow.rs(run_workflow_inner 抽取)/ mod.rs(execute_run_workflow_for_tool)/ commands.rs(ai_approve run_workflow 分支)/ tool_registry.rs(handler 注释+防御文案)。
-
commit: e23b316。
-
主代核查(2026-06-17): cargo check --workspace EXIT 0(6 warning 全 pre-existing)/ cargo test -p devflow --lib 79 passed(无回归)/ grep 核验形态(workflow.rs:87 run_workflow_inner + mod.rs:446 execute_run_workflow_for_tool + commands.rs:317 ai_approve 分支 + trust_key_for:294 在分支前 AE-04 不破坏)。
-
方案选型:agent 选 a2(后端分支)而非 a1(前端拦截)。理由:ai_approve 已持 app+state,后端直接调 run_workflow_inner 最干净,无需新 IPC/前端分支/工作流知识泄漏。符合用户推荐 a 大方向(绕开 ai_tools.execute 必 Err)。
-
审查 agent 待复审重点:
- ①run_workflow_inner 抽取语义零变化:workflow.rs:87 抽取 run_workflow 命令体为共用核心,命令层瘦转发(:79)。核心逻辑(模板选 DAG/落记录/spawn 转发+执行+联动)全部移入,语义不变。
- ②execute_run_workflow_for_tool:mod.rs:446 从 args 取 task_id/target_status + 构造空 DagDef+config + name 拼"AI 推进任务" + 调 run_workflow_inner + 回填 {task_id,target_status,execution_id,status:"running"} 让 LLM 收成功。
- ③ai_approve run_workflow 分支:commands.rs:317
if approval.tool_name == "run_workflow"→ execute_run_workflow_for_tool,否则原 ai_tools.execute。后续 audit_finalize/emit_data_changed/replace_tool_result/emit/save/try_continue 共用零变化。 - ④AE-04 trust 不破坏:trust_key_for 写 trust(:294)在 run_workflow 分支(:317)前;audit trust_hit 查询(audit.rs:568)未动;create/switch clear(:1177/1257)未动。
- ⑤重试循环根治:a2 真实执行→成功 tool_result(非 Err)+ 既有 High 去重缓存(find_cached_high_risk_result audit.rs:406 失败 tool_result 复用)双层保障。不新增 no_retry 标志(无字段+无消费方+crate 改动大,去重缓存已等效)。
- ⑥handler 防御文案:tool_registry.rs:556 run_workflow handler Err 改防御兜底(正常流程不可达,a2 分支接管)+"勿盲目重试"明示(对齐 run_command 超时标注模式)。
- ⑦FR-S1:run_workflow 无 api_key,tool_result 仅 execution_id+task_id+target_status。
- 待修项回流 todo: 无(a2 实施正确,grep+test+构建全 PASS;no_retry 不增为设计取舍,去重缓存等效)。
CR-260616-53 波11 CR-51 yellow修(audit trust-hit持锁重构) + UX-01工具失败视觉 + UX-03自动收起增强(332a6bb+31ea151) — ✅ 已审(PASS·⚪2)
- 复审结论(2026-06-17·审查 agent 亲跑 cargo+vue-tsc+test+独立 grep/read 核验): ✅ PASS — 🔴0 🟡0 ⚪2
- 验证:
cargo check --workspaceEXIT 0(7 warning 全 pre-existing dead_code)/npx vue-tsc --noEmitEXIT 0/cargo test -p devflow --lib88 passed(无回归)。 - ①CR-51 yellow 并行化(非"移锁外") PASS:修复前后 session 锁均由调用方(agentic.rs:839
session_arc.lock().await)持有至 process_tool_calls 返回,锁持有时长不变。真正改进是并行化:修复前 trust-hit 分支内 inline.await execute串行执行每个工具(阻塞期间锁被持有,run_command 慢命令阻塞同会话所有触 state.ai_session 的 IPC);修复后收集 trust_hits Vec→join_all 并行执行(锁仍被持有,但多工具并行减少总阻塞时间)。audit.rs:664-713 实现(trust_hits 收集 L568+join_all 闭包 L671-701+串行回填 L704-712)正确,注释"锁外"应理解为"闭包内不访问 session"而非"锁已释放"。 - ②语义不变 9 条全 PASS:真执行(audit.rs:676 tools.execute)+emit toast(L599 AiToolAutoApproved)+emit completed(L680)+emit_data_changed(L687)+audit auto_trust(L711 decided_by=Some("auto_trust"))+Med-High 非 trust 路径零改动(L630-660 pending 分支)+Low risk 零改动(L718-764 独立并行分支)+pending_count 不变(L630 仅 trust-hit 跳过,pending 仍累加)+错误信息格式不变(L680/690 AiToolCallCompleted 结构一致)。
- ③UX-01 isToolFailure 判定可靠 PASS:run_command 路径(ToolCard.vue:213-217 parseResult 命中合法 JSON 看 exit_code≠0,可靠)+通用工具路径(ToolCard.vue:220-224 parseResult 返回 null 后正则
/执行失败|failed|error[:\s]/i启发式匹配,对齐后端 AR-6 err_msg 格式format!("工具 {} 执行失败: {}", draft.name, e)audit.rs:744)。正则可能误判含 "error"/"failed" 字样的成功结果(如 "no errors found"、"error handling passed"),但这是启发式检测已知取舍,非阻断(误报概率低,最坏多一次视觉提示)。 - ④UX-01 视觉完整 PASS:--failed 红框(ToolCard.vue:727-730 border-color+box-shadow)+banner(L119)+红底结果框(L745-751 background+border)+状态点(L20 failed 分类)+副标题(L17 failed 标签)。CSS 三类(.ai-tool-card--failed/.ai-tool-status-dot--failed/.ai-tool-result--failed)完整。
- ⑤UX-03 streaming watch 守卫 PASS:AiChat.vue:1807
generatingConvId===activeConversationId防后台生成误触+防恢复会话 onMounted 时 streaming 初值 true 的误触(仅当前视图正在生成时触发收起)。守卫正确。 - ⑥UX-03 wasNearBottom 边沿检测 PASS:AiChat.vue:1749 初值 true+1760
if (near && !wasNearBottom)转换瞬间触发一次+1763wasNearBottom = near更新。边沿检测防每 scroll tick 收起,正确。 - ⑦UX-03 userExpandedCards 记忆态 PASS:ToolCardList.vue:71 定义+197 展开时 add+193 收起时 delete+180 isCardExpanded 优先读+221 collapseInactive 不清除。记忆态防"刚在看被收起",正确。
- ⑧join_all 保序 PASS:trust_hits 收集按 tc_list 原始 index 顺序(audit.rs:533 sort_unstable_by_key+540 retain 后顺序不变)→join_all 保持输入顺序→串行回填按结果顺序。LLM 按 tool_call_id 匹配 tool_result,不依赖到达顺序,保序非必需但一致性更优。
- ⚪ CR-53-1(low,注释"锁外"有误导性):audit.rs:664 注释"execute + 即时 emit 在闭包内(不持 session 锁)"易误解为"锁已释放",实则是"闭包内不访问 session"。session 锁在整个 process_tool_calls 期间由调用方持有(agentic.rs:839
session_arc.lock().await至 L840process_tool_calls(...).await返回),修复前后锁持有时长不变。改进是并行化非"移锁外"。 - ⚪ CR-53-2(low,UX-01 通用工具文本匹配启发式误报风险):ToolCard.vue:223 正则
/执行失败|failed|error[:\s]/i可能误判含这些字样的成功结果(如 "error handling passed"、"no errors found")。但这是启发式检测已知取舍,后端 err_msg 格式固定(AR-6format!("工具 {} 执行失败: {}", draft.name, e)),误报概率低,最坏多一次视觉提示不阻断执行,非阻断性问题。 - 待修项回流 todo: 无(2 low 全非阻断:注释误导性可后续澄清/启发式误报风险可接受)。
- 验证:
CR-260616-54 波12 F-04多Provider负载均衡池(数据层+选择器+并发原语) + CR-52白项清理(79b6a43) — ✅ 已审(PASS)
- 复审结论(2026-06-17·CR-54 agent 亲跑 cargo+test+vue-tsc+独立 grep/read 核验): ✅ PASS — 🔴0 🟡0 ⚪0
- 验证:
cargo check --workspaceEXIT 0(7 warning 全 pre-existing dead_code)/cargo test -p devflow --lib88 passed(79 基线+9 provider_pool 新增全过)/cargo test -p df-storage33 passed/npx vue-tsc --noEmitEXIT 0。 - ①provider_pool select 3键排序完备 PASS:provider_pool.rs:57-91 过滤
enabled && weight>0(:64) + 稳定 3 键(:71-88 模型亲和 true 排前 / weight 降序 / is_default 兜底)。9 单测全覆盖(empty_pool_returns_singleton/single/disabled/zero_weight/model_affinity/model_id_none/weight_tie/is_default/full_scenario),亲跑全过。边界完备:model_id None 全亲和(:73-76 map_or true)/ 0 enabled 空 Vec 调用方兜底(:346-352)/ 1 enabled 单元素首位=唯一(:136-138)。 - ②单 provider 零变化 PASS:agentic.rs:342-345 ProviderPool::select 取首位,单 provider 池返回
[p1],首位p1等价原provider_config。老库迁移 V19 migrations.rs:343-356DEFAULT 1/DEFAULT 50,crud.rs:1072-1073 unwrap_or(1)/unwrap_or(50),老行启用后等价 F-01 前。空池兜底(:346-352)用provider_config.clone()作唯一候选,同启动行为。 - ③per_provider permit Option 安全 PASS:state.rs:112 HashMap 定义 + :158-172 acquire_for_provider 返
Option<OwnedSemaphorePermit>,map.get?+acquireOwned(:164-171)。未配 set_provider_caps 时 HashMap 空,返 None,drop(None) 无副作用(:671_provider_permit绑定作用域自动释放)。本轮 set_provider_caps 无消费方(留 F-04c 前端),单 provider 零变化达成。 - ④agentic 接入完整 PASS:agentic.rs:342
ProviderPool::select选主候选 + :346-352 split_first 取 primary + candidates + :663-710for candidate in &candidate_chain循环 + :667acquire_for_providerpermit + stream_one_provider(:669-670) 单 provider 流式 + :698 F-04b 日志"切换下一 provider"(基建就位,本轮候选链仅 primary 不触发切换)。单 provider 路径不破坏。 - ⑤triggered_by 透传链完整 PASS:workflow.rs:93
run_workflow_inner加triggered_by: &str参数 + :133 落triggered_by: Some(triggered_by.to_string())入库 + :80 命令层传 "manual"(前端人工) + mod.rs:475 ai 路径传 "ai"(execute_run_workflow_for_tool)。grep 核验仅 2 调用点,无遗漏。tool_registry.rs:547-560 删let _db = db.clone();死代码,handler 仅返 Err 引导走审批路径,正确。 - ⑥V19 迁移幂等 PASS:migrations.rs:342-360
PRAGMA column_exists探测 enabled/weight + :345DEFAULT 1/ :352DEFAULT 50+ V9 建表 SQL(:548-549 同步加列)。老库 ALTER 后取 DEFAULT,新库直接有列。crud.rs:1072-1073 ai_provider_from_rowunwrap_or(1)/unwrap_or(50)兼容缺失列(幂等测试场景)。insert/update SQL(:1159-1168/:1177-1178) 含 enabled/weight 列。 - ⑦fallback 拆波诚实 PASS:agentic.rs:330-334 注释明确"F-04b 切换下一 provider"(本 candidate 重试耗尽切下一),:663-710
for candidate in &candidate_chain循环已就位(:698 continue),仅候选链仅 primary(无 fallback 配置)不触发真实切换。基础设施可用,留 F-04b 独立批实际启用多候选切换。 - ⑧4 处构造点补字段 PASS:commands.rs:986-1001(ai_save_provider 新建默认 enabled=true/weight=50)/ crud.rs:1762-1777(单测)/ secret.rs:209-215(单测)/ ai_node.rs:714-729(单测),4 处全补
enabled: true, weight: 50。 - 待修项回流 todo: 无(7 维度全 PASS,零问题)。
- 验证:
CR-260616-55 波13 B-21工具卡重复治本(audit tc_list按id去重保证emit Started唯一)(247dc5d) — ✅ PASS
- 范围: workflow wice28c6y(前端守卫 agent + 后端走查 agent)。①前端治标守卫(HEAD 已有,非本轮新加):useAiEvents.ts:205 AiToolCallStarted push 前
if (!findToolCall(event.id))幂等守卫(对齐 startToolSlowTimer:58)。②后端走查结论(不改代码):候选A确认——LLM 流式输出两个 tool_use block 不同 content_block index 但相同 id → stream_recv.rs:225 按 index(u32 键非 id)分桶 → 两条同 id 不同 index draft 并存 → audit.rs:538-549 每 draft emit Started → 同 id 两次 → 前端 push 两卡 → Completed 按 id 只 update 首张 → 次张残留 running 0行。候选B排除(provider 每轮新 id)。③治本兜底(commit247dc5d):audit.rs:533 sort 后加seen_ids HashSet + tc_list.retain(draft.id)按 id 去重,同 id 保留最小 index 首个,emit Started 唯一。前端守卫双保险。详 docs/02-架构设计/B-260616-21排查方案。 - 维度: ①去重正确性(同 id 保留最小 index 首个,retain 后 drafts 唯一 id emit) ②不破坏正常多工具(不同 id 不受影响) ③HashSet import(audit.rs:3 加) ④与 F-05(High risk 去重缓存)无冲突(不同维度)。
- commit: 247dc5d。
- 主代核查: cargo check EXIT 0 + 88 passed + grep retain 形态。
- 审查结论: ✅ PASS。核验通过:
- ①去重正确性:audit.rs:533
tc_list.sort_unstable_by_key(|(i, _)| *i)先按 index 排序 → :540seen_ids.insert(draft.id.clone())retain 保留首个同 id(即最小 index) → :548 emit AiToolCallStarted 仅一次。逻辑闭环。 - ②不破坏多工具:retain 仅过滤同 id,不同 id 工具正常通过,stream_recv.rs:225 证实分桶键为 index 非 id。
- ③HashSet import:audit.rs:3
use std::collections::{HashMap, HashSet}正确。 - ④与 F-05 无冲突:F-05(audit.rs:612 find_cached_high_risk_result) 按 tool_name+args 去重(不同 id 同命令),本条按 id 去重(同 id),维度正交。
- ⑤前端守卫:useAiEvents.ts:205
if (!findToolCall(event.id))确认存在,双保险兜底。 - ⑥cargo check:EXIT 0,零编译错误。
- ①去重正确性:audit.rs:533
- 待修项回流 todo: 无(治标守卫 + 治本去重 双保险就位)。
CR-260616-56 波14 F-04c后端(provider池enabled/weight更新IPC+set_caps启动接入)(b3684f4) — ✅ PASS
- 范围: workflow wodppv1hd。①ai_save_provider(commands.rs:929-948)编辑路径保 enabled/weight(修原硬编码清回默认 bug)。②ai_update_provider_pool IPC(commands.rs:1022-1056)轻量专用更新 enabled/weight 经 update_full,weight clamp[0,100],落库后 reload_provider_caps 重建 caps。lib.rs:128 注册。③set_provider_caps 启动接入(state.rs:297 AppState::init 调 reload_provider_caps L305-323 从 DB enabled providers 建 per_provider HashMap,cap=global_cap=3)。单点真理 helper(state 不依赖 commands 避免环形)。
- 维度: ①ai_update_provider_pool 不触 ai_save_provider 密钥迁移分支(避免空 api_key 重发整表) ②reload_provider_caps 单点真理 ③cap=global_cap=3 单 provider 零变化 ④ai_save_provider 编辑保字段(新建默认 true/50)。
- commit: b3684f4。
- 主代核查: cargo check EXIT 0 + 88 passed。
- 复审结论: PASS。6 维度全核验通过:①ai_update_provider_pool 无 api_key 参数不触密钥迁移(commands.rs:1023-1056) ②reload_provider_caps 单点真理(state.rs:311-327) ③global_cap=3 与单 provider 等价(state.rs:321+324-326) ④编辑保留(commands.rs:945-947) ⑤provider_pool::select 过滤 enabled/weight(provider_pool.rs:64) ⑥weight clamp(commands.rs:1037)。无 high/med/low 发现。
- 待修项回流 todo: 无(后端就绪,前端 Settings UI 留波16)。
CR-260616-57 波15 F-04b多provider fallback切换(stream_one_provider+候选链+resolved_model重算)(80c0955) — ✅ 已审(PASS)
- 审查时间: 2026-06-17(独立审查 agent · 亲跑 cargo check EXIT 0 + grep/read 核验源码)
- 总体结论: ✅ PASS — 🔴0 🟡0 ⚪0
- 逐件核验(全 PASS):①stream_one_provider 抽取 ✅(agentic.rs:155-298 封装单 candidate,StreamOutcome 三态 Success/InitFailedExhausted/Fatal 驱动外层切换)/ ②resolved_model 重算 ✅(:188-199 select_model_id 在 candidate.model_configs 上重算+ model_override 穿透,防切换 provider 后模型池不同吃 400/404)/ ③候选链外层 ✅(:663 'candidate for 循环+ :656-658 Vec clone primary 入链避借用冲突)/ ④InitFailedExhausted 切换 ✅(:691-701 continue + :188-199 resolved_model 重算,stream_one_provider 内重建 build_provider)/ ⑤Fatal 立即放弃 ✅(:703-707 guard.reset+return,对齐 retry.rs Fatal 分类 + provider_pool 文档)/ ⑥per-provider permit 切换释放 ✅(:667 acquire_for_provider 循环内取,切换 Drop 自动释放旧槽)/ ⑦retry_deadline 共享预算 ✅(:642 单次 Instant::now+30s,各 candidate 共享不重置,防串行累加超预算)/ ⑧单 provider 零变化 ✅(:352 空池兜底 candidates=Vec::new,candidate_chain 仅 primary,循环跑一次耗尽 return)。
- 退避策略一致性: ✅ 复用 df_ai::retry::backoff_delay(agentic.rs:14 use df_ai::retry + :270 调用 retry::backoff_delay((retry_attempt+1) as u32)),retry.rs:74-85 实现 1s→2s→4s ±20% jitter,与 CR-30-1 对齐一致。
已审归档
CR-260616-66 波23 Settings拆4子组件(Provider/Connection/General/Knowledge·零行为变化)(9c60c8f) — ✅ 已审(PASS)
- 复审结论(2026-06-17·审查 agent 亲跑 vue-tsc + cargo check + 独立 grep/read 核验): ✅ PASS — 🔴0 🟡0 ⚪0
- 验证:
npx vue-tsc --noEmitEXIT 0 ✅ /cargo check --workspaceEXIT 0(6 pre-existing dead_code warnings 无关本 CR)。 - ①4 子组件拆分边界 PASS:ProviderPanel 收纳 AI 模型配置 + Provider 表单(providerForm 双向共享按数据耦合正确收敛:171-204 openProviderForm 编辑/新建完整分支) / ConnectionPanel 独占 localStorage CRUD / GeneralPanel 独占主题/语言/并发/Agent 轮次 / KnowledgePanel 独占知识库配置(embeddingProviders 依赖 ProviderPanel 的 aiProviders props:108-109 正确)。零交叉污染。
- ②props/emits 接线 PASS:Settings.vue:26 传
:confirm-dialog="confirmDialog"函数引用 + :75useConfirm()返回 confirmDialog/confirmState/answerConfirm 三件套 / ProviderPanel.vue:150 接收 props.confirmDialog + :267/ConnectionPanel.vue:174 调用 props.confirmDialog 删除确认 / emit('toast') 上下传完整(Settings.vue:27/35 接收 + :65 showToast) / emit('providers-changed') 上抛同步 aiProviders(Settings.vue:85-87)。 - ③CSS scoped 零视觉变化 PASS:各子组件自持 scoped 样式副本(ProviderPanel:347-458 / ConnectionPanel:184-262 / GeneralPanel:280-322 / KnowledgePanel:171-213).panel/.btn/.toggle/.setting-* 类完整重复,父 scoped 无法穿透子根故需子自持(Settings.vue:179-181 注释明确)。视觉 CSS 全保留。
- ④F-04c enabled/weight 拆 ProviderPanel 保留 PASS:ProviderPanel.vue:40
@change="onPoolToggle"+ :50@change="onPoolWeightChange"/ :301-312 onPoolToggle(prev 保存 + 乐观更新 + catch 内 revert) / :318-332 onPoolWeightChange(debounce 300ms + clamp[0,100] + NaN 兜底回退 50) / aiApi.updateProviderPool 即时调 IPC(:305/:325) / reload_provider_caps 语义完整(注释:291-294)。 - ⑤sync×3 拆 GeneralPanel 保留 PASS:GeneralPanel.vue:92
@change="syncConcurrencyConfig"+ :114@change="syncAgentMaxIterations"+ :125@change="syncAgentMaxRetries"/ :202-219 syncConcurrencyConfig(debounce 300ms + clamp 双并发 + 单对话不超过全局) / :226-237 syncAgentMaxIterations(debounce 300ms + clamp[1,50]) / :243-254 syncAgentMaxRetries(debounce 300ms + clamp[0,10]) / :265/:267/:269 onMounted 启动同步一次防止刷新后丢失。 - ⑥AE-05 useConfirm 留 shell PASS:Settings.vue:7-15 confirm-mask/confirm-box 模板完整 + :75
useConfirm()返回 confirmState 留 shell共享(confirmState.visible/msg 在 setup 自动解包) + answerConfirm 双按钮绑定。 - ⑦6 timer 全覆盖 PASS:Settings.vue:96 _toastTimer 清理 + :97 providerPanelRef.clearPoolTimer() (ProviderPanel.vue:343 暴露) / ProviderPanel.vue:322 _poolTimer clearTimeout / ConnectionPanel 无 timer / GeneralPanel.vue:274-276 三 timer 清理(_concurrencyTimer/_agentIterTimer/_agentRetryTimer) / KnowledgePanel.vue:167 _knowledgeSaveTimer 清理。各子组件 onUnmounted 自管(Settings.vue:95 注释明确)。
- ⑧i18n 复用无新增 PASS:grep 核验 panelAiProvider/panelConnection/panelGeneral/panelKnowledge 全在既有 settings.ts(zh-CN:7/62/77/115 + en:7/62/77/115)。零新增 key。
- 待修项回流 todo: 无(8 维度全 PASS,零问题。纯重构零功能,行为零变化硬约束满足,运行时留实测)。
- 验证:
CR-260616-34 batch58 F-260616-10 provider 默认值重启失效 bug — ⚠ 已审(PASS-WATCH)
- 审查时间: 2026-06-16(主线程兜底·agent 撞 GLM 限流 1305 · grep/read 核验源码 + 类型确认)
- 总体结论: ⚠ PASS-WATCH — 🔴0 🟡1 ⚪3(核心 bug 双恢复修复 OK,已删除 provider 残留守卫 med 待修)
- 逐件核验:核心 bug 修复 ✅(重启 activeProvider=null → useAiPanel.ts:53-54 模块级 savedProvider 同步恢复 + loadProviders:101-104 DB is_default 兜底,双恢复链路)/ is_default 字段确认 ✅(types.ts:177 AiProviderConfig.is_default boolean,ai_list_providers 返回)/ cycleProvider 兜底 ✅(AiChat.vue:1184 startId=activeProvider||is_default||null 三级兜底,L1187 Math.max(idx,0) 防 -1 越界)。
- 🟡 CR-34-1(med,已删除 provider 残留未校正):useAiPanel.ts:101
if (!state.activeProvider)守卫只查 null 不查有效性。localStorage savedProvider 指向已删除 provider(L54 设 activeProvider=deleted-id)时,loadProviders 守卫 false 跳过 DB is_default 恢复 → activeProvider 残留无效 id → cycleProvider:1185 findIndex=-1 → :1187 next=ps[1] 跳过 ps[0],且潜在消费方(发消息读 activeProvider 调后端)用无效 id。修法:守卫改if (!state.activeProvider || !list.some(p => p.id === state.activeProvider))残留无效时回落 DB is_default。 - ⚪ low:1(L52 注释「以 DB 为准」与行为不符,实际 savedProvider 优先 DB 兜底,行为合理注释误导)/ 2(setProvider:121 void appSettings.set 持久化失败静默,仅影响重启恢复可接受)/ 3(多窗口/分离窗口 activeProvider 一致性:模块级 state 各 webview 独立,主 setProvider 写 SQLite 分离窗口 state 滞后,既存架构非 CR-34 引入)。
- 待修项回流 todo: CR-34-1(med,useAiPanel.ts:101 已删除 provider 残留守卫改查有效性)。
CR-260616-33 batch57 CR-260615-04 splitBlocks→marked.lexer() — ✅ 已审(PASS)
- 审查时间: 2026-06-16(主线程兜底·agent 撞 GLM 限流 1305 · grep/read 核验源码)
- 总体结论: ✅ PASS — 🔴0 🟡0 ⚪3
- 逐件核验(全 PASS):splitBlocks(AiChat.vue:671-691 marked.lexer 切块,L672 getMarked 取实例 + L676 未就绪兜底
return [text]不引入正则 + L677 lexer tokenize + L678-689 for 循环)/ code token 整段一块(L681-685type==='code'+ raw.trim 守卫空代码块跳过 + continue)/ 非代码 token 按\n{2,}切段(L688 split + b.trim 守卫)/ 空 token 非 string raw 防御(L679typeof stringcontinue)/ 空兜底(L690blocks.length ? blocks : [text])/ 签名不变(splitBlocks(text):string[]+ renderStreamingBlocks:723 调用)/ 切块边界与 parse 渲染边界一致(同 marked 实例同围栏规则,CR-260615-04 根治目标达成)/ 流式路径回归(renderStreamingBlocks:718-733 + parseBlock/parseBlockNoCache 块缓存机制不变)。 - ⚪ low:1(lexer 全文 tokenize 性能略高于旧正则,rAF 节流+块缓存吸收,流式实测无影响)/ 2(末块未闭合围栏场景 lexer 可能把后续段落吞进 code raw,流式中间态短暂切块不准,末块重 parse 修正)/ 3(L690 空兜底 lexer 空文本返空数组时回退原文)。
- 待修项回流 todo: 无(核心目标切块/渲染边界一致达成,防御充分)。
CR-260616-32 batch57 B-260619 Knowledge.vue CSS窄屏标题挤压 — ✅ 已审(PASS)
- 审查时间: 2026-06-16(独立审查 agent · 亲跑 vue-tsc EXIT 0 + grep/read 核验源码)
- 总体结论: ✅ PASS — 🔴0 🟡0 ⚪3
- 逐件核验(全 PASS):min-width:0 传播链完整(L576 kn-detail-panel → L622 detail-head → L623 detail-title-row flex:1 → L625 detail-title,窄屏标题可正常收缩)/ 760px 断点 column-wrap 与 grid 兼容(L730 kn-layout 单列降级 + L731 detail-head flex-direction:column 无冲突)/ 按钮换行间距(L626 detail-actions gap 6px + flex-wrap:wrap,L732 窄屏 width:100%)。
- ⚪ low:1(L623
.detail-title-row { overflow:hidden }冗余,word-break 多行标题 height auto 自适应不真裁断,语义不清,建议删保留 min-width:0+flex:1)/ 2(L733 窄屏字号降级仅覆盖 .detail-title 18→15px,编辑态 .edit-input-title L692 固定 16px 未同步,窄屏编辑态 16px 反超展示态 15px)/ 3(18→15px 偏激进,详情主标题窄屏建议 ≥16px)。 - 待修项回流 todo: 无(三 low 风格/一致性微调非阻断)。
CR-260616-31 batch56 F-260616-08 分页 offset+探总量 — ✅ 已审(PASS)
- 审查时间: 2026-06-16(主线程兜底·agent 撞 GLM 限流 1305 · grep/read 核验源码形态;未亲跑 cargo check,标准 API 语法无编译风险,待 agent GLM 恢复补 cargo 验证)
- 总体结论: ✅ PASS — 🔴0 🟡0 ⚪2
- 逐件核验(全 PASS):①offset 边界(tool_registry.rs:216/237/254 offset
as_u64().unwrap_or(0)→usize,JSON 负数 as_u64 None→0,skip(offset).take(limit) 超 total 取空不 panic;has_more(offset+page.len())<total超时 false)/ ②total 准确(list_projects:214-215list_active().len()分页前全量=真实总量;list_tasks:236/list_ideas:253 同)/ ③探总量与 list 一致(get_project_count:1091-1093 list_active().len()=list_projects:214-215 同源;get_task_count:1104-1108 project_id 分支+list_all 兜底=list_tasks:231-234 同逻辑,range 一致)/ ④knowledge_search 解钳(knowledge.rs:110.min(20)替原.min(3),允许 LLM 翻更多候选合理)/ ⑤schema 描述(:208/225/246 含 offset/limit/total/has_more 翻页语义 LLM 可理解)。 - ⚪ low:1(内存放大:list_/get_count 先全量 load(list_active/list_all)再 skip/take,数据量大时内存放大;AI 工具调用 + MAX_LIST_RESULTS 钳 limit 实际可控)/ 2(list_tasks/get_task_count 用 list_all 未排除软删任务,而 list_projects/get_project_count 用 list_active 排除软删,任务与项目软删语义不对称;但 getcount 与 list 同源一致,且为既存逻辑非 CR-31 引入)。
- 待修项回流 todo: 无(五维度全 PASS)。
CR-260616-30 batch56 F-260616-07 流式自动重试 — ⚠ 已审(ISSUES)
- 审查时间: 2026-06-16(独立审查 agent · 亲跑 cargo check EXIT 0 + cargo test df-ai retry 6 passed 附 log 末尾 + grep/read 核验 + 重试循环正确性专项)
- 总体结论: ⚠ ISSUES — 🔴0 🟡2 ⚪3(核心重试循环逻辑正确 permit/guard.reset/request 重建均 OK,但退避策略注释误称对齐 + AiStreamRetry 死事件 med)
- 亲跑
cargo check --workspace✅ EXIT 0(8 warning 全既存 dead_code 非本批)+cargo test -p df-ai retry✅ EXIT 0 6 passed(backoff_is_monotonic_within_jitter/status_classification/fatal_aborts_immediately/first_attempt_ok/retry_once_then_ok/all_retryable_exhausts,finished 3.35s)。 - 重试循环正确性专项(全 PASS):permit 持有释放 ✅(
_global_permit/_per_conv_permitagentic.rs:239-240 外层函数体作用域,耗尽 return:285/成功 drop:333-334 均 RAII 释放无并发槽泄漏,重试期间持 permit 有意防新请求挤占)/ 耗尽路径 guard.reset ✅(:284guard.reset().awaitreturn 前显式复位 generating,max_retries=0 时0..=0单次失败 is_last→reset+return 边界正确)/ request 每轮重建 ✅(:248-263 每轮session_arc.lock()+build_for_request重建 messages 无累积/丢失,与循环外:207-215request变量重复轻微冗余无害)。 - 🟡 CR-30-1(med,退避策略注释误称对齐):agentic.rs:298
1u64 << retry_attempt纯指数无 jitter/无总预算/一律重试(含 MidStream);retry.rs:67-78backoff_delay有 ±20% jitter + 30s 总预算(:34)+ 4xx 不可重试分类(:102-104 Fatal 立即放弃)。三处注释(agentic.rs:37/commands.rs:1106/state.rs:185)均称「对齐 retry.rs 退避策略(1s→2s→4s+jitter)」与实现不符。修法:要么 agentic 加 jitter+总预算+错误分类真对齐,要么删注释「对齐 retry.rs」改为「独立简化策略」。 - 🟡 CR-30-2(med,AiStreamRetry 死事件 + MidStream 重试文本混乱):后端 emit AiStreamRetry(agentic.rs:310),types.ts:214 有 variant,但 useAiEvents.ts switch 无
case 'AiStreamRetry'(十 case 无 retry)事件穿透忽略。更严重:MidStream 失败重试时已 emit 的 AiTextDelta 累积进前端 currentText,重试新 delta 追加 → 文本重复/混乱(stream_recv.rs:210/240 MidStream return None,agentic.rs:294 一律重试无清空 currentText 机制)。修法:useAiEvents 补 case + 重试前清空 currentText;或后端 MidStream 失败不重试只重试 Init 失败(对齐决策 a 只重试流前失败)。 - ⚪ low:1(agentic.rs:243/317
last_error声明赋值但 result 分支:323-330 未读 dead code)/ 2(:294-296 MidStream 一律重试注释承认「简化」,与决策 a 只重试流前失败相悖)/ 3(run_agentic_loop 3 调用点 commands.rs:108/204/444 非登记「4 入口」,try_continue 走审批恢复复用非独立入口)。 - 待修项回流 todo: CR-30-1(med,退避策略注释对齐)+ CR-30-2(med,AiStreamRetry 死事件+MidStream 重试文本混乱)。
CR-260616-29 batch56 ARC-06 composable 循环依赖破环 — ✅ 已审(PASS)
- 审查时间: 2026-06-16(主线程兜底·agent 撞 GLM 限流 1305 · grep/read 核验 import + 源码)
- 总体结论: ✅ PASS — 🔴0 🟡0 ⚪1
- 逐件核验(全 PASS):①循环依赖真破(useAiEvents.ts import L14-23 无 useAiSend,改 L284
emit('ai-drain-queue')事件总线;useAiSend.ts:25 仍 import useAiEvents(startListener)单向,双向环已断)/ ②事件总线桥接(useAiEvents:284 emit + useAiSend:395-398 initDrainQueueListenerif(_drainUnlisten) return幂等守卫 + stores/ai.ts:134 void 注册)/ ③findToolCall 下沉一致(aiShared.ts:41-51 反向扫 messages + 同消息正向扫 toolCalls,两调用方 import 同一实现,注释:37-40 说明反向扫描避免陈旧引用)/ ④startApprovalTimer 幂等(aiShared.ts:66if(_approvalTimers.has) return同 id 不重建 + clearApprovalTimer/clearAllApprovalTimers 清理完整)。 - ⚪ low:1(事件总线桥接理论竞态:initDrainQueueListener stores/ai.ts:134 void 不 await,注册前若 AiCompleted 到达事件丢失 drainQueue 不触发;实际初始化远早于首次生成完成低风险,原 direct call 同步无此问题,可靠性微小降级)。
- 待修项回流 todo: 无(四维度全 PASS,循环依赖真破)。
CR-260616-28 batch56 F-260616-06 审批幂等+防抖 — ✅ 已审(PASS)
- 审查时间: 2026-06-16(主线程兜底·agent 撞 GLM 限流 1305 · grep/read 核验源码 + 方法存在性确认)
- 总体结论: ✅ PASS — 🔴0 🟡0 ⚪2
- 逐件核验(全 PASS):①幂等三态覆盖(commands.rs:220 内存 pending_approvals.remove None → :224 查审计表 find_by_tool_call_id → :227 executed/rejected/failed 返回 Ok「已处理」三态全覆盖;pending 态落 :231 Err 合理——内存 pending_approvals 丢失即审批上下文不可恢复,DB pending 占位仅日志非可恢复审批)/ ②防抖(useAiSend.ts:307 _pendingApprovalIds.has 守卫无时间窗口竞态 + :308 add + :332-334 finally delete 无论成功异常均清理,模块级 Set 不泄漏)/ ③find_by_tool_call_id 存在确认(crud.rs:1198-1201
pub async fn(&self,&str)->Result<Option<AiToolExecutionRecord>>,:224 unwrap_or_default Err→None→:231 Err,DB 故障不误判已处理)。 - ⚪ low:1(pending 审计记录 + 内存 pending_approvals 丢失→:231 Err「未找到挂起的审批」文案略困惑,实际审批上下文已不可恢复 Err 正确,文案对有 pending 审计记录场景不精确)/ 2(防抖 _pendingApprovalIds 模块级跨调用累积,finally 清理可靠无泄漏,机制说明非问题)。
- 待修项回流 todo: 无(三维度全 PASS)。
CR-260616-27 batch54 复核-新⑪ detach 清主窗口 state 看门狗幽灵(useAiWindow.ts) — ✅ 已审(PASS)
- 审查时间: 2026-06-16(主线程兜底·agent 撞 GLM 限流 1305 · grep/read 核验 + vue-tsc EXIT 0)
- 总体结论: ✅ PASS — 🔴0 🟡0 ⚪0
- 逐件核验(全 PASS):detachPanel(useAiWindow.ts:28-38 快照在清 state 前写入 — L28-31 localStorage.setItem df-ai-gen/df-ai-text → L37-38 清 state.streaming/generatingConvId,顺序正确)/ state 独立性(主/分离窗口独立 webview JS context,resumeInDetached:87-119 靠 localStorage df-ai-gen/df-ai-text 重建 streaming L116-117,反证清主窗口 state 不影响分离窗口生成态)/ watchdog 兜底(stopListener clearStreamWatchdog 已由 CR-260615-24 兜底,本次仅清 state 视觉残留,注释:32-36 说明链路)/ reattach/closeDetachedWindow 清快照(L82-83/L125-126)。
- 待修项回流 todo: 无(全件 PASS)。
CR-260616-26 batch53 F-05 去重根治(audit.rs High risk 审批前缓存命中去重) — ✅ 已审(PASS)
- 审查时间: 2026-06-16(独立审查 agent · 亲跑 cargo + vue-tsc 双 EXIT 0 附 log 末尾证据 + grep/read 核验全件 + 边界核验)
- 总体结论: ✅ PASS — 🔴0 🟡0 ⚪3
- 亲跑
cargo check --workspace+npx vue-tsc --noEmit✅ — 双 EXIT 0(cargo Finished 0.68s + vue-tsc log 空;5 warning 全既存 dead_code 非本批)。无中间态误报(附 log 末尾)。 - 逐件核验(全 PASS):find_cached_high_risk_result(audit.rs:400-458 反向扫
msgs.iter().rev()+ tool_name 过滤 :423 + canonical_args_key 规范化比较 :430 + 旧 id 反查 tool_result :444-456)+ High 分支查缓存命中回传(:536-553 guard High → push tool_result 新 tool_call_id :544 + emit AiToolCallCompleted :545-549 + 审计 completed/auto_dedup :551 + continue :552 跳过 pending insert)+ 仅 High 去重(:534 Medium|High 共分支 + :536 内嵌 High guard,Med 走原审批 :555-573)+ pending 占位不命中(:452 占位串「需要用户审批,等待确认」return None,与 push :564 全角逗号一致)+ args 规范化(sort_object_keys :462-492 sort_by 字典序 + 递归值/Array + to_string 紧凑稳定)+ 不同命令/参数不命中(timeout 重发可重试)+ tool_call_id 来源修正(stream_recv.rs:181 draft.id 来自 tc_delta.id 非 stream_recv UUID)+ 零越界(纯 audit.rs 3 函数 + High 分支,未碰 batch42/44/51)。 - 关键边界核验:二次循环自吞(第一次重试命中缓存 push tool_result → 第二次重试 find_cached 反扫命中第一次 tool_call 又跳过审批)= 期望行为(每次重试跳过审批断循环,非 bug)。
- ⚪ 观察项(全 low):OBS1(canonical_args_key to_string unwrap_or_default 理论序列化失败空串误等价,实际 JSON 不会失败)/ OBS2(空参数同名 High 工具误命中,当前工具集无)/ OBS3(去重命中 tool_result 下轮被命中 = 期望行为断循环)。
- 待修项回流 todo: 无(全件 PASS,3 OBS 全 low)。完整 agentic loop 通用去重(search_files 重复卡片 + 滑动窗口)③类待用户,本批仅 run_command 循环核心。
CR-260616-25 batch52 UX-10 @实体引用全栈(@ popover + prompt 任务段注入) — ✅ 已审(PASS)
- 审查时间: 2026-06-16(独立审查 agent · 亲跑 cargo + vue-tsc 双 EXIT 0 附 log 末尾证据 + grep/read 核验全件)
- 总体结论: ✅ PASS — 🔴0 🟡0 ⚪3
- 亲跑
cargo check --workspace+npx vue-tsc --noEmit✅ — 双真实 EXIT 0(cargo Finished 0.72s + vue-tsc log 空 0 error;5 warning 全既存 dead_code 非本批)。无中间态误报(附 log 末尾,呼应 CR-23-1 教训防 E0594 类瞬态漂移)。 - 逐件核验(全 PASS):@ popover 复用
.ai-skill-popover(AiChat.vue:563)+ 联想状态三件(mentionOpen/Index/Start :992/993/995 独立于 skill)+ detectMentionTrigger 邮箱排除(:927-953 prev 非空白 break)+ selectMention[类型: 名]插入(:1057-1079)+ mentionItems 项目+任务各 slice(0,20)(:1009-1037)+ mentionGroups 分组(:1046-1049)+ mentionTotal 空/无匹配区分(:1040-1043)+ 键盘导航与技能联想互斥(:902-920 watch inputText skill 块优先强制关 mention + selectSkill/selectMention 互清,skillOpen 与 mentionOpen 不可同时 true)+ Enter 不冲突(:1433-1456 popover 开 selectMention preventDefault 覆盖发送,关 fallthrough handleSend)+ prompt.rs:117-126 任务段(state.tasks.list_active take(20) + 注入 title/status/description 镜像项目段范式 :108-115)+ 签名零变化(build_system_prompt :102 4 调用方 commands.rs:69/161/391 + agentic.rs:478 零改)+ 降级合理(prompt 走后端 state repo :119 非前端 mentionEntities 透传,避碰 batch45/48/49/51 领地)+ 实体 id 未透传(仅[类型:名]文本,重名任务风险低已知降级)+ i18n 5 key 双语(aiChat.ts:76-80)。 - ⚪ 观察项(全 low):OBS1(mentionHint 死键定义无 .vue 消费,建议删或激活)/ OBS2(mentionQuery :1003-1006 slice 到串尾非光标前,光标中移时偏差,常规用法无影响)/ OBS3(desc 仅 popover 展示不进 prompt,prompt 任务段用 tk.description 摘要列间接影响 LLM,语义链路清晰)。
- 待修项回流 todo: 无(全件 PASS,3 OBS 全 low)。
CR-260616-24 batch51 UX-09 编辑已发送消息并重新生成全栈(messages JSON status + ai_chat_edit IPC + AiChat 编辑 UI) — ✅ 已审(PASS)
- 审查时间: 2026-06-16(独立审查 agent · 亲跑 cargo + vue-tsc 双 EXIT 0 + grep/read 核验全件 + CR-23-1 独立核验)
- 总体结论: ✅ PASS — 🔴0 🟡0 ⚪4
- 亲跑
cargo check --workspace+npx vue-tsc --noEmit✅ — 双 EXIT 0(cargo Finished 0.84s,5 warning 全既存 dead_code 非本批)。 - 🔴 CR-23-1 独立核验:不成立 ✅ —
context.rs:478-495当前是 for 循环(L488for t in ...iter_mut()+ L490t.message.status=Some("truncated")+ L491count += 1;计数非 filter 闭包体),workspace cargo check 真实 EXIT 0(全量非分 crate)。原 CR-23 agent 报 E0594 是中间态误报(batch51 最终 for 循环规避双层引用,呼应 CR-21/18/16 瞬态漂移)。主代理声明属实,CR-23-1 标无效。 - 逐件核验(全 PASS):provider.rs:54-55 ChatMessage
status: Option<String>(serde default+skip_serializing_if 向前兼容 None=active 无 V17 迁移)+ 5 构造函数补 None(:60/63/66/69/72)+ is_active()(:76-77)+ sanitize_messages step0 过滤 truncated(context.rs:287-296)+ replace_last_active_user_content(:501-512 rposition+token 重估)+ truncate_after_user_message(:478-495 for 循环)+ ai_chat_edit IPC(commands.rs:351-445 复用 ai_regenerate spawn 模式:generating 拦截:363+活跃一致:367+replace 末条 user:371-377+truncate 其后:379-382+save:418+spawn:428-442)+ 只末条 active user 可编辑(replace rposition + Err)+ title.rs:51 + lib.rs:108 + 前端 api/ai.ts:33 editMessage + useAiSend.ts:212-270(splice 截断+改 content+空气泡占位+IPC+失败回滚)+ useAiConversations.ts:72 filter truncated + AiChat.vue:329-337 编辑按钮 hover(isLastUser && !streaming)+ isLastUser/startEdit/cancelEdit(:1028-1059)+ handleSend 编辑分支(:1267-1285)+ ESC 取消(:1211-1215)+ 切换取消(:883-885)+ i18n 4 key 双语(aiChat.ts:100-103)。 - ⚪ 观察项(全 low):OBS1(导出 batch46 含 truncated 软删历史,markdown/txt 视觉见旧回复段,完整审计语义取舍)/ OBS2(truncate_after_user_message/replace_last_active_user_content 无单测,token 计数同步易错建议补 case)/ OBS3(useAiSend editMessage convId 空回滚不彻底,极端场景后端未触发 IPC)/ OBS4(editMessage 无 generating 前端预检,DOM 隐藏+后端拦截双兜底无并发风险)。
- 待修项回流 todo: 无(CR-23-1 标无效,4 OBS 全 low 非阻断)。
CR-260616-23 batch50 审查 med 修复聚合(CR-15-1 kill_on_drop + CR-08-O1 blocked 排除 + CR-10-A/B 停下报告) — ⚠ 已审(ISSUES)
- 审查时间: 2026-06-16(独立审查 agent · 亲跑 cargo check EXIT 101(撞同攒批 CR-23-1)+ vue-tsc EXIT 0 + grep/read 核验四件)
- 总体结论: ⚠ ISSUES — 🔴1 🟡0 ⚪1(三件声明项全 PASS,但同攒批工作区 context.rs:491 编译失败阻断整批)
- 亲跑
cargo check --workspace⚠ EXIT 101(df-ai 编译失败,CR-23-1,非三件范围)/npx vue-tsc --noEmit✅ EXIT 0。 - 三件声明项核验(全 PASS):
- CR-15-1 kill_on_drop ✅
shell.rs:85.kill_on_drop(true)match 块外统一调用,三类型(PowerShell:62/Cmd:68/Sh:74)全覆盖,注释:81-84 配合 tokio::time::timeout:96-101 防孤儿;正常完成路径 cmd.output() 已读完 output 自然 drop 无害 — 不破坏 cmd.output() 成立。 - CR-08-O1 blocked 排除 ✅
TaskDetail.vue:265-274WF_EXCLUDED_FROM={'blocked'}:265 + wfAdvanceAction computed :269if has return null,DOM :71 v-if 不渲染。不动 ADVANCE_MAP(:219-246 五态原推进项全保留),其他态不受影响。 - CR-10-B 否决 ✅ 同意主代理「不成立,审查锚定漂移」:独立 Read commands.rs:37-110,L94 save_conversation 锁外(L46-65 锁块 L65 关闭),L105 spawn 在 save 后,时序正确。L56/59 是 pop 失败防御性错误处理(复位 generating + 报错)非 save/emit 时序。原 CR-10-B 把 pop-失败-错误分支误读为时序冲突,审查锚定漂移坐实。
- CR-10-A 降级 P2 ✅ 三入口(ai_regenerate:37/ai_chat_send:128/ai_chat_force_send:453)统一「锁内变更+锁外 spawn」,兜底充分(generating 占用 busy 拦截 + stop_flag 复位 + force_send:460-468 复位三件套 + ai_chat_stop:520-536 3s 兜底任务防 panic/异常退出卡死),接受现状合理。
- CR-15-1 kill_on_drop ✅
- 🔴 CR-23-1(high,编译失败,同攒批非三件范围):
crates/df-ai/src/context.rs:491E0594cannot assign to t.message.status, which is behind a & reference。truncate_after_user_message的self.messages[i+1..].iter_mut().filter(|t| {... t.message.status=...})filter 闭包参数 t 是&&mut TrackedMessage(双层引用)不能写字段。git stash HEAD EXIT 0 / pop 工作区 EXIT 101 → UX-09 新增代码引入。阻断整个 df-ai crate 构建,下游 commands.rs/agentic.rs 全 fail。修法:filter 改 for 循环 + 计数,或闭包内let t=&mut **t;解引用。 - ⚪ 观察项:OBS1(agent 自报 cargo EXIT 0 与实际 EXIT 101 不符,可能跑 HEAD 或分 crate,结论可信度打折,建议后续 agent 自报附 log 末尾证据)/ OBS2(CR-23-1 非三件范围但同攒批,合并前必先修否则整批构建失败)。
- 待修项回流 todo: CR-23-1(high,context.rs:491 编译失败,改 for 循环或解引用,阻断 df-ai 构建 — 紧急,主会话合并前必修)。
CR-260616-22 batch49 UX-17 对话置顶全栈(pinned 字段+迁移+IPC+排序+图钉按钮) — ✅ 已审(PASS-WATCH)
- 审查时间: 2026-06-16(独立审查 agent · 亲跑 cargo check + cargo test df-storage + vue-tsc 三 EXIT 0 + grep/read 核验 14 件)
- 总体结论: ⚠ PASS-WATCH — 🔴0 🟡1 ⚪3(CR-22-1 med 已置顶图钉非 hover 不可见)
- 亲跑
cargo check --workspace+cargo test -p df-storage+npx vue-tsc --noEmit✅ — 三 EXIT 0(cargo 9 warning 全既存 dead_code;df-storage 22 单元 + 11 集成 passed;vue-tsc 0 error)。 - 独立核验(14 件):models.rs:163
pub pinned: bool(非 Option 必填,list 摘要手写 json! snake_case 对齐)/ crud.rs:1141-1148 INSERT 含 pinned(?8)+ :1153-1160 UPDATE 含 pinned(?7)params 对齐 / :1050 ai_conversation_from_row 读 pinned i32→bool / :1657-1672 set_pinned 仅 SET pinned 不动 updated_at(纯元数据)/ migrations.rs:285-296 V16 column_exists 守卫幂等 + ALTER ADD COLUMN pinned INTEGER NOT NULL DEFAULT 0(老库非 NULL)+ :53 注册 (16, migrate_v16)/ commands.rs:849-859 ai_conversation_set_pinned IPC / :746 list 摘要 json! 加 pinned / lib.rs:128 注册 / conversation.rs:141 pinned: false(proactive-bug-fix 坐实:models.rs:163 非 Option 必填,insert 不补编译断 E0063;update :108 从库 get rec.pinned 回写不丢失) / AiChat.vue 排序 :1054(groupedActive)+ :1069(archivedConvs)Number(b.pinned?1:0)-Number(a.pinned?1:0)||Number(b.updated_at)-Number(a.updated_at)pinned DESC→updated_at DESC + V8 stable / :53/120/162 三处图钉按钮 toggle / i18n zh:9-10/en:9-10 双语 / useAiConversations.ts:174-178 setPinnedConversation IPC+本地同步 / api/ai.ts:156-157 + types.ts:246。 - 🟡 CR-22-1(med,UX 缺陷):AiChat.vue:1872-1873
.ai-conv-item-actions { display:none }+ :1878:hover才display:flex→ 所有操作按钮(含图钉)默认隐藏,:1903--pinned仅改 color 未覆盖 display → 已置顶对话非 hover 态图钉不可见,「常驻强调色一眼识别」UX 意图落空。功能(toggle/排序)正常。修法:.ai-conv-item-act--pinned补display:flex。登记自相矛盾(hover 浮出 toggle 与常驻强调色无法同时满足),需产品定调。 - ⚪ 观察项:OBS1(filteredConversations 搜索态未加 pinned 排序,置顶不前置,登记自认降级可接受)/ OBS2(V16 无独立测试靠 column_exists 探测,项目一贯模式)/ OBS3(update_full:1151 读库 rec.pinned 回写不丢失,未来绕过 get 路径风险当前无)。
- 待修项回流 todo: CR-22-1(med,AiChat.vue:1872-1903 已置顶图钉非 hover 不可见,「常驻强调色」UX 未达成,补 display 规则或调 actions 可见性策略)。
CR-260616-21 batch48 UX-18 对话导出前端(exportConversation 封装 + 3 处导出按钮 + Blob 下载) — ✅ 已审(PASS)
- 审查时间: 2026-06-16(独立审查 agent · 亲跑 vue-tsc + cargo check 双 EXIT 0 + grep/read 核验 12 件)
- 总体结论: ✅ PASS — 🔴0 🟡0 ⚪3
- 亲跑
npx vue-tsc --noEmit+cargo check --workspace✅ — 双 EXIT 0。vue-tsc EXIT 0 确认 batch48 完成后toggleExportMenu/exportingId/exportOpenId/exportConversation均已定义,CR-15/16 agent 早报的 12 TS2339 瞬态已消除(cargo 5 warning 全既存 dead_code NodeExecutionRepo/ReleaseRepo 非本批,本批纯前端)。 - 独立核验(12 件):api/ai.ts:165-167 exportConversation 封装(camelCase 仿 continueLoop/stopLoop,签名
(conversationId, format): Promise<string>,invokeai_conversation_exportsnake→camel 对齐 commands.rs:872)/ AiChat.vue 3 处导出按钮 DOM(搜索态 :59-68 / 活跃态 :126-135 / 归档态 :168-177,每处 pin→archive→export→delete 顺序,@click.stop 阻冒泡,:disabled=exportingId===conv.id 防重复)/:last-child红色态保留(:1898-1901 delete 是末子 export 倒数第二,精确命中删除按钮不被破坏)/ toggleExportMenu/exportOpenId 单展开(:912-917 三元切换同时一开)/ Blob 下载 + revokeObjectURL(:941-950 setTimeout(0) 下轮事件循环释放防泄漏,mime 三格式精确 text/markdown|application/json|text/plain)/ sanitizeFilename 安全(:920-927 空 trim→fallback conv-id + replace 折叠 Win/Unix 路径分隔符+非法+控制字符 + slice(0,60) 限长 + 全清空降级)/ exportingId 防重复(:913/931-932/956 入口 guard+set+finally 三段)/ onExportOutsideClick + 生命周期(:961-966 早返回+closest 防误关 + :1381/1317 add/remove 对称)/ IPC catch 兜底(:951-954 e.message||String(e)+toast 不崩溃)/ i18n 5 key 双语(:112-116 exportFailed 含 {msg})/ 消费 batch46 IPC(commands.rs:872 + lib.rs:129)/ 零越界(batch45 continueLoop/stopLoop :37/45 + batch43 搜索框 :23-34 + batch47 确认 handler :819-841 独立区未改)。 - ⚪ 观察项:OBS1(revokeObjectURL setTimeout(0) 下载时机,社区惯例无可见问题)/ OBS2(菜单无 Esc 关闭,onGlobalKeydown Esc 已消费他途,UX 小瑕疵)/ OBS3(filename 无时间戳,同名浏览器自动加后缀,设计取舍)。
- 待修项回流 todo: 无(12 件全过,双 check EXIT 0,零越界)。
CR-260616-15 batch42 F-04 run_command 超时根治(tool_registry.rs 超时标注 + 常量提取) — ✅ 已审(PASS-WATCH)
- 审查时间: 2026-06-16(独立审查 agent · 亲跑 cargo EXIT 0 + vue-tsc EXIT 2 非本批 + grep/read 核验 8 件)
- 总体结论: ⚠ PASS-WATCH — 🔴0 🟡1 ⚪2(CR-15-1 med shell.rs kill_on_drop 缺失)
- 亲跑
cargo check --workspace✅ — EXIT 0(本批纯 Rust tool_registry + shell)。npx vue-tsc --noEmit⚠ EXIT 2(12 errors 全在 AiChat.vue 导出功能 toggleExportMenu/exportingId/exportOpenId/exportConversation,系并发 batch48 未完成实现,与本批 F-04 Rust 零关联)。 - 独立核验(8 件):tool_registry.rs:24 常量
DEFAULT_RUN_COMMAND_TIMEOUT_SECS=60/ :495 unwrap_or 用常量单一使用点 / :510-520 map_err 拦超时改写文案(contains 匹配 shell.rs:95 产出串前缀一致)/ shell.rs:89-95 超时根因点(tokio::time::timeout包cmd.output())/ commands.rs:251-256 High 审批路径e.to_string()透传 / audit.rs:450 Low/并行路径同源(经 ai_tools.execute 分发无遗漏)/ commands.rs:269 replace_tool_result_content 断重试链路(LLM 收改写文案不再触发新 tool_call_id 重审批)/ tool_registry.rs:472 run_command 单注册。 - 🟡 CR-15-1(med,真 bug):
crates/df-execute/src/shell.rs:60-79构造tokio::process::Command缺.kill_on_drop(true)(tokio 1.52.3)→tokio::time::timeoutdrop future 后 tokio 不主动 kill 子进程,子进程变孤儿继续后台跑,但tool_registry.rs:514文案称「进程已终止」名不副实。长 hang 命令(大构建/死循环)超时后仍耗资源。治本应 shell.rs:60-79 构造 cmd 后加.kill_on_drop(true)。根因层缺陷(非本批 tool_registry 标注层引入),但本批文案依赖该假设。 - ⚪ 观察项:OBS1(tool_registry.rs:473 描述 + :494 注释硬编码「默认 60 秒」字面量未引用 const,改常量需手改两处易漏)/ OBS2(const :24 模块私有,shell.rs 未共享)。
- 待修项回流 todo: CR-15-1(med,
crates/df-execute/src/shell.rs:60-79缺.kill_on_drop(true),孤儿进程 + 文案「进程已终止」语义不实)。
CR-260616-16 batch43 UX-06 对话搜索 UI 接线(AiChat.vue 侧栏) — ✅ 已审(PASS)
- 审查时间: 2026-06-16(独立审查 agent · 亲跑 cargo EXIT 0 + vue-tsc 首跑 EXIT 2 缓存陈旧清缓存重跑 EXIT 0 + grep/read 核验 8 件)
- 总体结论: ✅ PASS — 🔴0 🟡0 ⚪3
- 亲跑
cargo check --workspace✅ — EXIT 0(7 warning 全既存 dead_code)。npx vue-tsc --noEmit首跑 EXIT 2(6 TS2339 全在 AiChat.vue:57-63 导出功能 toggleExportMenu 等,属并存 batch48 导出 UI 非本批)→ 清增量缓存重跑 EXIT 0,确认缓存陈旧 quirk 非真实错。 - 独立核验(8 件):搜索框 DOM(AiChat.vue:23-34 .ai-conv-search + v-model + kbd Ctrl+K)/ Ctrl+K 绑定(:996-1003 preventDefault + 侧栏未开 toggleSidebar + nextTick focus,顺序 Ctrl+L→K→B→Esc 无重复)/ keydown listener 生命周期对称(:1273 注册 / :1211 移除)/ searchInputRef(:847)/ 平铺渲染(:36-74 template v-if searchResults + :key 's+' 防键冲突 + 空态 searchEmpty + v-else 原分组逐字保留 editingConvId 双击改名零回归)/ searchResults computed(:974-976 显式 .value 解包 ComputedRef 坑已修)/ 消费 batch41 store 契约零改(CR-14 已审 store 侧)/ i18n 3 key 双语(:13-15)。Ctrl+K 冲突核验:grep 无既有 Ctrl+K,Tauri WebView2 无地址栏。
- ⚪ 观察项:OBS1(Ctrl+K 输入焦点抢占,打字时按 K 切走光标,UX 选择非破坏)/ OBS2(kbd 与 title 复用同一 i18n key,tooltip 语义弱)/ OBS3(范围声明漂移:搜索态平铺项被 batch48 加 export 菜单,审查时点与现状漂移,归 batch48 审)。
- 待修项回流 todo: 无(vue-tsc EXIT 2 缓存陈旧非真实错)。
CR-260616-17 batch44 F-03 达 max 后端(agentic 暂停态 + AiMaxRoundsReached 事件 + continue/stop IPC) — ✅ 已审(PASS)
- 审查时间: 2026-06-16(独立审查 agent · 亲跑 cargo + vue-tsc 双 EXIT 0 + grep/read 核验 6 件 + 交叉核验)
- 总体结论: ✅ PASS — 🔴0 🟡0 ⚪2
- 亲跑
cargo check --workspace+npx vue-tsc --noEmit✅ — 双 EXIT 0(cargo 7 warning 全既存 dead_code)。 - 独立核验(6 件 + 交叉):agentic.rs:322-347 达上限改(save 先行 :340 + disarm :342 + emit AiMaxRoundsReached :343-345 + return :346 保 generating 暂停态,对齐审批等待模式 L313-316)/ mod.rs:117-122 variant +
#[serde(tag="type")]/ commands.rs:446-465 ai_continue_loop(generating + active 双校验 + 复位 stop_flag + try_continue)/ commands.rs:476-502 ai_stop_loop(双校验 + 置 stop_flag 双保险 + generating=false + emit AiCompleted 不重复 save)/ lib.rs:112-113 注册 / 决策 a(loop 局部 iteration agentic.rs:159,续跑 try_continue 重 spawn 天然重计)。交叉:try_continue guard(should_continue)、ai_chat_stop 暂停态交互(走 3s 兜底正确非最优)、ai_chat_force_send(复位 generating 清审批走新 loop)、stop_flag 双保险(continue 复位 :460 + stop 置 :490)。 - ⚪ 观察项:OBS1(ai_chat_stop 暂停态落流式分支走 3s 兜底非最优,前端延迟,建议审批分支前加暂停态判定即时清理,既存 stop 路径未适配新状态)/ OBS2(无硬上限产品风险,前端操作卡建议多次继续后二次确认)。
- 待修项回流 todo: 无。
CR-260616-18 batch45 F-03 达 max 前端操作卡(pendingMaxRounds + 续/停按钮) — ✅ 已审(PASS)
- 审查时间: 2026-06-16(独立审查 agent · 亲跑 cargo + vue-tsc 双 EXIT 0 + grep/read 核验 6 件 + 死锁风险核验)
- 总体结论: ✅ PASS — 🔴0 🟡0 ⚪2
- 亲跑
cargo check --workspace+npx vue-tsc --noEmit✅ — 双 EXIT 0(cargo 5 warning 全既存 dead_code)。 - 独立核验(6 件 + 死锁):types.ts:210-211 AiMaxRoundsReached variant 联合 + serde 对齐 / useAiEvents.ts:93 pendingMaxRounds 模块级 ref 独立事件源 + :200-204 dispatch + :286/320 AiCompleted/AiError 双清 + :41 NO_RESET_WATCHDOG 含 / api/ai.ts:37-38/45-46 continueLoop/stopLoop 仿 ai_approve / AiChat.vue:468-483 操作卡 + :1625-1626 showMaxRoundsCard 双守卫 + :1632-1658 handler IPC 失败回滚 acting + :1660-1663 watch 复位 + :2384-2395 样式 / i18n 6 key 双语(:102-107)/ 后端契约对齐(agentic.rs:343 + commands.rs:446/476 + lib.rs:112-113)。死锁核验:try_continue 三路径(should_continue=false 补 AiCompleted / provider-Err emit AiError / 正常 spawn run_agentic_loop)全覆盖,所有出口触发清 pendingMaxRounds + watch 复位 acting,无死锁。
- ⚪ 观察项:OBS1(无 max 暂停态查询 IPC,跨重启恢复缺失,设计取舍 force_send 可复位)/ OBS2(continue/stop IPC 命名风格微异:ai_approve 动词 vs continue/stop 动词+名词)。
- 待修项回流 todo: 无。
CR-260616-19 batch46 UX-18 对话导出后端(ai_conversation_export IPC) — ✅ 已审(PASS)
- 审查时间: 2026-06-16(独立审查 agent · 亲跑 cargo check EXIT 0 + grep/read 核验 11 件)
- 总体结论: ✅ PASS — 🔴0 🟡0 ⚪2
- 亲跑
cargo check --workspace✅ — EXIT 0(workspace 干净,0 warning 0 error)。 - 独立核验(11 件):conversation.rs:19/24/48 确无导出 IPC(只 TokenAccumulator + save_conversation)/ IPC 位置 commands.rs:853-913 / format 双保险防 panic(:860-863
matches!前置 Err + :908-909_ => Err末尾)/ 落库 messages serde_json::from_str 零内存读(:866-871 与 switch 一致)/ markdown 四臂 match 真穷举无_通配(:878-883 MessageRole 4 variant 编译器强制)/ markdown content 原样三反引号不转义(:886)/ json to_string_pretty(:892-893)/ txt 仅读 m.content 略 tool_calls/tool_call_id/model(:898-904)/ 空对话 markdown/txt 空串 json"[]"(:889/906/892)/ lib.rs:128 注册(与 batch44 L112-113 不同行)/ 零越界(与 batch44 continue-stop 不同段)。 - ⚪ 观察项:OBS1(json 空对话返
"[]"非"",语义合理前端 JSON.parse 正常)/ OBS2(markdown 顺序依赖落库保序约定)。 - 待修项回流 todo: 无。
CR-260616-20 batch47 UX-08 新建对话确认弹窗 — ✅ 已审(PASS)
- 审查时间: 2026-06-16(独立审查 agent · 亲跑 vue-tsc + cargo check 双 EXIT 0 + grep/read 核验 10 件)
- 总体结论: ✅ PASS — 🔴0 🟡0 ⚪0
- 亲跑
npx vue-tsc --noEmit+cargo check --workspace✅ — 双 EXIT 0(cargo 5 warning 全既存 dead_code NodeExecutionRepo 非本批)。 - 独立核验(10 件):confirmNewConversation handler(AiChat.vue:832-837 非生成态零行为变化直接 newConversation)/ isViewingGenerating 双条件(:965-967 streaming && generatingConvId===activeConversationId,切走后台不弹)/ store 三字段(ai.ts:47/51/59)/ 两处按钮改造(:18 侧栏 + :206 聊天头,登记写 L176 实际 L206 行号偏差)/ ConfirmDialog 复用(:812 useConfirm Promise + 并发覆盖防永挂 useConfirm.ts:39)/ store.newConversation 软复位(useAiConversations.ts:31-41 B-260615-10 零改)/ commands.rs 零改(git diff 仅 batch46 ai_conversation_export)/ i18n zh/en(:84)/ Esc 优先级协同(:1099-1105 confirmState.visible 时 Esc 视取消 resolve Promise 不卡死)/ 零越界(batch43/45/36/47 独立区块)。
- 待修项回流 todo: 无(W-1 low 范围偏差 L176→L206 代码正确)。
CR-260616-14 batch41 Sidebar+ai store 域(UX-06 store 契约 + B-45 内存泄漏核查) — ✅ 已审(PASS)
- 审查时间: 2026-06-16(独立审查 agent · 亲跑 cargo check + vue-tsc 双 EXIT 0 + grep/read 核验 7 件)
- 总体结论: ✅ PASS — 🔴0 🟡0 ⚪3(filteredConversations .slice() 冗余 + sort 稳定性依赖 + 范围声明与实际不符,均 low 非阻塞)
- 亲跑
cargo check --workspace+npx vue-tsc --noEmit✅ — 双 EXIT 0(cargo 4 warning 全既存 dead_code 非本批;vue-tsc 零 TS error,AiChat.vue:940computed<AiConversationSummary[] | null>类型精确匹配)。 - 独立核验(7 件):
- filteredConversations 契约(stores/ai.ts:118-125)✅ — 空 query trim→null 信号(消费方走原分组)/ 非空
filter((c.title ?? '').toLowerCase().includes(q))case-insensitive 全量(活跃+归档)/ updated_at DESC(b 在前为正,正确)。 - 类型契约(api/types.ts:237-247)✅ AiConversationSummary 含 title?/archived/updated_at 类型安全。
- useAiStore 返回值(stores/ai.ts:136)✅ filteredConversations 已展开。
- i18n 3 key(zh/en aiChat.ts:13-15)✅ searchPlaceholder/searchEmpty/searchShortcutHint 双语对称。
- UI 接线已落地(AiChat.vue:22-64/939-941)✅ — v-model 双绑 + 搜索态平铺 + 空态提示 + computed 解包 store.filteredConversations.value;搜索项含 archive/delete 操作与原分组对称(登记称「留 batch43」,实际 batch41 已完整接线)。
- B-45 stopListener(useAiEvents.ts:344-354)✅ — 三 listener unlisten + 置 null + clearStreamWatchdog + clearAllToolSlowTimers + clearAllApprovalTimers 五件全清。
- B-45 AiChat onBeforeUnmount(AiChat.vue:1172-1191)✅ — removeEventListener keydown + cancelAnimationFrame + 拖拽 listener + body class + store.stopListener() + _unlistenToolSlow + _toastTimer/_titleFlashTimer/_providerBarTimer 八件全清。
- MESSAGE_CAP=200(stores/ai.ts:36/94-105)✅ — watch 拦 push 式增长(>CAP)splice 最早条;切对话整体替换放行。
- filteredConversations 契约(stores/ai.ts:118-125)✅ — 空 query trim→null 信号(消费方走原分组)/ 非空
- ⚪ 观察项(均 low):
- OBS1(冗余 slice):stores/ai.ts:123
.slice()紧跟.filter(),filter 已返新数组,slice 冗余拷贝。无功能影响,建议删。 - OBS2(sort 稳定性):updated_at 相等返回 0 依赖 V8 ES2019+ stable sort。会话同秒 updated_at 概率低,无可见问题。
- OBS3(范围声明不符):登记称「UI 接线留 batch43」,实际 batch41 已完整接线。建议主会话核对 batch43 是否剩 i18n 微调。
- OBS1(冗余 slice):stores/ai.ts:123
- 待修项回流 todo: 无(三观察项均 low 非阻断)。
CR-260616-13 batch40 CR-06 审查观察项测试补强(②-3/②-4 回调测试 + ignored 确认) — ✅ 已审(PASS)
- 审查时间: 2026-06-16(独立审查 agent · 亲跑 cargo test + grep/read 核验)
- 总体结论: ✅ PASS — 🔴0 🟡1⚪1(in_progress 失败→todo 状态机拦截产品决策点 med 非阻断 + 测试数登记 +9 vs +10 low)
- 亲跑
cargo test -p df-nodes -p df-workflow✅ — df-nodes 67 passed + 1 ignored(57→67,+10 测试)/ df-workflow 23 passed,双 EXIT 0。 - 独立核验:
- ignored 确认(ai_node.rs:298-300)✅ — glm_live_complete 真调 GLM 集成测试,合理 #[ignore] 保留。
- 9 新测试覆盖 ✅ — 3 templates callback(task_workflow_templates:callback 边界 + reject 可达性)+ 6 advance callback(task_advance_node:regression_target 镜像契约 + 三段 completed→target/failed→regression/非终态跳过 + 落库验证);镜像契约机制防 workflow.rs:44-51 私有函数回归漂移。
- 🟡 观察项 O1(med,产品决策点,非本批引入):in_progress 模板失败→regression_target 给 todo,但状态机
backward_to_todo_rejected全拒(task_state_machine.rs:188-193)→ 回调 advance_task_atomic 被 InvalidState 拦截(task_advance_node.rs:72-77),任务原地保留 in_progress。三选项待定:a 接受现状补 UI 提示 / b 改regression_target("in_progress")=None对齐状态机 / c 放开 todo 回退(破坏闸门)。本批测试显式锁定此行为(防回归)。 - ⚪ 观察项 O2(low):测试数登记 +9 vs 实测 +10(差 1 来自既有测试重构,非问题)。
- 待修项回流 todo: CR-13-O1 产品决策点(非代码 bug,回流 todo 作产品决策 a/b/c)。
CR-260616-12 batch38 settings 域(F-01 agentic max 可配) — ✅ 已审(PASS)
- 审查时间: 2026-06-16(独立审查 agent · 亲跑 cargo check + vue-tsc 双 EXIT 0 + grep/read 核验 15 件)
- 总体结论: ✅ PASS — 🔴0 🟡0 ⚪3(前端默认值硬编码 + clamp 回写闪现 + batch45 演进关系,均 low 非阻塞)
- 亲跑
cargo check --workspace+npx vue-tsc --noEmit✅ — 双 EXIT 0(cargo 7 warning 全既存 dead_code;并发 batch45 改动共存编译通过)。注:审查中一次中间态 grep 抓到 batch45 改 mod.rs 时的 E0599,最终完整重跑 EXIT 0 确认 batch38 本身无错。 - 独立核验(15 件):1:1 严格复刻 llm_concurrency 范式(AppState 字段
agent_max_iterations: Arc<AtomicUsize>+ init 引用DEFAULT_MAX_AGENT_ITERATIONS=10(agentic.rs:33)/run_agentic_loop(max_iterations)形参(agentic.rs:101)+0..max_iterations循环(agentic.rs:159)+ 达上限 warn+emit(agentic.rs:324-336)+ 3 调用点 ai_chat_send(commands.rs:103-106)/ai_regenerate(commands.rs:197-200)/try_continue_agent_loop(agentic.rs:477-487)load 快照透传 /ai_set_agent_max_iterationsclamp 1-50 store(commands.rs:812-821)/ lib.rs:130 注册 / api/ai.ts:87-89 / Settings.vue input min1 max50(:275-277)+ watch 持久化 SQLite(:663-665)+ syncAgentMaxIterations Math clamp+debounce 300ms(:694-705)+ onMounted 同步防刷新(:766-775)/ onUnmounted 清 _agentIterTimer(:778-783)/ i18n 双语(settings.ts:79-80))。grep 确认无第 4 调用点遗漏。 - ⚪ 观察项(均 low):
- OBS1(前端默认值硬编码):Settings.vue:628/696 前端
appSettings.get(..., 10)默认 10 与后端 const 巧合一致但非引用同一常量。范式层面观察(llm_concurrency 亦如此)。 - OBS2(clamp 回写闪现):clamp 前瞬时值短暂显示后修正,视觉闪现功能无损。
- OBS3(batch45 演进):lib.rs:111 注释 F-260616-03 标注 max_iterations 是 batch45 暂停态续跑前置基础,两批共存编译通过,演进链路清晰。
- OBS1(前端默认值硬编码):Settings.vue:628/696 前端
- 待修项回流 todo: 无。
CR-260616-11 batch39 ai chat 性能域(AR-8 节流评估 + B-53 snap 优化) — ✅ 已审(PASS)
- 审查时间: 2026-06-16(独立审查 agent · 亲跑 cargo check + vue-tsc 双 EXIT 0 + grep/read 核验 6 件)
- 总体结论: ✅ PASS — 🔴0 🟡0 ⚪2(增量缓存 quirk + deep watch 每帧检测,均 low 非本批)
- 亲跑
cargo check --workspace+npx vue-tsc --noEmit✅ — 双 EXIT 0(cargo 5 warning 全既存 dead_code;首跑增量缓存陈旧报 AiMaxRoundsReached not found E0599,清缓存重跑 EXIT 0 确认)。 - 独立核验(6 件):
- AR-8 前端滚动跟随已存在(AiChat.vue:1110-1114 isNearBottom 80px + 234 onMessagesScroll + 412 showBackToBottom + 1139-1147 onContentChange)✅ — 完整存在,非本批新增。
- AR-8 后端 50ms 合批未改(stream_recv.rs diff 纯 B-42 error_type 映射,零 batch39 改动)✅ — 负 ROI 论证成立(rAF ARC-08 吸收重 parse + heartbeat 语义冲突 + flush 复杂度)。
- B-53 snap 优化正确性(AiChat.vue:1207-1210)✅ — 新 snap
{n: msgs.length, tc: msgs.map(m=>(m.toolCalls||[]).map(t=>({id,s})))}剔除冗余 id/role/len;body 逻辑(AiChat.vue:1218-1226)仅读 toolCalls 状态从不读被删字段 → 行为安全;流式 delta 不再触发 body 重算(toolCall 状态翻转/splice 淘汰仍触发)。 - B-53 短路 + deep + MESSAGE_CAP 全保留(AiChat.vue:1213 snap===lastMsgSnapshot return + 1233 deep:true + ai.ts:36/94-105 MESSAGE_CAP=200 length watch 独立)✅。
- B-53 自包含(lastMsgSnapshot 模块局部无外部消费 + collapseInactive 唯一调用点)✅。
- 注释清晰度(AiChat.vue:1197-1202)✅ — 与代码完全吻合无误导。
- ⚪ 观察项(均 low):
- OBS1(缓存 quirk):增量缓存陈旧致首跑 E0599,清缓存重跑过。建议 CI 走 cargo check 不带增量。
- OBS2(deep watch 每帧检测):B-53 仅短路 body,deep watch 本身仍每帧遍历 messages 含 content,极长流式消息 O(n) 成本。rAF 节流已吸收,非阻塞。
- 待修项回流 todo: 无(纯优化批次,零回归)。
CR-260616-10 batch36 ai chat UX 域聚合(UX-02 消息操作栏 + UX-03 错误气泡操作入口) — ✅ 已审(PASS)
- 审查时间: 2026-06-16(独立审查 agent · 亲跑 cargo check + vue-tsc 双 EXIT 0 + 独立重跑双 check 佐证)
- 总体结论: ✅ PASS — 🔴0 🟡2⚪3(ai_regenerate 并发竞态 med + commands.rs 时序倒置 med 非阻断 + 3 low)
- 亲跑
cargo check --workspace+npx vue-tsc --noEmit✅ — 双 EXIT 0(vue-tsc 首跑报 useAiEvents.ts 缺 import 已补后过;agent 独立重跑双 check 佐证)。 - 独立核验:
- UX-02 操作栏(AiChat.vue hover
.ai-msg-actions仅末条 复制+重新生成)✅。 - ai_regenerate IPC(commands.rs)✅ — generating 占用校验 + pop_last_assistant_round + save + spawn run_agentic_loop 复用 + active_conversation_id 一致性。
- pop_last_assistant_round(context.rs)✅ — 空 false / 末尾 user popped_any=false / 跨多轮 tool 三元组一并弹不留半截 / history_tokens saturating_sub。
- UX-03 错误气泡(useAiEvents.ts AiError push 附 errorType
as AiMessagecast B-42 零改 + AiChat.vue 错误气泡重试复用 regenerate + canOpenSettings 判 auth|provider_config)✅。 - 只读消费 B-42(ErrorType/AiErrorType/AiError 零改)✅。
- UX-02 操作栏(AiChat.vue hover
- 🟡 观察项(待修项,细节见 agent transcript):
- CR-10-A(med 时序倒置):commands.rs:56/59 save 与 emit 时序。回流 todo。
- CR-10-B(med 竞态):ai_regenerate 并发竞态窗口。回流 todo。
- ⚪ 观察项:代码块复制按钮(v-html 子元素无法绑事件)遗留单独立项;AI 气泡内嵌复制与新操作栏复制共存最小改动;context.rs 跨 crate(df-ai)改动非 batch35/37 锁。
- 待修项回流 todo: CR-10-A(commands.rs:56/59 时序倒置 med)+ CR-10-B(ai_regenerate 竞态 med)。
CR-260616-09 batch37 knowledge 域(F-260616-02 pending_review 归位·决策 a) — ✅ 已审(PASS)
- commit: 工作区未 commit(攒批)
- 审查时间: 2026-06-16(独立审查 agent · 亲跑 cargo check + cargo test df-storage 双 EXIT 0 + grep/read 核验)
- 总体结论: ✅ PASS — 🔴0 🟡0 ⚪2(注释遗留 + list_non_archived dead_code,均 low 非阻塞)
- 亲跑
cargo check --workspace+cargo test -p df-storage✅ — 双 EXIT 0;df-storage 11 passed(含list_by_status_orders_by_confidence_semantics/list_by_status_filters_other_status/test_knowledge_cols_matches_record三个 knowledge 测试)。 - 独立核验(5 件):
- knowledge_list 默认收窄(knowledge.rs:78-88)✅ — L86
None => list_by_status("published")收窄纯 published;L84Some(s) => list_by_status(&s)显式透传(含 archived)向后兼容。 - 调用方零破坏 ✅ — grep list_non_archived src-tauri/ 零 Rust 调用方;前端 src/api/knowledge.ts:15 + stores/knowledge.ts:61-66 loadList 默认 undefined→null→后端默认 published;Knowledge.vue:275/522 loadList() 无参走默认。无调用方依赖旧「非 archived 全部」语义。
- merge_by_confidence 排序等价(knowledge.rs:238-273)✅ — rank high=3/med=2/low=1/_=0 + created_at DESC,与 SQL(crud.rs:1363-1368
CASE...DESC, created_at DESC)权重+方向一致;双指针合并前 a/b 各 sort_by(cmp) 重排保证有序(L257-258);相等时 candidate 优先(L262)结果确定。 - 向后兼容 ✅ — published 仍在 library(默认)/ archived 显式可查
knowledge_list(status='archived')/ candidate+pending_review 仍在 inbox(knowledge_list_candidates L226-231)。 - i18n 文案(zh/en knowledge.ts)✅ — inbox「待处理」/「Pending」+ 注释说明 F-260616-02 决策 a。
- knowledge_list 默认收窄(knowledge.rs:78-88)✅ — L86
- ⚪ 观察项:
- OBS1(low 注释遗留):Knowledge.vue:17/250 注释仍写「审核收件箱」,i18n 已改「待处理」;注释与新语义不符,建议清理(不影响功能)。
- OBS2(low dead_code):crud.rs:1478
list_non_archived()收窄后全仓零调用方,成孤儿方法(编译器未报,宏展开 pub method 豁免);建议删除或#[allow(dead_code)]。
- 文档遗留(非代码,仅记录):df-knowledge 模块文档 L52/58/174-175 + 经验记录 L78 + df-storage 存储层文档 L114 多处 knowledge_list 默认语义/list_non_archived 描述过时,建议后续清理批次更新。
- 待修项回流 todo: 无(代码层面 PASS;两 low 观察项 + 文档遗留均非阻塞)。
CR-260616-08 batch35 TaskDetail 域聚合(①-1 工作流推进按钮 + B-41 进度内嵌 + ①-3 i18n) — ✅ 已审(PASS)
- commit: 工作区未 commit(攒批)
- 审查时间: 2026-06-16(独立审查 agent · 亲跑 cargo check + vue-tsc 双 EXIT 0 + grep/read 核验)
- 总体结论: ✅ PASS — 🔴0 🟡1⚪1(blocked 态推进按钮语义偏差 med 非阻断 + lib.rs 混入非本批 low)
- 亲跑
cargo check --workspace+npx vue-tsc --noEmit✅ — 双 EXIT 0(4 warning 均既有 dead_code 非本批)。 - 独立核验(11 件,grep + read 亲验):
- workflow.rs 空dag选模板补丁(L81-89)✅ — 三分支覆盖(nodes 非空→原 dag / 空+target Some→template_for 失败 Err「无对应工作流模板」 / 空+target None→原 build_dag);与 CR-06 审的 ①-1 同代码,描述差异源于措辞非冲突;dag shadowing 后 build_dag + dag_json 序列化落库实际拓扑(L92/L96)。
- ②-3/②-4 回调(workflow.rs:232-233/267-305)✅ — cb_task_id/cb_target_status clone move;completed→Some(target)/failed→regression_target/任一 None→跳过;回调失败 warn 不回滚;advance_task_atomic 签名匹配(task_advance_node.rs:43-47)。
- regression_target(workflow.rs:44-51)✅ — testing→in_review/in_review→in_progress/in_progress→todo/其他→None。
- TaskDetail.vue 推进按钮(L71 v-if wfAdvanceAction / L260-267 computed / L77 disabled 互含 / L288-310 handleWorkflowAdvance 空 dag+taskId+target 调 run)✅。
- B-41 进度内嵌 + exec_id 过滤(L174-184 状态 / L313-314 过滤 / L411 onMounted + L419 onBeforeUnmount 生命周期对称)✅。
- 多 listen 无冲突(workflow.ts:37-41 独立 unlisten / store 收全量 vs TaskDetail 按 exec_id 过滤,职责隔离)✅。
- 进度轻量近似(L318-323 total 用已启动+已完成,前端无法预知后端模板节点数)✅。
- template_for 对齐 ADVANCE_MAP(三模板 in_progress/testing/done;in_progress 态 primary=in_review 无模板不显示)✅。
- 与手动 advance 并存(L54-64 手动保留 / L66-91 工作流独立区块)+ 不碰 router/lib.rs listen ✅。
- i18n 8 key 对称(zh/en taskDetail.ts:41-49)✅。
- 🟡 观察项 O1(med,非阻断):blocked 态推进按钮语义偏差 — blocked→in_progress primary(TaskDetail.vue:241-243)WF_SUPPORTED_TARGETS.has('in_progress')=true 显示按钮,但 template_for('in_progress') 是「todo→in_progress 从头执行」拓扑,blocked 恢复语义不该重跑 AiNode。UX 需确认(blocked 态应排除工作流推进或用恢复语义)。非阻断(后端回调逻辑正确,仅前端按钮显示语义)。
- ⚪ 观察项 O2(low):lib.rs +3 行(ai_regenerate L107 / list_tool_executions L127-128)非 batch35 范畴,属其他批混入工作区,不影响本批正确性。
- 待修项回流 todo: 无(均观察项非阻断;O1 blocked 语义建议主会话评估是否前端排除)。
CR-260616-07 batch34 并行(B-42 错误码统一 + AE-08 审批历史面板) — ✅ 已审(PASS)
- commit: 工作区未 commit(攒批)
- 审查时间: 2026-06-16(独立审查 agent · 亲跑 cargo check + vue-tsc 双 EXIT 0 + grep/read 核验源码)
- 总体结论: ✅ PASS — 🔴0 🟡0 ⚪2(既存死代码 warning + B-42 前端消费未改留 batch36)
- 亲跑
cargo check --workspace+npx vue-tsc --noEmit✅ — 双 EXIT 0(output L68/L69);cargo check Finished 48.33s(L67);6 warning 全既存死代码(df-ai finish_reason/call_type/SessionState/session_state + devflow risk_level/releases/node_executions never read),非本批引入。 - 独立核验(B-42 + AE-08,grep 定位 + read 亲验源码):
- B-42 ErrorType 枚举(mod.rs:73-86)✅ —
#[serde(rename_all = "snake_case")]+ 5 variant(Auth/Network/Timeout/ProviderConfig/Unknown),每个文档注释说明分类标准;ProviderConfig→provider_config 与 types.ts:188 联合对齐。 - B-42 AiError variant(mod.rs:107-112)✅ —
error_type: Option<ErrorType>,None 表示未分类(向后兼容旧 emit 点,L106 注释)。 - B-42 8 emit 点映射(agentic.rs×3 + stream_recv.rs×5)✅ — Auth(agentic:128)/ ProviderConfig(agentic:452)/ Unknown(agentic:333 + stream_recv:207)/ Timeout(stream_recv:163)/ Network(stream_recv:237/278)/ None(stream_recv:308 建连失败混合源难精确归类)。映射合理。
- AE-08 list_recent SQL(crud.rs:1246-1275)✅ — 专用 SELECT
ORDER BY requested_at DESC LIMIT ?1 OFFSET ?2(L1259),绕过通用 query 宏(后者硬编码 ORDER BY created_at,本表无该列);limit.min(200) 钳制防滥用(L1253)。 - AE-08 list_tool_executions IPC(audit.rs:85-113)✅ — limit.unwrap_or(50)/offset.unwrap_or(0) → list_recent → DTO map;敏感字段截断(arguments_brief 120 / result_brief 160,L104-105),完整原值留库。
- AE-08 truncate_chars(audit.rs:71-78)✅ —
chars().count()<=max早返回 / 否则chars().take(max)+ push '…';按 char 边界切,中文/emoji 安全。 - AE-08 lib.rs 全路径注册(lib.rs:128)✅ —
commands::ai::audit::list_tool_executions,避碰 mod.rs glob,零行为破坏。 - AE-08 AuditLog.vue(src/views/AuditLog.vue:1-311)✅ — 7 列表格 + 分页(prevPage/nextPage + hasMore=records.length===PAGE_SIZE + 空页回退 L105-108)+ 风险/状态/决策者标签映射;类型内联(ToolExecutionRecord interface L69-83,注释「不进 api/types.ts 避撞 B-42」)— 无 types.ts 交叉污染(关注点② ✅)。
- B-42 前端消费(关注点③)✅ — grep
error_typesrc/ 仅 types.ts:204 定义,零消费逻辑(AiChat.vue 等不读 error_type);UX-03 留 batch36 符合任务约定。
- B-42 ErrorType 枚举(mod.rs:73-86)✅ —
- ⚪ 观察项:
- 既存死代码 warning(cargo check 6 个,df-ai/devflow never read)— 非本批引入,既存 lint 级;建议后续清理(SessionState/risk_level 可能阶段2 未接入死代码)。
- B-42 前端消费未改(关注点③已知)— error_type 字段已加但前端暂不读,UX-03 留 batch36;字段先落地不阻塞,合理。
- 待修项回流 todo: 无(B-42 + AE-08 全过,零缺陷;两观察项均已知/既存非回归)。
CR-260616-06 batch33 阶段2 续(②-2/②-3/②-4 run_workflow task_id+回调 + ②-6 DAG模板) — ✅ 已审(PASS)
- commit: 工作区未 commit(攒批,与 batch32 + 决策记录 + todo/待审查 混合)
- 审查时间: 2026-06-16(独立审查 agent · 亲跑 cargo test 全量无 tail + grep/read 核验源码)
- 总体结论: ✅ PASS — 🔴0 🟡0 ⚪2(df-nodes 1 ignored + ②-3/②-4 回调无集成测试)
- 亲跑
cargo test -p df-workflow -p df-nodes 2>&1(无 tail 全量) ✅ — exit 0;df-nodes 58 running → 57 passed; 0 failed; 1 ignored(output L30/L90,比 CR-05 的 53 多 4 模板测试);df-workflow 23 passed; 0 failed(L119);无 FAILED 无 error[。去 tail 修正 CR-05 截断问题。 - 独立核验(四件 + ①-1,grep 定位 + read 亲验源码):
- ②-2 run_workflow 参数 + 前端透传(workflow.rs:64-73 + api/workflow.ts:16-24 + stores/project/workflow.ts:23-26)✅ — 签名
task_id: Option<String>, target_status: Option<String>(L71-72)向后兼容;WorkflowRecord.task_id 填入(L104);前端 apitaskId?/targetStatus?(L16-17)snake_case 透传(L23-24);stores runWorkflow 透传(L26)。旧调用方不传→None 零行为破坏。 - ②-3 完成回调(workflow.rs:267-273)✅ —
match (cb_task_id.as_ref(), cb_target_status.as_deref())都 Some → completed 分支advance_target=Some(target)(L270)→ advance_task_atomic 推进到 target_status。 - ②-4 失败回调退回(workflow.rs:44-51 + 271)✅ — failed 分支
advance_target=regression_target(target);映射 testing→in_review / in_review→in_progress / in_progress→todo / _(done/blocked/cancelled)→None;in_progress→todo 回起点符合「执行未达预期重排」直觉(L42-43 注释);起点 todo 无可退态→None。 - 回调解耦语义(workflow.rs:274-294)✅ — advance_task_atomic Err 仅
tracing::warn!不回滚(工作流 status 已写库 L251,回调失败不撤销);TaskRepo::new(&db)(L275)复用 db Arc(clone 廉价)。 - ②-6 DAG 模板(task_workflow_templates.rs:1-152)✅ —
template_for(target)match in_progress/testing/done→Some,else None(L28-35)不 panic;三模板拓扑正确(in_progress 单 ai / testing ai→human 串行 / done 单 human);testing+done 模板 human options 含「拒绝」(L67/L85)触发 ②-5 reject→Err→failed→②-4 退回闭环;config 留空由 run_workflow 全局注入+deep_merge(衔接 ④-1 L14-17);4 测试断言拓扑(unknown_none/single_ai/ai_to_human_serial 含「拒绝」断言 L141/single_human)。 - ①-1 DagDef 来源选模板(workflow.rs:81-89)✅ — dag.nodes 空+target Some→
template_for(L83).ok_or_else(Err「无对应工作流模板」)(L84);空+None→原路径 build_dag 校验(L85);非空→用传入 dag(L88)。三分支零行为破坏。
- ②-2 run_workflow 参数 + 前端透传(workflow.rs:64-73 + api/workflow.ts:16-24 + stores/project/workflow.ts:23-26)✅ — 签名
- ⚪ 观察项:
- df-nodes 1 ignored(L90,running 58 / passed 57 / ignored 1)— 登记关注点提「57 passed」未提 ignored;ignored 非 failed,建议确认哪个测试
#[ignore](可能慢/集成测试留阶段2收尾)。 - ②-3/②-4 回调无集成测试(登记关注点②已知)— 已亲核 diff 逻辑正确(match 分支 + 解耦语义),但缺自动化回归保护;回调失败/退回路径仅靠代码审查覆盖。建议阶段2 收尾补集成测试(workflow 跑通→advance 验证)。
- df-nodes 1 ignored(L90,running 58 / passed 57 / ignored 1)— 登记关注点提「57 passed」未提 ignored;ignored 非 failed,建议确认哪个测试
- 待修项回流 todo: 无(四件+①-1 全过,零缺陷;两观察项均已知/非回归)。
CR-260616-05 batch32 阶段2 首批(④-1 config 下沉 + ②-1 注册 TaskAdvanceNode + ②-5 HumanNode reject) — ✅ 已审(PASS)
- commit: 工作区未 commit(攒批,与 CR-03/04 + 决策记录 + todo/待审查 混合)
- 审查时间: 2026-06-16(独立审查 agent · 亲跑 cargo test + grep/read 核验源码,不信 agent 自报"53 passed")
- 总体结论: ✅ PASS — 🔴0 🟡0 ⚪1(df-nodes 测试数字 tail 截断未单独显示,exit 0 佐证通过)
- 亲跑
timeout 240 cargo test -p df-nodes -p df-workflow✅ — exit 0;df-workflow 23 passed(deep_merge_tests 6 + executornode_config_overrides_global_in_node_context1 + cancelled 3 + state/conditions 13);df-nodes human_node reject 测试 exit 0 保证通过(tail -40截断未单独显示数字,exit code 0 佐证)。不信 agent 自报「53 passed」。 - 独立核验(三件,grep 定位 + read 亲验源码):
- ④-1 deep_merge config 下沉(dag.rs:169 + executor.rs:105 + registry.rs:52)✅ —
deep_merge(global,node)(dag.rs:169-188):两端 Object 递归合并(同 key 节点级覆盖 L177-181 / 新 key 保留 L179);非两端 Object 节点级直接覆盖 L186(含 Null 显式空);Null 语义=显式置空覆盖(非跳过),跳过=不写 node_configs key(L164-165 注释)符合登记关注点。NodeContext.config(executor.rs:105-108):match node_configs.get(id) → Some(deep_merge(global,node_cfg)) / None(initial_config.clone());缺失 key 走 None 分支 == 旧 initial_config.clone() 零行为破坏。build_dag(registry.rs:52):add_node_with_config(id,node,node_def.config.clone())下沉 NodeDef.config。- 5 维度全测过:同 key 胜(
node_overrides_global_scalar)/ 新 key 留(node_adds_new_key)/ 嵌套递归(nested_object_recursive_merge)/ 非 Object 覆盖(array_replaces_not_concat)/ 空 no-op(empty_node_config_is_noop+missing_node_config_treated_as_noop)。 - 零行为破坏:HumanNode/AiNode/TaskAdvanceNode 节点 NodeDef.config 空/缺失 → node_configs 无 key → None 分支 == 旧行为。
- ②-1 TaskAdvanceNode 注册(state.rs:235-250)✅ —
build_registry(db: Arc<Database>)改签名;register("task_advance", move |_| Box::new(TaskAdvanceNode::new(db.clone())))(state.rs:248-250)move 闭包捕获 Arc,clone 满足 Send+Sync+'static 满足 NodeFactory 签名;initbuild_registry(db.clone())(state.rs:194)db move 前 clone 避 E0382。D-03 走 df-nodes Node trait 对齐。 - ②-5 HumanNode reject 语义化(human_node.rs:19-132)✅ —
REJECT_KEYWORDS(L19-22):中「拒绝/驳回/退回/否决」+ 英「reject/decline/declined/deny/denied/no/block」。is_reject_decision(L25-28):trim+to_lowercase 归一化 + 整词精确匹配(== 非 contains) → 防「拒绝啦」误伤(测 L846!is_reject_decision("拒绝啦")✅)。contains_reject(L31-33):picked 任一命中即拒(多选场景)。- execute(L120-132):
count_ok && each_valid后contains_reject→ Err「人工审批被拒绝(用户选择: X);意见: Y」。 - 边界全过:整词防误伤(L846)/ 大小写空白归一(L836-838)/ 多选任一即拒(L848)/ options 空自由文本仅拒绝词 Err 其余 Ok(L13-15 注释)/ 行为变更 Ok→Err executor set_failed → 工作流 failed(L113-119 注释)。
- ④-1 deep_merge config 下沉(dag.rs:169 + executor.rs:105 + registry.rs:52)✅ —
- ⚪ 观察项:亲跑命令带
tail -40截断 df-nodes 测试数字(仅见 df-workflow 23 passed);exit code 0 佐证 df-nodes+df-workflow 全过,非缺陷。后续审查亲跑命令去 tail 或用2>&1 | tee保全量。 - 待修项回流 todo: 无(三件全过,零缺陷)。
CR-260616-03 batch30 ①类修复批次 — ✅ 已审(PASS)
- commit: 工作区未 commit(攒批,与 CR-04 混合;CR-03 范围文件干净,无阶段2混入)
- 审查时间: 2026-06-16(独立审查 agent · 亲核 diff + grep 核验,不信主代理"46 passed"声明)
- 总体结论: ✅ PASS — 🔴0 🟡0 ⚪1(set_cancelled TOCTOU 低频,条目已知)
- 独立核验(CR-03 登记范围 8 文件,范围干净):
- CR-01-D 错误区分(task_advance_node.rs)✅ — 同态(from==to)→
Validation("相同状态,无需推进");非法转换→InvalidState{current:"X→Y(非法状态转换)"};NotFound(任务不存在)。三类拒绝错误类型+消息区分,前端 e.toString() 可分辨。3 测试 assert 含 from→to 上下文(illegal_skip/terminal_done/same_status)。 - CR-03-b openai source 链 ✅ —
Error::from(e).context("SSE 流错误: {e}"),Display=ctx 逐字节等价旧anyhow!,source 链保留。同 CR-04 anthropic 模式。 - CR-03-a resolve_project_label 三臂(audit.rs)✅ — Ok(Some)→项目名 / Ok(None)→"项目已不存在" / Err→裸 id+warn(DB 故障不误报"已不存在"误导用户)。
- CR-03-c ToolCard find(ToolCard.vue)✅ — computed Map(O(N)建表)→function find(O(N)提前退出);2 调用点
.value[id]→(id)改对。 - CR-01-G i18n 分级(TaskDetail.vue + i18n×2)✅ — loadFailed/advanceFailed 加
{msg}插值;catch 走t(key,{msg:e.toString() ?? common.unknownError});common.unknownError zh/en 均有(common.ts:16)。关注点② 前端兜底可显示区分消息 ✅。 - 复核-新④ 终态守卫(workflow.rs cancel_workflow_node)✅ — get→match:Pending/Running/Waiting→set_cancelled+Ok;终态→Err"已终态,无法取消"。行为变更正确(避免静默覆盖终态)。
- CR-01-D 错误区分(task_advance_node.rs)✅ — 同态(from==to)→
- ⚪ 观察项(关注点①,条目已知"低频忽略"):
set_cancelled(state.rs:95-100)无终态守卫,直接insert(Cancelled)。IPC 层 get→match→set_cancelled 两步非原子:理论 TOCTOU 窗口(get 时 Pending 通过守卫,set 前被 executor 并发改终态)→ set_cancelled 强制覆盖终态(如 Completed→Cancelled 语义错位)。实际低频(人工取消+executor 竞争窗口极小)+ 最坏仅终态语义错位不崩溃 → med 观察项,IPC 守卫是主防线。彻底修需 set_cancelled 加守卫或 CAS,低频场景成本不值。 - 待修项回流 todo: 无明确缺陷;TOCTOU 属条目已知低频观察,非 CR-03 引入回归。
CR-260616-04 batch31 NodeCancelled 事件语义+anthropic source链 — ✅ 已审(PASS)
- commit: 工作区未 commit(攒批,与 CR-03 + 阶段2 混合)
- 审查时间: 2026-06-16(独立审查 agent · 亲跑 test + grep 核验,不信主代理"16 passed"声明)
- 总体结论: ✅ PASS — 🔴0 🟡0 ⚪1(前端消费缺失,既存非回归)+ ⚠ 范围混淆 flag
- 独立核验(CR-04 登记范围 4 文件):
- events.rs NodeCancelled variant ✅ —
NodeCancelled { node_id }(events.rs:64)新增,语义注释清晰(取消=用户主动 vs NodeFailed=失败)。 - executor.rs is_cancelled 二分 ✅ — L148-166:取消节点→emit NodeCancelled(不 set_failed,Cancelled 已终态避免非法转换崩溃);失败节点→set_failed+NodeFailed。零行为破坏(工作流仍 Err 中止,first_err 逻辑保留)。
- types.ts node_cancelled ✅ — L128 手对齐后端 serde(rename_all snake_case)。
- anthropic_compat.rs:533 Display 不变 ✅ —
Error::from(e).context(format!("Anthropic SSE 错误: {}", e)),Display = context = "Anthropic SSE 错误: {e}" 逐字节等价旧anyhow!;e 进.source()链可追溯。 - 亲跑
cargo test -p df-workflow✅ — 23 passed(主代理称 16,实际 23 含 deep_merge/node_config 新测试),关键test_cancelled_node_emits_node_cancelled_event✅ 验证取消节点 emit NodeCancelled 非 NodeFailed。 - claim③ 无穷举 match ✅ — 亲 grep:后端无
match WorkflowEvent穷尽(全 matches!/构造点),前端 src/ node_failed/node_cancelled 仅 types.ts 定义无消费。
- events.rs NodeCancelled variant ✅ —
- ⚠ 范围混淆 flag(重要):executor.rs 工作区 diff 混入阶段2 config 下沉(L99-108 deep_merge 全局+节点 config + EchoConfigNode 测试 + node_config_overrides 测试),非 CR-04 范围(未登记待审查)。该部分 test 过但不属本批结论覆盖。阶段2 改动(
dag.rs+102 /human_node.rs+46 /state.rs+21 / executor.rs config 部分)需主会话另立 CR-05 登记后审。 - ⚪ 观察项(既存非回归,建议评估):NodeCancelled 事件前端无消费逻辑(src/ 仅 types.ts 类型定义,无 switch/render 处理)。取消节点用户暂无"已取消"区分 UI 反馈。非 CR-04 引入(CR-04 前 node_failed 前端亦不处理,属既存渐进式类型对齐),NodeCancelled 用户价值依赖后续补前端事件处理。
- 待修项回流 todo: 无明确代码缺陷;前端事件消费缺失属既存功能不完整,建议主会话评估是否立项(非 CR-04 待修项)。
CR-260616-02 batch29+R-PD-10 代码改动批次 — ✅ 已审(PASS)
- commit:
fc767e1优化: R-PD-10 storage_err DRY统一 + FR-D3 migrations if链转数组循环 - 审查时间: 2026-06-16(独立审查 agent · 亲跑 test 核验,不信主代理"全过"声明)
- 总体结论: ✅ PASS — 🔴0 🟡0 ⚪0
- 独立核验:
- 亲跑
cargo test -p df-storage✅ — crud 单元 22 passed + project_soft_delete 集成 11 passed,0 failed(覆盖 storage_err 替换后 CRUD / advance_status_atomic CAS / soft_delete / update_field 白名单 / migrations run() 全量路径)。主代理"11集成全过"声明属实,额外验证 22 单元。 - R-PD-10 storage_err DRY ✅ — helper
fn storage_err<E: ToString>(e: E) -> Error { Error::Storage(e.to_string()) }(crud.rs:19)泛型等价旧闭包;残留旧模式 grep =0(无遗漏);test 全过证 110 处替换无语义偏差。 - FR-D3 migrations if→数组循环 ✅ —
steps: [(i32, fn(&Connection)->Result<()>); 15](migrations.rs:43)+for (version, migrate_fn) in steps { if current_version < version { migrate_fn(conn)?; } },与原 15 个if current_version<N { migrate_vN(conn)?; }逐字等价(顺序[1..15]+条件<version+?传播)。零行为变更属实。 - migrate_vN 签名匹配 ✅ — migrate_v1~v15 全
fn(&Connection) -> Result<()>,与 steps 数组 fn 指针类型精确匹配。 - 迁移数据安全三层保留 ✅ — ①版本门控 run() 开头
MAX(version)(migrations.rs:29)②各 migrate_vN 末尾INSERT INTO schema_version(L67/75/.../274)③column_exists PRAGMA 幂等探测(L7-9,v4-v15 各处)。
- 亲跑
- 待修项回流 todo: 无(全过,无新缺陷)。
CR-260616-01-fixes 审查待修项修复批次 — ✅ 已审(PASS)
- commit:
4dc5219修复: CR-01审查待修项①类7项(CSS补 testing/blocked 徽章 + CAS防回收站推进 + DRY + 注释 + 测试改名 + advancing 接文案) - 审查时间: 2026-06-16(独立审查 agent · 防"主代理已核查双绿"代入,亲核源码)
- 总体结论: ✅ PASS — 🔴0 🟡0 ⚪2(范围外观察项)
- 独立核验(不信主代理核查声明,grep/read 亲验 7 项①类修复):
- ① CR-01-B CAS deleted_at 语义 ✅ —
soft_delete(crud.rs:800)设deleted_at非空;advance_status_atomic两分支(bump_rounds + 普通,crud.rs:855/859)均加AND deleted_at IS NULL,回收站任务affected=0 → None必败。D-02 软删收口正确,一处关闭。 - ② CR-01-A CSS 配色跨视图一致 ✅ — TaskDetail.vue:344-345 与 Tasks.vue:413-414 逐字镜像(testing 橙
#ff9800/ blocked 红danger 0.5px 边框),跨视图统一。 - ③ CR-01-C DRY 文案零变更 ✅ —
ALL_STATES(task_state_machine.rs:48-56)=["todo","in_progress","in_review","testing","done","blocked","cancelled"],join("/")= 原硬编码串,顺序与值全等,无运行时文案变化。 - CR-01-H 测试改名 ✅ —
cas_returns_none_when_status_mismatch(task_advance_node.rs:313)真存在,改名后注释指向正确的 CAS 失败测试。 - CR-01-I advancing i18n ✅ — zh/en 均有(taskDetail.ts:38
推进中.../Advancing...),TaskDetail.vue:63 接 advancing 态。 - CR-01-F 白名单注释 ✅ —
allowed_columns_for("tasks")(crud.rs:331)数组无review_rounds+ 注释说明专用路径(仅 advance_status_atomic),收口防误补。 - CR-01-E state.rs 注释 ✅ — build_registry(state.rs:240)注释准确,说明 TaskAdvanceNode 未注册原因(阶段2 DAG 联动时加)。
- ① CR-01-B CAS deleted_at 语义 ✅ —
- 范围外(记录不审,非本批缺陷):
- ⚪ 工作区 crud.rs 226行未提交 =
R-PD-10storage_errDRY 重构(提取包装函数替换散落.map_err(|e| Error::Storage(e.to_string()))),非4dc5219范围,独立审查留待其 commit。 - ⚪
status-abandonedCSS class ↔cancelled后端态 = 历史命名别名(constants/project.ts:83cancelled: 'status-abandoned'映射闭环,功能正确),非本批引入。命名语义瑕疵(abandoned 弃用 ≠ cancelled 取消),low,可选优化。
- ⚪ 工作区 crud.rs 226行未提交 =
- 待修项回流 todo: 无(7 项全过,无新缺陷)。
CR-260616-01 任务推进链(batch28) — ✅ 已审
- commit:
d2cb38c新增: 任务推进链(7态状态机+advance_task CAS原子写)+软删除+前后端7态对齐 - 审查时间: 2026-06-16(5 路并行多角度·防代入独立核验)
- 决策依据: D-260616-01(前端7态对齐) / D-02(任务软删除) / D-03(advance_task 走 df-nodes Node trait) / D-04(阶段1先行)
- 总体结论: 🔴0 🟡6 ⚪4 — 质量优
- 8 点 checklist 全过:
- 状态机矩阵 ✅(7态15转换,前后端单一真相源)
- CAS 原子 ✅(
WHERE id AND status=expected+ affected=0 报错) - 退回累加原子 ✅(同 UPDATE)
- V15 迁移幂等 ✅(版本门控 + column_exists 双层,八版零事故)
- IPC 真 thin ✅(D-03 守住,task.rs 仅 3 行转发)
- i18n 成对零遗漏 ✅(28 key)
- create_task 不暴露 rounds ✅
- 测试 25 个 ✅
- F-03 status 双写 = 已知延后(阶段3 三件同落地,非新缺陷)
- 范围外澄清: df-ai/lib.rs(
mod retry) + script_node.rs(shell_type) = 同 commit 无关连带改,零风险误报 - 待修项回流 todo(CR-01-A~I,见 todo.md "审查发现待修项"区):
- [P1] CR-01-A TaskDetail.vue scoped style 补
.status-testing/.status-blocked(D-01 前端7态遗漏,testing/blocked 徽章裸渲染丢样式;同查 Tasks.vue/Dashboard) - [P1] CR-01-B crud.rs advance_status_atomic CAS SQL 加
AND deleted_at IS NULL(get_by_id 宏无软删过滤→回收站任务可被 advance,D-02 软删对齐遗漏,语义错乱) - [P2] CR-01-C task_advance_node.rs:46 错误7态硬编码改
ALL_STATES.join("/")(DRY 单一事实源) - [P2] CR-01-D task_advance_node.rs:62 同态拒绝(from==to)/非法转换区分(现合并 InvalidState,前端无法分辨)
- [P2] CR-01-E state.rs build_registry 注释补「task_advance 未注册(阶段2联动时加)」
- [P2] CR-01-F crud.rs:325 白名单注释补 review_rounds 专用路径
- [P3] CR-01-G TaskDetail.vue:218 advance 失败错误走 i18n 分级(en locale 也中文)
- [P3] CR-01-H task_advance_node.rs:297 测试改名(实际未测 CAS 失败)
- [P3] CR-01-I i18n
taskDetail.advancing冗余 key 处理
- [P1] CR-01-A TaskDetail.vue scoped style 补