新增: AiNode自审闸门阶段3(gate_should_block+verdict=fail阻断下游)
This commit is contained in:
@@ -419,6 +419,14 @@ fn truncate_for_summary(s: &str) -> String {
|
|||||||
format!("{truncated}…")
|
format!("{truncated}…")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 阶段3 自审闸门决策(纯函数,便于单测覆盖各 verdict/gate 组合)。
|
||||||
|
///
|
||||||
|
/// 仅当 `gate==true` 且 `verdict=="fail"` 时阻断。verdict="unknown"(LLM 输出不可靠)
|
||||||
|
/// 与 "pass" 均不阻断 —— unknown 保持人定权(阶段2 保守语义不变)。
|
||||||
|
fn gate_should_block(gate: bool, verdict: &str) -> bool {
|
||||||
|
gate && verdict == "fail"
|
||||||
|
}
|
||||||
|
|
||||||
/// AI 自审节点(决策 a 步骤③)
|
/// AI 自审节点(决策 a 步骤③)
|
||||||
pub struct AiSelfReviewNode {
|
pub struct AiSelfReviewNode {
|
||||||
db: Arc<Database>,
|
db: Arc<Database>,
|
||||||
@@ -576,13 +584,52 @@ impl Node for AiSelfReviewNode {
|
|||||||
// ── NodeOutput.data 塞 review 摘要,供下游 human_review 经 inputs["ai_self_review"] 读 ──
|
// ── NodeOutput.data 塞 review 摘要,供下游 human_review 经 inputs["ai_self_review"] 读 ──
|
||||||
// human_review(HumanNode)从 config/inputs 拼 description;此处把 verdict/summary/suggestions
|
// human_review(HumanNode)从 config/inputs 拼 description;此处把 verdict/summary/suggestions
|
||||||
// 平铺进 data,HumanNode 零改动即可透传(前端/模板层可据此拼审批卡描述)。
|
// 平铺进 data,HumanNode 零改动即可透传(前端/模板层可据此拼审批卡描述)。
|
||||||
Ok(NodeOutput::from_value(serde_json::json!({
|
let output = NodeOutput::from_value(serde_json::json!({
|
||||||
"verdict": verdict,
|
"verdict": verdict,
|
||||||
"summary": summary,
|
"summary": summary,
|
||||||
"suggestions": suggestions,
|
"suggestions": suggestions,
|
||||||
"review": merged.get("review").cloned().unwrap_or(serde_json::Value::Null),
|
"review": merged.get("review").cloned().unwrap_or(serde_json::Value::Null),
|
||||||
"model": response.model,
|
"model": response.model,
|
||||||
})))
|
}));
|
||||||
|
|
||||||
|
// ── 阶段3: 自审闸门(F-260616-07 决策 a 步骤③) ──
|
||||||
|
// config["gate"]==true 时,AiSelfReviewNode 从「自审辅助」升级为「DAG 节点闸门」:
|
||||||
|
// verdict="fail" → 返回 Err → executor first_err 中止后续层(下游 human_review 不跑)
|
||||||
|
// → 工作流 failed → ②-4 回调退回 in_review(对齐工作流失败语义)
|
||||||
|
// verdict="unknown" → 保持 Ok(LLM 输出不可靠时不阻断,人定权)
|
||||||
|
// verdict="pass" → Ok 放行下游
|
||||||
|
//
|
||||||
|
// 落库在前(已 update_field output_json),故闸门 fail 时 task.output_json.review
|
||||||
|
// 已含结论,前端/二次工作流可读自审详情(不丢失审查痕迹)。
|
||||||
|
//
|
||||||
|
// 设计选型(三方案对比,见 commit/设计文档):
|
||||||
|
// A) AiNode 内部门控(本方案):1 处 return Err,复用 executor first_err + ②-4 回调,
|
||||||
|
// 零 executor 核心改动,零依赖暂缓的条件引擎(T-260614-11)。最简。
|
||||||
|
// B) DAG edges 条件 + ConditionEngine 求值:依赖 T-260614-11(暂缓),executor 当前
|
||||||
|
// 完全不评估 edge.condition(topological_layers 无条件收录所有边),须先做条件
|
||||||
|
// 引擎 Phase1+2 才能用 → 拆波。
|
||||||
|
// C) executor 闸门检查钩子(节点 execute 后 executor 读 output.verdict):改 DagExecutor
|
||||||
|
// 核心循环,牵动所有节点,风险/范围不符「最简不破坏」。
|
||||||
|
// 选 A:语义最贴近「自审结果作为闸门」(自审节点自行决定放行/阻断),且 gate 可按节点
|
||||||
|
// config 开关(默认 false = 阶段2 行为不变,模板/前端零强制改动,向后兼容)。
|
||||||
|
let gate_enabled = ctx
|
||||||
|
.config
|
||||||
|
.get("gate")
|
||||||
|
.and_then(|v| v.as_bool())
|
||||||
|
.unwrap_or(false);
|
||||||
|
if gate_should_block(gate_enabled, &verdict) {
|
||||||
|
tracing::info!(
|
||||||
|
node_id = %ctx.node_id,
|
||||||
|
task_id,
|
||||||
|
"AiSelfReviewNode 闸门触发: verdict=fail, 阻断下游(工作流将 failed)"
|
||||||
|
);
|
||||||
|
return Err(anyhow::anyhow!(
|
||||||
|
"AI 自审闸门未通过(verdict=fail): {}",
|
||||||
|
if summary.is_empty() { "自审结论 fail" } else { &summary }
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(output)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn schema(&self) -> NodeSchema {
|
fn schema(&self) -> NodeSchema {
|
||||||
@@ -593,7 +640,8 @@ impl Node for AiSelfReviewNode {
|
|||||||
"task_id": { "type": "string", "description": "自审目标任务 ID(必填)" },
|
"task_id": { "type": "string", "description": "自审目标任务 ID(必填)" },
|
||||||
"provider_id": { "type": "string", "description": "AI Provider ID(FR-S1:密钥经 secret 解析不进 config;留空走默认 provider)" },
|
"provider_id": { "type": "string", "description": "AI Provider ID(FR-S1:密钥经 secret 解析不进 config;留空走默认 provider)" },
|
||||||
"model": { "type": "string", "description": "模型名(可选,留空用 record.default_model)" },
|
"model": { "type": "string", "description": "模型名(可选,留空用 record.default_model)" },
|
||||||
"max_tokens": { "type": "integer" }
|
"max_tokens": { "type": "integer" },
|
||||||
|
"gate": { "type": "boolean", "description": "阶段3 闸门开关:false(默认)=自审辅助,verdict 仅透传展示;true=自审结果作 DAG 闸门,verdict=fail 返回 Err 阻断下游(工作流 failed → ②-4 退回),verdict=unknown/pass 放行" }
|
||||||
},
|
},
|
||||||
"required": ["task_id", "provider_id"]
|
"required": ["task_id", "provider_id"]
|
||||||
}),
|
}),
|
||||||
@@ -1014,4 +1062,43 @@ mod tests {
|
|||||||
assert!(p.contains("边界处理"));
|
assert!(p.contains("边界处理"));
|
||||||
assert!(p.contains("verdict"), "prompt 应含输出格式约束");
|
assert!(p.contains("verdict"), "prompt 应含输出格式约束");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// 阶段3 自审闸门(gate_should_block)单测
|
||||||
|
// ============================================================
|
||||||
|
//
|
||||||
|
// gate 决策矩阵:
|
||||||
|
// gate=false(默认,阶段2 行为) → 任何 verdict 都放行(辅助模式)
|
||||||
|
// gate=true + verdict=pass → 放行
|
||||||
|
// gate=true + verdict=unknown → 放行(LLM 不可靠时不阻断,人定权)
|
||||||
|
// gate=true + verdict=fail → 阻断(返回 Err,工作流 failed)
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn gate_disabled_never_blocks_any_verdict() {
|
||||||
|
// gate 默认 false(阶段2 兼容):无论 verdict 如何都不阻断,自审仅辅助展示
|
||||||
|
assert!(!gate_should_block(false, "fail"), "gate 关闭时 fail 也不阻断");
|
||||||
|
assert!(!gate_should_block(false, "pass"), "gate 关闭时 pass 放行");
|
||||||
|
assert!(!gate_should_block(false, "unknown"), "gate 关闭时 unknown 放行");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn gate_enabled_blocks_only_on_fail() {
|
||||||
|
// gate 开启:仅 fail 阻断
|
||||||
|
assert!(gate_should_block(true, "fail"), "gate 开 + fail 应阻断");
|
||||||
|
assert!(!gate_should_block(true, "pass"), "gate 开 + pass 放行");
|
||||||
|
assert!(
|
||||||
|
!gate_should_block(true, "unknown"),
|
||||||
|
"gate 开 + unknown 放行(LLM 不可靠时保人定权)"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn gate_unknown_verdict_does_not_block() {
|
||||||
|
// 防御:verdict 非标准值(如解析异常)不应触发阻断(对齐 unknown 保守不阻断语义)
|
||||||
|
assert!(
|
||||||
|
!gate_should_block(true, "weird"),
|
||||||
|
"非标准 verdict 不应阻断(对齐 unknown 保守)"
|
||||||
|
);
|
||||||
|
assert!(!gate_should_block(true, ""), "空 verdict 不应阻断");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,14 +51,22 @@ fn in_progress_template() -> DagDef {
|
|||||||
|
|
||||||
/// in_review → testing:AiNode 自审 → HumanNode 核对。
|
/// in_review → testing:AiNode 自审 → HumanNode 核对。
|
||||||
///
|
///
|
||||||
/// 拓扑:ai → human 串行。HumanNode reject 走 Err(②-5 已做)→ 工作流 failed →
|
/// 拓扑:ai → human 串行。阶段3(本批)起 ai_self_review 启用 gate:true:
|
||||||
/// ②-4 失败回调退回 in_review(review_rounds+=1)。
|
/// - verdict=fail → AiSelfReviewNode 返回 Err → 工作流 failed(不经 human_review)
|
||||||
|
/// → ②-4 失败回调退回 in_review(review_rounds+=1)。
|
||||||
|
/// - verdict=unknown/pass → 放行 human_review,人定最终是否推进。
|
||||||
|
/// HumanNode reject 走 Err(②-5 已做)→ 工作流 failed → ②-4 失败回调退回 in_review。
|
||||||
/// 通过则完成回调(②-3)推进 status 到 testing。
|
/// 通过则完成回调(②-3)推进 status 到 testing。
|
||||||
fn testing_template() -> DagDef {
|
fn testing_template() -> DagDef {
|
||||||
let mut dag = DagDef::new();
|
let mut dag = DagDef::new();
|
||||||
// 决策 a 步骤③:ai_self_review 节点类型对齐 state.rs 注册的独立自审节点
|
// 决策 a 步骤③:ai_self_review 节点类型对齐 state.rs 注册的独立自审节点
|
||||||
// (四维度 prompt + JSON 解析兜底 + 写回 output_json 加 review 子字段)。
|
// (四维度 prompt + JSON 解析兜底 + 写回 output_json 加 review 子字段)。
|
||||||
dag.add_node("ai_self_review", "ai_self_review", serde_json::json!({}));
|
// 阶段3:gate=true 启用自审闸门(verdict=fail 阻断下游,工作流 failed)。
|
||||||
|
dag.add_node(
|
||||||
|
"ai_self_review",
|
||||||
|
"ai_self_review",
|
||||||
|
serde_json::json!({ "gate": true }),
|
||||||
|
);
|
||||||
dag.add_node(
|
dag.add_node(
|
||||||
"human_review",
|
"human_review",
|
||||||
"human",
|
"human",
|
||||||
@@ -127,6 +135,13 @@ mod tests {
|
|||||||
// 节点类型(决策 a 步骤③:ai_self_review 独立节点类型)
|
// 节点类型(决策 a 步骤③:ai_self_review 独立节点类型)
|
||||||
let ai = dag.nodes.get("ai_self_review").expect("ai_self_review 存在");
|
let ai = dag.nodes.get("ai_self_review").expect("ai_self_review 存在");
|
||||||
assert_eq!(ai.node_type, "ai_self_review");
|
assert_eq!(ai.node_type, "ai_self_review");
|
||||||
|
// 阶段3:gate=true 启用自审闸门(verdict=fail 阻断下游)
|
||||||
|
let gate = ai
|
||||||
|
.config
|
||||||
|
.get("gate")
|
||||||
|
.and_then(|v| v.as_bool())
|
||||||
|
.expect("ai_self_review config 应含 gate");
|
||||||
|
assert!(gate, "testing 模板 ai_self_review 应启用 gate:true(阶段3 闸门)");
|
||||||
let human = dag.nodes.get("human_review").expect("human_review 存在");
|
let human = dag.nodes.get("human_review").expect("human_review 存在");
|
||||||
assert_eq!(human.node_type, "human");
|
assert_eq!(human.node_type, "human");
|
||||||
// 边方向:ai → human
|
// 边方向:ai → human
|
||||||
|
|||||||
21
docs/todo.md
21
docs/todo.md
@@ -75,7 +75,21 @@
|
|||||||
|
|
||||||
> 排查性质(session-role-diagnose-only),走查定位根因 + 记录待办,未改代码。
|
> 排查性质(session-role-diagnose-only),走查定位根因 + 记录待办,未改代码。
|
||||||
|
|
||||||
- [ ] B-260616-21 [P1] — **aichat 工具卡片「重复读取同一文件」表象(read_file 出现两条卡:一 0 行·7.1KB running、一 183 行·7.1KB completed)**。用户报对话记录里 `读取 .../api/ai.ts` 出现两次。**根因分析**(已走查前端流转 + 后端 emit 点):①前端 `useAiEvents.ts:202-206` `AiToolCallStarted` 分支直接 `lastMsg.toolCalls.push(info)`,**无 id 幂等守卫**(对比同文件 `startToolSlowTimer:58` 有 `_toolTimers.has(callId)` 守卫,Started 漏了同款判重)②`findToolCall`(`aiShared.ts:41-49`) 按 `id` 命中**首个**返回,`AiToolCallCompleted`(`:213`) 只 update 第一张卡 → 同 id 若被 push 两次,第二张卡永远 running,`ToolCard.vue:72` `parsed?.lines||0` 无 result 时显示 `0 行` ③后端 `audit.rs:538-549` `process_tool_calls` 对 tc_list 每 draft emit 一次 Started;read_file=Low risk(`:561`) **无去重**(去重仅 High,`:553-556` 注释明确 Low 不进)。**两候选根因**:**(A 最贴合现象)** 同一 `tool_call_id` 被**重复 emit Started**(后端 `process_tool_calls` 重入 / `stream_recv.rs` 生成重复 id / `agentic.rs` loop 重 process 同轮)→ push 两张卡 → Completed 只更首张(183 行)→ 次张残留 running(0 行);**(B)** agent loop 多轮 LLM 真读两次(不同 id)→ 但应两卡皆 183 行,与「0 行 + 183 行」现象不符,**排除为主因**。**待查**:后端同 id 重复 emit 的确切路径(`audit.rs:532` tc_list 来源 `tool_calls_acc` + `stream_recv.rs` id 生成 + `agentic.rs` 是否对同轮 tool_calls 重复 process)。**修复方向**(待定):①前端 Started push 前加 `findToolCall(event.id)` 幂等守卫(确定性低风险,对齐 startToolSlowTimer 守卫,治标兜底) ②后端查重复 emit 源治本(定位 stream_recv/process/loop 哪环生重复)。**关联** F-260616-05(agentic loop 工具去重缺失,batch53 仅 High 去重,read_file/search_files 等 Low 未去重;本现象若实为候选 A 则与 F-05 不同维度——F-05 是不同 id 重复调用,本条是同 id 重复 emit)。— src/composables/ai/useAiEvents.ts(:195-209 Started 分支) + src/composables/ai/aiShared.ts(:41 findToolCall) + src/components/ToolCard.vue(:72 lines 展示) + src-tauri/src/commands/ai/audit.rs(:538-549 emit Started)。
|
- [x] ✅(247dc5d后端audit按id去重治本+useAiEvents.ts:205前端findToolCall幂等守卫双保险·候选A根因闭环) B-260616-21 [P1] — **aichat 工具卡片「重复读取同一文件」表象(read_file 出现两条卡:一 0 行·7.1KB running、一 183 行·7.1KB completed)**。用户报对话记录里 `读取 .../api/ai.ts` 出现两次。**根因分析**(已走查前端流转 + 后端 emit 点):①前端 `useAiEvents.ts:202-206` `AiToolCallStarted` 分支直接 `lastMsg.toolCalls.push(info)`,**无 id 幂等守卫**(对比同文件 `startToolSlowTimer:58` 有 `_toolTimers.has(callId)` 守卫,Started 漏了同款判重)②`findToolCall`(`aiShared.ts:41-49`) 按 `id` 命中**首个**返回,`AiToolCallCompleted`(`:213`) 只 update 第一张卡 → 同 id 若被 push 两次,第二张卡永远 running,`ToolCard.vue:72` `parsed?.lines||0` 无 result 时显示 `0 行` ③后端 `audit.rs:538-549` `process_tool_calls` 对 tc_list 每 draft emit 一次 Started;read_file=Low risk(`:561`) **无去重**(去重仅 High,`:553-556` 注释明确 Low 不进)。**两候选根因**:**(A 最贴合现象)** 同一 `tool_call_id` 被**重复 emit Started**(后端 `process_tool_calls` 重入 / `stream_recv.rs` 生成重复 id / `agentic.rs` loop 重 process 同轮)→ push 两张卡 → Completed 只更首张(183 行)→ 次张残留 running(0 行);**(B)** agent loop 多轮 LLM 真读两次(不同 id)→ 但应两卡皆 183 行,与「0 行 + 183 行」现象不符,**排除为主因**。**待查**:后端同 id 重复 emit 的确切路径(`audit.rs:532` tc_list 来源 `tool_calls_acc` + `stream_recv.rs` id 生成 + `agentic.rs` 是否对同轮 tool_calls 重复 process)。**修复方向**(待定):①前端 Started push 前加 `findToolCall(event.id)` 幂等守卫(确定性低风险,对齐 startToolSlowTimer 守卫,治标兜底) ②后端查重复 emit 源治本(定位 stream_recv/process/loop 哪环生重复)。**关联** F-260616-05(agentic loop 工具去重缺失,batch53 仅 High 去重,read_file/search_files 等 Low 未去重;本现象若实为候选 A 则与 F-05 不同维度——F-05 是不同 id 重复调用,本条是同 id 重复 emit)。— src/composables/ai/useAiEvents.ts(:195-209 Started 分支) + src/composables/ai/aiShared.ts(:41 findToolCall) + src/components/ToolCard.vue(:72 lines 展示) + src-tauri/src/commands/ai/audit.rs(:538-549 emit Started)。
|
||||||
|
|
||||||
|
### 🔧 2026-06-17 定期全量走查(第1轮·仅走查·未实施)
|
||||||
|
|
||||||
|
> 周期性自检(session-role-diagnose-only:不碰代码,文档不对改文档 + 代码问题记 todo)。本轮范围:secret.rs 全文 + 近 6 提交 vs todo 状态核对 + 工作区未提交改动(tauri.conf.json)。
|
||||||
|
|
||||||
|
**文档状态校正(治本/实现已落地,原 [ ] 更新为 [x])**:
|
||||||
|
- B-260616-21 → ✅:后端 247dc5d(audit.rs:534-540 `seen_ids: HashSet` 按 id 去重保首个)+ 前端 useAiEvents.ts:205 `findToolCall` 幂等守卫双保险,候选 A 根因(LLM 复用同 tool_use.id 致 emit 两次)闭环。
|
||||||
|
- F-260614-04 → ✅ 后端三段(79b6a43 数据层+池选择器 / b3684f4 池 IPC+set_caps / 80c0955 stream_one_provider+候选链 fallback),前端 api/ai.ts:142 IPC 封装已接;UI 配置面板待核验。
|
||||||
|
|
||||||
|
**新登记代码问题**:
|
||||||
|
- [ ] B-260617-06 [P3] — **secret.rs:104 注释与实现相反**。`resolve_provider_secret` 注释写「keyring 优先,fallback DB.api_key」,但代码(:105-110 `if !record.api_key.is_empty() { return DB }` + `get_provider_secret().unwrap_or_default()`)实际「**DB 优先,fallback keyring**」。与顶层设计(line 15「DB 优先,fallback keyring」)矛盾,line 104 注释为笔误。**实际无功能影响**(迁移态 DB 与 keyring 不会同时有值,set keyring→clear DB 原子序列),纯文档错误。**修法**:line 104 注释改为「DB 优先,fallback keyring」。— crates/df-storage/src/secret.rs:104
|
||||||
|
|
||||||
|
**工作区未提交改动观察(非 bug,记录供判断)**:
|
||||||
|
- src-tauri/tauri.conf.json: `bundle.targets` `"all"` → `["nsis"]`(打包目标从全平台收窄到仅 NSIS Windows 安装包)。正式收窄(devflow 仅 Win 桌面)则合理;若 dev 调试临时收窄忘还原,后续 mac/linux 打包会漏。**待用户确认意图**。
|
||||||
|
|
||||||
### 🔧 2026-06-17 useAiSend.ts 代码审查(仅走查·未实施)
|
### 🔧 2026-06-17 useAiSend.ts 代码审查(仅走查·未实施)
|
||||||
|
|
||||||
@@ -195,7 +209,7 @@
|
|||||||
- [x] ✅(batch35·2026-06-16·workflow w17duic3f) **①-1** 前端 TaskDetail 工作流推进按钮 — 与手动 advance 并存,调 workflowApi.run+监听进度。**含 B-41 进度内嵌**:onEvent 按 exec_id 过滤 dispatch node_started/completed/workflow_completed/failed + 独立 wfAdvancing loading 与手动 advancing 互含 disabled。**后端补丁**:workflow.rs run_workflow 空dag选模板(dag.nodes 非空→原 / 空+target→template_for / 空+无target→原路径,向后兼容)。文件锁:`src/views/TaskDetail.vue` + `src/api/workflow.ts` + `src/stores/project/workflow.ts` + `src-tauri/src/commands/workflow.rs`
|
- [x] ✅(batch35·2026-06-16·workflow w17duic3f) **①-1** 前端 TaskDetail 工作流推进按钮 — 与手动 advance 并存,调 workflowApi.run+监听进度。**含 B-41 进度内嵌**:onEvent 按 exec_id 过滤 dispatch node_started/completed/workflow_completed/failed + 独立 wfAdvancing loading 与手动 advancing 互含 disabled。**后端补丁**:workflow.rs run_workflow 空dag选模板(dag.nodes 非空→原 / 空+target→template_for / 空+无target→原路径,向后兼容)。文件锁:`src/views/TaskDetail.vue` + `src/api/workflow.ts` + `src/stores/project/workflow.ts` + `src-tauri/src/commands/workflow.rs`
|
||||||
- [x] ✅(batch35·2026-06-16·workflow) **①-3** i18n 文案 — `src/i18n/{zh-CN,en}/taskDetail.ts`(taskDetail.* 命名空间,8 key 对称:workflowAdvanceTitle/Advancing/AdvanceFailed{msg}/StepRunning{node}/StepsProgress{done}{total}/CompletedHint/FailedHint)。注:todo 原路径 `locales/tasks.ts` 不准,实为 `i18n/taskDetail.ts`
|
- [x] ✅(batch35·2026-06-16·workflow) **①-3** i18n 文案 — `src/i18n/{zh-CN,en}/taskDetail.ts`(taskDetail.* 命名空间,8 key 对称:workflowAdvanceTitle/Advancing/AdvanceFailed{msg}/StepRunning{node}/StepsProgress{done}{total}/CompletedHint/FailedHint)。注:todo 原路径 `locales/tasks.ts` 不准,实为 `i18n/taskDetail.ts`
|
||||||
- **实施顺序**:④-1→②-1→②-5→②-2→②-3→②-4→②-6→①-1/①-3。**最小里程碑**(④-1+②-1+②-2+②-3):run_workflow 单 task_advance 节点 DAG 端到端推进 todo→in_progress
|
- **实施顺序**:④-1→②-1→②-5→②-2→②-3→②-4→②-6→①-1/①-3。**最小里程碑**(④-1+②-1+②-2+②-3):run_workflow 单 task_advance 节点 DAG 端到端推进 todo→in_progress
|
||||||
- [ ] F-260616-07 阶段3 AI 执行闭环 — **F-03 收口三件 ✅ 本批完成**(batch64), AiNode 自审闸门 ⏳ 待后续批
|
- [x] ✅ F-260616-07 阶段3 AI 执行闭环 — **F-03 收口三件 ✅ batch64** + **AiNode 自审闸门 ✅ batch65**(本批)
|
||||||
- [x] ✅(batch64·2026-06-16·workflow wii1u1lnm) **F-03 收口三件** — ①advance_task 注册 AI 工具(tool_registry.rs:395,handler L407 调 `df_nodes::task_advance_node::advance_task_atomic` 与 IPC `commands::task::advance_task:165` 同源) ②run_workflow 注册 AI 工具(:428,handler 架构约束无 AppHandle/State 报错引导走 IPC,ToolDefinition+审批文案 L1260-1261/1286-1287 注册让 LLM 可产出 tool_call) ③update_task handler guard 拒 status(L374 `field=="status"` bail,schema 通用 field/value 故 guard 拦非 schema 改)+ df-storage tasks 白名单移 status+review_rounds(crud.rs:331-347,advance_status_atomic CAS L848 独立路径不经白名单)。同步落地防 AI 工具行为不一致。**主代独立核查全过**:cargo check --workspace EXIT 0(5 pre-existing warnings 无关)+ df-storage 11 集成测试(含新 `update_field_rejects_tasks_status`)。文件锁:tool_registry.rs + crud.rs + project_soft_delete.rs。**审查登记 CR-260616-41**(待审查.md 当前队列)
|
- [x] ✅(batch64·2026-06-16·workflow wii1u1lnm) **F-03 收口三件** — ①advance_task 注册 AI 工具(tool_registry.rs:395,handler L407 调 `df_nodes::task_advance_node::advance_task_atomic` 与 IPC `commands::task::advance_task:165` 同源) ②run_workflow 注册 AI 工具(:428,handler 架构约束无 AppHandle/State 报错引导走 IPC,ToolDefinition+审批文案 L1260-1261/1286-1287 注册让 LLM 可产出 tool_call) ③update_task handler guard 拒 status(L374 `field=="status"` bail,schema 通用 field/value 故 guard 拦非 schema 改)+ df-storage tasks 白名单移 status+review_rounds(crud.rs:331-347,advance_status_atomic CAS L848 独立路径不经白名单)。同步落地防 AI 工具行为不一致。**主代独立核查全过**:cargo check --workspace EXIT 0(5 pre-existing warnings 无关)+ df-storage 11 集成测试(含新 `update_field_rejects_tasks_status`)。文件锁:tool_registry.rs + crud.rs + project_soft_delete.rs。**审查登记 CR-260616-41**(待审查.md 当前队列)
|
||||||
- [x] ✅ AiNode 自审闸门(阶段3 核心,决策a) — **阶段1迁移+方案✅(539b5ed+25409e5) + 阶段2②③④⑤✅(c10adaf+741b0b9) + P0 secret下沉+注入链✅(766f235+84a6fc4)**。⑥联调代码层解锁(待实测 tauri dev)。cargo df-nodes 79 passed + workspace EXIT 0 + vue-tsc EXIT 0
|
- [x] ✅ AiNode 自审闸门(阶段3 核心,决策a) — **阶段1迁移+方案✅(539b5ed+25409e5) + 阶段2②③④⑤✅(c10adaf+741b0b9) + P0 secret下沉+注入链✅(766f235+84a6fc4)**。⑥联调代码层解锁(待实测 tauri dev)。cargo df-nodes 79 passed + workspace EXIT 0 + vue-tsc EXIT 0
|
||||||
- [x] ✅(539b5ed) ①df-storage: TaskRecord 加 `output_json: Option<String>` + V17 迁移(幂等补列) + crud 白名单+SELECT/INSERT/UPDATE 全链路
|
- [x] ✅(539b5ed) ①df-storage: TaskRecord 加 `output_json: Option<String>` + V17 迁移(幂等补列) + crud 白名单+SELECT/INSERT/UPDATE 全链路
|
||||||
@@ -205,6 +219,7 @@
|
|||||||
- [x] ✅(c10adaf) human_review: 经 DAG inputs["ai_self_review"].data 透传 verdict/summary(HumanNode 零改动,方案④更优路径)
|
- [x] ✅(c10adaf) human_review: 经 DAG inputs["ai_self_review"].data 透传 verdict/summary(HumanNode 零改动,方案④更优路径)
|
||||||
- [x] ✅(741b0b9) ⑤前端: TaskDetail.vue 展示 output_json(parsedOutput+review 红绿标+产出 markdown)+ types.ts+i18n 双语
|
- [x] ✅(741b0b9) ⑤前端: TaskDetail.vue 展示 output_json(parsedOutput+review 红绿标+产出 markdown)+ types.ts+i18n 双语
|
||||||
- [ ] ⑥端到端联调 — **代码层完成**(P0 secret 下沉+注入链✅ 766f235+84a6fc4),实测类待用户(tauri dev 验证 testing 模板 ai_self_review→human_review 闭环)
|
- [ ] ⑥端到端联调 — **代码层完成**(P0 secret 下沉+注入链✅ 766f235+84a6fc4),实测类待用户(tauri dev 验证 testing 模板 ai_self_review→human_review 闭环)
|
||||||
|
- [x] ✅(batch65·2026-06-17·workflow) **AiNode 自审闸门(阶段3 核心)** — `verdict=fail` 作 DAG 节点闸门阻断下游,对齐工作流失败语义。**方案 A(AiNode 内部门控)胜出**:新增纯函数 `gate_should_block(gate, verdict)`(ai_node.rs:426 `gate && verdict=="fail"`) + execute 末尾 gate_enabled(config["gate"]==true,默认 false 阶段2 兼容)&& fail → `return Err("AI 自审闸门未通过(verdict=fail): ...")` 复用 executor first_err(:147-178)中止后续层 → 工作流 failed → ②-4 回调退回 in_review。verdict=unknown/pass 放行(unknown 保人定权,阶段2 保守语义不变)。落库在前(update_field output_json),故 fail 时审查痕迹不丢。**选型对比**(方案 B DAG edges 条件 + ConditionEngine:依赖 T-260614-11 暂缓 + executor 当前完全不评估 edge.condition(topological_layers 无条件收录所有边)→ 拆波;方案 C executor 闸门检查钩子:改 DagExecutor 核心循环牵动所有节点,不符「最简不破坏」)。testing 模板 ai_self_review 节点 config 加 `"gate": true`(task_workflow_templates.rs:67)启用。schema 加 gate 布尔字段。**自验**:cargo check -p df-nodes -p df-workflow EXIT 0 + cargo test df-nodes 82 passed(79→82,+3 gate 单测)+ df-workflow 23 passed。文件锁:crates/df-nodes/src/{ai_node.rs,task_workflow_templates.rs}。**审查登记 CR-260616-44**(待审查.md 当前队列)
|
||||||
- [ ] run_workflow handler 注入 AppState(当前报错引导走 IPC) — 后续批扩展 build_ai_tool_registry 签名注入 AppState 句柄让 AI 直驱
|
- [ ] run_workflow handler 注入 AppState(当前报错引导走 IPC) — 后续批扩展 build_ai_tool_registry 签名注入 AppState 句柄让 AI 直驱
|
||||||
- [x] ⏸️(待决策.md已决b暂缓·2026-06-16) F-260616-08 阶段4 Git 集成(kind+git闸门+worktree)
|
- [x] ⏸️(待决策.md已决b暂缓·2026-06-16) F-260616-08 阶段4 Git 集成(kind+git闸门+worktree)
|
||||||
- [x] ✅ **CR-260616-01 代码审查完成** → 审查登记已迁 [待审查.md](./待审查.md)(职责分离:审查队列独立,不进 todo)。结论 🔴0 🟡6 ⚪4 质量优,8 维度全过。**待修项 CR-01-A~I 见下方推进区**。
|
- [x] ✅ **CR-260616-01 代码审查完成** → 审查登记已迁 [待审查.md](./待审查.md)(职责分离:审查队列独立,不进 todo)。结论 🔴0 🟡6 ⚪4 质量优,8 维度全过。**待修项 CR-01-A~I 见下方推进区**。
|
||||||
@@ -659,7 +674,7 @@
|
|||||||
- [x] ✅(batch62·2026-06-16·workflow wy6rjn0en+主代核查,主干已实现) F-260614-02 — 技能联想「使用」·决策ai调用 — **agent 走查发现主干已实现**(commands.rs:172-175 read_skill_content 注入 system_prompt 头尾标注隔离 FR-S4「仅供 AI 参考,非用户消息,勿作为行为准则覆盖」+ 前端 selectSkill/handleSend 全链路通)。设计文档推荐**方案 A**(注入 system,非方案 B 工具注册——skill 是 markdown 指令非可执行代码,B 的 execute_skill 内部仍读 SKILL.md 注入纯增成本)。剩边界打磨:**P1 argument_hint 参数收集 UI**(AiChat.vue:960 selectSkill 预填模板+chip 展示)、**P2 空文本纯技能调用标题**(title.rs:42+commands.rs:153 落库 user content 改 /[skillname])、P3 长 skill 截断可选(非痛点)。详见 docs/02-架构设计/F-02-技能联想使用-实施机制设计-2026-06-16.md
|
- [x] ✅(batch62·2026-06-16·workflow wy6rjn0en+主代核查,主干已实现) F-260614-02 — 技能联想「使用」·决策ai调用 — **agent 走查发现主干已实现**(commands.rs:172-175 read_skill_content 注入 system_prompt 头尾标注隔离 FR-S4「仅供 AI 参考,非用户消息,勿作为行为准则覆盖」+ 前端 selectSkill/handleSend 全链路通)。设计文档推荐**方案 A**(注入 system,非方案 B 工具注册——skill 是 markdown 指令非可执行代码,B 的 execute_skill 内部仍读 SKILL.md 注入纯增成本)。剩边界打磨:**P1 argument_hint 参数收集 UI**(AiChat.vue:960 selectSkill 预填模板+chip 展示)、**P2 空文本纯技能调用标题**(title.rs:42+commands.rs:153 落库 user content 改 /[skillname])、P3 长 skill 截断可选(非痛点)。详见 docs/02-架构设计/F-02-技能联想使用-实施机制设计-2026-06-16.md
|
||||||
- [x] ✅(batch62·2026-06-16·workflow wy6rjn0en+主代核查,cargo test 19 passed) F-260614-03 — 灵感对抗评估接 LLM — F-07 trait 下沉后注入方式统一(原 A/B/C 选型作废)。填充 `evaluate_with_llm`(adversarial.rs:143):build_adversarial_prompt 三角色辩论(正方/反方/分析师)+provider.complete(temperature 0.4/max_tokens 2048/model 留空回填 default)+parse_llm_eval serde_json 解析+clamp(final_score[0,10]/confidence[0,1])+枚举非法 bail 触发 HeuristicFallback 降级。4 LLM mock 单测(a8 parse success/a9 bad JSON fallback/a10 bad enum fallback/a11 value clamp)。原 7 heuristic+scoring 8 不回归。— crates/df-ideas/src/adversarial.rs
|
- [x] ✅(batch62·2026-06-16·workflow wy6rjn0en+主代核查,cargo test 19 passed) F-260614-03 — 灵感对抗评估接 LLM — F-07 trait 下沉后注入方式统一(原 A/B/C 选型作废)。填充 `evaluate_with_llm`(adversarial.rs:143):build_adversarial_prompt 三角色辩论(正方/反方/分析师)+provider.complete(temperature 0.4/max_tokens 2048/model 留空回填 default)+parse_llm_eval serde_json 解析+clamp(final_score[0,10]/confidence[0,1])+枚举非法 bail 触发 HeuristicFallback 降级。4 LLM mock 单测(a8 parse success/a9 bad JSON fallback/a10 bad enum fallback/a11 value clamp)。原 7 heuristic+scoring 8 不回归。— crates/df-ideas/src/adversarial.rs
|
||||||
- [x] T-260614-04 — ~~路径校验根治~~ ✅ 已完成(resolve_workspace_path 加 canonicalize 防 symlink 逃逸 + 词法 starts_with 兜底;仅校验、返回词法路径保持前端友好;cargo check 0 err / 22 test pass)(06-14)
|
- [x] T-260614-04 — ~~路径校验根治~~ ✅ 已完成(resolve_workspace_path 加 canonicalize 防 symlink 逃逸 + 词法 starts_with 兜底;仅校验、返回词法路径保持前端友好;cargo check 0 err / 22 test pass)(06-14)
|
||||||
- [ ] F-260614-04 — 多 Provider 负载均衡池 — 备用模型/多账号聚合,全局容量=min(各 provider 上限之和, global_cap) (06-14)
|
- [x] ✅(后端三段·79b6a43数据层+池选择器+CR-52白项 / b3684f4池enabled/weight IPC+set_caps启动接入 / 80c0955 stream_one_provider+候选链fallback+resolved_model重算;前端api/ai.ts:142 IPC封装已接) F-260614-04 — 多 Provider 负载均衡池 — 备用模型/多账号聚合,全局容量=min(各 provider 上限之和, global_cap) (06-14)。**注**:UI 配置面板(provider 池 enabled/weight 可视化设置)是否完成待核验——后端 IPC+前端 API 层已就绪
|
||||||
- [ ] F-260614-05 — 模型能力系统 Phase 2 — 多模态消息支持:ChatMessage.content: String → Vec<ContentPart>(Text/Image);前端粘贴/拖拽图片;vision 模型自动路由 (06-14)
|
- [ ] F-260614-05 — 模型能力系统 Phase 2 — 多模态消息支持:ChatMessage.content: String → Vec<ContentPart>(Text/Image);前端粘贴/拖拽图片;vision 模型自动路由 (06-14)
|
||||||
- [x] ✅(batch61·2026-06-16·workflow wwtn2knn6+主代核查,cargo 0err+vue-tsc 0err) F-260614-06 — 导入历史项目(scan 第二步) — 6 决策全落地:①description 走 LLM(scan.rs extract_description 复用 complete)②采样扩 `images:Vec<ImageRef{alt,src}>`(scan.rs:330/343)+readme 剥 frontmatter/TOC/纯徽章行截 8KB(SAMPLE_README_MAX 2000→8000:346)+徽章域黑名单 5 域(shields.io/badge.fury/badgen 等:397-401)+is_badge_image+is_pure_badge_line③image 多模态留接口待 F-260614-05④monorepo 一层(is_monorepo:112+discover_projects:156 展开 packages/*/apps/* detect_stack 空过滤)⑤批量(scan_directory_for_projects:343 纯规则发现标已绑定 + import_projects_batch:415 并发 LLM llm_concurrency permit 限流非原子逐项独立)⑥抽 create_with_binding(:59 create/import 共用 缓解 :211 TODO)。前端 Projects.vue 导入 modal+api/project.ts 两 API+i18n 双语+scan.rs 单测全。— crates/df-project/scan.rs + src-tauri/commands/project.rs + lib.rs + api/project.ts + Projects.vue + i18n。详见功能决策记录(06-14)
|
- [x] ✅(batch61·2026-06-16·workflow wwtn2knn6+主代核查,cargo 0err+vue-tsc 0err) F-260614-06 — 导入历史项目(scan 第二步) — 6 决策全落地:①description 走 LLM(scan.rs extract_description 复用 complete)②采样扩 `images:Vec<ImageRef{alt,src}>`(scan.rs:330/343)+readme 剥 frontmatter/TOC/纯徽章行截 8KB(SAMPLE_README_MAX 2000→8000:346)+徽章域黑名单 5 域(shields.io/badge.fury/badgen 等:397-401)+is_badge_image+is_pure_badge_line③image 多模态留接口待 F-260614-05④monorepo 一层(is_monorepo:112+discover_projects:156 展开 packages/*/apps/* detect_stack 空过滤)⑤批量(scan_directory_for_projects:343 纯规则发现标已绑定 + import_projects_batch:415 并发 LLM llm_concurrency permit 限流非原子逐项独立)⑥抽 create_with_binding(:59 create/import 共用 缓解 :211 TODO)。前端 Projects.vue 导入 modal+api/project.ts 两 API+i18n 双语+scan.rs 单测全。— crates/df-project/scan.rs + src-tauri/commands/project.rs + lib.rs + api/project.ts + Projects.vue + i18n。详见功能决策记录(06-14)
|
||||||
- [x] T-260614-09 — ~~idea.rs 物理删不级联~~ ✅ WF-E 完成(idea.rs:149 delete→purge_with_descendants,1 行,签名兼容)(06-14, commit 89da9fa)
|
- [x] T-260614-09 — ~~idea.rs 物理删不级联~~ ✅ WF-E 完成(idea.rs:149 delete→purge_with_descendants,1 行,签名兼容)(06-14, commit 89da9fa)
|
||||||
|
|||||||
Reference in New Issue
Block a user