新增: 初始化 DevFlow 项目仓库

Tauri 2 + Vue 3 + Vite 6 桌面应用,Rust workspace 含 13 个 crate
(df-ai / df-storage / df-workflow / df-core / df-execute 等)。
核心能力:AI 聊天 agentic 循环(工具调用+人工审批)、工作流引擎、
任务/想法/项目/阶段管理、可追溯性,及配套前端组件。
This commit is contained in:
2026-06-12 01:31:05 +08:00
commit 98393b4908
178 changed files with 27859 additions and 0 deletions

View File

@@ -0,0 +1,147 @@
//! 节点状态机 — 管理节点的状态转换
//!
//! 合法转换Pending → RunningRunning → Completed / Failed
//! 非法转换返回错误,避免状态被随意覆盖。
use std::collections::HashMap;
use df_core::types::{NodeId, NodeStatus};
/// 节点状态机
#[derive(Debug, Clone)]
pub struct StateMachine {
/// 节点状态映射
states: HashMap<NodeId, NodeStatus>,
}
impl StateMachine {
/// 创建状态机
pub fn new() -> Self {
Self {
states: HashMap::new(),
}
}
/// 获取节点状态
pub fn get(&self, node_id: &NodeId) -> NodeStatus {
self.states
.get(node_id)
.cloned()
.unwrap_or(NodeStatus::Pending)
}
/// 判断状态转换是否合法
fn is_legal(from: &NodeStatus, to: &NodeStatus) -> bool {
matches!(
(from, to),
(NodeStatus::Pending, NodeStatus::Running)
| (NodeStatus::Running, NodeStatus::Completed)
| (NodeStatus::Running, NodeStatus::Failed)
)
}
/// 状态转换 — 校验合法性后更新,非法转换返回错误
pub fn transition(&mut self, node_id: NodeId, target: NodeStatus) -> anyhow::Result<()> {
let current = self.get(&node_id);
if !Self::is_legal(&current, &target) {
anyhow::bail!(
"节点 {} 状态转换非法:{} -> {}",
node_id,
current.as_str(),
target.as_str()
);
}
self.states.insert(node_id, target);
Ok(())
}
/// 设置节点为运行中(仅允许 Pending -> Running
pub fn set_running(&mut self, node_id: NodeId) -> anyhow::Result<()> {
self.transition(node_id, NodeStatus::Running)
}
/// 设置节点为已完成(仅允许 Running -> Completed
pub fn set_completed(&mut self, node_id: NodeId) -> anyhow::Result<()> {
self.transition(node_id, NodeStatus::Completed)
}
/// 设置节点为失败(仅允许 Running -> Failed
pub fn set_failed(&mut self, node_id: NodeId) -> anyhow::Result<()> {
self.transition(node_id, NodeStatus::Failed)
}
/// 设置节点为等待中(暂未纳入转换校验)
pub fn set_waiting(&mut self, node_id: NodeId) {
self.states.insert(node_id, NodeStatus::Waiting);
}
/// 设置节点为已跳过(暂未纳入转换校验)
pub fn set_skipped(&mut self, node_id: NodeId) {
self.states.insert(node_id, NodeStatus::Skipped);
}
/// 获取所有状态快照
pub fn snapshot(&self) -> &HashMap<NodeId, NodeStatus> {
&self.states
}
/// 检查节点是否被取消
pub fn is_cancelled(&self, node_id: &NodeId) -> bool {
matches!(self.get(node_id), NodeStatus::Cancelled)
}
}
impl Default for StateMachine {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_legal_transitions() {
let mut sm = StateMachine::new();
let id = "node-a".to_string();
// Pending -> Running -> Completed 全程合法
assert!(sm.set_running(id.clone()).is_ok());
assert_eq!(sm.get(&id), NodeStatus::Running);
assert!(sm.set_completed(id.clone()).is_ok());
assert_eq!(sm.get(&id), NodeStatus::Completed);
// Running -> Failed 合法
let id2 = "node-b".to_string();
sm.set_running(id2.clone()).unwrap();
assert!(sm.set_failed(id2.clone()).is_ok());
assert_eq!(sm.get(&id2), NodeStatus::Failed);
}
#[test]
fn test_illegal_transitions_rejected() {
let mut sm = StateMachine::new();
let id = "node-a".to_string();
// Pending -> Completed 非法
let err = sm.set_completed(id.clone()).unwrap_err();
assert!(err.to_string().contains("状态转换非法"));
assert!(err.to_string().contains("node-a"));
// 状态保持不变
assert_eq!(sm.get(&id), NodeStatus::Pending);
// Pending -> Failed 非法
assert!(sm.set_failed(id.clone()).is_err());
// Completed 为终态,不允许再转 Running
sm.set_running(id.clone()).unwrap();
sm.set_completed(id.clone()).unwrap();
assert!(sm.set_running(id.clone()).is_err());
// Running -> Running 重复置位非法
let id2 = "node-b".to_string();
sm.set_running(id2.clone()).unwrap();
assert!(sm.set_running(id2.clone()).is_err());
}
}