mod builtin;
mod engine;
#[cfg(test)]
mod property_tests;
pub use builtin::*;
pub use engine::WorkflowEngine;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Workflow {
pub id: String,
pub name: String,
pub phase: WorkflowPhase,
pub description: String,
pub steps: Vec<WorkflowStep>,
pub variables: HashMap<String, String>,
pub checkpoints: Vec<String>,
#[serde(default)]
pub builtin: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum WorkflowPhase {
Analysis,
Planning,
Solutioning,
Implementation,
QuickFlow,
Testing,
Documentation,
DevOps,
}
impl std::fmt::Display for WorkflowPhase {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
WorkflowPhase::Analysis => write!(f, "Analysis"),
WorkflowPhase::Planning => write!(f, "Planning"),
WorkflowPhase::Solutioning => write!(f, "Solutioning"),
WorkflowPhase::Implementation => write!(f, "Implementation"),
WorkflowPhase::QuickFlow => write!(f, "Quick Flow"),
WorkflowPhase::Testing => write!(f, "Testing"),
WorkflowPhase::Documentation => write!(f, "Documentation"),
WorkflowPhase::DevOps => write!(f, "DevOps"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WorkflowStep {
pub id: String,
pub name: String,
pub description: String,
pub agent: String,
pub actions: Vec<String>,
pub condition: Option<String>,
pub branches: Vec<WorkflowBranch>,
pub is_checkpoint: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WorkflowBranch {
pub condition: String,
pub next_step: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WorkflowProgress {
pub session_id: String,
pub workflow_id: String,
pub current_step: String,
pub completed_steps: Vec<String>,
pub variables: HashMap<String, String>,
pub started_at: String,
pub updated_at: String,
}
#[derive(Debug, Clone)]
pub struct WorkflowSession {
pub id: String,
pub workflow: Workflow,
pub progress: WorkflowProgress,
}
#[derive(Debug, Clone)]
pub struct StepResult {
pub success: bool,
pub output: Option<String>,
pub next_step: Option<String>,
pub pause_for_review: bool,
}
impl Workflow {
pub fn new(
id: impl Into<String>,
name: impl Into<String>,
phase: WorkflowPhase,
description: impl Into<String>,
) -> Self {
Self {
id: id.into(),
name: name.into(),
phase,
description: description.into(),
steps: Vec::new(),
variables: HashMap::new(),
checkpoints: Vec::new(),
builtin: false,
}
}
pub fn with_step(mut self, step: WorkflowStep) -> Self {
if step.is_checkpoint {
self.checkpoints.push(step.id.clone());
}
self.steps.push(step);
self
}
pub fn with_variable(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.variables.insert(key.into(), value.into());
self
}
pub fn as_builtin(mut self) -> Self {
self.builtin = true;
self
}
pub fn get_step(&self, id: &str) -> Option<&WorkflowStep> {
self.steps.iter().find(|s| s.id == id)
}
pub fn first_step(&self) -> Option<&WorkflowStep> {
self.steps.first()
}
pub fn next_step(&self, current_id: &str) -> Option<&WorkflowStep> {
let current_idx = self.steps.iter().position(|s| s.id == current_id)?;
self.steps.get(current_idx + 1)
}
}
impl WorkflowStep {
pub fn new(
id: impl Into<String>,
name: impl Into<String>,
description: impl Into<String>,
agent: impl Into<String>,
) -> Self {
Self {
id: id.into(),
name: name.into(),
description: description.into(),
agent: agent.into(),
actions: Vec::new(),
condition: None,
branches: Vec::new(),
is_checkpoint: false,
}
}
pub fn with_action(mut self, action: impl Into<String>) -> Self {
self.actions.push(action.into());
self
}
pub fn with_actions(mut self, actions: Vec<String>) -> Self {
self.actions.extend(actions);
self
}
pub fn with_condition(mut self, condition: impl Into<String>) -> Self {
self.condition = Some(condition.into());
self
}
pub fn with_branch(
mut self,
condition: impl Into<String>,
next_step: impl Into<String>,
) -> Self {
self.branches.push(WorkflowBranch {
condition: condition.into(),
next_step: next_step.into(),
});
self
}
pub fn as_checkpoint(mut self) -> Self {
self.is_checkpoint = true;
self
}
}
impl WorkflowProgress {
pub fn new(session_id: impl Into<String>, workflow: &Workflow) -> Self {
let now = chrono::Utc::now().to_rfc3339();
Self {
session_id: session_id.into(),
workflow_id: workflow.id.clone(),
current_step: workflow
.first_step()
.map(|s| s.id.clone())
.unwrap_or_default(),
completed_steps: Vec::new(),
variables: workflow.variables.clone(),
started_at: now.clone(),
updated_at: now,
}
}
pub fn complete_step(&mut self, step_id: &str) {
if !self.completed_steps.contains(&step_id.to_string()) {
self.completed_steps.push(step_id.to_string());
}
self.updated_at = chrono::Utc::now().to_rfc3339();
}
pub fn set_current_step(&mut self, step_id: impl Into<String>) {
self.current_step = step_id.into();
self.updated_at = chrono::Utc::now().to_rfc3339();
}
pub fn set_variable(&mut self, key: impl Into<String>, value: impl Into<String>) {
self.variables.insert(key.into(), value.into());
self.updated_at = chrono::Utc::now().to_rfc3339();
}
pub fn get_variable(&self, key: &str) -> Option<&String> {
self.variables.get(key)
}
}
impl StepResult {
pub fn success(output: Option<String>) -> Self {
Self {
success: true,
output,
next_step: None,
pause_for_review: false,
}
}
pub fn failure(output: Option<String>) -> Self {
Self {
success: false,
output,
next_step: None,
pause_for_review: false,
}
}
pub fn with_next_step(mut self, step_id: impl Into<String>) -> Self {
self.next_step = Some(step_id.into());
self
}
pub fn with_pause(mut self) -> Self {
self.pause_for_review = true;
self
}
}