新增: 推进链阶段3收口(advance_task/run_workflow工具+status状态机收口)
This commit is contained in:
@@ -331,11 +331,15 @@ pub fn allowed_columns_for(table: &str) -> Option<&'static [&'static str]> {
|
||||
"tasks" => &[
|
||||
// id/created_at 不列入:主键与创建时间不可通过通用 update_field 改写
|
||||
// (防篡改主键/伪造创建时间/跨项目移动)。
|
||||
// status 不列入(D-260616-04 status 收口):所有 status 改动走
|
||||
// advance_status_atomic(CAS SQL `WHERE id AND status=expected`,唯一 status 写入路径),
|
||||
// 不经通用 update_field。防 update_task IPC / AI 工具旁路改 status 绕过状态机
|
||||
// (can_transition 校验)与 review_rounds 累加。后人勿把 status 补回白名单。
|
||||
// review_rounds 不列入:它仅 advance_status_atomic 退回转换
|
||||
// (in_review→in_progress / testing→in_review)时原子 +1(收口:仅该专用路径可改,
|
||||
// update_task/update_field 白名单均不含)。后人勿把 review_rounds 补进白名单,
|
||||
// 否则破坏「review_rounds 唯一写入路径」收口、引入旁路写导致计数错乱。
|
||||
"project_id", "title", "description", "status", "priority", "branch_name",
|
||||
"project_id", "title", "description", "priority", "branch_name",
|
||||
"assignee", "workflow_def_id", "base_branch", "updated_at",
|
||||
// TODO(B-260616-16): project_id 跨表存在性校验待 commands/task.rs 层补。
|
||||
// 通用 CRUD 层(db repo)只懂表/列语义,不持有跨表业务约束(查 projects 表存在性)。
|
||||
|
||||
@@ -247,15 +247,23 @@ async fn update_field_rejects_cross_table_column_tasks_name() {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn update_field_allows_tasks_status() {
|
||||
async fn update_field_rejects_tasks_status() {
|
||||
// D-260616-04 status 收口:status 已从 tasks 白名单移除,所有 status 改动须走
|
||||
// advance_status_atomic(CAS SQL 独立路径,不经通用 update_field)。update_field
|
||||
// 改 status 应在白名单阶段被拒(防 update_task IPC / AI 工具旁路绕过状态机与 review_rounds 累加)。
|
||||
let (projects, tasks, _r, _b) = setup().await;
|
||||
projects.insert(project("p1")).await.unwrap();
|
||||
tasks.insert(task("t1", "p1")).await.unwrap();
|
||||
|
||||
let ok = tasks.update_field("t1", "status", "done").await.unwrap();
|
||||
assert!(ok);
|
||||
let res = tasks.update_field("t1", "status", "done").await;
|
||||
assert!(
|
||||
res.is_err(),
|
||||
"status 不在 tasks 白名单,应被拒(改走 advance_status_atomic)"
|
||||
);
|
||||
|
||||
// 对照:status 未被改写,仍为初始值(task fixture 的初始 status)
|
||||
let rec = tasks.get_by_id("t1").await.unwrap().unwrap();
|
||||
assert_eq!(rec.status, "done");
|
||||
assert_ne!(rec.status, "done", "白名单拒绝后 status 不应被改写");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -125,7 +125,10 @@
|
||||
- [x] ✅(batch35·2026-06-16·workflow w17duic3f) **①-1** 前端 TaskDetail 工作流推进按钮 — 与手动 advance 并存,调 workflowApi.run+监听进度。**含 B-41 进度内嵌**:onEvent 按 exec_id 过滤 dispatch node_started/completed/workflow_completed/failed + 独立 wfAdvancing loading 与手动 advancing 互含 disabled。**后端补丁**:workflow.rs run_workflow 空dag选模板(dag.nodes 非空→原 / 空+target→template_for / 空+无target→原路径,向后兼容)。文件锁:`src/views/TaskDetail.vue` + `src/api/workflow.ts` + `src/stores/project/workflow.ts` + `src-tauri/src/commands/workflow.rs`
|
||||
- [x] ✅(batch35·2026-06-16·workflow) **①-3** i18n 文案 — `src/i18n/{zh-CN,en}/taskDetail.ts`(taskDetail.* 命名空间,8 key 对称:workflowAdvanceTitle/Advancing/AdvanceFailed{msg}/StepRunning{node}/StepsProgress{done}{total}/CompletedHint/FailedHint)。注:todo 原路径 `locales/tasks.ts` 不准,实为 `i18n/taskDetail.ts`
|
||||
- **实施顺序**:④-1→②-1→②-5→②-2→②-3→②-4→②-6→①-1/①-3。**最小里程碑**(④-1+②-1+②-2+②-3):run_workflow 单 task_advance 节点 DAG 端到端推进 todo→in_progress
|
||||
- [ ] F-260616-07 阶段3 AI 执行闭环(advance_task/run_workflow 工具+AiNode+自审)+ **F-03收口三件**(advance_task注册AI工具+update_task描述/schema移status+df-storage tasks白名单移status,同步落地防AI工具行为不一致)
|
||||
- [ ] F-260616-07 阶段3 AI 执行闭环 — **F-03 收口三件 ✅ 本批完成**(batch64), AiNode 自审闸门 ⏳ 待后续批
|
||||
- [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 当前队列)
|
||||
- [ ] AiNode 自审闸门(阶段3 核心,需设计) — 待后续批
|
||||
- [ ] run_workflow handler 注入 AppState(当前报错引导走 IPC) — 后续批扩展 build_ai_tool_registry 签名注入 AppState 句柄让 AI 直驱
|
||||
- [x] ⏸️(待决策.md已决b暂缓·2026-06-16) F-260616-08 阶段4 Git 集成(kind+git闸门+worktree)
|
||||
- [x] ✅ **CR-260616-01 代码审查完成** → 审查登记已迁 [待审查.md](./待审查.md)(职责分离:审查队列独立,不进 todo)。结论 🔴0 🟡6 ⚪4 质量优,8 维度全过。**待修项 CR-01-A~I 见下方推进区**。
|
||||
|
||||
|
||||
10
docs/待决策.md
10
docs/待决策.md
@@ -142,7 +142,7 @@
|
||||
- c: closure 注入
|
||||
- **推荐**:**a**(trait 解耦,但前置 F-07 trait 下沉)
|
||||
- **关联**:todo F-260614-03 / F-07
|
||||
- **状态**:🟡 待决(阻塞于 F-07)
|
||||
- **状态**:✅ 已实施(F-07 batch61·2069f79 解锁 + F-03 batch62·dfe0096 evaluate_with_llm 真实实现落地,19单测过,CR-40审查中)
|
||||
|
||||
#### F-260616-07 阶段3 AI 执行闭环 + F-03 收口三件
|
||||
- **背景**:advance_task/run_workflow 注册为 AI 工具 + AiNode 自审闸门;F-03 收口三件(advance_task 注册 + update_task 描述/schema 移 status + df-storage tasks 白名单移 status)须同步落地防 AI 工具行为不一致。
|
||||
@@ -153,7 +153,7 @@
|
||||
- c: 暂缓(等 F-07 + 真实推进场景)
|
||||
- **推荐**:**b 先做 F-03 收口**(防现状行为不一致),AiNode 自审等 F-07
|
||||
- **关联**:todo F-260616-07 阶段3 / F-07
|
||||
- **状态**:🟡 待决(阻塞于 F-07)
|
||||
- **状态**:✅ 决策b 实施中(F-07 batch61·2069f79 解锁,F-03收口三件 batch64·workflow wii1u1lnm 推进中,AiNode自审等后续批)
|
||||
|
||||
#### F-260616-08 阶段4 Git 集成
|
||||
- **背景**:kind 字段 + git 闸门节点 + worktree 隔离。
|
||||
@@ -321,10 +321,8 @@
|
||||
|
||||
### C类 — 用户实测/澄清(需用户操作给反馈,主会话无法代办)
|
||||
|
||||
#### S-260615-01 aichat API Key 401 根因
|
||||
- **待用户操作**:跑直连 curl 测试,区分 key/provider_type/base_url/model 哪项错。见 [aichat-apikey-401排查-2026-06-15.md](./09-问题排查/aichat-apikey-401排查-2026-06-15.md)「验证步骤」。
|
||||
- **背景**:代码链路全对(保存 keyring/读取/鉴权头/URL 拼接),401 来自服务端非 devflow bug。根因四选一。
|
||||
- **状态**:🟡 待用户反馈
|
||||
#### ~~S-260615-01 aichat API Key 401 根因~~
|
||||
- **状态**:✅ 已解决(2026-06-16)— 用户确认已解决。
|
||||
|
||||
#### S-260614-01 「显示多开」需求澄清
|
||||
- **待用户操作**:截图确认。用户报"设置勾选显示多开但 AiChat 未显示",全 src grep 零命中,疑似旧版本/指分离窗口/想新增开关。
|
||||
|
||||
30
docs/待审查.md
30
docs/待审查.md
@@ -99,13 +99,39 @@
|
||||
- **主代独立核查**: ✅ 全过(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 真实实现) — ⏳ 待审
|
||||
### 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**: 待提交(batch62 合批)。
|
||||
- **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 adversarial` **11 passed** EXIT 0(7 heuristic a1-a7 + 4 LLM a8-a11)/ `cargo test -p df-ideas` **19 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(schema ~10 字段全填~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 降级,最坏后果降级启发式不致数据错。
|
||||
- **⚪ 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 若输出 `好的,以下是评估:\n```json\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) — 🟡 待审
|
||||
|
||||
- **范围**: 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)` 与 IPC `commands::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 L374 `if 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_atomic` L848 CAS SQL `WHERE 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 审查**(对 batch62 dfe0096 evaluate_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 CAS `WHERE 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。
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -359,7 +359,7 @@ pub fn build_ai_tool_registry(db: &Arc<Database>) -> AiToolRegistry {
|
||||
})},
|
||||
);
|
||||
registry.register(
|
||||
"update_task", "更新任务的指定字段(title/description/status/priority/assignee 等),需要提供任务 ID、字段名和新值",
|
||||
"update_task", "更新任务的指定字段(title/description/priority/assignee 等),需要提供任务 ID、字段名和新值。注意:status 改动须走 advance_task 工具(状态机推进,7 态 todo/in_progress/in_review/testing/blocked/done/cancelled,按 target_status 推进不可跳跃),本工具不接受 status 字段(防绕过状态机)",
|
||||
df_ai::ai_tools::object_schema(vec![("id", "string", true), ("field", "string", true), ("value", "string", true)]),
|
||||
RiskLevel::Medium,
|
||||
{ let db = db.clone(); Box::new(move |args: serde_json::Value| {
|
||||
@@ -368,6 +368,14 @@ pub fn build_ai_tool_registry(db: &Arc<Database>) -> AiToolRegistry {
|
||||
let id = args["id"].as_str().ok_or_else(|| anyhow::anyhow!("缺少 id"))?;
|
||||
let field = args["field"].as_str().ok_or_else(|| anyhow::anyhow!("缺少 field"))?;
|
||||
let value = args["value"].as_str().ok_or_else(|| anyhow::anyhow!("缺少 value"))?;
|
||||
// F-03 收口(子项3):status 改动必须走 advance_task 工具(状态机推进),
|
||||
// 本工具显式拒绝 status 字段,防 AI 绕过状态机直接改库。
|
||||
// 对齐 Agent B df-storage tasks 白名单移 status——双重防御(schema 拒绝 + 白名单拒绝)。
|
||||
if field == "status" {
|
||||
anyhow::bail!(
|
||||
"status 改动须走 advance_task 工具(状态机推进,7 态不可跳跃),本工具不接受 status 字段"
|
||||
);
|
||||
}
|
||||
// 复用 df-storage CRUD 白名单(按表隔离),与 update_field 校验同源
|
||||
if !df_storage::crud::is_allowed_column("tasks", field) {
|
||||
anyhow::bail!("不允许更新字段 '{}'", field);
|
||||
@@ -378,6 +386,65 @@ pub fn build_ai_tool_registry(db: &Arc<Database>) -> AiToolRegistry {
|
||||
})
|
||||
})},
|
||||
);
|
||||
// F-03 收口(子项1):advance_task AI 工具 — 推进任务状态的唯一合法路径。
|
||||
// D-260616-03:推进链业务逻辑(状态机校验 + 原子 CAS + review_rounds 累加)落
|
||||
// df-nodes::task_advance_node::advance_task_atomic,本 handler 直接调它(与 IPC
|
||||
// commands::task::advance_task 同源,thin 转发),无需 AppHandle/State/EventBus。
|
||||
// 风险 Medium:status 变更属状态机推进(非破坏),但改变任务流转语义需人工知情。
|
||||
registry.register(
|
||||
"advance_task", "推进任务状态,必须走状态机(7 态: todo/in_progress/in_review/testing/blocked/done/cancelled),按 target_status 推进,不可跳跃(非法转换会被状态机拦截)。退回转换(in_review→in_progress 等)自动累加 review_rounds。返回推进后的最新 TaskRecord",
|
||||
df_ai::ai_tools::object_schema(vec![("id", "string", true), ("target_status", "string", true)]),
|
||||
RiskLevel::Medium,
|
||||
{ let db = db.clone(); Box::new(move |args: serde_json::Value| {
|
||||
let db = db.clone();
|
||||
Box::pin(async move {
|
||||
let id = args["id"].as_str().ok_or_else(|| anyhow::anyhow!("缺少 id"))?;
|
||||
let target_status = args["target_status"].as_str()
|
||||
.ok_or_else(|| anyhow::anyhow!("缺少 target_status"))?;
|
||||
// 复用 df-nodes 推进链核心逻辑(状态机 + 原子 CAS + review_rounds),
|
||||
// 与 commands::task::advance_task IPC 同源,避免双轨。
|
||||
let repo = df_storage::crud::TaskRepo::new(&db);
|
||||
let updated = df_nodes::task_advance_node::advance_task_atomic(
|
||||
&repo, id, target_status,
|
||||
).await?;
|
||||
// 返回推进后的 TaskRecord(含新 status / 累加后的 review_rounds),供 LLM 确认推进结果。
|
||||
Ok(serde_json::to_value(&updated)?)
|
||||
})
|
||||
})},
|
||||
);
|
||||
// F-260616-07 阶段3(子项2):run_workflow AI 工具注册实装。
|
||||
// 实施路径文档 §三 列为阶段3 必做项(tool_registry.rs 此前无此工具连空壳都没有)。
|
||||
// 描述明确按任务 target_status 推进对应工作流(含 AiNode 自审 / HumanNode 核对闸门)。
|
||||
//
|
||||
// handler 约束说明:run_workflow 真正执行需要 AppHandle(转发 workflow-event 到前端) +
|
||||
// AppState(registry 构建 DAG / event_bus 订阅 / workflows Repo 落库 / workflow_state_registry
|
||||
// 注销)等 Tauri 注入态,这些在 tool handler(仅持有 db: Arc<Database>)中无法构造。
|
||||
// 现阶段仅注册 ToolDefinition(schema + risk + 描述)让 LLM 知晓此能力并产出 tool_call;
|
||||
// 真正触发须走 Tauri IPC(经 invoke_handler 注册的 run_workflow 命令,持有完整 State)。
|
||||
// handler 显式报错引导走 IPC,避免在 handler 内重放 DAG 执行引擎(违反单一执行路径原则)。
|
||||
// 后续若需 AI 直驱完整工作流,需扩展 build_ai_tool_registry 注入 AppState 句柄(改 state.rs,
|
||||
// 留待推进链后续批次)。
|
||||
registry.register(
|
||||
"run_workflow", "按任务 target_status 推进对应工作流(含 AiNode 自审 / HumanNode 核对闸门)。参数 task_id + target_status 同时提供才联动任务推进(完成后按 target_status 推进任务,失败按退回态回滚)。属高风险操作(触发工作流引擎执行),须人工批准",
|
||||
df_ai::ai_tools::object_schema(vec![("task_id", "string", true), ("target_status", "string", true)]),
|
||||
RiskLevel::High,
|
||||
{ let db = db.clone(); Box::new(move |args: serde_json::Value| {
|
||||
let _db = db.clone();
|
||||
Box::pin(async move {
|
||||
let task_id = args["task_id"].as_str().ok_or_else(|| anyhow::anyhow!("缺少 task_id"))?;
|
||||
let target_status = args["target_status"].as_str()
|
||||
.ok_or_else(|| anyhow::anyhow!("缺少 target_status"))?;
|
||||
// handler 无法访问 AppHandle/State(registry/event_bus/workflows Repo),
|
||||
// 真正执行须走 run_workflow Tauri IPC(持有完整 State)。
|
||||
// 这里返回明确错误引导前端走 IPC,而非在 handler 内重放 DAG 引擎。
|
||||
Err(anyhow::anyhow!(
|
||||
"run_workflow 工具需经 Tauri IPC 执行(持有 AppHandle/State),handler 无 State 句柄。\
|
||||
请前端收到此 tool_call 后转调 invoke('run_workflow', {{ task_id: {}, target_status: {} }})。",
|
||||
task_id, target_status
|
||||
))
|
||||
})
|
||||
})},
|
||||
);
|
||||
// FR-D6:delete_task 对齐 commands::task::delete_task(D-260616-02 软删除)
|
||||
// tasks 表 V14 加 deleted_at,delete_task 改调 soft_delete 移入回收站(对标 delete_project)
|
||||
registry.register(
|
||||
@@ -1189,6 +1256,9 @@ pub(crate) fn display_hint_for_tool(name: &str) -> Option<(&'static str, &'stati
|
||||
"patch_file" => ("修改文件:{}", &["path"]),
|
||||
"delete_file" => ("删除文件:{}({})", &["path", "permanent"]),
|
||||
"rename_file" => ("重命名/移动:{} → {}", &["from", "to"]),
|
||||
// F-03 收口:推进链工具审批文案(advance_task/run_workflow)
|
||||
"advance_task" => ("推进任务状态:{} → {}", &["id", "target_status"]),
|
||||
"run_workflow" => ("触发工作流:任务{} 推进到 {}", &["task_id", "target_status"]),
|
||||
_ => return None,
|
||||
};
|
||||
Some((template, keys))
|
||||
@@ -1213,6 +1283,8 @@ pub(crate) fn tool_display_hint(name: &str) -> Option<&'static str> {
|
||||
"bind_directory" => Some("绑定目录"),
|
||||
"create_idea" => Some("捕获灵感"),
|
||||
"update_task" => Some("修改任务"),
|
||||
"advance_task" => Some("推进任务状态"),
|
||||
"run_workflow" => Some("触发工作流"),
|
||||
"delete_task" => Some("删除任务"),
|
||||
"update_idea" => Some("修改灵感"),
|
||||
"delete_idea" => Some("删除灵感"),
|
||||
|
||||
Reference in New Issue
Block a user