修复: gen_stream判重+重试分类+状态机最短路径+既有测试修复

This commit is contained in:
lxy
2026-08-12 00:29:06 +08:00
parent 9407f821b9
commit d31a27512c
36 changed files with 2614 additions and 264 deletions
+25 -7
View File
@@ -38,7 +38,7 @@ pub fn register(registry: &mut AiToolRegistry, db: &Arc<Database>) {
registry,
db: Arc<Database>,
"list_tasks",
"列出任务,可按 project_id/status 筛选(status: todo/in_progress/in_review/testing/blocked/done/cancelled),支持 offset/limit 分页。返回 items、total、has_more。默认 limit=50",
"列出任务,可按 project_id/status 筛选(status: todo/in_progress/in_review/testing/blocked/done/cancelled/deferred),支持 offset/limit 分页。返回 items、total、has_more。默认 limit=50",
RiskLevel::Low,
schema: object_schema(vec![("project_id", "string", false), ("status", "string", false), ("offset", "integer", false), ("limit", "integer", false)]),
args => {
@@ -48,7 +48,7 @@ pub fn register(registry: &mut AiToolRegistry, db: &Arc<Database>) {
} else {
repo.list_all().await?
};
// 按状态过滤(可选):todo/in_progress/in_review/testing/blocked/done/cancelled
// 按状态过滤(可选):todo/in_progress/in_review/testing/blocked/done/cancelled/deferred
if let Some(status) = args.get("status").and_then(|v| v.as_str()) {
tasks.retain(|t| t.status.as_str() == status);
}
@@ -127,7 +127,7 @@ pub fn register(registry: &mut AiToolRegistry, db: &Arc<Database>) {
registry,
db: Arc<Database>,
"update_task",
"更新任务的指定字段(title/description/priority/assignee 等),需要提供任务 ID、字段名和新值。注意:status 改动须走 advance_task 工具(状态机推进,7 态 todo/in_progress/in_review/testing/blocked/done/cancelled,按 target_status 推进不可跳跃),本工具不接受 status 字段(防绕过状态机)",
"更新任务的指定字段(title/description/priority/assignee 等),需要提供任务 ID、字段名和新值。注意:status 改动须走 advance_task 工具(状态机推进,8 态 todo/in_progress/in_review/testing/blocked/done/cancelled/deferred,按 target_status 推进不可跳跃),本工具不接受 status 字段(防绕过状态机)",
RiskLevel::Medium,
schema: object_schema(vec![("id", "string", true), ("field", "string", true), ("value", "string", true)]),
args => {
@@ -139,7 +139,7 @@ pub fn register(registry: &mut AiToolRegistry, db: &Arc<Database>) {
// 对齐 Agent B df-storage tasks 白名单移 status——双重防御(schema 拒绝 + 白名单拒绝)。
if field == "status" {
anyhow::bail!(
"status 改动须走 advance_task 工具(状态机推进,7 态不可跳跃),本工具不接受 status 字段"
"status 改动须走 advance_task 工具(状态机推进,8 态不可跳跃),本工具不接受 status 字段"
);
}
// 复用 df-storage CRUD 白名单(按表隔离),与 update_field 校验同源
@@ -166,7 +166,7 @@ pub fn register(registry: &mut AiToolRegistry, db: &Arc<Database>) {
registry,
db: Arc<Database>,
"advance_task",
"推进任务状态,必须走状态机(7 态: todo/in_progress/in_review/testing/blocked/done/cancelled),按 target_status 推进,不可跳跃(非法转换会被状态机拦截)。退回转换(in_review→in_progress 等)自动累加 review_rounds。返回推进后的最新 TaskRecord",
"推进任务状态,必须走状态机(8 态: todo/in_progress/in_review/testing/blocked/done/cancelled/deferred),按 target_status 推进,不可跳跃(非法转换会被状态机拦截)。退回转换(in_review→in_progress 等)自动累加 review_rounds。返回推进后的最新 TaskRecord",
RiskLevel::Medium,
schema: object_schema(vec![("id", "string", true), ("target_status", "string", true)]),
args => {
@@ -177,11 +177,29 @@ pub fn register(registry: &mut AiToolRegistry, db: &Arc<Database>) {
// 与 commands::task::advance_task IPC 同源,避免双轨。设计 D3:子任务推进后
// 自动触发父 status 聚合(advance_task_with_parent,父聚合失败仅 warn 不阻断)。
let repo = df_storage::crud::TaskRepo::new(&db);
// 推进前读 from(供 applied 断言对比,推进链内部也读,多一次轻量读可接受)。
// 读失败不阻断推进 — from 缺失时 applied.from 留 null(向后兼容)。
let from_status = repo.get_by_id(id).await
.ok().flatten().map(|t| t.status.as_str().to_string());
let updated = df_nodes::task_advance_node::advance_task_with_parent(
&repo, id, target_status,
).await?;
// 返回推进后的 TaskRecord(含新 status / 累加后的 review_rounds),供 LLM 确认推进结果。
Ok(serde_json::to_value(&updated)?)
// 返回推进后的 TaskRecord + applied 结构化断言。matched_target = 实际 to 是否
// 等于调用方传的 target(CAS 冲突后状态可能≠预期,matched_target=false 暴露真相)。
// loop 收敛判定(阶段1-2)据此判目标达成,而非只看"LLM 停止调工具"。
let to_status = updated.status.as_str();
let matched = to_status == target_status;
// TaskRecord 字段平铺在顶层(前端 formatAdvanceTask 直接读 r.title/r.status 等,
// 零破坏);applied 作为附加嵌套字段供 loop 收敛判定读取(阶段1-2)。
let mut value = serde_json::to_value(&updated)?;
if let Some(obj) = value.as_object_mut() {
obj.insert("applied".to_string(), serde_json::json!({
"from": from_status,
"to": to_status,
"matched_target": matched,
}));
}
Ok(value)
}
);