新增: AI Chat多项增强(审批去重/编辑重发/导出/实体引用/会话置顶搜索)+任务推进链df-nodes落地
This commit is contained in:
@@ -8,8 +8,11 @@ use tauri::{AppHandle, Emitter, State};
|
||||
use tokio::sync::broadcast::error::RecvError;
|
||||
|
||||
use df_core::events::{WorkflowEvent, HumanApprovalResponse, SelectType};
|
||||
use df_core::types::new_id;
|
||||
use df_storage::crud::WorkflowRepo;
|
||||
use df_core::types::{new_id, NodeStatus};
|
||||
use df_nodes::task_advance_node::advance_task_atomic;
|
||||
// F-260616-06 ①-1: 空 dag + target_status 时按目标态自动选推进链模板
|
||||
use df_nodes::task_workflow_templates::template_for;
|
||||
use df_storage::crud::{TaskRepo, WorkflowRepo};
|
||||
use df_storage::models::WorkflowRecord;
|
||||
use df_workflow::dag_def::DagDef;
|
||||
use df_workflow::executor::DagExecutor;
|
||||
@@ -27,11 +30,36 @@ struct WorkflowEventPayload {
|
||||
event: WorkflowEvent,
|
||||
}
|
||||
|
||||
/// F-260616-06 ②-4: 工作流失败时按 target_status 推算任务退回态。
|
||||
///
|
||||
/// 映射表(失败退一步):
|
||||
/// - testing → in_review
|
||||
/// - in_review → in_progress
|
||||
/// - in_progress → todo
|
||||
/// - 其他(done/blocked/cancelled/todo 等) → None(无退回映射,跳过回调)
|
||||
///
|
||||
/// 设计选择:in_progress → todo(而非 None)。理由:in_progress 是执行态,失败退回 todo
|
||||
/// 符合"执行未达预期 → 回起点重排"的直觉语义;若选 None 会丢失"执行失败"信号。
|
||||
/// 起点状态 todo 无可退态 → None。
|
||||
fn regression_target(target: &str) -> Option<&str> {
|
||||
match target {
|
||||
"testing" => Some("in_review"),
|
||||
"in_review" => Some("in_progress"),
|
||||
"in_progress" => Some("todo"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// 触发工作流执行(核心命令)
|
||||
///
|
||||
/// 流程:build_dag 校验 → 写入执行记录(status=running) → 后台异步执行 →
|
||||
/// 事件经 EventBus 转发到前端 → 完成后更新执行记录状态。
|
||||
/// 立即返回执行记录 ID,前端凭此关联后续事件。
|
||||
///
|
||||
/// F-260616-06 阶段2 工作流联动(②-2):
|
||||
/// - `task_id` / `target_status` 同时 Some 时,完成后按 target_status 推进任务(②-3),
|
||||
/// 失败时按 regression_target 退回(②-4)。
|
||||
/// - Option 向后兼容:旧调用方不传即 None,不触发任何任务回调,零行为破坏。
|
||||
#[tauri::command]
|
||||
pub async fn run_workflow(
|
||||
app: AppHandle,
|
||||
@@ -39,7 +67,27 @@ pub async fn run_workflow(
|
||||
name: String,
|
||||
dag: DagDef,
|
||||
config: serde_json::Value,
|
||||
// F-260616-06 ②-2: 工作流联动任务的关联 ID 与目标态(同时 Some 才联动)
|
||||
task_id: Option<String>,
|
||||
target_status: Option<String>,
|
||||
) -> Result<String, String> {
|
||||
// 0. F-260616-06 ①-1: DagDef 来源选模板(方案A·最小)。
|
||||
// 规则:
|
||||
// - dag.nodes 非空 → 用传入 dag(向后兼容,旧调用方/编辑器自定义工作流不变)
|
||||
// - dag.nodes 为空 + target_status Some → 调 template_for 自动选推进链模板;
|
||||
// 模板不存在(target 非三合法态) → Err「无对应工作流模板」
|
||||
// - dag.nodes 为空 + target_status None → 原行为(空 dag 让 build_dag 自行报错,零新分支)
|
||||
// 前端选 target_status 后传空 dag,零模板知识零新 IPC,后端收敛选模板逻辑。
|
||||
let dag = if dag.nodes.is_empty() {
|
||||
match target_status.as_deref() {
|
||||
Some(target) => template_for(target)
|
||||
.ok_or_else(|| format!("无对应工作流模板: target_status={}", target))?,
|
||||
None => dag, // 空dag+无target: 走原路径由 build_dag 校验
|
||||
}
|
||||
} else {
|
||||
dag
|
||||
};
|
||||
|
||||
// 1. 先构建运行时 DAG,校验失败直接返回,不落库
|
||||
let runtime_dag = state.registry.build_dag(&dag).map_err(err_str)?;
|
||||
|
||||
@@ -53,7 +101,7 @@ pub async fn run_workflow(
|
||||
status: "running".to_string(),
|
||||
triggered_by: Some("manual".to_string()),
|
||||
project_id: None,
|
||||
task_id: None,
|
||||
task_id: task_id.clone(),
|
||||
created_at: now_millis(),
|
||||
completed_at: None,
|
||||
};
|
||||
@@ -180,6 +228,9 @@ pub async fn run_workflow(
|
||||
let db = state.db.clone();
|
||||
let exec_id = execution_id.clone();
|
||||
let state_registry = state.workflow_state_registry.clone();
|
||||
// F-260616-06 ②-2: move task_id / target_status 进闭包供完成/失败回调使用
|
||||
let cb_task_id = task_id.clone();
|
||||
let cb_target_status = target_status.clone();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
let mut executor = DagExecutor::new(event_bus.clone(), exec_id.clone());
|
||||
// 注册执行器状态机:StateMachine 内部 Arc<Mutex>,clone 共享底层 HashMap,
|
||||
@@ -207,13 +258,58 @@ pub async fn run_workflow(
|
||||
tracing::error!("更新工作流完成时间失败: {}", e);
|
||||
}
|
||||
|
||||
// F-260616-06 ②-3 / ②-4: 工作流联动任务推进回调
|
||||
// - 成功(completed): task_id 与 target_status 都 Some 时推进到 target_status
|
||||
// - 失败(failed): 按 regression_target 推算退回态推进;无映射则跳过
|
||||
// task_id / target_status 任一 None → 跳过(向后兼容,旧工作流不联动任务)
|
||||
// 回调失败仅 tracing::warn!,不回滚(工作流成功语义与任务推进解耦 — 回调失败不撤销
|
||||
// 已完成工作流,前端可后续手动处理)
|
||||
match (cb_task_id.as_ref(), cb_target_status.as_deref()) {
|
||||
(Some(tid), Some(target)) => {
|
||||
let advance_target = match status {
|
||||
"completed" => Some(target),
|
||||
"failed" => regression_target(target),
|
||||
_ => None,
|
||||
};
|
||||
if let Some(to) = advance_target {
|
||||
let repo = TaskRepo::new(&db);
|
||||
if let Err(e) = advance_task_atomic(&repo, tid, to).await {
|
||||
tracing::warn!(
|
||||
execution_id = %exec_id,
|
||||
task_id = %tid,
|
||||
target_status = %to,
|
||||
workflow_status = %status,
|
||||
"工作流联动推进任务失败(不回滚): {}",
|
||||
e
|
||||
);
|
||||
} else {
|
||||
tracing::info!(
|
||||
execution_id = %exec_id,
|
||||
task_id = %tid,
|
||||
target_status = %to,
|
||||
workflow_status = %status,
|
||||
"工作流联动推进任务成功"
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// failed 且无退回映射:warn 跳过(常见:target 是 todo 起点无可退态)
|
||||
tracing::warn!(
|
||||
execution_id = %exec_id,
|
||||
task_id = %tid,
|
||||
target_status = %target,
|
||||
"工作流失败但 target_status 无退回映射,跳过任务回调"
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// 执行结束(成功/失败)清理状态注册表,防内存泄漏
|
||||
state_registry.lock().await.remove(&exec_id);
|
||||
|
||||
// 执行失败时补发 WorkflowFailed(执行器内部只发 NodeFailed)
|
||||
// failed_node 从状态机取首个失败/取消节点(R9⑪: 原 String::new() 空值)
|
||||
if let Some(error) = error {
|
||||
use df_core::types::NodeStatus;
|
||||
let failed_node = executor
|
||||
.state_machine()
|
||||
.snapshot()
|
||||
@@ -337,6 +433,11 @@ pub async fn approve_human_approval(
|
||||
/// 从执行器状态机注册表取出共享引用,调 `set_cancelled` 置目标节点为 Cancelled。
|
||||
/// StateMachine 内部 Arc<Mutex> 共享底层 HashMap,IPC 写入直达运行中阻塞节点(HumanNode)
|
||||
/// 的 select! 轮询分支,其 is_cancelled 检测到后返回 Err "人工审批被取消"。
|
||||
///
|
||||
/// 终态前置守卫:仅 Pending(排队中)/ Running(执行中,含阻塞等审批)允许取消;
|
||||
/// 终态节点(Completed/Failed/Skipped/Cancelled)返 Err,避免静默覆盖终态。
|
||||
/// 守卫放 IPC 层(非 set_cancelled 内):保留 set_cancelled 作为 executor 内部受控旁路语义,
|
||||
/// 让状态变更的合法入口收敛到 IPC 这一道。
|
||||
#[tauri::command]
|
||||
pub async fn cancel_workflow_node(
|
||||
state: State<'_, AppState>,
|
||||
@@ -349,11 +450,25 @@ pub async fn cancel_workflow_node(
|
||||
Some(sm) => sm,
|
||||
None => return Err(format!("工作流 {} 不存在或已结束", execution_id)),
|
||||
};
|
||||
sm.set_cancelled(node_id.clone());
|
||||
tracing::info!(
|
||||
"工作流节点取消:execution_id={}, node_id={}",
|
||||
execution_id,
|
||||
node_id
|
||||
);
|
||||
Ok(())
|
||||
// 终态守卫:get() 返回缺失条目默认 Pending(尚未执行)→ 允许取消
|
||||
let current = sm.get(&node_id);
|
||||
match current {
|
||||
NodeStatus::Pending | NodeStatus::Running | NodeStatus::Waiting => {
|
||||
sm.set_cancelled(node_id.clone());
|
||||
tracing::info!(
|
||||
"工作流节点取消:execution_id={}, node_id={}",
|
||||
execution_id,
|
||||
node_id
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
// 终态:不允许取消(避免静默覆盖终态)
|
||||
NodeStatus::Completed | NodeStatus::Failed | NodeStatus::Skipped | NodeStatus::Cancelled => {
|
||||
Err(format!(
|
||||
"节点 {} 已终态({}),无法取消",
|
||||
node_id,
|
||||
current.as_str()
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user