Replace RunConfig with RunSettings

This commit is contained in:
Bryan Helmkamp 2026-03-24 21:11:16 -04:00
parent 1d456e5021
commit 70a89f6f53
17 changed files with 1238 additions and 1960 deletions

View file

@ -23,7 +23,7 @@ use crate::jwt_auth::{AuthMode, AuthenticatedService, AuthenticatedUser};
use fabro_interview::{Answer, Interviewer, QuestionType, WebInterviewer};
use fabro_workflows::checkpoint::Checkpoint;
use fabro_workflows::context::Context;
use fabro_workflows::engine::{RunConfig, WorkflowRunEngine};
use fabro_workflows::engine::{RunSettings, WorkflowRunEngine};
use fabro_workflows::event::{EventEmitter, WorkflowRunEvent};
use fabro_workflows::handler::HandlerRegistry;
@ -648,24 +648,21 @@ async fn execute_run(state: Arc<AppState>, run_id: String) {
}
}
let config = RunConfig {
let run_record = fabro_workflows::run_record::RunRecord::load(&run_dir)
.expect("RunRecord must exist — written by start_run");
let config = RunSettings {
config: run_record.config,
run_dir,
cancel_token: Some(cancel_token),
dry_run: state.dry_run,
run_id: run_id.clone(),
git_checkpoint_enabled: false,
host_repo_path: None,
base_sha: None,
run_branch: None,
meta_branch: None,
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
labels: run_record.labels,
git_author: state.git_author.clone(),
base_branch: None,
pull_request: None,
asset_globs: Vec::new(),
workflow_slug: None,
github_app: None,
base_branch: None,
host_repo_path: None,
git: None,
};
let result = tokio::select! {

View file

@ -60,6 +60,7 @@ pub(crate) fn normalize_config(
if flags.preserve_sandbox {
config.sandbox.get_or_insert_default().preserve = Some(true);
}
config.pull_request = config.pull_request.take().filter(|p| p.enabled);
config
}

View file

@ -13,7 +13,7 @@ use fabro_model::{Catalog, Provider};
use fabro_util::terminal::Styles;
use fabro_workflows::backend::{AgentApiBackend, AgentCliBackend, BackendRouter};
use fabro_workflows::checkpoint::Checkpoint;
use fabro_workflows::engine::RunConfig;
use fabro_workflows::engine::{GitCheckpointSettings, RunSettings};
use fabro_workflows::event::{EventEmitter, RunNoticeLevel};
use fabro_workflows::outcome::StageStatus;
use fabro_workflows::run_record::RunRecord;
@ -102,7 +102,7 @@ struct ResumeContext {
/// Kept as Arc so the sandbox event callbacks can emit through it. Listeners
/// that need to be added later (e.g. ProgressUI) are registered separately.
emitter: Arc<EventEmitter>,
config: RunConfig,
settings: RunSettings,
setup_commands: Vec<String>,
/// Devcontainer lifecycle phases (on_create, post_create, post_start) resolved from config.
devcontainer_phases: Vec<(String, Vec<fabro_devcontainer::Command>)>,
@ -231,7 +231,7 @@ async fn prepare_from_checkpoint(
write_run_config_snapshot(&run_dir, workflow_toml_path.as_deref()).await?;
// Write RunRecord for the resumed run
{
let settings_config = {
let working_directory = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
let cli_flags = super::create::CliFlags {
dry_run: args.dry_run,
@ -252,7 +252,7 @@ async fn prepare_from_checkpoint(
let record = fabro_workflows::run_record::RunRecord {
run_id: run_id.clone(),
created_at: chrono::Utc::now(),
config: normalized,
config: normalized.clone(),
graph: graph.clone(),
workflow_slug: workflow_slug.clone(),
working_directory: working_directory.clone(),
@ -261,7 +261,8 @@ async fn prepare_from_checkpoint(
labels: std::collections::HashMap::new(),
};
let _ = record.save(&run_dir);
}
normalized
};
let original_cwd = std::env::current_dir()?;
let emitter = Arc::new(EventEmitter::new());
@ -439,41 +440,23 @@ async fn prepare_from_checkpoint(
};
let sandbox: Arc<dyn Sandbox> = Arc::new(fabro_agent::ReadBeforeWriteSandbox::new(sandbox));
let config = RunConfig {
let settings = RunSettings {
config: settings_config,
run_dir: run_dir.clone(),
cancel_token: None,
dry_run: args.dry_run,
run_id: run_id.clone(),
git_checkpoint_enabled: false,
host_repo_path: None,
base_sha: None,
run_branch: None,
meta_branch: None,
git: None,
labels: args
.label
.iter()
.filter_map(|s| s.split_once('='))
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect(),
checkpoint_exclude_globs: run_cfg
.as_ref()
.map(|cfg| cfg.checkpoint.exclude_globs.clone())
.unwrap_or_else(|| run_defaults.checkpoint.exclude_globs.clone()),
github_app: github_app.clone(),
git_author,
base_branch: None,
pull_request: run_cfg
.as_ref()
.and_then(|cfg| cfg.pull_request.as_ref())
.or(run_defaults.pull_request.as_ref())
.filter(|p| p.enabled)
.cloned(),
asset_globs: run_cfg
.as_ref()
.and_then(|cfg| cfg.assets.as_ref())
.or(run_defaults.assets.as_ref())
.map(|a| a.include.clone())
.unwrap_or_default(),
workflow_slug,
};
@ -496,7 +479,7 @@ async fn prepare_from_checkpoint(
run_cfg,
sandbox,
emitter,
config,
settings,
setup_commands,
devcontainer_phases,
devcontainer_env,
@ -639,7 +622,7 @@ async fn prepare_from_branch(
write_run_config_snapshot(&run_dir, None).await?;
// Write RunRecord for the resumed run
{
let settings_config = {
let (model_str, provider_str) = resolve_model_provider(
args.model.as_deref(),
args.provider.as_deref(),
@ -666,7 +649,7 @@ async fn prepare_from_branch(
let record = fabro_workflows::run_record::RunRecord {
run_id: run_id.clone(),
created_at: chrono::Utc::now(),
config: normalized,
config: normalized.clone(),
graph: graph.clone(),
workflow_slug: workflow_slug.clone(),
working_directory: resume_repo_path.clone(),
@ -675,7 +658,8 @@ async fn prepare_from_branch(
labels: std::collections::HashMap::new(),
};
let _ = record.save(&run_dir);
}
normalized
};
let emitter = Arc::new(EventEmitter::new());
@ -888,42 +872,27 @@ async fn prepare_from_branch(
.unwrap_or_default();
setup_commands.extend(sandbox.resume_setup_commands(&run_branch));
let meta_branch = Some(fabro_workflows::git::MetadataStore::branch_name(&run_id));
let config = RunConfig {
let settings = RunSettings {
config: settings_config,
run_dir: run_dir.clone(),
cancel_token: None,
dry_run: args.dry_run,
run_id: run_id.clone(),
git_checkpoint_enabled: true,
host_repo_path: Some(resume_repo_path.clone()),
base_sha,
run_branch: Some(run_branch),
meta_branch,
git: Some(GitCheckpointSettings {
base_sha,
run_branch: Some(run_branch),
meta_branch: Some(fabro_workflows::git::MetadataStore::branch_name(&run_id)),
}),
labels: args
.label
.iter()
.filter_map(|s| s.split_once('='))
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect(),
checkpoint_exclude_globs: run_cfg
.as_ref()
.map(|cfg| cfg.checkpoint.exclude_globs.clone())
.unwrap_or_else(|| run_defaults.checkpoint.exclude_globs.clone()),
github_app: github_app.clone(),
git_author,
base_branch: detected_base_branch,
pull_request: run_cfg
.as_ref()
.and_then(|cfg| cfg.pull_request.as_ref())
.or(run_defaults.pull_request.as_ref())
.filter(|p| p.enabled)
.cloned(),
asset_globs: run_cfg
.as_ref()
.and_then(|cfg| cfg.assets.as_ref())
.or(run_defaults.assets.as_ref())
.map(|a| a.include.clone())
.unwrap_or_default(),
workflow_slug,
};
@ -940,7 +909,7 @@ async fn prepare_from_branch(
run_cfg,
sandbox,
emitter,
config,
settings,
setup_commands,
devcontainer_phases,
devcontainer_env,
@ -968,7 +937,7 @@ async fn run_resumed(
mut run_cfg,
sandbox,
emitter,
mut config,
settings: mut config,
setup_commands,
devcontainer_phases,
devcontainer_env,
@ -1386,7 +1355,7 @@ async fn run_resumed(
// Auto-create PR on successful completion (mirrors run_command)
let mut pushed_branch: Option<String> = None;
let mut pr_url: Option<String> = None;
if let Some(ref pr_cfg) = config.pull_request {
if let Some(pr_cfg) = config.pull_request() {
if config.dry_run {
debug!("Skipping PR creation: dry-run mode");
} else if let Err(ref e) = engine_result {
@ -1408,12 +1377,12 @@ async fn run_resumed(
Some(ref origin),
) = (
&config.base_branch,
&config.run_branch,
config.git.as_ref().and_then(|g| g.run_branch.as_ref()),
&github_app,
&origin_url,
) {
if config.git_checkpoint_enabled {
pushed_branch = Some(run_branch.clone());
if config.git.is_some() {
pushed_branch = Some(run_branch.to_string());
}
let auto_merge = if pr_cfg.auto_merge {

View file

@ -20,7 +20,7 @@ use fabro_workflows::checkpoint::Checkpoint;
use fabro_workflows::conclusion::Conclusion;
use fabro_workflows::cost::{compute_stage_cost, format_cost};
use fabro_workflows::devcontainer_bridge;
use fabro_workflows::engine::{RunConfig, WorkflowRunEngine};
use fabro_workflows::engine::{GitCheckpointSettings, RunSettings, WorkflowRunEngine};
use fabro_workflows::event::{EventEmitter, RunNoticeLevel, WorkflowRunEvent};
use fabro_workflows::git::GitSyncStatus;
use fabro_workflows::handler::default_registry;
@ -1018,6 +1018,29 @@ async fn run_command_impl(
record.save(&run_dir)?;
}
let settings_config = if cached_run_restart {
existing_record
.as_ref()
.map(|r| r.config.clone())
.unwrap_or_default()
} else {
super::create::normalize_config(
run_cfg.as_ref(),
&run_defaults,
&model,
provider.as_deref(),
sandbox_provider,
&graph,
super::create::CliFlags {
dry_run: dry_run_flag,
auto_approve: auto_approve_flag,
no_retro: no_retro_flag,
verbose: verbose_flag,
preserve_sandbox: preserve_sandbox_flag,
},
)
};
// Now resolve ${env.VARNAME} references for runtime use.
if let Some(ref mut cfg) = run_cfg {
run_config::resolve_sandbox_env(cfg)?;
@ -1537,7 +1560,7 @@ async fn run_command_impl(
"worktree_setup_failed",
format!("Git worktree setup failed ({e}), running without worktree."),
);
// Reset so RunConfig does not enable git checkpointing
// Reset so RunSettings does not enable git checkpointing
worktree_path = None;
worktree_branch = None;
worktree_base_sha = None;
@ -1710,53 +1733,39 @@ async fn run_command_impl(
// 7. Execute
// Set up metadata branch for git checkpointing (host or remote — engine fills remote)
let meta_branch = if worktree_path.is_some() {
Some(fabro_workflows::git::MetadataStore::branch_name(&run_id))
let git = if worktree_path.is_some() {
Some(GitCheckpointSettings {
base_sha: worktree_base_sha,
run_branch: worktree_branch,
meta_branch: Some(fabro_workflows::git::MetadataStore::branch_name(&run_id)),
})
} else {
None
};
let checkpoint_exclude_globs = run_cfg
.as_ref()
.map(|c| c.checkpoint.exclude_globs.clone())
.unwrap_or_default();
let mut config = RunConfig {
let mut config = RunSettings {
config: settings_config,
run_dir: run_dir.clone(),
cancel_token: None,
dry_run: dry_run_mode,
run_id: run_id.clone(),
git_checkpoint_enabled: worktree_path.is_some(),
host_repo_path: existing_record
.as_ref()
.and_then(|r| r.host_repo_path.as_deref().map(PathBuf::from))
.or_else(|| Some(original_cwd.clone())),
base_sha: worktree_base_sha,
run_branch: worktree_branch,
meta_branch,
labels: label_vec
.iter()
.filter_map(|s| s.split_once('='))
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect(),
checkpoint_exclude_globs,
git_author: git_author.clone(),
workflow_slug: workflow_slug.clone(),
github_app: github_app.clone(),
git_author,
base_branch: existing_record
.as_ref()
.and_then(|r| r.base_branch.clone())
.or(detected_base_branch),
pull_request: run_cfg
host_repo_path: existing_record
.as_ref()
.and_then(|c| c.pull_request.as_ref())
.or(run_defaults.pull_request.as_ref())
.filter(|p| p.enabled)
.cloned(),
asset_globs: run_cfg
.as_ref()
.and_then(|c| c.assets.as_ref())
.or(run_defaults.assets.as_ref())
.map(|a| a.include.clone())
.unwrap_or_default(),
workflow_slug: workflow_slug.clone(),
.and_then(|r| r.host_repo_path.as_deref().map(PathBuf::from))
.or_else(|| Some(original_cwd.clone())),
git,
};
// Build lifecycle config for sandbox init, setup commands, and devcontainer phases
@ -1844,7 +1853,7 @@ async fn run_command_impl(
// Auto-create PR on successful completion (skip in dry-run mode)
let mut pushed_branch: Option<String> = None;
let mut pr_url: Option<String> = None;
if let Some(ref pr_cfg) = config.pull_request {
if let Some(pr_cfg) = config.pull_request() {
if dry_run_mode {
debug!("Skipping PR creation: dry-run mode");
} else if let Err(ref e) = engine_result {
@ -1866,14 +1875,14 @@ async fn run_command_impl(
Some(ref origin),
) = (
&config.base_branch,
&config.run_branch,
config.git.as_ref().and_then(|g| g.run_branch.as_ref()),
&github_app,
&origin_url,
) {
// Run branch was pushed during checkpoint commits;
// just record it for the PR creation.
if config.git_checkpoint_enabled {
pushed_branch = Some(run_branch.clone());
if config.git.is_some() {
pushed_branch = Some(run_branch.to_string());
}
let auto_merge = if pr_cfg.auto_merge {
@ -2689,10 +2698,11 @@ async fn run_preflight(
///
/// This captures the last diff.patch (written after the final checkpoint) and retro.json.
/// Best-effort: errors are logged as warnings.
pub(crate) async fn write_finalize_commit(config: &RunConfig, run_dir: &std::path::Path) {
let (Some(ref meta_branch), Some(ref repo_path)) =
(&config.meta_branch, &config.host_repo_path)
else {
pub(crate) async fn write_finalize_commit(config: &RunSettings, run_dir: &std::path::Path) {
let (Some(meta_branch), Some(repo_path)) = (
config.git.as_ref().and_then(|g| g.meta_branch.as_ref()),
config.host_repo_path.as_ref(),
) else {
return;
};

View file

@ -62,7 +62,7 @@ assert_eq!(graph.goal(), "Run tests");
### Running a Pipeline
```rust
use arc_workflows::engine::{PipelineEngine, RunConfig};
use arc_workflows::engine::{PipelineEngine, RunSettings};
use arc_workflows::event::EventEmitter;
use arc_workflows::handler::HandlerRegistry;
use arc_workflows::handler::start::StartHandler;
@ -78,8 +78,19 @@ registry.register("exit", Box::new(ExitHandler));
registry.register("agent", Box::new(AgentHandler::new(None)));
let engine = PipelineEngine::new(registry, EventEmitter::new());
let config = RunConfig {
let config = RunSettings {
config: fabro_config::FabroConfig::default(),
run_dir: "/tmp/pipeline-run".into(),
cancel_token: None,
dry_run: false,
run_id: "example-run".into(),
labels: std::collections::HashMap::new(),
git_author: fabro_workflows::git::GitAuthor::default(),
workflow_slug: None,
github_app: None,
base_branch: None,
host_repo_path: None,
git: None,
};
// engine.run(&graph, &config).await

View file

@ -12,7 +12,7 @@ use super::super::graph::WorkflowGraph;
use super::super::WorkflowNode;
use super::circuit_breaker::CircuitBreakerLifecycle;
use crate::checkpoint::Checkpoint;
use crate::engine::{self, RunConfig};
use crate::engine::{self, RunSettings};
use crate::event::{EventEmitter, RunNoticeLevel, WorkflowRunEvent};
use crate::outcome::StageUsage;
@ -24,7 +24,7 @@ pub struct DiskLifecycle {
pub run_dir: PathBuf,
pub run_id: String,
pub graph: Arc<fabro_graphviz::graph::types::Graph>,
pub config: Arc<RunConfig>,
pub config: Arc<RunSettings>,
pub emitter: Arc<EventEmitter>,
pub circuit_breaker: Arc<CircuitBreakerLifecycle>,
pub checkpoint_enabled: bool,

View file

@ -12,7 +12,7 @@ use fabro_core::state::RunState;
use super::super::graph::WorkflowGraph;
use super::super::WorkflowNode;
use crate::artifact::ArtifactStore;
use crate::engine::{self, RunConfig};
use crate::engine::{self, RunSettings};
use crate::event::{EventEmitter, RunNoticeLevel, WorkflowRunEvent};
use crate::outcome::{Outcome, StageStatus, StageUsage};
@ -33,7 +33,7 @@ pub struct GitLifecycle {
pub emitter: Arc<EventEmitter>,
pub run_dir: PathBuf,
pub run_id: String,
pub config: Arc<RunConfig>,
pub config: Arc<RunSettings>,
pub start_node_id: Option<String>,
// Cross-lifecycle data (shared with EventLifecycle)
pub checkpoint_git_result: Arc<Mutex<Option<GitCheckpointResult>>>,
@ -52,9 +52,13 @@ impl RunLifecycle<WorkflowGraph> for GitLifecycle {
*self.checkpoint_git_result.lock().unwrap() = None;
// Init metadata branch (best-effort)
if let (Some(_), Some(ref repo_path)) =
(&self.config.meta_branch, &self.config.host_repo_path)
{
if let (Some(_), Some(repo_path)) = (
self.config
.git
.as_ref()
.and_then(|g| g.meta_branch.as_ref()),
self.config.host_repo_path.as_ref(),
) {
let store = crate::git::MetadataStore::new(repo_path, &self.config.git_author);
let run_json = std::fs::read(self.run_dir.join("run.json")).ok();
let start_json = std::fs::read(self.run_dir.join("start.json")).ok();
@ -91,15 +95,19 @@ impl RunLifecycle<WorkflowGraph> for GitLifecycle {
let node_id = node.id();
// Skip git checkpoint for the start node (always empty) or if git disabled
if self.start_node_id.as_deref() == Some(node_id) || !self.config.git_checkpoint_enabled {
if self.start_node_id.as_deref() == Some(node_id) || self.config.git.is_none() {
*self.checkpoint_git_result.lock().unwrap() = None;
return Ok(());
}
// Shadow commit (best-effort, metadata branch)
let shadow_sha: Option<String> = if let (Some(_), Some(ref repo_path)) =
(&self.config.meta_branch, &self.config.host_repo_path)
{
let shadow_sha: Option<String> = if let (Some(_), Some(repo_path)) = (
self.config
.git
.as_ref()
.and_then(|g| g.meta_branch.as_ref()),
self.config.host_repo_path.as_ref(),
) {
let store = crate::git::MetadataStore::new(repo_path, &self.config.git_author);
// Build checkpoint JSON for shadow branch
let checkpoint_path = self.run_dir.join("checkpoint.json");
@ -148,7 +156,7 @@ impl RunLifecycle<WorkflowGraph> for GitLifecycle {
&result.outcome.status.to_string(),
completed_count,
shadow_sha,
&self.config.checkpoint_exclude_globs,
self.config.checkpoint_exclude_globs(),
&self.config.git_author,
)
.await;
@ -177,10 +185,12 @@ impl RunLifecycle<WorkflowGraph> for GitLifecycle {
// Push run branch (skip in dry-run mode)
if !self.config.dry_run {
if let Some(ref branch) = self.config.run_branch {
if let Some(branch) =
self.config.git.as_ref().and_then(|g| g.run_branch.as_ref())
{
let push_ok = if self.sandbox.git_push_branch(branch).await {
true
} else if let Some(ref repo_path) = self.config.host_repo_path {
} else if let Some(repo_path) = self.config.host_repo_path.as_ref() {
let refspec = format!("refs/heads/{branch}");
engine::git_push_host(
repo_path,
@ -195,9 +205,13 @@ impl RunLifecycle<WorkflowGraph> for GitLifecycle {
git_result.push_results.push((branch.clone(), push_ok));
}
// Push metadata branch (always from host)
if let (Some(ref meta_branch), Some(ref repo_path)) =
(&self.config.meta_branch, &self.config.host_repo_path)
{
if let (Some(meta_branch), Some(repo_path)) = (
self.config
.git
.as_ref()
.and_then(|g| g.meta_branch.as_ref()),
self.config.host_repo_path.as_ref(),
) {
let refspec = format!("refs/heads/{meta_branch}");
let meta_push_ok = engine::git_push_host(
repo_path,
@ -219,7 +233,7 @@ impl RunLifecycle<WorkflowGraph> for GitLifecycle {
.lock()
.unwrap()
.clone()
.or_else(|| self.config.base_sha.clone())
.or_else(|| self.config.git.as_ref().and_then(|g| g.base_sha.clone()))
.unwrap_or_else(|| sha.clone());
let diff_dest = engine::node_dir(&self.run_dir, node_id, visit).join("diff.patch");
@ -259,11 +273,11 @@ impl RunLifecycle<WorkflowGraph> for GitLifecycle {
async fn on_run_end(&self, outcome: &Outcome, _state: &WfRunState) {
// Write final.patch on success
if (outcome.status == StageStatus::Success || outcome.status == StageStatus::PartialSuccess)
&& self.config.git_checkpoint_enabled
&& self.config.git.is_some()
{
if let Some(ref base_sha) = self.config.base_sha {
if let Some(base_sha) = self.config.git.as_ref().and_then(|g| g.base_sha.clone()) {
let diff_dest = self.run_dir.join("final.patch");
match engine::git_diff(&*self.sandbox, base_sha).await {
match engine::git_diff(&*self.sandbox, &base_sha).await {
Ok(patch) if !patch.is_empty() => {
let _ = std::fs::write(&diff_dest, patch);
}

View file

@ -27,7 +27,7 @@ use super::graph::WorkflowGraph;
use super::WorkflowNode;
use crate::artifact::ArtifactStore;
use crate::context;
use crate::engine::RunConfig;
use crate::engine::RunSettings;
use crate::event::EventEmitter;
use crate::outcome::{Outcome, StageUsage};
use fabro_hooks::HookRunner;
@ -79,7 +79,7 @@ impl WorkflowLifecycle {
sandbox: Arc<dyn Sandbox>,
graph: Arc<fabro_graphviz::graph::types::Graph>,
run_dir: PathBuf,
config: Arc<RunConfig>,
config: Arc<RunSettings>,
is_resume: bool,
) -> Self {
let restarted_from: Arc<Mutex<Option<(String, String)>>> = Arc::new(Mutex::new(None));
@ -91,8 +91,12 @@ impl WorkflowLifecycle {
let circuit_breaker = Arc::new(CircuitBreakerLifecycle::new(loop_restart_signature_limit));
let local_git_checkpoint =
config.git_checkpoint_enabled && sandbox.host_git_dir().is_some();
let has_run_branch = config
.git
.as_ref()
.and_then(|g| g.run_branch.as_ref())
.is_some();
let local_git_checkpoint = has_run_branch && sandbox.host_git_dir().is_some();
let working_directory = if local_git_checkpoint {
Some(sandbox.working_directory().to_string())
} else {
@ -105,8 +109,8 @@ impl WorkflowLifecycle {
run_id: config.run_id.clone(),
run_start: Mutex::new(Instant::now()),
restarted_from: Arc::clone(&restarted_from),
base_sha: config.base_sha.clone(),
run_branch: config.run_branch.clone(),
base_sha: config.git.as_ref().and_then(|g| g.base_sha.clone()),
run_branch: config.git.as_ref().and_then(|g| g.run_branch.clone()),
worktree_dir: working_directory.clone(),
goal: (!graph.goal().is_empty()).then(|| graph.goal().to_string()),
artifact_store: Arc::clone(&artifact_store),
@ -154,7 +158,7 @@ impl WorkflowLifecycle {
Some(run_dir.clone()),
Arc::clone(&emitter),
run_dir,
config.asset_globs.clone(),
config.asset_globs().to_vec(),
);
Self {

File diff suppressed because it is too large Load diff

View file

@ -9,7 +9,7 @@ use async_trait::async_trait;
use crate::condition::evaluate_condition;
use crate::context::keys;
use crate::context::{Context, WorkflowContext};
use crate::engine::{RunConfig, WorkflowRunEngine};
use crate::engine::{RunSettings, WorkflowRunEngine};
use crate::error::FabroError;
use crate::outcome::{Outcome, OutcomeExt, StageStatus};
use crate::workflow::{prepare_from_file, prepare_from_source};
@ -127,7 +127,7 @@ impl Handler for SubWorkflowHandler {
}
};
// Build child RunConfig
// Build child RunSettings
let visit = crate::engine::visit_from_context(context) as u64;
let child_logs = run_dir.join(format!("nodes/{}_{visit}/child", node.id));
let _ = std::fs::create_dir_all(&child_logs);
@ -137,27 +137,22 @@ impl Handler for SubWorkflowHandler {
let child_cancel = Arc::clone(&cancel_token);
let git_state = services.git_state();
let child_config = RunConfig {
let child_config = RunSettings {
config: fabro_config::FabroConfig::default(),
run_dir: child_logs,
cancel_token: Some(cancel_token),
dry_run: services.dry_run,
run_id: format!("{parent_run_id}_child_{}", node.id),
git_checkpoint_enabled: false,
host_repo_path: None,
base_sha: None,
run_branch: None,
meta_branch: None,
labels: HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author: git_state
.as_ref()
.map(|gs| gs.git_author.clone())
.unwrap_or_default(),
base_branch: None,
pull_request: None,
asset_globs: Vec::new(),
workflow_slug: None,
github_app: None,
base_branch: None,
host_repo_path: None,
git: None,
};
// Clone parent context for child; inject parent preamble

View file

@ -10,7 +10,7 @@ pub async fn execute(init: Initialized) -> Executed {
graph,
source: _,
engine,
config,
settings,
checkpoint,
emitter,
sandbox,
@ -19,7 +19,7 @@ pub async fn execute(init: Initialized) -> Executed {
let start = Instant::now();
let outcome = engine
.execute_graph(&graph, &config, checkpoint.as_ref())
.execute_graph(&graph, &settings, checkpoint.as_ref())
.await;
let duration_ms = crate::millis_u64(start.elapsed());
@ -27,7 +27,7 @@ pub async fn execute(init: Initialized) -> Executed {
Executed {
graph,
outcome,
config,
settings,
engine,
emitter,
sandbox,

View file

@ -14,7 +14,7 @@ pub async fn finalize(
let Retroed {
graph: _,
outcome,
config,
settings,
engine: _,
emitter: _,
sandbox: _,
@ -45,7 +45,7 @@ pub async fn finalize(
};
Ok(Finalized {
run_id: config.run_id,
run_id: settings.run_id,
outcome,
conclusion,
pr_url: None,

View file

@ -47,16 +47,16 @@ pub async fn initialize(
// Prepare sandbox (initialize, git setup, setup commands, devcontainer)
engine
.prepare_sandbox(&graph, &mut options.run_config, options.lifecycle)
.prepare_sandbox(&graph, &mut options.run_settings, options.lifecycle)
.await?;
// At this point run_config may have been mutated by prepare_sandbox (base_sha, run_branch, etc.)
// At this point run_settings may have been mutated by prepare_sandbox (base_sha, run_branch, etc.)
Ok(Initialized {
graph,
source,
engine,
config: options.run_config,
settings: options.run_settings,
checkpoint: None,
emitter: options.emitter,
sandbox: options.sandbox,

View file

@ -8,7 +8,7 @@ pub async fn retro(executed: Executed, _options: &RetroOptions) -> Retroed {
let Executed {
graph,
outcome,
config,
settings,
engine,
emitter,
sandbox,
@ -20,7 +20,7 @@ pub async fn retro(executed: Executed, _options: &RetroOptions) -> Retroed {
Retroed {
graph,
outcome,
config,
settings,
engine,
emitter,
sandbox,

View file

@ -8,7 +8,7 @@ use fabro_validate::Diagnostic;
use crate::checkpoint::Checkpoint;
use crate::conclusion::Conclusion;
use crate::engine::{LifecycleConfig, RunConfig, WorkflowRunEngine};
use crate::engine::{LifecycleConfig, RunSettings, WorkflowRunEngine};
use crate::error::FabroError;
use crate::event::EventEmitter;
use crate::handler::HandlerRegistry;
@ -103,7 +103,7 @@ pub struct InitOptions {
pub sandbox: Arc<dyn Sandbox>,
pub registry: HandlerRegistry,
pub lifecycle: LifecycleConfig,
pub run_config: RunConfig,
pub run_settings: RunSettings,
pub hooks: fabro_hooks::HookConfig,
pub sandbox_env: HashMap<String, String>,
}
@ -114,7 +114,7 @@ pub struct Initialized {
pub graph: Graph,
pub source: String,
pub engine: WorkflowRunEngine,
pub config: RunConfig,
pub settings: RunSettings,
pub(crate) checkpoint: Option<Checkpoint>,
pub emitter: Arc<EventEmitter>,
pub sandbox: Arc<dyn Sandbox>,
@ -125,7 +125,7 @@ pub struct Initialized {
pub struct Executed {
pub graph: Graph,
pub outcome: Result<Outcome, FabroError>,
pub config: RunConfig,
pub settings: RunSettings,
pub engine: WorkflowRunEngine,
pub emitter: Arc<EventEmitter>,
pub sandbox: Arc<dyn Sandbox>,
@ -137,7 +137,7 @@ pub struct Executed {
pub struct Retroed {
pub graph: Graph,
pub outcome: Result<Outcome, FabroError>,
pub config: RunConfig,
pub settings: RunSettings,
pub engine: WorkflowRunEngine,
pub emitter: Arc<EventEmitter>,
pub sandbox: Arc<dyn Sandbox>,

View file

@ -8,13 +8,14 @@ use std::path::Path;
use std::sync::Arc;
use fabro_agent::Sandbox;
use fabro_config::FabroConfig;
use fabro_graphviz::graph::{AttrValue, Edge, Graph, Node};
use fabro_llm::provider::Provider;
use fabro_sandbox::daytona::{DaytonaConfig, DaytonaSandbox, DaytonaSnapshotConfig};
use fabro_workflows::artifact::sync_artifacts_to_env;
use fabro_workflows::checkpoint::Checkpoint;
use fabro_workflows::context::Context;
use fabro_workflows::engine::{RunConfig, WorkflowRunEngine};
use fabro_workflows::engine::{GitCheckpointSettings, RunSettings, WorkflowRunEngine};
use fabro_workflows::error::FabroError;
use fabro_workflows::event::EventEmitter;
use fabro_workflows::handler::exit::ExitHandler;
@ -386,26 +387,20 @@ async fn daytona_pipeline_artifact_offload_and_sync() {
registry.register("exit", Box::new(ExitHandler));
let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), env.clone());
let config = RunConfig {
let config = RunSettings {
config: FabroConfig::default(),
run_dir: dir.path().to_path_buf(),
cancel_token: None,
dry_run: false,
run_id: "test-run".into(),
git_checkpoint_enabled: false,
host_repo_path: None,
base_sha: None,
run_branch: None,
meta_branch: None,
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author: fabro_workflows::git::GitAuthor::default(),
base_branch: None,
pull_request: None,
asset_globs: Vec::new(),
workflow_slug: None,
github_app: None,
base_branch: None,
host_repo_path: None,
git: None,
};
let outcome = engine
.run(&graph, &config)
.await
@ -536,7 +531,7 @@ async fn daytona_git_checkpoint_remote_emits_events() {
}
// Set up git in the sandbox
let (run_id, base_sha, branch_name) = setup_daytona_git(&*env).await;
let (_run_id, base_sha, branch_name) = setup_daytona_git(&*env).await;
// Pipeline: start -> work -> exit
let mut graph = Graph::new("DaytonaGitCheckpoint");
@ -583,26 +578,24 @@ async fn daytona_git_checkpoint_remote_emits_events() {
registry.register("exit", Box::new(ExitHandler));
let engine = WorkflowRunEngine::new(registry, Arc::new(emitter), env.clone());
let config = RunConfig {
let config = RunSettings {
config: FabroConfig::default(),
run_dir: dir.path().to_path_buf(),
cancel_token: None,
dry_run: false,
run_id,
git_checkpoint_enabled: true,
host_repo_path: Some(dir.path().to_path_buf()),
base_sha: Some(base_sha),
run_branch: Some(branch_name),
meta_branch: None,
run_id: "git-cp-test".into(),
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author: fabro_workflows::git::GitAuthor::default(),
base_branch: None,
pull_request: None,
asset_globs: Vec::new(),
workflow_slug: None,
github_app: None,
base_branch: None,
host_repo_path: Some(dir.path().to_path_buf()),
git: Some(GitCheckpointSettings {
base_sha: Some(base_sha),
run_branch: Some(branch_name),
meta_branch: None,
}),
};
let outcome = engine
.run(&graph, &config)
.await
@ -771,26 +764,24 @@ async fn daytona_parallel_git_branching_e2e() {
let engine = WorkflowRunEngine::new(registry, Arc::new(emitter), Arc::clone(&env));
let config = RunConfig {
let config = RunSettings {
config: FabroConfig::default(),
run_dir: run_tmp.path().to_path_buf(),
cancel_token: None,
dry_run: false,
run_id: run_id.clone(),
git_checkpoint_enabled: true,
host_repo_path: Some(run_tmp.path().to_path_buf()),
base_sha: Some(base_sha),
run_branch: Some(branch_name),
meta_branch: None,
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author: fabro_workflows::git::GitAuthor::default(),
base_branch: None,
pull_request: None,
asset_globs: Vec::new(),
workflow_slug: None,
github_app: None,
base_branch: None,
host_repo_path: Some(run_tmp.path().to_path_buf()),
git: Some(GitCheckpointSettings {
base_sha: Some(base_sha),
run_branch: Some(branch_name),
meta_branch: None,
}),
};
let outcome = engine
.run(&graph, &config)
.await
@ -1149,26 +1140,24 @@ async fn daytona_git_checkpoint_with_shadow_branch() {
let meta_branch = MetadataStore::branch_name(&run_id);
let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), env.clone());
let config = RunConfig {
let config = RunSettings {
config: FabroConfig::default(),
run_dir: dir.path().to_path_buf(),
cancel_token: None,
dry_run: false,
run_id: run_id.clone(),
git_checkpoint_enabled: true,
host_repo_path: Some(host_repo.path().to_path_buf()),
base_sha: Some(base_sha),
run_branch: Some(branch_name),
meta_branch: Some(meta_branch),
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author: fabro_workflows::git::GitAuthor::default(),
base_branch: None,
pull_request: None,
asset_globs: Vec::new(),
workflow_slug: None,
github_app: None,
base_branch: None,
host_repo_path: Some(host_repo.path().to_path_buf()),
git: Some(GitCheckpointSettings {
base_sha: Some(base_sha),
run_branch: Some(branch_name),
meta_branch: Some(meta_branch),
}),
};
let outcome = engine
.run(&graph, &config)
.await
@ -1291,26 +1280,25 @@ async fn daytona_asset_collection() {
graph.edges.push(Edge::new("start", "create_assets"));
graph.edges.push(Edge::new("create_assets", "exit"));
let config = RunConfig {
let config = RunSettings {
config: FabroConfig {
assets: Some(fabro_config::run::AssetsConfig {
include: vec!["test-results/**".to_string()],
}),
..FabroConfig::default()
},
run_dir: dir.path().to_path_buf(),
cancel_token: None,
dry_run: false,
run_id: "asset-test-daytona".into(),
git_checkpoint_enabled: false,
host_repo_path: None,
base_sha: None,
run_branch: None,
meta_branch: None,
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author: fabro_workflows::git::GitAuthor::default(),
base_branch: None,
pull_request: None,
asset_globs: vec!["test-results/**".to_string()],
workflow_slug: None,
github_app: None,
base_branch: None,
host_repo_path: None,
git: None,
};
let outcome = engine
.run(&graph, &config)
.await
@ -1548,26 +1536,24 @@ async fn daytona_git_push_run_branch_to_origin() {
registry.register("exit", Box::new(ExitHandler));
let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), env.clone());
let config = RunConfig {
let config = RunSettings {
config: FabroConfig::default(),
run_dir: dir.path().to_path_buf(),
cancel_token: None,
dry_run: false,
run_id: run_id.clone(),
git_checkpoint_enabled: true,
host_repo_path: Some(dir.path().to_path_buf()),
base_sha: Some(base_sha),
run_branch: Some(branch_name.clone()),
meta_branch: None,
labels: std::collections::HashMap::new(),
checkpoint_exclude_globs: Vec::new(),
github_app: None,
git_author: fabro_workflows::git::GitAuthor::default(),
base_branch: None,
pull_request: None,
asset_globs: Vec::new(),
workflow_slug: None,
github_app: None,
base_branch: None,
host_repo_path: Some(dir.path().to_path_buf()),
git: Some(GitCheckpointSettings {
base_sha: Some(base_sha),
run_branch: Some(branch_name.clone()),
meta_branch: None,
}),
};
let outcome = engine
.run(&graph, &config)
.await

File diff suppressed because it is too large Load diff