//! Top-level workflow execution functions. //! //! `run_workflow` and `run_workflow_tracked` are the public entry-points //! for running a complete `WorkflowScript`. They create an isolated findings //! scope and delegate to `execute_primitive`, then format the results into a //! human-readable summary string. use crate::app::workflow::script::WorkflowScript; use std::collections::HashMap; use std::sync::{ atomic::AtomicBool, Arc, Mutex, }; use super::primitives::{execute_primitive, PrimitiveCtx}; use super::LiveStateFn; /// Run a `WorkflowScript` with the given template arguments and produce a /// summary string. Uses no live-state callback. /// /// Return: a human-readable summary string. pub fn run_workflow( script: &WorkflowScript, args: &HashMap, session_dir: &std::path::Path, workspaces: &[std::path::PathBuf], ) -> anyhow::Result { run_workflow_tracked(script, args, &None, None, session_dir, workspaces) } /// Run a `WorkflowScript` with real-time live-state callbacks so the TUI /// panel updates as each agent transitions between Idle/Running/Done/Failed. /// /// Flow: create an empty findings Arc (scoped to this invocation) → cap /// concurrency to 8 → call `execute_primitive` with the live callback and /// findings → format results. /// /// Why: findings are scoped to an `Arc>>` rather than a /// global static, so concurrent `run_workflow_tracked` calls from different /// `spawn_agents` invocations remain fully isolated. /// /// Return: a human-readable summary string. pub fn run_workflow_tracked( script: &WorkflowScript, args: &HashMap, abort_flag: &Option>, live: Option<&LiveStateFn>, session_dir: &std::path::Path, workspaces: &[std::path::PathBuf], ) -> anyhow::Result { let concurrency_cap = if script.options.max_concurrency > 0 { script.options.max_concurrency.min(10) // allow up to 10 parallel agents } else { 10 }; let findings = Arc::new(Mutex::new(Vec::new())); let results = execute_primitive(PrimitiveCtx { primitive: &script.script, args, concurrency_cap, continue_on_error: script.options.continue_on_error, abort_flag, live, session_dir, workspaces, findings: &findings, timeout_ms: script.options.timeout_ms, })?; let summary = if results.is_empty() { "workflow completed with no output".to_string() } else { format!( "workflow '{}' completed. {} agent result(s):\n{}", script.name, results.len(), results .iter() .enumerate() .map(|(i, r)| format!("[{}] {}", i + 1, r.lines().next().unwrap_or(r))) .collect::>() .join("\n") ) }; Ok(summary) }