mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-09-10 22:43:37 +00:00
Rename TaskConfig → WorkflowRunConfig, task → goal
Better reflects domain semantics: the config describes a workflow run, and the free-text field is the run's goal, not a generic "task". Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
62b4e9df74
commit
c888ab8be5
7 changed files with 67 additions and 67 deletions
|
|
@ -97,7 +97,7 @@ export default function RunConfiguration({ loaderData }: Route.ComponentProps) {
|
|||
<div className="min-w-0 flex-1">
|
||||
{configText ? (
|
||||
<CollapsibleFile
|
||||
file={{ name: "task.toml", contents: configText, lang: "toml" }}
|
||||
file={{ name: "run.toml", contents: configText, lang: "toml" }}
|
||||
/>
|
||||
) : (
|
||||
<p className="text-sm text-fg-muted">No configuration found.</p>
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ export default function WorkflowDefinition() {
|
|||
return (
|
||||
<div className="flex flex-col gap-6">
|
||||
<CollapsibleFile
|
||||
file={{ name: "task.toml", contents: workflow.config, lang: "toml" }}
|
||||
file={{ name: "run.toml", contents: workflow.config, lang: "toml" }}
|
||||
defaultOpen={false}
|
||||
/>
|
||||
{dotReady && (
|
||||
|
|
|
|||
|
|
@ -867,7 +867,7 @@ mod workflows {
|
|||
title: "Fix Build".into(), slug: "fix_build".into(), filename: "fix_build.dot".into(),
|
||||
description: "Automatically diagnoses and fixes CI build failures by analyzing error logs, identifying root causes, and applying targeted code changes.".into(),
|
||||
config: r#"version = 1
|
||||
task = "Diagnose and fix CI build failures"
|
||||
goal = "Diagnose and fix CI build failures"
|
||||
graph = "fix_build.dot"
|
||||
|
||||
[llm]
|
||||
|
|
@ -917,7 +917,7 @@ disk = 10
|
|||
title: "Implement Feature".into(), slug: "implement".into(), filename: "implement.dot".into(),
|
||||
description: "Generates production-ready code from a technical blueprint, including tests, documentation, and a pull request ready for review.".into(),
|
||||
config: r#"version = 1
|
||||
task = "Implement feature from technical blueprint"
|
||||
goal = "Implement feature from technical blueprint"
|
||||
graph = "implement.dot"
|
||||
|
||||
[llm]
|
||||
|
|
@ -986,7 +986,7 @@ disk = 20
|
|||
title: "Sync Drift".into(), slug: "sync_drift".into(), filename: "sync_drift.dot".into(),
|
||||
description: "Detects configuration and code drift between environments, then generates reconciliation patches to bring everything back in sync.".into(),
|
||||
config: r#"version = 1
|
||||
task = "Detect and reconcile configuration drift across environments"
|
||||
goal = "Detect and reconcile configuration drift across environments"
|
||||
graph = "sync_drift.dot"
|
||||
|
||||
[llm]
|
||||
|
|
@ -1042,7 +1042,7 @@ disk = 10
|
|||
title: "Expand Product".into(), slug: "expand".into(), filename: "expand.dot".into(),
|
||||
description: "Evolves the product by analyzing usage patterns and specifications to propose and implement incremental improvements.".into(),
|
||||
config: r#"version = 1
|
||||
task = "Propose and implement incremental product improvements"
|
||||
goal = "Propose and implement incremental product improvements"
|
||||
graph = "expand.dot"
|
||||
|
||||
[llm]
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ pub mod backend;
|
|||
pub mod cli_backend;
|
||||
pub mod run;
|
||||
pub mod runs;
|
||||
pub mod task_config;
|
||||
pub mod run_config;
|
||||
pub mod validate;
|
||||
|
||||
use std::path::Path;
|
||||
|
|
|
|||
|
|
@ -25,8 +25,8 @@ use arc_llm::provider::Provider;
|
|||
|
||||
use super::backend::AgentApiBackend;
|
||||
use super::cli_backend::{BackendRouter, AgentCliBackend};
|
||||
use super::task_config;
|
||||
use super::task_config::TaskConfig;
|
||||
use super::run_config;
|
||||
use super::run_config::WorkflowRunConfig;
|
||||
use super::{
|
||||
compute_stage_cost, format_cost, format_duration_human,
|
||||
format_event_summary, format_tokens_human, print_diagnostics, read_dot_file,
|
||||
|
|
@ -52,13 +52,13 @@ fn default_model_for_provider(provider: Provider) -> &'static str {
|
|||
fn resolve_model_provider(
|
||||
cli_model: Option<&str>,
|
||||
cli_provider: Option<&str>,
|
||||
task_cfg: Option<&TaskConfig>,
|
||||
run_cfg: Option<&WorkflowRunConfig>,
|
||||
graph: &crate::graph::types::Graph,
|
||||
) -> (String, Option<String>) {
|
||||
let toml_model = task_cfg
|
||||
let toml_model = run_cfg
|
||||
.and_then(|c| c.llm.as_ref())
|
||||
.and_then(|l| l.model.as_deref());
|
||||
let toml_provider = task_cfg
|
||||
let toml_provider = run_cfg
|
||||
.and_then(|c| c.llm.as_ref())
|
||||
.and_then(|l| l.provider.as_deref());
|
||||
|
||||
|
|
@ -125,16 +125,16 @@ pub async fn run_command(args: RunArgs, styles: &'static Styles) -> anyhow::Resu
|
|||
.as_ref()
|
||||
.ok_or_else(|| anyhow::anyhow!("--workflow is required unless --run-branch is provided"))?;
|
||||
|
||||
// 0. Load task config if TOML, resolve DOT path, run setup
|
||||
let (dot_path, task_cfg) = if workflow_path.extension().is_some_and(|ext| ext == "toml") {
|
||||
let cfg = task_config::load_task_config(workflow_path)?;
|
||||
let dot = task_config::resolve_graph_path(workflow_path, &cfg.graph);
|
||||
// 0. Load run config if TOML, resolve DOT path, run setup
|
||||
let (dot_path, run_cfg) = if workflow_path.extension().is_some_and(|ext| ext == "toml") {
|
||||
let cfg = run_config::load_run_config(workflow_path)?;
|
||||
let dot = run_config::resolve_graph_path(workflow_path, &cfg.graph);
|
||||
(dot, Some(cfg))
|
||||
} else {
|
||||
(workflow_path.clone(), None)
|
||||
};
|
||||
|
||||
if let Some(ref cfg) = task_cfg {
|
||||
if let Some(ref cfg) = run_cfg {
|
||||
if let Some(ref dir) = cfg.directory {
|
||||
std::env::set_current_dir(dir)
|
||||
.map_err(|e| anyhow::anyhow!("Failed to set working directory to {dir}: {e}"))?;
|
||||
|
|
@ -142,7 +142,7 @@ pub async fn run_command(args: RunArgs, styles: &'static Styles) -> anyhow::Resu
|
|||
}
|
||||
|
||||
// Collect setup commands — they'll be run inside the sandbox
|
||||
let setup_commands: Vec<String> = task_cfg
|
||||
let setup_commands: Vec<String> = run_cfg
|
||||
.as_ref()
|
||||
.and_then(|c| c.setup.as_ref())
|
||||
.map(|s| s.commands.clone())
|
||||
|
|
@ -150,8 +150,8 @@ pub async fn run_command(args: RunArgs, styles: &'static Styles) -> anyhow::Resu
|
|||
|
||||
// 1. Parse and validate workflow
|
||||
let source = read_dot_file(&dot_path)?;
|
||||
let source = match task_cfg.as_ref().and_then(|c| c.vars.as_ref()) {
|
||||
Some(vars) => task_config::expand_vars(&source, vars)?,
|
||||
let source = match run_cfg.as_ref().and_then(|c| c.vars.as_ref()) {
|
||||
Some(vars) => run_config::expand_vars(&source, vars)?,
|
||||
None => source,
|
||||
};
|
||||
let (graph, diagnostics) = WorkflowBuilder::new().prepare(&source)?;
|
||||
|
|
@ -184,7 +184,7 @@ pub async fn run_command(args: RunArgs, styles: &'static Styles) -> anyhow::Resu
|
|||
// 2. Pre-flight: check git cleanliness before creating any files
|
||||
// (must happen before logs dir is created, which may be inside the repo)
|
||||
let sandbox_provider_preview = {
|
||||
let toml_exec = task_cfg
|
||||
let toml_exec = run_cfg
|
||||
.as_ref()
|
||||
.and_then(|c| c.sandbox.as_ref())
|
||||
.and_then(|e| e.provider.as_deref())
|
||||
|
|
@ -203,7 +203,7 @@ pub async fn run_command(args: RunArgs, styles: &'static Styles) -> anyhow::Resu
|
|||
};
|
||||
|
||||
if args.preflight {
|
||||
return run_preflight(&graph, &task_cfg, &args, git_clean, sandbox_provider_preview, styles).await;
|
||||
return run_preflight(&graph, &run_cfg, &args, git_clean, sandbox_provider_preview, styles).await;
|
||||
}
|
||||
|
||||
// 3. Create logs directory
|
||||
|
|
@ -219,7 +219,7 @@ pub async fn run_command(args: RunArgs, styles: &'static Styles) -> anyhow::Resu
|
|||
tokio::fs::write(logs_dir.join("run.pid"), std::process::id().to_string()).await?;
|
||||
if workflow_path.extension().is_some_and(|ext| ext == "toml") {
|
||||
if let Ok(toml_contents) = tokio::fs::read(workflow_path).await {
|
||||
tokio::fs::write(logs_dir.join("task.toml"), toml_contents).await?;
|
||||
tokio::fs::write(logs_dir.join("run.toml"), toml_contents).await?;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -368,7 +368,7 @@ pub async fn run_command(args: RunArgs, styles: &'static Styles) -> anyhow::Resu
|
|||
};
|
||||
|
||||
// 5. Resolve sandbox: CLI flag > TOML > default
|
||||
let toml_sandbox = task_cfg
|
||||
let toml_sandbox = run_cfg
|
||||
.as_ref()
|
||||
.and_then(|c| c.sandbox.as_ref())
|
||||
.and_then(|e| e.provider.as_deref())
|
||||
|
|
@ -400,7 +400,7 @@ pub async fn run_command(args: RunArgs, styles: &'static Styles) -> anyhow::Resu
|
|||
};
|
||||
|
||||
let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
|
||||
let daytona_config = task_cfg
|
||||
let daytona_config = run_cfg
|
||||
.as_ref()
|
||||
.and_then(|c| c.sandbox.as_ref())
|
||||
.and_then(|e| e.daytona.clone());
|
||||
|
|
@ -554,7 +554,7 @@ pub async fn run_command(args: RunArgs, styles: &'static Styles) -> anyhow::Resu
|
|||
let (model, provider) = resolve_model_provider(
|
||||
args.model.as_deref(),
|
||||
args.provider.as_deref(),
|
||||
task_cfg.as_ref(),
|
||||
run_cfg.as_ref(),
|
||||
&graph,
|
||||
);
|
||||
|
||||
|
|
@ -1045,7 +1045,7 @@ async fn run_from_branch(
|
|||
/// a structured report.
|
||||
async fn run_preflight(
|
||||
graph: &crate::graph::types::Graph,
|
||||
task_cfg: &Option<task_config::TaskConfig>,
|
||||
run_cfg: &Option<run_config::WorkflowRunConfig>,
|
||||
args: &RunArgs,
|
||||
git_clean: bool,
|
||||
sandbox_provider: SandboxProvider,
|
||||
|
|
@ -1055,7 +1055,7 @@ async fn run_preflight(
|
|||
|
||||
// 1. Sandbox boot check
|
||||
let original_cwd = std::env::current_dir()?;
|
||||
let daytona_config = task_cfg
|
||||
let daytona_config = run_cfg
|
||||
.as_ref()
|
||||
.and_then(|c| c.sandbox.as_ref())
|
||||
.and_then(|e| e.daytona.clone());
|
||||
|
|
@ -1124,7 +1124,7 @@ async fn run_preflight(
|
|||
let (model, provider) = resolve_model_provider(
|
||||
args.model.as_deref(),
|
||||
args.provider.as_deref(),
|
||||
task_cfg.as_ref(),
|
||||
run_cfg.as_ref(),
|
||||
graph,
|
||||
);
|
||||
|
||||
|
|
@ -1142,7 +1142,7 @@ async fn run_preflight(
|
|||
};
|
||||
|
||||
// 5. Count setup commands for display
|
||||
let setup_command_count = task_cfg
|
||||
let setup_command_count = run_cfg
|
||||
.as_ref()
|
||||
.and_then(|c| c.setup.as_ref())
|
||||
.map_or(0, |s| s.commands.len());
|
||||
|
|
@ -1337,12 +1337,12 @@ mod tests {
|
|||
#[test]
|
||||
fn resolve_model_provider_cli_overrides_toml() {
|
||||
let graph = crate::graph::types::Graph::new("test");
|
||||
let cfg = task_config::TaskConfig {
|
||||
let cfg = run_config::WorkflowRunConfig {
|
||||
version: 1,
|
||||
task: "test".to_string(),
|
||||
goal: "test".to_string(),
|
||||
graph: "test.dot".to_string(),
|
||||
directory: None,
|
||||
llm: Some(task_config::LlmConfig {
|
||||
llm: Some(run_config::LlmConfig {
|
||||
model: Some("toml-model".to_string()),
|
||||
provider: Some("openai".to_string()),
|
||||
}),
|
||||
|
|
@ -1367,12 +1367,12 @@ mod tests {
|
|||
graph.attrs.insert("default_model".to_string(), AttrValue::String("graph-model".to_string()));
|
||||
graph.attrs.insert("default_provider".to_string(), AttrValue::String("gemini".to_string()));
|
||||
|
||||
let cfg = task_config::TaskConfig {
|
||||
let cfg = run_config::WorkflowRunConfig {
|
||||
version: 1,
|
||||
task: "test".to_string(),
|
||||
goal: "test".to_string(),
|
||||
graph: "test.dot".to_string(),
|
||||
directory: None,
|
||||
llm: Some(task_config::LlmConfig {
|
||||
llm: Some(run_config::LlmConfig {
|
||||
model: Some("toml-model".to_string()),
|
||||
provider: Some("openai".to_string()),
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -10,9 +10,9 @@ const SUPPORTED_VERSION: u32 = 1;
|
|||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct TaskConfig {
|
||||
pub struct WorkflowRunConfig {
|
||||
pub version: u32,
|
||||
pub task: String,
|
||||
pub goal: String,
|
||||
pub graph: String,
|
||||
pub directory: Option<String>,
|
||||
pub llm: Option<LlmConfig>,
|
||||
|
|
@ -42,14 +42,14 @@ pub struct SandboxConfig {
|
|||
pub daytona: Option<DaytonaConfig>,
|
||||
}
|
||||
|
||||
/// Load and validate a task config from a TOML file.
|
||||
/// Load and validate a run config from a TOML file.
|
||||
///
|
||||
/// The `graph` path in the returned config is resolved relative to the
|
||||
/// TOML file's parent directory.
|
||||
pub fn load_task_config(path: &Path) -> anyhow::Result<TaskConfig> {
|
||||
pub fn load_run_config(path: &Path) -> anyhow::Result<WorkflowRunConfig> {
|
||||
let contents = std::fs::read_to_string(path)
|
||||
.with_context(|| format!("Failed to read {}", path.display()))?;
|
||||
let config = parse_task_config(&contents)?;
|
||||
let config = parse_run_config(&contents)?;
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
|
@ -67,13 +67,13 @@ pub fn resolve_graph_path(toml_path: &Path, graph: &str) -> PathBuf {
|
|||
}
|
||||
}
|
||||
|
||||
fn parse_task_config(contents: &str) -> anyhow::Result<TaskConfig> {
|
||||
let config: TaskConfig =
|
||||
toml::from_str(contents).context("Failed to parse task config TOML")?;
|
||||
fn parse_run_config(contents: &str) -> anyhow::Result<WorkflowRunConfig> {
|
||||
let config: WorkflowRunConfig =
|
||||
toml::from_str(contents).context("Failed to parse run config TOML")?;
|
||||
|
||||
if config.version != SUPPORTED_VERSION {
|
||||
bail!(
|
||||
"Unsupported task config version {}. Only version {SUPPORTED_VERSION} is supported.",
|
||||
"Unsupported run config version {}. Only version {SUPPORTED_VERSION} is supported.",
|
||||
config.version
|
||||
);
|
||||
}
|
||||
|
|
@ -163,14 +163,14 @@ mod tests {
|
|||
fn parse_toml_with_vars() {
|
||||
let toml = r#"
|
||||
version = 1
|
||||
task = "Run tests"
|
||||
goal = "Run tests"
|
||||
graph = "workflow.dot"
|
||||
|
||||
[vars]
|
||||
repo_url = "https://github.com/org/repo"
|
||||
language = "python"
|
||||
"#;
|
||||
let config = parse_task_config(toml).unwrap();
|
||||
let config = parse_run_config(toml).unwrap();
|
||||
let vars = config.vars.unwrap();
|
||||
assert_eq!(vars["repo_url"], "https://github.com/org/repo");
|
||||
assert_eq!(vars["language"], "python");
|
||||
|
|
@ -223,13 +223,13 @@ language = "python"
|
|||
fn parse_toml_with_sandbox() {
|
||||
let toml = r#"
|
||||
version = 1
|
||||
task = "Run tests"
|
||||
goal = "Run tests"
|
||||
graph = "workflow.dot"
|
||||
|
||||
[sandbox]
|
||||
provider = "daytona"
|
||||
"#;
|
||||
let config = parse_task_config(toml).unwrap();
|
||||
let config = parse_run_config(toml).unwrap();
|
||||
let sandbox = config.sandbox.unwrap();
|
||||
assert_eq!(sandbox.provider.as_deref(), Some("daytona"));
|
||||
assert!(sandbox.daytona.is_none());
|
||||
|
|
@ -239,7 +239,7 @@ provider = "daytona"
|
|||
fn parse_toml_with_daytona_config() {
|
||||
let toml = r#"
|
||||
version = 1
|
||||
task = "Run tests"
|
||||
goal = "Run tests"
|
||||
graph = "workflow.dot"
|
||||
|
||||
[sandbox]
|
||||
|
|
@ -258,7 +258,7 @@ memory = 8
|
|||
disk = 10
|
||||
dockerfile = "FROM rust:1.85-slim-bookworm\nRUN apt-get update"
|
||||
"#;
|
||||
let config = parse_task_config(toml).unwrap();
|
||||
let config = parse_run_config(toml).unwrap();
|
||||
let sandbox = config.sandbox.unwrap();
|
||||
assert_eq!(sandbox.provider.as_deref(), Some("daytona"));
|
||||
|
||||
|
|
@ -282,7 +282,7 @@ dockerfile = "FROM rust:1.85-slim-bookworm\nRUN apt-get update"
|
|||
fn parse_toml_with_daytona_no_snapshot() {
|
||||
let toml = r#"
|
||||
version = 1
|
||||
task = "Run tests"
|
||||
goal = "Run tests"
|
||||
graph = "workflow.dot"
|
||||
|
||||
[sandbox]
|
||||
|
|
@ -291,7 +291,7 @@ provider = "daytona"
|
|||
[sandbox.daytona]
|
||||
auto_stop_interval = 30
|
||||
"#;
|
||||
let config = parse_task_config(toml).unwrap();
|
||||
let config = parse_run_config(toml).unwrap();
|
||||
let daytona = config.sandbox.unwrap().daytona.unwrap();
|
||||
assert_eq!(daytona.auto_stop_interval, Some(30));
|
||||
assert!(daytona.snapshot.is_none());
|
||||
|
|
@ -301,12 +301,12 @@ auto_stop_interval = 30
|
|||
fn parse_minimal_toml() {
|
||||
let toml = r#"
|
||||
version = 1
|
||||
task = "Run tests"
|
||||
goal = "Run tests"
|
||||
graph = "workflow.dot"
|
||||
"#;
|
||||
let config = parse_task_config(toml).unwrap();
|
||||
let config = parse_run_config(toml).unwrap();
|
||||
assert_eq!(config.version, 1);
|
||||
assert_eq!(config.task, "Run tests");
|
||||
assert_eq!(config.goal, "Run tests");
|
||||
assert_eq!(config.graph, "workflow.dot");
|
||||
assert!(config.directory.is_none());
|
||||
assert!(config.llm.is_none());
|
||||
|
|
@ -317,7 +317,7 @@ graph = "workflow.dot"
|
|||
fn parse_full_toml() {
|
||||
let toml = r#"
|
||||
version = 1
|
||||
task = "Full workflow"
|
||||
goal = "Full workflow"
|
||||
graph = "workflow.dot"
|
||||
directory = "/tmp/repo"
|
||||
|
||||
|
|
@ -329,8 +329,8 @@ provider = "anthropic"
|
|||
commands = ["pip install -r requirements.txt", "npm install"]
|
||||
timeout_ms = 60000
|
||||
"#;
|
||||
let config = parse_task_config(toml).unwrap();
|
||||
assert_eq!(config.task, "Full workflow");
|
||||
let config = parse_run_config(toml).unwrap();
|
||||
assert_eq!(config.goal, "Full workflow");
|
||||
assert_eq!(config.directory.as_deref(), Some("/tmp/repo"));
|
||||
|
||||
let llm = config.llm.unwrap();
|
||||
|
|
@ -346,13 +346,13 @@ timeout_ms = 60000
|
|||
fn unsupported_version_rejected() {
|
||||
let toml = r#"
|
||||
version = 2
|
||||
task = "x"
|
||||
goal = "x"
|
||||
graph = "p.dot"
|
||||
"#;
|
||||
let err = parse_task_config(toml).unwrap_err();
|
||||
let err = parse_run_config(toml).unwrap_err();
|
||||
assert!(
|
||||
err.to_string()
|
||||
.contains("Unsupported task config version 2"),
|
||||
.contains("Unsupported run config version 2"),
|
||||
"unexpected error: {err}"
|
||||
);
|
||||
}
|
||||
|
|
@ -373,17 +373,17 @@ graph = "p.dot"
|
|||
|
||||
#[test]
|
||||
fn missing_required_fields() {
|
||||
let no_task = r#"
|
||||
let no_goal = r#"
|
||||
version = 1
|
||||
graph = "p.dot"
|
||||
"#;
|
||||
assert!(parse_task_config(no_task).is_err());
|
||||
assert!(parse_run_config(no_goal).is_err());
|
||||
|
||||
let no_graph = r#"
|
||||
version = 1
|
||||
task = "x"
|
||||
goal = "x"
|
||||
"#;
|
||||
assert!(parse_task_config(no_graph).is_err());
|
||||
assert!(parse_run_config(no_graph).is_err());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
version = 1
|
||||
task = "Run cargo check in a Daytona cloud sandbox"
|
||||
goal = "Run cargo check in a Daytona cloud sandbox"
|
||||
graph = "check.dot"
|
||||
|
||||
[llm]
|
||||
Loading…
Add table
Reference in a new issue