From e50df2b58bddaef23c5cc59f01cdcd5f8bb387ea Mon Sep 17 00:00:00 2001 From: Bryan Helmkamp Date: Thu, 30 Apr 2026 15:11:34 -0400 Subject: [PATCH] feat(command): distinguish cancelled commands from timeouts Represent command termination explicitly across sandbox results, events, run projections, API types, and the run stage UI. This removes the fake -1 exit code path for timeout/cancel and lets consumers tell cancelled commands apart from timed-out commands. --- apps/fabro-web/app/routes/run-stages.tsx | 12 +- docs/public/api-reference/fabro-api.yaml | 12 ++ lib/crates/fabro-agent/src/session.rs | 6 +- lib/crates/fabro-agent/src/tools.rs | 55 +++++---- lib/crates/fabro-api/build.rs | 1 + lib/crates/fabro-api/src/lib.rs | 6 +- .../tests/command_termination_round_trip.rs | 52 ++++++++ .../fabro-api/tests/node_state_round_trip.rs | 3 +- lib/crates/fabro-hooks/src/executor.rs | 2 +- lib/crates/fabro-retro/src/retro_agent.rs | 3 +- lib/crates/fabro-sandbox/src/daytona/mod.rs | 34 +++--- lib/crates/fabro-sandbox/src/docker.rs | 53 ++++---- lib/crates/fabro-sandbox/src/error.rs | 24 ++-- lib/crates/fabro-sandbox/src/local.rs | 52 +++++--- lib/crates/fabro-sandbox/src/sandbox.rs | 55 +++++---- lib/crates/fabro-sandbox/src/test_support.rs | 9 +- lib/crates/fabro-sandbox/src/worktree.rs | 16 +-- lib/crates/fabro-server/src/run_files.rs | 12 +- lib/crates/fabro-server/src/server.rs | 9 +- lib/crates/fabro-store/src/run_state.rs | 1 + .../tests/serializable_projection.rs | 1 + lib/crates/fabro-types/src/command_output.rs | 28 +++++ lib/crates/fabro-types/src/lib.rs | 2 +- lib/crates/fabro-types/src/run_event/misc.rs | 4 +- lib/crates/fabro-types/src/run_projection.rs | 2 + .../fabro-workflow/src/artifact_snapshot.rs | 5 +- .../fabro-workflow/src/devcontainer_bridge.rs | 29 +++-- lib/crates/fabro-workflow/src/event.rs | 14 +-- lib/crates/fabro-workflow/src/git.rs | 4 +- .../fabro-workflow/src/handler/agent.rs | 4 +- .../fabro-workflow/src/handler/command.rs | 50 +++++--- .../fabro-workflow/src/handler/llm/cli.rs | 35 +++--- .../fabro-workflow/src/handler/parallel.rs | 7 +- .../fabro-workflow/src/pipeline/initialize.rs | 15 ++- lib/crates/fabro-workflow/src/run_dump.rs | 1 + lib/crates/fabro-workflow/src/sandbox_git.rs | 58 +++++---- .../fabro-workflow/src/sandbox_metadata.rs | 14 ++- .../tests/it/daytona_integration.rs | 99 +++++++++------ .../fabro-workflow/tests/it/integration.rs | 106 +++++++++------- .../src/.openapi-generator/FILES | 1 + .../fabro-api-client/src/api/models-api.ts | 3 +- .../src/api/run-internals-api.ts | 35 +++--- .../src/api/run-outputs-api.ts | 10 +- .../fabro-api-client/src/api/runs-api.ts | 115 +++++++++--------- .../src/models/agent-permissions.ts | 5 +- .../src/models/aggregate-billing-totals.ts | 3 +- .../src/models/approval-mode.ts | 5 +- .../src/models/artifacts-settings.ts | 3 +- .../src/models/billed-token-counts.ts | 3 +- .../src/models/command-termination.ts | 30 +++++ .../models/create-run-pull-request-request.ts | 1 + ...daytona-network-layer-one-of-allow-list.ts | 3 +- .../models/daytona-network-layer-one-of.ts | 3 +- .../src/models/daytona-network-layer.ts | 4 +- .../src/models/daytona-settings.ts | 3 +- .../src/models/daytona-snapshot-settings.ts | 3 +- .../fabro-api-client/src/models/diff-stats.ts | 4 +- .../src/models/docker-settings.ts | 3 +- .../src/models/dockerfile-source-inline.ts | 4 +- .../src/models/dockerfile-source-path.ts | 4 +- .../src/models/dockerfile-source.ts | 4 +- .../fabro-api-client/src/models/file-diff.ts | 4 +- .../src/models/git-author-settings.ts | 3 +- .../src/models/hook-definition.ts | 4 +- .../fabro-api-client/src/models/hook-event.ts | 5 +- .../fabro-api-client/src/models/index.ts | 1 + .../src/models/interview-provider-settings.ts | 3 +- .../src/models/local-sandbox-settings.ts | 5 +- .../src/models/mcp-server-settings.ts | 3 +- .../src/models/mcp-transport-http.ts | 4 +- .../src/models/mcp-transport-sandbox.ts | 4 +- .../src/models/mcp-transport-stdio.ts | 4 +- .../src/models/mcp-transport.ts | 4 +- .../src/models/model-costs.ts | 3 +- .../src/models/model-features.ts | 3 +- .../src/models/model-limits.ts | 3 +- .../fabro-api-client/src/models/model.ts | 5 +- .../fabro-api-client/src/models/node-state.ts | 9 +- .../src/models/node-status-record.ts | 5 +- .../models/notification-provider-settings.ts | 3 +- .../src/models/notification-route-settings.ts | 3 +- .../src/models/project-namespace.ts | 3 +- .../src/models/pull-request-settings.ts | 5 +- .../src/models/run-agent-settings.ts | 5 +- .../src/models/run-billing-totals.ts | 3 +- .../src/models/run-checkpoint-settings.ts | 3 +- .../src/models/run-execution-settings.ts | 5 +- .../src/models/run-git-settings.ts | 3 +- .../src/models/run-goal-file.ts | 4 +- .../src/models/run-goal-inline.ts | 4 +- .../fabro-api-client/src/models/run-goal.ts | 4 +- .../src/models/run-interviews-settings.ts | 3 +- .../fabro-api-client/src/models/run-mode.ts | 5 +- .../src/models/run-model-settings.ts | 3 +- .../src/models/run-namespace.ts | 3 +- .../src/models/run-prepare-settings.ts | 3 +- .../src/models/run-projection.ts | 5 +- .../src/models/run-sandbox-settings.ts | 3 +- .../src/models/run-scm-settings.ts | 3 +- .../fabro-api-client/src/models/run-spec.ts | 3 +- .../fabro-api-client/src/models/run-stage.ts | 5 +- .../src/models/stage-outcome.ts | 5 +- .../fabro-api-client/src/models/tls-mode.ts | 5 +- .../src/models/workflow-namespace.ts | 3 +- .../src/models/workflow-settings.ts | 5 +- .../src/models/worktree-mode.ts | 5 +- 106 files changed, 842 insertions(+), 478 deletions(-) create mode 100644 lib/crates/fabro-api/tests/command_termination_round_trip.rs create mode 100644 lib/packages/fabro-api-client/src/models/command-termination.ts diff --git a/apps/fabro-web/app/routes/run-stages.tsx b/apps/fabro-web/app/routes/run-stages.tsx index 88b4621fd..30a0cb733 100644 --- a/apps/fabro-web/app/routes/run-stages.tsx +++ b/apps/fabro-web/app/routes/run-stages.tsx @@ -50,7 +50,9 @@ type TurnType = | { kind: "system"; content: string } | { kind: "assistant"; content: string } | { kind: "tool"; tools: ToolUse[] } - | { kind: "command"; stageId: string; script: string; language: string; stdout?: string; stderr?: string; exitCode?: number | null; durationMs?: number; timedOut?: boolean; running: boolean }; + | { kind: "command"; stageId: string; script: string; language: string; stdout?: string; stderr?: string; exitCode?: number | null; durationMs?: number; termination?: CommandTermination; running: boolean }; + +type CommandTermination = "exited" | "timed_out" | "cancelled"; interface RawEvent { node_id?: string; @@ -128,7 +130,7 @@ function turnsFromEvents(events: RawEvent[], stageId: string): TurnType[] { stderr: props.stderr as string ?? "", exitCode: props.exit_code as number | null ?? null, durationMs: props.duration_ms as number ?? 0, - timedOut: props.timed_out as boolean ?? false, + termination: props.termination as CommandTermination ?? "exited", running: false, }); pendingCommand = undefined; @@ -478,7 +480,7 @@ function CommandBlock({ runId: string | undefined; turn: Extract; }) { - const failed = !turn.running && turn.exitCode !== 0; + const failed = !turn.running && (turn.termination !== "exited" || turn.exitCode !== 0); const stdout = useCommandLog(runId, turn.stageId, "stdout", turn.running); const stderr = useCommandLog(runId, turn.stageId, "stderr", turn.running); const borderColor = turn.running ? "border-teal-500/20" : failed ? "border-coral/15" : "border-mint/15"; @@ -495,8 +497,10 @@ function CommandBlock({
{turn.running ? ( Running… - ) : turn.timedOut ? ( + ) : turn.termination === "timed_out" ? ( Timed out + ) : turn.termination === "cancelled" ? ( + Cancelled ) : ( <> diff --git a/docs/public/api-reference/fabro-api.yaml b/docs/public/api-reference/fabro-api.yaml index 53ad92424..bd1858b5a 100644 --- a/docs/public/api-reference/fabro-api.yaml +++ b/docs/public/api-reference/fabro-api.yaml @@ -4891,6 +4891,14 @@ components: - stdout - stderr + CommandTermination: + description: Terminal state for a command execution. + type: string + enum: + - exited + - timed_out + - cancelled + CommandLogResponse: description: Byte-offset command log slice. type: object @@ -5115,6 +5123,10 @@ components: type: ["boolean", "null"] live_streaming: type: ["boolean", "null"] + termination: + oneOf: + - $ref: "#/components/schemas/CommandTermination" + - type: "null" InterviewOption: description: Option stored with an interview question in the event log. diff --git a/lib/crates/fabro-agent/src/session.rs b/lib/crates/fabro-agent/src/session.rs index 6273703eb..bdff3c0c2 100644 --- a/lib/crates/fabro-agent/src/session.rs +++ b/lib/crates/fabro-agent/src/session.rs @@ -363,7 +363,7 @@ impl Session { .exec_command("git rev-parse --abbrev-ref HEAD", 5000, None, None, None) .await .ok() - .filter(|r| r.exit_code == 0) + .filter(fabro_sandbox::ExecResult::is_success) .map(|r| r.stdout.trim().to_string()); let is_git_repo = git_branch.is_some(); @@ -373,7 +373,7 @@ impl Session { .exec_command("git status --short", 5000, None, None, None) .await .ok() - .filter(|r| r.exit_code == 0) + .filter(fabro_sandbox::ExecResult::is_success) .map(|r| r.stdout.trim().to_string()) .filter(|s| !s.is_empty()) } else { @@ -385,7 +385,7 @@ impl Session { .exec_command("git log --oneline -10", 5000, None, None, None) .await .ok() - .filter(|r| r.exit_code == 0) + .filter(fabro_sandbox::ExecResult::is_success) .map(|r| r.stdout.trim().to_string()) .filter(|s| !s.is_empty()) } else { diff --git a/lib/crates/fabro-agent/src/tools.rs b/lib/crates/fabro-agent/src/tools.rs index eee73ab37..95eb55e34 100644 --- a/lib/crates/fabro-agent/src/tools.rs +++ b/lib/crates/fabro-agent/src/tools.rs @@ -260,13 +260,19 @@ pub fn make_shell_tool_with_config(config: &SessionOptions) -> RegisteredTool { .map_err(|e| e.display_with_causes())?; let mut output = String::new(); - if result.timed_out { + if result.is_timed_out() { output.push_str("Command timed out.\n"); + } else if result.is_cancelled() { + output.push_str("Command cancelled.\n"); } let _ = write!( output, "Exit code: {}\nstdout:\n{}\nstderr:\n{}", - result.exit_code, result.stdout, result.stderr + result + .exit_code + .map_or_else(|| "none".to_string(), |code| code.to_string()), + result.stdout, + result.stderr ); Ok(output) }) @@ -615,10 +621,10 @@ pub(crate) fn make_web_fetch_tool(summarizer: Option) -> Reg .await .map_err(|e| e.display_with_causes())?; - if result.exit_code != 0 { + if !result.is_success() { return Err(format!( "curl failed (exit code {}): {}", - result.exit_code, + result.display_exit_code(), result.stderr.trim() )); } @@ -671,6 +677,7 @@ mod tests { use std::collections::HashMap; use fabro_llm::provider::ProviderAdapter; + use fabro_types::CommandTermination; use tokio_util::sync::CancellationToken; use super::*; @@ -861,8 +868,8 @@ mod tests { exec_result: ExecResult { stdout: "hello".into(), stderr: String::new(), - exit_code: 0, - timed_out: false, + exit_code: Some(0), + termination: CommandTermination::Exited, duration_ms: 10, }, ..Default::default() @@ -902,8 +909,8 @@ mod tests { exec_result: ExecResult { stdout: String::new(), stderr: "error".into(), - exit_code: 1, - timed_out: false, + exit_code: Some(1), + termination: CommandTermination::Exited, duration_ms: 10, }, ..Default::default() @@ -926,8 +933,8 @@ mod tests { exec_result: ExecResult { stdout: String::new(), stderr: String::new(), - exit_code: -1, - timed_out: true, + exit_code: None, + termination: CommandTermination::TimedOut, duration_ms: 10000, }, ..Default::default() @@ -984,8 +991,8 @@ mod tests { exec_result: ExecResult { stdout: "fetched content".into(), stderr: String::new(), - exit_code: 0, - timed_out: false, + exit_code: Some(0), + termination: CommandTermination::Exited, duration_ms: 100, }, ..Default::default() @@ -1109,8 +1116,8 @@ mod tests { exec_result: ExecResult { stdout: "

hello

".into(), stderr: String::new(), - exit_code: 0, - timed_out: false, + exit_code: Some(0), + termination: CommandTermination::Exited, duration_ms: 100, }, ..Default::default() @@ -1221,8 +1228,8 @@ mod tests { exec_result: ExecResult { stdout: large_content, stderr: String::new(), - exit_code: 0, - timed_out: false, + exit_code: Some(0), + termination: CommandTermination::Exited, duration_ms: 100, }, ..Default::default() @@ -1248,8 +1255,8 @@ mod tests { exec_result: ExecResult { stdout: String::new(), stderr: "curl: (6) Could not resolve host".into(), - exit_code: 6, - timed_out: false, + exit_code: Some(6), + termination: CommandTermination::Exited, duration_ms: 100, }, ..Default::default() @@ -1296,8 +1303,8 @@ mod tests { stdout: "

Lots of content about Rust...

" .into(), stderr: String::new(), - exit_code: 0, - timed_out: false, + exit_code: Some(0), + termination: CommandTermination::Exited, duration_ms: 100, }, ..Default::default() @@ -1327,8 +1334,8 @@ mod tests { "

Rust is a systems programming language.

" .into(), stderr: String::new(), - exit_code: 0, - timed_out: false, + exit_code: Some(0), + termination: CommandTermination::Exited, duration_ms: 100, }, ..Default::default() @@ -1396,8 +1403,8 @@ mod tests { exec_result: ExecResult { stdout: "

Page content

".into(), stderr: String::new(), - exit_code: 0, - timed_out: false, + exit_code: Some(0), + termination: CommandTermination::Exited, duration_ms: 100, }, ..Default::default() diff --git a/lib/crates/fabro-api/build.rs b/lib/crates/fabro-api/build.rs index 30e35bb3a..7f8a4c41e 100644 --- a/lib/crates/fabro-api/build.rs +++ b/lib/crates/fabro-api/build.rs @@ -320,6 +320,7 @@ fn main() { "fabro_types::CommandOutputStream", &[], ), + ("CommandTermination", "fabro_types::CommandTermination", &[]), ("NodeState", "fabro_types::NodeState", &[]), ("SecretMetadata", "fabro_types::SecretMetadata", &[]), ("InterviewOption", "fabro_types::InterviewOption", &[]), diff --git a/lib/crates/fabro-api/src/lib.rs b/lib/crates/fabro-api/src/lib.rs index 3211ca942..78e3f7ab1 100644 --- a/lib/crates/fabro-api/src/lib.rs +++ b/lib/crates/fabro-api/src/lib.rs @@ -29,9 +29,9 @@ pub mod types { BlockedReason, FailureReason, RunControlAction, RunStatus, SuccessReason, TerminalStatus, }; pub use fabro_types::{ - ActorKind, ActorRef, BilledTokenCounts, CommandOutputStream, DiffStats, DirtyStatus, - EventEnvelope, GitContext, InterviewOption, InterviewQuestionRecord, NodeState, - NodeStatusRecord, PendingInterviewRecord, PreRunPushOutcome, QuestionType, + ActorKind, ActorRef, BilledTokenCounts, CommandOutputStream, CommandTermination, DiffStats, + DirtyStatus, EventEnvelope, GitContext, InterviewOption, InterviewQuestionRecord, + NodeState, NodeStatusRecord, PendingInterviewRecord, PreRunPushOutcome, QuestionType, RepositoryReference, RunEvent, RunProjection, RunSummary, SecretMetadata, SecretType, ServerSettings, StageOutcome, StageState, WorkflowSettings, }; diff --git a/lib/crates/fabro-api/tests/command_termination_round_trip.rs b/lib/crates/fabro-api/tests/command_termination_round_trip.rs new file mode 100644 index 000000000..180abff3f --- /dev/null +++ b/lib/crates/fabro-api/tests/command_termination_round_trip.rs @@ -0,0 +1,52 @@ +use std::any::{TypeId, type_name}; + +use fabro_api::types::CommandTermination as ApiCommandTermination; +use fabro_types::CommandTermination; +use serde_json::json; + +#[test] +fn command_termination_reuses_canonical_type() { + assert_same_type::(); +} + +#[test] +fn command_termination_serializes_as_state_names() { + assert_eq!( + serde_json::to_value(CommandTermination::Exited).unwrap(), + json!("exited") + ); + assert_eq!( + serde_json::to_value(CommandTermination::TimedOut).unwrap(), + json!("timed_out") + ); + assert_eq!( + serde_json::to_value(CommandTermination::Cancelled).unwrap(), + json!("cancelled") + ); +} + +#[test] +fn command_termination_deserializes_representative_values() { + assert_eq!( + serde_json::from_value::(json!("exited")).unwrap(), + CommandTermination::Exited + ); + assert_eq!( + serde_json::from_value::(json!("timed_out")).unwrap(), + CommandTermination::TimedOut + ); + assert_eq!( + serde_json::from_value::(json!("cancelled")).unwrap(), + CommandTermination::Cancelled + ); +} + +fn assert_same_type() { + assert_eq!( + TypeId::of::(), + TypeId::of::(), + "{} should be the same type as {}", + type_name::(), + type_name::() + ); +} diff --git a/lib/crates/fabro-api/tests/node_state_round_trip.rs b/lib/crates/fabro-api/tests/node_state_round_trip.rs index 97393a6f6..7bc1ea598 100644 --- a/lib/crates/fabro-api/tests/node_state_round_trip.rs +++ b/lib/crates/fabro-api/tests/node_state_round_trip.rs @@ -26,7 +26,8 @@ fn node_state_round_trips_representative_json() { "script_timing": { "duration_ms": 42 }, "parallel_results": [{ "branch": 0, "status": "succeeded" }], "stdout": "ok", - "stderr": "" + "stderr": "", + "termination": "exited" }); let state: NodeState = serde_json::from_value(value.clone()).unwrap(); diff --git a/lib/crates/fabro-hooks/src/executor.rs b/lib/crates/fabro-hooks/src/executor.rs index dbdc86021..22dbcdd4b 100644 --- a/lib/crates/fabro-hooks/src/executor.rs +++ b/lib/crates/fabro-hooks/src/executor.rs @@ -180,7 +180,7 @@ impl HookExecutorImpl { .exec_command(&command, timeout_ms, None, Some(&env_vars), None) .await { - Ok(result) => Self::parse_decision(result.exit_code, &result.stdout), + Ok(result) => Self::parse_decision(result.exit_code.unwrap_or(-1), &result.stdout), Err(e) => HookDecision::Block { reason: Some(format!("sandbox exec failed: {e}")), }, diff --git a/lib/crates/fabro-retro/src/retro_agent.rs b/lib/crates/fabro-retro/src/retro_agent.rs index 4ec077292..eaa8a3d06 100644 --- a/lib/crates/fabro-retro/src/retro_agent.rs +++ b/lib/crates/fabro-retro/src/retro_agent.rs @@ -560,7 +560,7 @@ async fn ensure_remote_dir(sandbox: &Arc, path: &Path) -> anyhow::R .exec_command(&command, 10_000, None, None, None) .await .map_err(|e| anyhow::anyhow!("Failed to create retro upload dir: {e}"))?; - if result.exit_code != 0 { + if !result.is_success() { return Err(anyhow::anyhow!( "Failed to create retro upload dir {}: {}", parent.display(), @@ -675,6 +675,7 @@ mod tests { stderr_bytes: None, streams_separated: None, live_streaming: None, + termination: None, }); upload_data_files( diff --git a/lib/crates/fabro-sandbox/src/daytona/mod.rs b/lib/crates/fabro-sandbox/src/daytona/mod.rs index a93e057ff..8d99ed323 100644 --- a/lib/crates/fabro-sandbox/src/daytona/mod.rs +++ b/lib/crates/fabro-sandbox/src/daytona/mod.rs @@ -6,7 +6,7 @@ use std::time::Instant; use async_trait::async_trait; use daytona_sdk::api_types::SignedPortPreviewUrl; use fabro_github::GitHubCredentials; -use fabro_types::{CommandOutputStream, RunId}; +use fabro_types::{CommandOutputStream, CommandTermination, RunId}; use rand::Rng; use tokio::sync::OnceCell; use tokio::{fs, time}; @@ -659,8 +659,8 @@ impl Sandbox for DaytonaSandbox { Ok(r) if r.exit_code != 0 => { let err = crate::Error::exec( "git remote set-url origin (Daytona post-clone)", - r.exit_code, - false, + Some(r.exit_code), + CommandTermination::Exited, 0, redact_auth_url(&r.result, Some(&auth_url)), String::new(), @@ -892,7 +892,7 @@ impl Sandbox for DaytonaSandbox { .map_err(|_| { crate::Error::message("Failed to refresh push credentials: set_url_exec_failed") })?; - if result.exit_code != 0 { + if !result.is_success() { return Err(result.into_exec_error_with_redactor( "git remote set-url origin (refresh push credentials)", |s| redact_auth_url(s, Some(&auth_url)), @@ -1116,8 +1116,8 @@ impl Sandbox for DaytonaSandbox { return Ok(ExecResult { stdout: String::new(), stderr: "Command timed out locally".to_string(), - exit_code: -1, - timed_out: true, + exit_code: None, + termination: CommandTermination::TimedOut, duration_ms: elapsed_ms(&start), }); } @@ -1129,8 +1129,8 @@ impl Sandbox for DaytonaSandbox { return Ok(ExecResult { stdout: String::new(), stderr: "Command cancelled".to_string(), - exit_code: -1, - timed_out: true, + exit_code: None, + termination: CommandTermination::Cancelled, duration_ms: elapsed_ms(&start), }); } @@ -1143,8 +1143,8 @@ impl Sandbox for DaytonaSandbox { Ok(ExecResult { stdout: result.result.clone(), stderr: String::new(), - exit_code: result.exit_code, - timed_out: false, + exit_code: Some(result.exit_code), + termination: CommandTermination::Exited, duration_ms, }) } @@ -1197,7 +1197,7 @@ impl Sandbox for DaytonaSandbox { let result = self .exec_command("rg --version", 10_000, None, None, None) .await; - matches!(result, Ok(r) if r.exit_code == 0) + matches!(result, Ok(r) if r.is_success()) }) .await; @@ -1241,14 +1241,15 @@ impl Sandbox for DaytonaSandbox { let result = self.exec_command(&cmd, 30_000, None, None, None).await?; - if result.exit_code == 1 { + if result.exit_code == Some(1) { // Both rg and grep exit 1 for no matches return Ok(Vec::new()); } - if result.exit_code != 0 { + if !result.is_success() { return Err(crate::Error::message(format!( "grep failed (exit {}): {}", - result.exit_code, result.stderr + result.display_exit_code(), + result.stderr ))); } @@ -1266,10 +1267,11 @@ impl Sandbox for DaytonaSandbox { let result = self.exec_command(&cmd, 30_000, None, None, None).await?; - if result.exit_code != 0 { + if !result.is_success() { return Err(crate::Error::message(format!( "glob failed (exit {}): {}", - result.exit_code, result.stderr + result.display_exit_code(), + result.stderr ))); } diff --git a/lib/crates/fabro-sandbox/src/docker.rs b/lib/crates/fabro-sandbox/src/docker.rs index e7b5ae57a..e7db252f0 100644 --- a/lib/crates/fabro-sandbox/src/docker.rs +++ b/lib/crates/fabro-sandbox/src/docker.rs @@ -16,7 +16,7 @@ use bollard::exec::{CreateExecOptions, StartExecResults}; use bollard::image::CreateImageOptions; use bollard::models::HostConfig; use fabro_github::GitHubCredentials; -use fabro_types::{CommandOutputStream, RunId}; +use fabro_types::{CommandOutputStream, CommandTermination, RunId}; use futures::StreamExt; use tokio::sync::OnceCell; use tokio::{fs, time}; @@ -319,8 +319,8 @@ impl DockerSandbox { Ok(ExecResult { stdout, stderr, - exit_code, - timed_out: false, + exit_code: Some(exit_code), + termination: CommandTermination::Exited, duration_ms, }) } @@ -329,8 +329,8 @@ impl DockerSandbox { Ok(ExecResult { stdout: String::new(), stderr: "Command timed out".to_string(), - exit_code: -1, - timed_out: true, + exit_code: None, + termination: CommandTermination::TimedOut, duration_ms, }) } @@ -339,8 +339,8 @@ impl DockerSandbox { Ok(ExecResult { stdout: String::new(), stderr: "Command cancelled".to_string(), - exit_code: -1, - timed_out: true, + exit_code: None, + termination: CommandTermination::Cancelled, duration_ms, }) } @@ -381,21 +381,21 @@ impl DockerSandbox { output_callback, )); - let mut interrupted = false; + let mut termination = CommandTermination::Exited; let output = tokio::select! { joined = &mut output_task => { joined .map_err(|e| crate::Error::context("Docker exec stream task failed", e))?? } () = time::sleep(timeout_duration) => { - interrupted = true; + termination = CommandTermination::TimedOut; self.request_docker_exec_stop(&stop_file).await?; output_task .await .map_err(|e| crate::Error::context("Docker exec stream task failed", e))?? } () = token.cancelled() => { - interrupted = true; + termination = CommandTermination::Cancelled; self.request_docker_exec_stop(&stop_file).await?; output_task .await @@ -409,8 +409,8 @@ impl DockerSandbox { result: ExecResult { stdout: String::from_utf8_lossy(&stdout).into_owned(), stderr: String::from_utf8_lossy(&stderr).into_owned(), - exit_code: if interrupted { -1 } else { exit_code }, - timed_out: interrupted, + exit_code: (termination == CommandTermination::Exited).then_some(exit_code), + termination, duration_ms, }, streams_separated: true, @@ -481,10 +481,11 @@ impl DockerSandbox { None, ) .await?; - if result.exit_code != 0 { + if !result.is_success() { return Err(crate::Error::message(format!( "Failed to create Docker workspace (exit {}): {}", - result.exit_code, result.stderr + result.display_exit_code(), + result.stderr ))); } Ok(()) @@ -494,7 +495,7 @@ impl DockerSandbox { let result = self .docker_exec_shell("git --version", 10_000, Some("/"), None, None) .await?; - if result.exit_code != 0 { + if !result.is_success() { return Err(crate::Error::message(format!( "Docker image '{}' must include git for repository clone and git lifecycle operations. Use an image with bash and git, such as buildpack-deps:noble.", self.config.image @@ -540,7 +541,7 @@ impl DockerSandbox { let result = self .docker_exec_shell(&command, 300_000, Some("/"), None, None) .await?; - if result.exit_code != 0 { + if !result.is_success() { let stderr = redact_auth_url(&result.stderr, auth_url.as_ref()); let err = crate::Error::message(if self.github_app.is_none() { format!( @@ -568,7 +569,7 @@ impl DockerSandbox { let result = self .docker_exec_shell(&command, 10_000, Some(WORKING_DIRECTORY), None, None) .await?; - if result.exit_code != 0 { + if !result.is_success() { let err = result .into_exec_error_with_redactor("git remote set-url origin (post-clone)", |s| { redact_auth_url(s, Some(auth_url)) @@ -655,7 +656,7 @@ impl DockerSandbox { None, ) .await?; - if result.exit_code != 0 { + if !result.is_success() { return Err(crate::Error::message(format!( "Failed to create parent dirs for {container_path}: {}", result.stderr @@ -1343,13 +1344,14 @@ impl Sandbox for DockerSandbox { let result = self .docker_exec_shell(&command, 30_000, None, None, None) .await?; - if result.exit_code == 1 { + if result.exit_code == Some(1) { return Ok(Vec::new()); } - if result.exit_code != 0 { + if !result.is_success() { return Err(crate::Error::message(format!( "grep failed (exit {}): {}", - result.exit_code, result.stderr + result.display_exit_code(), + result.stderr ))); } @@ -1374,10 +1376,11 @@ impl Sandbox for DockerSandbox { let result = self .docker_exec_shell(&command, 30_000, None, None, None) .await?; - if result.exit_code != 0 { + if !result.is_success() { return Err(crate::Error::message(format!( "glob failed (exit {}): {}", - result.exit_code, result.stderr + result.display_exit_code(), + result.stderr ))); } @@ -1486,7 +1489,7 @@ impl Sandbox for DockerSandbox { let result = self .docker_exec_shell(&command, 10_000, Some(WORKING_DIRECTORY), None, None) .await?; - if result.exit_code != 0 { + if !result.is_success() { return Err(result.into_exec_error_with_redactor( "git remote set-url origin (refresh push credentials)", |s| redact_auth_url(s, Some(&auth_url)), @@ -1615,7 +1618,7 @@ mod tests { .await .expect("streaming command should return a timeout result"); - assert!(result.result.timed_out); + assert!(result.result.is_timed_out()); assert!( String::from_utf8_lossy(&chunks.lock().unwrap()).contains("start"), "stream should include output emitted before timeout" diff --git a/lib/crates/fabro-sandbox/src/error.rs b/lib/crates/fabro-sandbox/src/error.rs index be2592535..2b7ae5ae2 100644 --- a/lib/crates/fabro-sandbox/src/error.rs +++ b/lib/crates/fabro-sandbox/src/error.rs @@ -1,5 +1,6 @@ #[cfg(feature = "docker")] use bollard::errors::Error as BollardError; +use fabro_types::CommandTermination; use fabro_util::error::{collect_causes, render_with_causes}; #[derive(Debug, thiserror::Error)] @@ -38,15 +39,16 @@ pub enum Error { }, #[error( - "{label} failed (exit {exit_code}, timed_out={timed_out}, duration_ms={duration_ms}) - hint: {hint}", + "{label} failed (exit {exit}, termination={termination}, duration_ms={duration_ms}) - hint: {hint}", + exit = format_exit_code(*exit_code), hint = classify_exec_failure(stderr) .or_else(|| classify_exec_failure(stdout)) .unwrap_or("unclassified") )] Exec { label: String, - exit_code: i32, - timed_out: bool, + exit_code: Option, + termination: CommandTermination, duration_ms: u64, stderr: String, stdout: String, @@ -70,8 +72,8 @@ impl Error { pub fn exec( label: impl Into, - exit_code: i32, - timed_out: bool, + exit_code: Option, + termination: CommandTermination, duration_ms: u64, stderr: impl Into, stdout: impl Into, @@ -79,7 +81,7 @@ impl Error { Self::Exec { label: label.into(), exit_code, - timed_out, + termination, duration_ms, stderr: stderr.into(), stdout: stdout.into(), @@ -162,6 +164,10 @@ pub(crate) fn classify_exec_failure(stderr: &str) -> Option<&'static str> { } } +fn format_exit_code(exit_code: Option) -> String { + exit_code.map_or_else(|| "none".to_string(), |code| code.to_string()) +} + pub type Result = std::result::Result; #[cfg(test)] @@ -176,8 +182,8 @@ mod tests { identity ~/.ssh/id_rsa_work"; let error = Error::exec( "git push origin refs/heads/run", - 128, - false, + Some(128), + CommandTermination::Exited, 210, stderr, "", @@ -199,7 +205,7 @@ mod tests { } assert!(rendered.contains("git push origin refs/heads/run")); assert!(rendered.contains("exit 128")); - assert!(rendered.contains("timed_out=false")); + assert!(rendered.contains("termination=exited")); assert!(rendered.contains("duration_ms=210")); assert!(rendered.contains("hint:")); } diff --git a/lib/crates/fabro-sandbox/src/local.rs b/lib/crates/fabro-sandbox/src/local.rs index f019e7765..4724510cd 100644 --- a/lib/crates/fabro-sandbox/src/local.rs +++ b/lib/crates/fabro-sandbox/src/local.rs @@ -3,7 +3,7 @@ use std::time::Instant; use async_trait::async_trait; use fabro_static::EnvVars; -use fabro_types::CommandOutputStream; +use fabro_types::{CommandOutputStream, CommandTermination}; use tokio::io::{AsyncRead, AsyncReadExt}; use tokio::process::{Child, Command}; use tokio::task::spawn_blocking; @@ -294,19 +294,19 @@ impl Sandbox for LocalSandbox { buf }); - let (timed_out, exit_code) = tokio::select! { + let (termination, exit_code) = tokio::select! { status_result = child.wait() => { let status = status_result .map_err(|e| crate::Error::context("Failed to wait for process", e))?; - (false, status.code().unwrap_or(-1)) + (CommandTermination::Exited, status.code()) } () = time::sleep(timeout_duration) => { sigterm_then_kill(&mut child).await; - (true, -1) + (CommandTermination::TimedOut, None) } () = token.cancelled() => { sigterm_then_kill(&mut child).await; - (true, -1) + (CommandTermination::Cancelled, None) } }; @@ -319,7 +319,7 @@ impl Sandbox for LocalSandbox { stdout: stdout_str, stderr: stderr_str, exit_code, - timed_out, + termination, duration_ms, }) } @@ -381,19 +381,19 @@ impl Sandbox for LocalSandbox { drain_command_pipe(stderr_pipe, CommandOutputStream::Stderr, stderr_callback).await }); - let (timed_out, exit_code) = tokio::select! { + let (termination, exit_code) = tokio::select! { status_result = child.wait() => { let status = status_result .map_err(|e| crate::Error::context("Failed to wait for process", e))?; - (false, status.code().unwrap_or(-1)) + (CommandTermination::Exited, status.code()) } () = time::sleep(timeout_duration) => { sigterm_then_kill(&mut child).await; - (true, -1) + (CommandTermination::TimedOut, None) } () = token.cancelled() => { sigterm_then_kill(&mut child).await; - (true, -1) + (CommandTermination::Cancelled, None) } }; @@ -410,7 +410,7 @@ impl Sandbox for LocalSandbox { stdout: String::from_utf8_lossy(&stdout_bytes).into_owned(), stderr: String::from_utf8_lossy(&stderr_bytes).into_owned(), exit_code, - timed_out, + termination, duration_ms, }, streams_separated: true, @@ -591,7 +591,7 @@ impl Sandbox for LocalSandbox { .exec_command("git remote get-url origin", 10_000, None, None, None) .await { - Ok(result) if result.exit_code == 0 => true, + Ok(result) if result.is_success() => true, Ok(_) => false, Err(err) => return Err(crate::Error::context("git remote get-url origin", err)), }; @@ -824,8 +824,8 @@ mod tests { .unwrap(); assert_eq!(result.stdout.trim(), "hello"); - assert_eq!(result.exit_code, 0); - assert!(!result.timed_out); + assert_eq!(result.exit_code, Some(0)); + assert_eq!(result.termination, CommandTermination::Exited); assert!(result.duration_ms < 5000); std::fs::remove_dir_all(&dir).unwrap(); } @@ -839,8 +839,8 @@ mod tests { .await .unwrap(); - assert_eq!(result.exit_code, 42); - assert!(!result.timed_out); + assert_eq!(result.exit_code, Some(42)); + assert_eq!(result.termination, CommandTermination::Exited); std::fs::remove_dir_all(&dir).unwrap(); } @@ -853,8 +853,24 @@ mod tests { .await .unwrap(); - assert!(result.timed_out); - assert_eq!(result.exit_code, -1); + assert_eq!(result.termination, CommandTermination::TimedOut); + assert_eq!(result.exit_code, None); + std::fs::remove_dir_all(&dir).unwrap(); + } + + #[tokio::test] + async fn exec_command_cancelled() { + let dir = temp_dir(); + let env = LocalSandbox::new(dir.clone()); + let token = CancellationToken::new(); + token.cancel(); + let result = env + .exec_command("sleep 10", 5000, None, None, Some(token)) + .await + .unwrap(); + + assert_eq!(result.termination, CommandTermination::Cancelled); + assert_eq!(result.exit_code, None); std::fs::remove_dir_all(&dir).unwrap(); } diff --git a/lib/crates/fabro-sandbox/src/sandbox.rs b/lib/crates/fabro-sandbox/src/sandbox.rs index 3afe17325..83ec73bef 100644 --- a/lib/crates/fabro-sandbox/src/sandbox.rs +++ b/lib/crates/fabro-sandbox/src/sandbox.rs @@ -7,7 +7,7 @@ use std::sync::Arc; use std::time::Duration; use async_trait::async_trait; -use fabro_types::CommandOutputStream; +use fabro_types::{CommandOutputStream, CommandTermination}; use serde::{Deserialize, Serialize}; use tokio::time; use tokio_util::sync::CancellationToken; @@ -399,21 +399,33 @@ pub fn format_lines_numbered(content: &str, offset: Option, limit: Option pub struct ExecResult { pub stdout: String, pub stderr: String, - pub exit_code: i32, - pub timed_out: bool, + pub exit_code: Option, + pub termination: CommandTermination, pub duration_ms: u64, } impl ExecResult { pub fn is_success(&self) -> bool { - self.exit_code == 0 && !self.timed_out + self.exit_code == Some(0) && self.termination == CommandTermination::Exited + } + + pub fn is_timed_out(&self) -> bool { + self.termination == CommandTermination::TimedOut + } + + pub fn is_cancelled(&self) -> bool { + self.termination == CommandTermination::Cancelled + } + + pub fn display_exit_code(&self) -> i32 { + self.exit_code.unwrap_or(-1) } pub fn into_exec_error(self, label: impl Into) -> crate::Error { crate::Error::exec( label, self.exit_code, - self.timed_out, + self.termination, self.duration_ms, self.stderr, self.stdout, @@ -430,7 +442,7 @@ impl ExecResult { crate::Error::exec( label, self.exit_code, - self.timed_out, + self.termination, self.duration_ms, stderr, stdout, @@ -674,7 +686,7 @@ pub async fn setup_git_via_exec( .map_err(|e| { crate::Error::message(format!("git rev-parse --abbrev-ref HEAD failed: {e}")) })?; - let base_branch = if branch_result.exit_code == 0 { + let base_branch = if branch_result.is_success() { let name = branch_result.stdout.trim().to_string(); if name.is_empty() || name == "HEAD" { None @@ -748,7 +760,7 @@ pub(crate) async fn fetch_source_run_ref( let fetch = sandbox .exec_command(&fetch_cmd, 30_000, None, None, None) .await?; - if fetch.exit_code != 0 { + if !fetch.is_success() { last_error = fetch .into_exec_error("git fetch source run ref") .to_string(); @@ -756,7 +768,7 @@ pub(crate) async fn fetch_source_run_ref( let check = sandbox .exec_command(&check_cmd, 10_000, None, None, None) .await?; - if check.exit_code == 0 { + if check.is_success() { return Ok(()); } last_error = check @@ -801,12 +813,12 @@ mod tests { let result = ExecResult { stdout: "out".into(), stderr: "err".into(), - exit_code: 1, - timed_out: true, + exit_code: Some(1), + termination: CommandTermination::Exited, duration_ms: 5000, }; - assert_eq!(result.exit_code, 1); - assert!(result.timed_out); + assert_eq!(result.exit_code, Some(1)); + assert_eq!(result.termination, CommandTermination::Exited); assert_eq!(result.duration_ms, 5000); } @@ -815,8 +827,8 @@ mod tests { let result = ExecResult { stdout: "out".into(), stderr: "fatal: could not read Username".into(), - exit_code: 128, - timed_out: false, + exit_code: Some(128), + termination: CommandTermination::Exited, duration_ms: 42, }; let error = result.into_result("git push").unwrap_err(); @@ -827,7 +839,7 @@ mod tests { panic!("expected Error::Exec, got {error:?}"); }; assert_eq!(label, "git push"); - assert_eq!(*exit_code, 128); + assert_eq!(*exit_code, Some(128)); assert!(error.to_string().contains("no credentials in origin URL")); } @@ -836,14 +848,15 @@ mod tests { let success = ExecResult { stdout: String::new(), stderr: String::new(), - exit_code: 0, - timed_out: false, + exit_code: Some(0), + termination: CommandTermination::Exited, duration_ms: 1, }; assert!(success.is_success()); let timeout = ExecResult { - timed_out: true, + exit_code: None, + termination: CommandTermination::TimedOut, ..success }; assert!(!timeout.is_success()); @@ -854,8 +867,8 @@ mod tests { let result = ExecResult { stdout: "stdout https://token@example.com".into(), stderr: "stderr https://token@example.com".into(), - exit_code: 1, - timed_out: false, + exit_code: Some(1), + termination: CommandTermination::Exited, duration_ms: 1, }; let error = result.into_exec_error_with_redactor("git set-url", |s| { diff --git a/lib/crates/fabro-sandbox/src/test_support.rs b/lib/crates/fabro-sandbox/src/test_support.rs index 46c579ea9..2566930fd 100644 --- a/lib/crates/fabro-sandbox/src/test_support.rs +++ b/lib/crates/fabro-sandbox/src/test_support.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; use std::sync::Mutex; use async_trait::async_trait; +use fabro_types::CommandTermination; use tokio::fs; use tokio_util::sync::CancellationToken; @@ -61,8 +62,8 @@ impl Default for MockSandbox { exec_result: ExecResult { stdout: "mock output".into(), stderr: String::new(), - exit_code: 0, - timed_out: false, + exit_code: Some(0), + termination: CommandTermination::Exited, duration_ms: 10, }, grep_results: vec![], @@ -323,8 +324,8 @@ impl Sandbox for MutableMockSandbox { Ok(ExecResult { stdout: String::new(), stderr: String::new(), - exit_code: 0, - timed_out: false, + exit_code: Some(0), + termination: CommandTermination::Exited, duration_ms: 0, }) } diff --git a/lib/crates/fabro-sandbox/src/worktree.rs b/lib/crates/fabro-sandbox/src/worktree.rs index aef1b76ee..08e5590d6 100644 --- a/lib/crates/fabro-sandbox/src/worktree.rs +++ b/lib/crates/fabro-sandbox/src/worktree.rs @@ -160,10 +160,10 @@ impl Sandbox for WorktreeSandbox { .inner .exec_command(&cmd, 30_000, None, None, None) .await?; - if result.exit_code != 0 { + if !result.is_success() { return Err(crate::Error::message(format!( "git branch --force failed (exit {}): {}", - result.exit_code, + result.display_exit_code(), result.stderr.trim() ))); } @@ -178,7 +178,7 @@ impl Sandbox for WorktreeSandbox { .inner .exec_command(&add_cmd, 30_000, None, None, None) .await?; - if result.exit_code != 0 { + if !result.is_success() { // Roll back the branch created above so we don't leak partial state. if !self.config.skip_branch_creation { let rollback_cmd = format!("{GIT} branch -D {branch}"); @@ -189,7 +189,7 @@ impl Sandbox for WorktreeSandbox { } return Err(crate::Error::message(format!( "git worktree add failed (exit {}): {}", - result.exit_code, + result.display_exit_code(), result.stderr.trim() ))); } @@ -349,7 +349,7 @@ impl Sandbox for WorktreeSandbox { .exec_command("git remote get-url origin", 10_000, None, None, None) .await { - Ok(result) if result.exit_code == 0 => true, + Ok(result) if result.is_success() => true, Ok(_) => false, Err(err) => return Err(crate::Error::context("git remote get-url origin", err)), }; @@ -404,6 +404,8 @@ impl Sandbox for WorktreeSandbox { mod tests { use std::sync::Mutex; + use fabro_types::CommandTermination; + use super::*; use crate::local::LocalSandbox; use crate::test_support::MockSandbox; @@ -562,8 +564,8 @@ mod tests { exec_result: ExecResult { stdout: String::new(), stderr: "fatal: not a git repo".to_string(), - exit_code: 128, - timed_out: false, + exit_code: Some(128), + termination: CommandTermination::Exited, duration_ms: 5, }, ..MockSandbox::linux() diff --git a/lib/crates/fabro-server/src/run_files.rs b/lib/crates/fabro-server/src/run_files.rs index 4d03e8799..599ace2b0 100644 --- a/lib/crates/fabro-server/src/run_files.rs +++ b/lib/crates/fabro-server/src/run_files.rs @@ -805,7 +805,7 @@ async fn resolve_head_sha_and_time( ) .await .map_err(|err| ApiError::new(StatusCode::SERVICE_UNAVAILABLE, err.display_with_causes()))?; - if res.exit_code != 0 { + if !res.is_success() { return Err(ApiError::new( StatusCode::SERVICE_UNAVAILABLE, "Failed to resolve sandbox HEAD.", @@ -1268,7 +1268,7 @@ fn count_flags(data: &[FileDiff]) -> (u64, u64, u64, u64) { mod tests { use std::sync::atomic::{AtomicUsize, Ordering}; - use fabro_types::RunId; + use fabro_types::{CommandTermination, RunId}; use tokio::time::{Duration, sleep}; use super::*; @@ -2304,8 +2304,8 @@ rename to .env.production ExecResult { stdout: stdout.to_string(), stderr: String::new(), - exit_code: 0, - timed_out: false, + exit_code: Some(0), + termination: CommandTermination::Exited, duration_ms: 0, } } @@ -2314,8 +2314,8 @@ rename to .env.production ExecResult { stdout: String::new(), stderr: stderr.to_string(), - exit_code: 1, - timed_out: false, + exit_code: Some(1), + termination: CommandTermination::Exited, duration_ms: 0, } } diff --git a/lib/crates/fabro-server/src/server.rs b/lib/crates/fabro-server/src/server.rs index 2c577de7c..8ce156dc3 100644 --- a/lib/crates/fabro-server/src/server.rs +++ b/lib/crates/fabro-server/src/server.rs @@ -8223,8 +8223,9 @@ mod tests { use fabro_model::Provider; use fabro_types::settings::ServerAuthMethod; use fabro_types::{ - AttrValue, FailureCategory, FailureDetail, Graph, InterviewQuestionRecord, Outcome, - QuestionType, RunAuthMethod, RunBlobId, RunId, RunSpec, StageOutcome, fixtures, + AttrValue, CommandTermination, FailureCategory, FailureDetail, Graph, + InterviewQuestionRecord, Outcome, QuestionType, RunAuthMethod, RunBlobId, RunId, RunSpec, + StageOutcome, fixtures, }; use httpmock::Method::POST; use httpmock::MockServer; @@ -11040,7 +11041,7 @@ slug = "fabro" stderr: stderr_ref, exit_code: Some(0), duration_ms: 5, - timed_out: false, + termination: CommandTermination::Exited, stdout_bytes: 11, stderr_bytes: 0, streams_separated: true, @@ -11111,7 +11112,7 @@ slug = "fabro" stderr: stderr_ref, exit_code: Some(0), duration_ms: 5, - timed_out: false, + termination: CommandTermination::Exited, stdout_bytes: 7, stderr_bytes: 0, streams_separated: true, diff --git a/lib/crates/fabro-store/src/run_state.rs b/lib/crates/fabro-store/src/run_state.rs index 5b0829c47..ada4202cb 100644 --- a/lib/crates/fabro-store/src/run_state.rs +++ b/lib/crates/fabro-store/src/run_state.rs @@ -346,6 +346,7 @@ impl RunProjectionReducer for RunProjection { node.stderr_bytes = Some(props.stderr_bytes); node.streams_separated = Some(props.streams_separated); node.live_streaming = Some(props.live_streaming); + node.termination = Some(props.termination); node.script_timing = Some(serde_json::to_value(props).map_err(|err| { Error::InvalidEvent(format!("invalid command.completed payload: {err}")) })?); diff --git a/lib/crates/fabro-store/tests/serializable_projection.rs b/lib/crates/fabro-store/tests/serializable_projection.rs index f9120e09b..b7feb76d3 100644 --- a/lib/crates/fabro-store/tests/serializable_projection.rs +++ b/lib/crates/fabro-store/tests/serializable_projection.rs @@ -100,6 +100,7 @@ fn serializable_projection_round_trips_and_trims_bulky_node_fields() { stderr_bytes: None, streams_separated: None, live_streaming: None, + termination: None, }); let serialized = serde_json::to_value(SerializableProjection(&projection)) diff --git a/lib/crates/fabro-types/src/command_output.rs b/lib/crates/fabro-types/src/command_output.rs index 26b38d830..e52789c51 100644 --- a/lib/crates/fabro-types/src/command_output.rs +++ b/lib/crates/fabro-types/src/command_output.rs @@ -23,6 +23,34 @@ pub enum CommandOutputStream { Stderr, } +#[derive( + Debug, + Clone, + Copy, + PartialEq, + Eq, + Hash, + Serialize, + Deserialize, + Display, + EnumString, + IntoStaticStr, +)] +#[serde(rename_all = "snake_case")] +#[strum(serialize_all = "snake_case")] +pub enum CommandTermination { + Exited, + TimedOut, + Cancelled, +} + +impl CommandTermination { + #[must_use] + pub fn as_str(self) -> &'static str { + self.into() + } +} + impl CommandOutputStream { #[must_use] pub fn as_str(self) -> &'static str { diff --git a/lib/crates/fabro-types/src/lib.rs b/lib/crates/fabro-types/src/lib.rs index 440a71f03..dd2e4b87b 100644 --- a/lib/crates/fabro-types/src/lib.rs +++ b/lib/crates/fabro-types/src/lib.rs @@ -43,7 +43,7 @@ pub use blob_ref::{ format_blob_ref, parse_blob_ref, parse_legacy_blob_file_ref, parse_managed_blob_file_ref, }; pub use checkpoint::Checkpoint; -pub use command_output::CommandOutputStream; +pub use command_output::{CommandOutputStream, CommandTermination}; pub use conclusion::{Conclusion, StageSummary}; pub use dense::{ServerSettings, UserSettings, WorkflowSettings}; pub use diff::DiffStats; diff --git a/lib/crates/fabro-types/src/run_event/misc.rs b/lib/crates/fabro-types/src/run_event/misc.rs index d86658107..e10203dbb 100644 --- a/lib/crates/fabro-types/src/run_event/misc.rs +++ b/lib/crates/fabro-types/src/run_event/misc.rs @@ -1,6 +1,8 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; +use crate::CommandTermination; + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct InterviewOption { pub key: String, @@ -206,7 +208,7 @@ pub struct CommandCompletedProps { #[serde(default, skip_serializing_if = "Option::is_none")] pub exit_code: Option, pub duration_ms: u64, - pub timed_out: bool, + pub termination: CommandTermination, #[serde(default)] pub stdout_bytes: u64, #[serde(default)] diff --git a/lib/crates/fabro-types/src/run_projection.rs b/lib/crates/fabro-types/src/run_projection.rs index e0866a92d..f3d404b73 100644 --- a/lib/crates/fabro-types/src/run_projection.rs +++ b/lib/crates/fabro-types/src/run_projection.rs @@ -57,6 +57,8 @@ pub struct NodeState { pub streams_separated: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub live_streaming: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub termination: Option, } impl RunProjection { diff --git a/lib/crates/fabro-workflow/src/artifact_snapshot.rs b/lib/crates/fabro-workflow/src/artifact_snapshot.rs index c10c07e1e..a4015c648 100644 --- a/lib/crates/fabro-workflow/src/artifact_snapshot.rs +++ b/lib/crates/fabro-workflow/src/artifact_snapshot.rs @@ -351,6 +351,7 @@ mod tests { use std::collections::HashMap; use fabro_agent::sandbox::ExecResult; + use fabro_types::CommandTermination; use super::*; @@ -369,8 +370,8 @@ mod tests { exec_result: ExecResult { stdout: exec_stdout.to_string(), stderr: String::new(), - exit_code: 0, - timed_out: false, + exit_code: Some(0), + termination: CommandTermination::Exited, duration_ms: 10, }, working_dir: "/home/test", diff --git a/lib/crates/fabro-workflow/src/devcontainer_bridge.rs b/lib/crates/fabro-workflow/src/devcontainer_bridge.rs index 57592eb56..c70a37585 100644 --- a/lib/crates/fabro-workflow/src/devcontainer_bridge.rs +++ b/lib/crates/fabro-workflow/src/devcontainer_bridge.rs @@ -122,28 +122,30 @@ pub async fn run_devcontainer_lifecycle( token.cancel(); } let cmd_duration = crate::millis_u64(cmd_start.elapsed()); - if result.exit_code != 0 { + if !result.is_success() { + let exit_code = result.display_exit_code(); emitter.emit( &Event::DevcontainerLifecycleFailed { phase: phase.clone(), command: name.clone(), index, - exit_code: result.exit_code, + exit_code, stderr: result.stderr.clone(), }, ); return Err(Error::engine(format!( "Devcontainer {phase} parallel command '{name}' failed (exit code {}): {}", - result.exit_code, + exit_code, result.stderr, ))); } + let exit_code = result.exit_code.unwrap_or(0); emitter.emit( &Event::DevcontainerLifecycleCommandCompleted { phase: phase.clone(), command: name.clone(), index, - exit_code: result.exit_code, + exit_code, duration_ms: cmd_duration, }, ); @@ -191,24 +193,26 @@ async fn run_single_lifecycle_command( token.cancel(); } let cmd_duration = crate::millis_u64(cmd_start.elapsed()); - if result.exit_code != 0 { + if !result.is_success() { + let exit_code = result.display_exit_code(); emitter.emit(&Event::DevcontainerLifecycleFailed { phase: phase.to_string(), command: command.to_string(), index, - exit_code: result.exit_code, + exit_code, stderr: result.stderr.clone(), }); return Err(Error::engine(format!( "Devcontainer {phase} command failed (exit code {}): {command}\n{}", - result.exit_code, result.stderr, + exit_code, result.stderr, ))); } + let exit_code = result.exit_code.unwrap_or(0); emitter.emit(&Event::DevcontainerLifecycleCommandCompleted { phase: phase.to_string(), command: command.to_string(), index, - exit_code: result.exit_code, + exit_code, duration_ms: cmd_duration, }); Ok(()) @@ -222,6 +226,7 @@ mod tests { use async_trait::async_trait; use fabro_agent::sandbox::{ExecResult, GrepOptions, Sandbox}; + use fabro_types::CommandTermination; use tokio_util::sync::CancellationToken; use super::*; @@ -317,8 +322,8 @@ mod tests { return Ok(ExecResult { stdout: String::new(), stderr: "cancelled".to_string(), - exit_code: -1, - timed_out: true, + exit_code: None, + termination: CommandTermination::Cancelled, duration_ms: 10, }); } @@ -329,8 +334,8 @@ mod tests { } else { String::new() }, - exit_code: self.exit_code, - timed_out: false, + exit_code: Some(self.exit_code), + termination: CommandTermination::Exited, duration_ms: 10, }) } diff --git a/lib/crates/fabro-workflow/src/event.rs b/lib/crates/fabro-workflow/src/event.rs index 6aa91ec1d..084a2e969 100644 --- a/lib/crates/fabro-workflow/src/event.rs +++ b/lib/crates/fabro-workflow/src/event.rs @@ -5,8 +5,8 @@ use std::sync::Arc; use std::sync::atomic::{AtomicI64, Ordering}; use ::fabro_types::{ - ActorRef, BilledTokenCounts, BlockedReason, FailureReason, ForkSourceRef, GitContext, - ParallelBranchId, PullRequestRecord, RunBlobId, RunControlAction, RunEvent, RunId, + ActorRef, BilledTokenCounts, BlockedReason, CommandTermination, FailureReason, ForkSourceRef, + GitContext, ParallelBranchId, PullRequestRecord, RunBlobId, RunControlAction, RunEvent, RunId, RunProvenance, StageId, StageOutcome, SuccessReason, run_event as fabro_types, }; use anyhow::{Context, Result}; @@ -505,7 +505,7 @@ pub enum Event { #[serde(default, skip_serializing_if = "Option::is_none")] exit_code: Option, duration_ms: u64, - timed_out: bool, + termination: CommandTermination, stdout_bytes: u64, stderr_bytes: u64, streams_separated: bool, @@ -1114,7 +1114,7 @@ impl Event { node_id, exit_code, duration_ms, - timed_out, + termination, stdout_bytes, stderr_bytes, .. @@ -1123,7 +1123,7 @@ impl Event { node_id, exit_code, duration_ms, - timed_out, + termination = %termination, stdout_bytes, stderr_bytes, "Command completed" @@ -2497,7 +2497,7 @@ fn event_body_from_event(event: &Event) -> EventBody { stderr, exit_code, duration_ms, - timed_out, + termination, stdout_bytes, stderr_bytes, streams_separated, @@ -2508,7 +2508,7 @@ fn event_body_from_event(event: &Event) -> EventBody { stderr: stderr.clone(), exit_code: *exit_code, duration_ms: *duration_ms, - timed_out: *timed_out, + termination: *termination, stdout_bytes: *stdout_bytes, stderr_bytes: *stderr_bytes, streams_separated: *streams_separated, diff --git a/lib/crates/fabro-workflow/src/git.rs b/lib/crates/fabro-workflow/src/git.rs index 592dccfe1..4ee731d46 100644 --- a/lib/crates/fabro-workflow/src/git.rs +++ b/lib/crates/fabro-workflow/src/git.rs @@ -343,7 +343,7 @@ mod tests { use std::time::Duration; use fabro_store::Database; - use fabro_types::fixtures; + use fabro_types::{CommandTermination, fixtures}; use object_store::memory::InMemory; use super::*; @@ -514,7 +514,7 @@ mod tests { stderr: String::new(), exit_code: Some(0), duration_ms: 10, - timed_out: false, + termination: CommandTermination::Exited, stdout_bytes: 3, stderr_bytes: 0, streams_separated: true, diff --git a/lib/crates/fabro-workflow/src/handler/agent.rs b/lib/crates/fabro-workflow/src/handler/agent.rs index a615f7e35..7fdfc3d27 100644 --- a/lib/crates/fabro-workflow/src/handler/agent.rs +++ b/lib/crates/fabro-workflow/src/handler/agent.rs @@ -371,7 +371,7 @@ impl Handler for AgentHandler { .exec_command("cat status.json", 5_000, None, None, None) .await { - if result.exit_code == 0 { + if result.is_success() { found_in_status_json = extract_status_fields(&result.stdout, &mut outcome); } } @@ -385,7 +385,7 @@ impl Handler for AgentHandler { .exec_command(&cmd, 5_000, None, None, None) .await { - if result.exit_code == 0 { + if result.is_success() { extract_status_fields(&result.stdout, &mut outcome); } } diff --git a/lib/crates/fabro-workflow/src/handler/command.rs b/lib/crates/fabro-workflow/src/handler/command.rs index 67724b8a3..c347a7575 100644 --- a/lib/crates/fabro-workflow/src/handler/command.rs +++ b/lib/crates/fabro-workflow/src/handler/command.rs @@ -3,6 +3,7 @@ use std::path::Path; use async_trait::async_trait; use fabro_agent::CommandOutputCallback; use fabro_graphviz::graph::{Graph, Node}; +use fabro_types::CommandTermination; use super::{EngineServices, Handler}; use crate::command_log::CommandLogRecorder; @@ -154,9 +155,9 @@ impl Handler for CommandHandler { node_id: node.id.clone(), stdout: finalized.stdout_ref.clone(), stderr: finalized.stderr_ref.clone(), - exit_code: (!result.timed_out).then_some(result.exit_code), + exit_code: result.exit_code, duration_ms: result.duration_ms, - timed_out: result.timed_out, + termination: result.termination, stdout_bytes: finalized.stdout_bytes, stderr_bytes: finalized.stderr_bytes, streams_separated: streaming.streams_separated, @@ -165,13 +166,19 @@ impl Handler for CommandHandler { &stage_scope, ); - if result.timed_out { + if result.termination == CommandTermination::TimedOut { let mut reason = format!("Script timed out after {timeout_ms}ms: {script}"); append_output_tails(&mut reason, &finalized.stdout_text, &finalized.stderr_text); return Err(Error::handler(reason)); } - if result.exit_code == 0 { + if result.termination == CommandTermination::Cancelled { + let mut reason = format!("Script cancelled: {script}"); + append_output_tails(&mut reason, &finalized.stdout_text, &finalized.stderr_text); + return Err(Error::handler(reason)); + } + + if result.exit_code == Some(0) { let mut outcome = Outcome::success(); outcome.context_updates.insert( keys::COMMAND_OUTPUT.to_string(), @@ -184,7 +191,10 @@ impl Handler for CommandHandler { outcome.notes = Some(format!("Script completed: {script}")); Ok(outcome) } else { - let mut reason = format!("Script failed with exit code: {}", result.exit_code); + let mut reason = format!( + "Script failed with exit code: {}", + result.exit_code.unwrap_or(-1) + ); append_output_tails(&mut reason, &finalized.stdout_text, &finalized.stderr_text); let mut outcome = Outcome::fail_classify(reason); outcome.context_updates.insert( @@ -613,7 +623,7 @@ mod tests { let json = node_state.script_timing.as_ref().unwrap(); assert!(json["duration_ms"].is_u64()); assert_eq!(json["exit_code"], 0); - assert_eq!(json["timed_out"], false); + assert_eq!(json["termination"], "exited"); } #[tokio::test] @@ -637,7 +647,7 @@ mod tests { let node_state = snapshot.node(&StageId::new("script_node", 1)).unwrap(); let json = node_state.script_timing.as_ref().unwrap(); assert_eq!(json["exit_code"], 1); - assert_eq!(json["timed_out"], false); + assert_eq!(json["termination"], "exited"); } #[tokio::test] @@ -668,7 +678,7 @@ mod tests { let json = node_state.script_timing.as_ref().unwrap(); assert!(json["duration_ms"].is_u64()); assert_eq!(json["exit_code"], serde_json::Value::Null); - assert_eq!(json["timed_out"], true); + assert_eq!(json["termination"], "timed_out"); } #[tokio::test] @@ -867,8 +877,8 @@ mod tests { exec_result: fabro_agent::sandbox::ExecResult { stdout: String::new(), stderr: String::new(), - exit_code: -1, - timed_out: false, + exit_code: Some(1), + termination: CommandTermination::Exited, duration_ms: 0, }, exec_error: Some(message.into()), @@ -978,8 +988,8 @@ mod tests { let spy = std::sync::Arc::new(SpySandbox::new(fabro_agent::sandbox::ExecResult { stdout: "SANDBOX_MARKER\n".into(), stderr: String::new(), - exit_code: 0, - timed_out: false, + exit_code: Some(0), + termination: CommandTermination::Exited, duration_ms: 5, })); @@ -1018,8 +1028,8 @@ mod tests { let spy = std::sync::Arc::new(SpySandbox::new(fabro_agent::sandbox::ExecResult { stdout: "PYTHON_SANDBOX\n".into(), stderr: String::new(), - exit_code: 0, - timed_out: false, + exit_code: Some(0), + termination: CommandTermination::Exited, duration_ms: 5, })); @@ -1061,8 +1071,8 @@ mod tests { let spy = std::sync::Arc::new(SpySandbox::new(fabro_agent::sandbox::ExecResult { stdout: String::new(), stderr: String::new(), - exit_code: 0, - timed_out: false, + exit_code: Some(0), + termination: CommandTermination::Exited, duration_ms: 5, })); @@ -1096,8 +1106,8 @@ mod tests { let spy = std::sync::Arc::new(SpySandbox::new(fabro_agent::sandbox::ExecResult { stdout: String::new(), stderr: String::new(), - exit_code: 0, - timed_out: false, + exit_code: Some(0), + termination: CommandTermination::Exited, duration_ms: 5, })); @@ -1127,8 +1137,8 @@ mod tests { let spy = std::sync::Arc::new(SpySandbox::new(fabro_agent::sandbox::ExecResult { stdout: "partial stdout\n".into(), stderr: "partial stderr\n".into(), - exit_code: -1, - timed_out: true, + exit_code: None, + termination: CommandTermination::TimedOut, duration_ms: 50, })); diff --git a/lib/crates/fabro-workflow/src/handler/llm/cli.rs b/lib/crates/fabro-workflow/src/handler/llm/cli.rs index f3989dce1..872b80668 100644 --- a/lib/crates/fabro-workflow/src/handler/llm/cli.rs +++ b/lib/crates/fabro-workflow/src/handler/llm/cli.rs @@ -8,6 +8,7 @@ use fabro_auth::{CliAgentKind, CredentialResolver, CredentialUsage, ResolvedCred use fabro_graphviz::graph::Node; use fabro_llm::types::TokenCounts; use fabro_model::Provider; +use fabro_types::CommandTermination; use tokio::time::sleep; use super::super::agent::{CodergenBackend, CodergenResult}; @@ -87,7 +88,7 @@ async fn ensure_cli( .await .map_err(|e| Error::handler(format!("Failed to check {cli_name} version: {e}")))?; - if version_check.exit_code == 0 { + if version_check.is_success() { let duration_ms = u64::try_from(start.elapsed().as_millis()).unwrap_or(u64::MAX); emitter.emit(&Event::CliEnsureCompleted { cli_name: cli_name.to_string(), @@ -113,7 +114,7 @@ async fn ensure_cli( .map_err(|e| Error::handler(format!("Failed to install {cli_name}: {e}")))?; let node_installed = true; - if install_result.exit_code != 0 { + if !install_result.is_success() { let duration_ms = u64::try_from(start.elapsed().as_millis()).unwrap_or(u64::MAX); let output = if install_result.stderr.is_empty() { &install_result.stdout @@ -130,7 +131,7 @@ async fn ensure_cli( .collect(); let error_msg = format!( "{cli_name} install exited with code {}: {detail}", - install_result.exit_code + install_result.display_exit_code() ); emitter.emit(&Event::CliEnsureFailed { cli_name: cli_name.to_string(), @@ -444,7 +445,7 @@ impl AgentCliBackend { let mut files: Vec = Vec::new(); if let Ok(result) = diff_result { - if result.exit_code == 0 { + if result.is_success() { files.extend( result .stdout @@ -456,7 +457,7 @@ impl AgentCliBackend { } if let Ok(result) = untracked_result { - if result.exit_code == 0 { + if result.is_success() { files.extend( result .stdout @@ -551,9 +552,9 @@ impl CodergenBackend for AgentCliBackend { .exec_command(login_cmd, 30_000, None, None, None) .await .map_err(|e| Error::handler(format!("codex login failed: {e}")))?; - if login_result.exit_code != 0 { + if !login_result.is_success() { tracing::warn!( - exit_code = login_result.exit_code, + exit_code = login_result.display_exit_code(), "codex login --with-api-key failed: {}", login_result.stderr ); @@ -648,8 +649,8 @@ impl CodergenBackend for AgentCliBackend { let result = ExecResult { stdout: stdout_result.stdout, stderr: stderr_result.stdout, - exit_code, - timed_out: false, + exit_code: Some(exit_code), + termination: CommandTermination::Exited, duration_ms, }; emitter.emit_scoped( @@ -657,7 +658,7 @@ impl CodergenBackend for AgentCliBackend { node_id: node.id.clone(), stdout: result.stdout.clone(), stderr: result.stderr.clone(), - exit_code: result.exit_code, + exit_code: result.exit_code.unwrap_or(-1), duration_ms: result.duration_ms, }, &stage_scope, @@ -668,7 +669,7 @@ impl CodergenBackend for AgentCliBackend { .exec_command(&format!("rm -f {tmp_prefix}_*"), 30_000, None, None, None) .await; - if result.exit_code != 0 { + if !result.is_success() { let tail = |s: &str, n: usize| -> String { s.chars() .rev() @@ -688,7 +689,7 @@ impl CodergenBackend for AgentCliBackend { }; return Err(Error::handler(format!( "CLI command exited with code {}: {detail}", - result.exit_code, + result.display_exit_code(), ))); } @@ -714,7 +715,7 @@ impl CodergenBackend for AgentCliBackend { let cmd = format!("ls -t {} | head -1", quoted_files.join(" ")); if let Ok(result) = sandbox.exec_command(&cmd, 5_000, None, None, None).await { let trimmed = result.stdout.trim().to_string(); - if result.exit_code == 0 && !trimmed.is_empty() { + if result.is_success() && !trimmed.is_empty() { Some(trimmed) } else { None @@ -975,20 +976,20 @@ mod tests { fn ok_result() -> ExecResult { ExecResult { - exit_code: 0, + exit_code: Some(0), + termination: CommandTermination::Exited, stdout: String::new(), stderr: String::new(), - timed_out: false, duration_ms: 10, } } fn fail_result(code: i32) -> ExecResult { ExecResult { - exit_code: code, + exit_code: Some(code), + termination: CommandTermination::Exited, stdout: String::new(), stderr: "error".to_string(), - timed_out: false, duration_ms: 10, } } diff --git a/lib/crates/fabro-workflow/src/handler/parallel.rs b/lib/crates/fabro-workflow/src/handler/parallel.rs index 8b2077efd..48b0aa634 100644 --- a/lib/crates/fabro-workflow/src/handler/parallel.rs +++ b/lib/crates/fabro-workflow/src/handler/parallel.rs @@ -392,7 +392,10 @@ impl Handler for ParallelHandler { .sandbox .exec_command(&add_cmd, 30_000, None, None, None) .await; - if add_result.as_ref().is_ok_and(|r| r.exit_code == 0) { + if add_result + .as_ref() + .is_ok_and(fabro_sandbox::ExecResult::is_success) + { let msg = format!("fabro({rid}): {nid} ({status_str})"); let commit_cmd = format!( "{git_r} -c 'user.name={name}' -c 'user.email={email}' commit --allow-empty -m '{msg}'", @@ -410,7 +413,7 @@ impl Handler for ParallelHandler { .exec_command(&sha_cmd, 10_000, None, None, None) .await; match sha_result { - Ok(r) if r.exit_code == 0 => { + Ok(r) if r.is_success() => { let sha = r.stdout.trim().to_string(); parent_run.emitter.emit_scoped( &Event::GitCommit { diff --git a/lib/crates/fabro-workflow/src/pipeline/initialize.rs b/lib/crates/fabro-workflow/src/pipeline/initialize.rs index 69bead9e8..8cce99cb8 100644 --- a/lib/crates/fabro-workflow/src/pipeline/initialize.rs +++ b/lib/crates/fabro-workflow/src/pipeline/initialize.rs @@ -61,7 +61,7 @@ async fn resolve_worktree_base_sha( ) .await .map_err(|err| Error::engine(format!("git rev-parse HEAD failed: {err}")))?; - if result.exit_code != 0 { + if !result.is_success() { let output = result.stderr.trim(); let output = if output.is_empty() { result.stdout.trim() @@ -73,7 +73,8 @@ async fn resolve_worktree_base_sha( } return Err(Error::engine(format!( "git rev-parse HEAD failed (exit {}): {}", - result.exit_code, output + result.display_exit_code(), + output ))); } @@ -672,22 +673,24 @@ pub async fn initialize( token.cancel(); } let duration_ms = crate::millis_u64(cmd_start.elapsed()); - if result.exit_code != 0 { + if !result.is_success() { + let exit_code = result.display_exit_code(); options.emitter.emit(&Event::SetupFailed { command: command.clone(), index, - exit_code: result.exit_code, + exit_code, stderr: result.stderr.clone(), }); return Err(Error::engine(format!( "Setup command failed (exit code {}): {command}\n{}", - result.exit_code, result.stderr, + exit_code, result.stderr, ))); } + let exit_code = result.exit_code.unwrap_or(0); options.emitter.emit(&Event::SetupCommandCompleted { command: command.clone(), index, - exit_code: result.exit_code, + exit_code, duration_ms, }); } diff --git a/lib/crates/fabro-workflow/src/run_dump.rs b/lib/crates/fabro-workflow/src/run_dump.rs index ccf4ceefc..f67591530 100644 --- a/lib/crates/fabro-workflow/src/run_dump.rs +++ b/lib/crates/fabro-workflow/src/run_dump.rs @@ -542,6 +542,7 @@ mod tests { stderr_bytes: None, streams_separated: None, live_streaming: None, + termination: None, }); let dump = RunDump::from_projection(&projection); diff --git a/lib/crates/fabro-workflow/src/sandbox_git.rs b/lib/crates/fabro-workflow/src/sandbox_git.rs index 09aa4a930..5b6c1e3b2 100644 --- a/lib/crates/fabro-workflow/src/sandbox_git.rs +++ b/lib/crates/fabro-workflow/src/sandbox_git.rs @@ -25,16 +25,19 @@ pub const GIT_REMOTE: &str = "git -c maintenance.auto=0 -c gc.auto=0 -c commit.gpgsign=false -c tag.gpgsign=false"; fn exec_err(label: &str, r: &fabro_sandbox::ExecResult) -> String { - if r.timed_out { + if r.is_timed_out() { return format!("{label} timed out after {}ms", r.duration_ms); } + if r.is_cancelled() { + return format!("{label} cancelled after {}ms", r.duration_ms); + } let detail = format!("{}{}", r.stdout, r.stderr); let detail = detail.trim(); if detail.is_empty() { - format!("{label} killed (exit {}, no output)", r.exit_code) + format!("{label} killed (exit {}, no output)", r.display_exit_code()) } else { - format!("{label} failed (exit {}): {detail}", r.exit_code) + format!("{label} failed (exit {}): {detail}", r.display_exit_code()) } } @@ -68,7 +71,7 @@ pub async fn git_checkpoint( .exec_command(&add_cmd, 30_000, None, None, None) .await; match &add_result { - Ok(r) if r.exit_code == 0 => {} + Ok(r) if r.is_success() => {} Ok(r) => return Err(exec_err("git add", r)), Err(e) => return Err(format!("git add failed: {e}")), } @@ -109,7 +112,7 @@ pub async fn git_checkpoint( .exec_command(&commit_cmd, 30_000, None, None, None) .await; match &commit_result { - Ok(r) if r.exit_code == 0 => {} + Ok(r) if r.is_success() => {} Ok(r) => return Err(exec_err("git commit", r)), Err(e) => return Err(format!("git commit failed: {e}")), } @@ -119,7 +122,7 @@ pub async fn git_checkpoint( .exec_command(&sha_cmd, 10_000, None, None, None) .await; match sha_result { - Ok(r) if r.exit_code == 0 => Ok(r.stdout.trim().to_string()), + Ok(r) if r.is_success() => Ok(r.stdout.trim().to_string()), Ok(r) => Err(exec_err("git rev-parse HEAD", &r)), Err(e) => Err(format!("git rev-parse HEAD failed: {e}")), } @@ -188,7 +191,7 @@ pub(crate) async fn git_diff_with_timeout( .exec_command(&cmd, timeout_ms, None, None, None) .await { - Ok(r) if r.exit_code == 0 => Ok(r.stdout), + Ok(r) if r.is_success() => Ok(r.stdout), Ok(r) => Err(exec_err("git diff", &r)), Err(e) => Err(e.display_with_causes()), } @@ -199,7 +202,7 @@ pub async fn git_create_branch_at(sandbox: &dyn Sandbox, name: &str, sha: &str) let cmd = format!("{GIT_REMOTE} branch --force {name} {sha}"); matches!( sandbox.exec_command(&cmd, 30_000, None, None, None).await, - Ok(r) if r.exit_code == 0 + Ok(r) if r.is_success() ) } @@ -208,7 +211,7 @@ pub async fn git_add_worktree(sandbox: &dyn Sandbox, path: &str, branch: &str) - let cmd = format!("{GIT_REMOTE} worktree add {path} {branch}"); matches!( sandbox.exec_command(&cmd, 30_000, None, None, None).await, - Ok(r) if r.exit_code == 0 + Ok(r) if r.is_success() ) } @@ -217,7 +220,7 @@ pub async fn git_remove_worktree(sandbox: &dyn Sandbox, path: &str) -> bool { let cmd = format!("{GIT_REMOTE} worktree remove --force {path}"); matches!( sandbox.exec_command(&cmd, 30_000, None, None, None).await, - Ok(r) if r.exit_code == 0 + Ok(r) if r.is_success() ) } @@ -226,7 +229,7 @@ pub async fn git_merge_ff_only(sandbox: &dyn Sandbox, sha: &str) -> bool { let cmd = format!("{GIT_REMOTE} merge --ff-only {sha}"); matches!( sandbox.exec_command(&cmd, 30_000, None, None, None).await, - Ok(r) if r.exit_code == 0 + Ok(r) if r.is_success() ) } @@ -377,12 +380,12 @@ pub async fn list_changed_files_raw( message: e.display_with_causes(), })?; - if res.timed_out { + if res.is_timed_out() { return Err(DiffError::Transient { message: "git diff --raw timed out".to_string(), }); } - if res.exit_code != 0 { + if !res.is_success() { // An unknown-object / bad-revision error is permanent; everything // else we treat as transient so the server can retry safely. let stderr = res.stderr.trim().to_string(); @@ -559,12 +562,12 @@ pub async fn list_diff_numstat( message: e.display_with_causes(), })?; - if res.timed_out { + if res.is_timed_out() { return Err(DiffError::Transient { message: "git diff --numstat timed out".to_string(), }); } - if res.exit_code != 0 { + if !res.is_success() { let stderr = res.stderr.trim().to_string(); if is_permanent_git_error(&stderr) { return Err(DiffError::Permanent { message: stderr }); @@ -650,12 +653,12 @@ pub async fn stream_blob_metadata( message: e.display_with_causes(), })?; - if res.timed_out { + if res.is_timed_out() { return Err(DiffError::Transient { message: "git cat-file --batch-check timed out".to_string(), }); } - if res.exit_code != 0 { + if !res.is_success() { return Err(DiffError::Transient { message: format!("git cat-file --batch-check failed: {}", res.stderr.trim()), }); @@ -717,12 +720,12 @@ pub async fn stream_blobs( message: e.display_with_causes(), })?; - if res.timed_out { + if res.is_timed_out() { return Err(DiffError::Transient { message: "git cat-file --batch timed out".to_string(), }); } - if res.exit_code != 0 { + if !res.is_success() { return Err(DiffError::Transient { message: format!("git cat-file --batch failed: {}", res.stderr.trim()), }); @@ -806,6 +809,7 @@ mod tests { use async_trait::async_trait; use fabro_agent::{DirEntry, ExecResult, GrepOptions}; + use fabro_types::CommandTermination; use tokio_util::sync::CancellationToken; use super::*; @@ -1064,8 +1068,8 @@ mod tests { ExecResult { stdout: String::new(), stderr: String::new(), - exit_code: 0, - timed_out: false, + exit_code: Some(0), + termination: CommandTermination::Exited, duration_ms: 1, } } @@ -1074,18 +1078,18 @@ mod tests { ExecResult { stdout: String::new(), stderr: String::new(), - exit_code: -1, - timed_out: true, + exit_code: None, + termination: CommandTermination::TimedOut, duration_ms, } } fn exec_failed(exit_code: i32, stdout: &str, stderr: &str) -> ExecResult { ExecResult { - stdout: stdout.to_string(), - stderr: stderr.to_string(), - exit_code, - timed_out: false, + stdout: stdout.to_string(), + stderr: stderr.to_string(), + exit_code: Some(exit_code), + termination: CommandTermination::Exited, duration_ms: 1, } } diff --git a/lib/crates/fabro-workflow/src/sandbox_metadata.rs b/lib/crates/fabro-workflow/src/sandbox_metadata.rs index f3b3c032b..1d29051b8 100644 --- a/lib/crates/fabro-workflow/src/sandbox_metadata.rs +++ b/lib/crates/fabro-workflow/src/sandbox_metadata.rs @@ -358,7 +358,7 @@ async fn exec_stdout( .exec_command(command, 30_000, None, env, None) .await .map_err(|err| SandboxMetadataError::Sandbox(err.display_with_causes()))?; - if result.exit_code == 0 { + if result.is_success() { Ok(result.stdout.trim().to_string()) } else { Err(SandboxMetadataError::Git(exec_err(command, &result))) @@ -374,15 +374,21 @@ async fn exec_ok( } fn exec_err(label: &str, result: &fabro_sandbox::ExecResult) -> String { - if result.timed_out { + if result.is_timed_out() { return format!("{label} timed out after {}ms", result.duration_ms); } + if result.is_cancelled() { + return format!("{label} cancelled after {}ms", result.duration_ms); + } let detail = format!("{}{}", result.stdout, result.stderr); let detail = detail.trim(); if detail.is_empty() { - format!("{label} failed with exit {}", result.exit_code) + format!("{label} failed with exit {}", result.display_exit_code()) } else { - format!("{label} failed with exit {}: {detail}", result.exit_code) + format!( + "{label} failed with exit {}: {detail}", + result.display_exit_code() + ) } } diff --git a/lib/crates/fabro-workflow/tests/it/daytona_integration.rs b/lib/crates/fabro-workflow/tests/it/daytona_integration.rs index 11e3abadc..bd95533f7 100644 --- a/lib/crates/fabro-workflow/tests/it/daytona_integration.rs +++ b/lib/crates/fabro-workflow/tests/it/daytona_integration.rs @@ -221,7 +221,7 @@ async fn daytona_exec_command() { .exec_command("echo hello", 30_000, None, None, None) .await .unwrap(); - assert_eq!(result.exit_code, 0); + assert_eq!(result.exit_code, Some(0)); assert!(result.stdout.contains("hello")); env.cleanup().await.unwrap(); @@ -237,7 +237,7 @@ async fn daytona_exec_command_with_pipe() { .exec_command("echo hello world | wc -w", 30_000, None, None, None) .await .unwrap(); - assert_eq!(result.exit_code, 0); + assert_eq!(result.exit_code, Some(0)); assert!(result.stdout.trim().contains('2')); env.cleanup().await.unwrap(); @@ -264,8 +264,8 @@ async fn daytona_exec_command_cancelled() { .await .unwrap(); - assert_eq!(result.exit_code, -1); - assert!(result.timed_out); + assert_eq!(result.exit_code, None); + assert!(result.is_cancelled()); assert_eq!(result.stderr, "Command cancelled"); env.cleanup().await.unwrap(); @@ -300,7 +300,7 @@ async fn daytona_exec_command_local_timeout() { duration < std::time::Duration::from_secs(3), "Command stalled for longer than the local timeout mechanism" ); - assert!(result.exit_code != 0); + assert!(!result.is_success()); env.cleanup().await.unwrap(); } @@ -345,7 +345,7 @@ async fn daytona_full_lifecycle() { .exec_command("pwd", 10_000, None, None, None) .await .unwrap(); - assert_eq!(result.exit_code, 0); + assert_eq!(result.exit_code, Some(0)); // List directory let entries = env.list_directory(".", None).await.unwrap(); @@ -384,7 +384,7 @@ async fn daytona_snapshot_sandbox() { .exec_command("rg --version", 10_000, None, None, None) .await .unwrap(); - assert_eq!(result.exit_code, 0); + assert_eq!(result.exit_code, Some(0)); assert!(result.stdout.contains("ripgrep")); env.cleanup().await.unwrap(); @@ -592,7 +592,8 @@ async fn setup_daytona_git(sandbox: &dyn Sandbox) -> (RunId, String, String) { .await .expect("git rev-parse HEAD should succeed"); assert_eq!( - sha_result.exit_code, 0, + sha_result.exit_code, + Some(0), "git rev-parse HEAD failed: {}", sha_result.stderr ); @@ -607,9 +608,12 @@ async fn setup_daytona_git(sandbox: &dyn Sandbox) -> (RunId, String, String) { .await .expect("git checkout should succeed"); assert_eq!( - checkout_result.exit_code, 0, - "git checkout -b failed (exit {}): stdout={} stderr={}", - checkout_result.exit_code, checkout_result.stdout, checkout_result.stderr + checkout_result.exit_code, + Some(0), + "git checkout -b failed (exit {:?}): stdout={} stderr={}", + checkout_result.exit_code, + checkout_result.stdout, + checkout_result.stderr ); (run_id, base_sha, branch_name) @@ -625,7 +629,7 @@ async fn daytona_git_checkpoint_remote_emits_events() { let git_check = env .exec_command("git --version", 10_000, None, None, None) .await; - if git_check.as_ref().map_or(true, |r| r.exit_code != 0) { + if git_check.as_ref().map_or(true, |r| !r.is_success()) { let install = env .exec_command( "apt-get update -qq && apt-get install -y -qq git >/dev/null 2>&1", @@ -637,7 +641,8 @@ async fn daytona_git_checkpoint_remote_emits_events() { .await .expect("apt-get install git should not error"); assert_eq!( - install.exit_code, 0, + install.exit_code, + Some(0), "git install failed: {}", install.stderr ); @@ -776,7 +781,7 @@ async fn daytona_parallel_git_branching_e2e() { let git_check = env .exec_command("git --version", 10_000, None, None, None) .await; - if git_check.as_ref().map_or(true, |r| r.exit_code != 0) { + if git_check.as_ref().map_or(true, |r| !r.is_success()) { let install = env .exec_command( "apt-get update -qq && apt-get install -y -qq git >/dev/null 2>&1", @@ -788,7 +793,8 @@ async fn daytona_parallel_git_branching_e2e() { .await .expect("apt-get install git should not error"); assert_eq!( - install.exit_code, 0, + install.exit_code, + Some(0), "git install failed: {}", install.stderr ); @@ -947,7 +953,11 @@ async fn daytona_parallel_git_branching_e2e() { .exec_command("cat branch_a.txt", 10_000, None, None, None) .await .expect("cat should succeed"); - assert_eq!(winner_check.exit_code, 0, "winner's file should exist"); + assert_eq!( + winner_check.exit_code, + Some(0), + "winner's file should exist" + ); assert!( winner_check.stdout.contains("branch_a"), "winner's file should have correct content, got: {}", @@ -1021,7 +1031,7 @@ async fn run_daytona_cli_test(provider: Provider, model: &str, install_command: None, ) .await; - if prereq_check.as_ref().map_or(true, |r| r.exit_code != 0) { + if prereq_check.as_ref().map_or(true, |r| !r.is_success()) { let prereq = env .exec_command( "apt-get update -qq && apt-get install -y -qq bash curl ca-certificates gnupg >/dev/null 2>&1 \ @@ -1035,7 +1045,8 @@ async fn run_daytona_cli_test(provider: Provider, model: &str, install_command: .await .expect("prerequisite install should not error"); assert_eq!( - prereq.exit_code, 0, + prereq.exit_code, + Some(0), "prerequisite install failed: {}", prereq.stderr ); @@ -1047,9 +1058,11 @@ async fn run_daytona_cli_test(provider: Provider, model: &str, install_command: .await .expect("install command should not error"); assert_eq!( - install_result.exit_code, 0, - "install command failed (exit {}): {}", - install_result.exit_code, install_result.stdout + install_result.exit_code, + Some(0), + "install command failed (exit {:?}): {}", + install_result.exit_code, + install_result.stdout ); let backend = AgentCliBackend::new_from_env(model.to_string(), provider); @@ -1133,7 +1146,7 @@ async fn daytona_git_checkpoint_with_shadow_branch() { let git_check = env .exec_command("git --version", 10_000, None, None, None) .await; - if git_check.as_ref().map_or(true, |r| r.exit_code != 0) { + if git_check.as_ref().map_or(true, |r| !r.is_success()) { let install = env .exec_command( "apt-get update -qq && apt-get install -y -qq git >/dev/null 2>&1", @@ -1145,7 +1158,8 @@ async fn daytona_git_checkpoint_with_shadow_branch() { .await .expect("apt-get install git should not error"); assert_eq!( - install.exit_code, 0, + install.exit_code, + Some(0), "git install failed: {}", install.stderr ); @@ -1228,7 +1242,7 @@ async fn daytona_git_checkpoint_with_shadow_branch() { ) .await .expect("git show should succeed"); - assert_eq!(run_json.exit_code, 0, "{}", run_json.stderr); + assert_eq!(run_json.exit_code, Some(0), "{}", run_json.stderr); let projection: fabro_store::RunProjection = serde_json::from_slice(run_json.stdout.as_bytes()).expect("run.json should parse"); let checkpoint = projection @@ -1248,7 +1262,7 @@ async fn daytona_git_checkpoint_with_shadow_branch() { .exec_command("git log --format=%B -1", 10_000, None, None, None) .await .expect("git log should succeed"); - assert_eq!(log_result.exit_code, 0); + assert_eq!(log_result.exit_code, Some(0)); let commit_msg = log_result.stdout.trim().to_string(); assert!( commit_msg.contains("Fabro-Checkpoint:"), @@ -1440,7 +1454,11 @@ async fn daytona_clone_private_repo_with_github_app_iat() { .exec_command("test -f CLAUDE.md && echo EXISTS", 10_000, None, None, None) .await .unwrap(); - assert_eq!(result.exit_code, 0, "CLAUDE.md should exist after clone"); + assert_eq!( + result.exit_code, + Some(0), + "CLAUDE.md should exist after clone" + ); assert!( result.stdout.contains("EXISTS"), "clone should have populated the workspace" @@ -1450,7 +1468,7 @@ async fn daytona_clone_private_repo_with_github_app_iat() { let git_check = env .exec_command("git --version", 10_000, None, None, None) .await; - if git_check.as_ref().map_or(true, |r| r.exit_code != 0) { + if git_check.as_ref().map_or(true, |r| !r.is_success()) { let install = env .exec_command( "apt-get update -qq && apt-get install -y -qq git >/dev/null 2>&1", @@ -1462,7 +1480,8 @@ async fn daytona_clone_private_repo_with_github_app_iat() { .await .expect("apt-get install git should not error"); assert_eq!( - install.exit_code, 0, + install.exit_code, + Some(0), "git install failed: {}", install.stderr ); @@ -1473,7 +1492,7 @@ async fn daytona_clone_private_repo_with_github_app_iat() { .exec_command("git remote get-url origin", 10_000, None, None, None) .await .unwrap(); - assert_eq!(result.exit_code, 0); + assert_eq!(result.exit_code, Some(0)); assert!( result.stdout.contains("fabro-sh/fabro"), "origin should point to fabro-sh/fabro, got: {}", @@ -1551,7 +1570,7 @@ async fn daytona_git_push_run_branch_to_origin() { let git_check = env .exec_command("git --version", 10_000, None, None, None) .await; - if git_check.as_ref().map_or(true, |r| r.exit_code != 0) { + if git_check.as_ref().map_or(true, |r| !r.is_success()) { let install = env .exec_command( "apt-get update -qq && apt-get install -y -qq git >/dev/null 2>&1", @@ -1563,7 +1582,8 @@ async fn daytona_git_push_run_branch_to_origin() { .await .expect("apt-get install git should not error"); assert_eq!( - install.exit_code, 0, + install.exit_code, + Some(0), "git install failed: {}", install.stderr ); @@ -1639,7 +1659,8 @@ async fn daytona_git_push_run_branch_to_origin() { .await .expect("git ls-remote should succeed"); assert_eq!( - ls_result.exit_code, 0, + ls_result.exit_code, + Some(0), "git ls-remote failed: {}", ls_result.stdout ); @@ -1655,7 +1676,7 @@ async fn daytona_git_push_run_branch_to_origin() { .exec_command(&delete_cmd, 30_000, None, None, None) .await; if let Ok(r) = &delete_result { - if r.exit_code != 0 { + if !r.is_success() { eprintln!( "Warning: failed to delete remote branch {branch_name}: {}", r.stdout @@ -1708,7 +1729,7 @@ async fn daytona_toolbox_idle_diagnostic() { match &result { Ok(r) => { eprintln!( - "[t=+{sleep_secs}s] OK exit_code={} stdout={}", + "[t=+{sleep_secs}s] OK exit_code={:?} stdout={}", r.exit_code, r.stdout.trim() ); @@ -1945,11 +1966,11 @@ async fn daytona_computer_use_browser_screenshot() { .await .unwrap(); eprintln!( - "Browser install exit_code={}, last_line={}", + "Browser install exit_code={:?}, last_line={}", install_result.exit_code, install_result.stdout.lines().last().unwrap_or("") ); - assert_eq!(install_result.exit_code, 0, "Chromium install failed"); + assert_eq!(install_result.exit_code, Some(0), "Chromium install failed"); } let browser_bin = env @@ -1989,7 +2010,7 @@ async fn daytona_computer_use_browser_screenshot() { .exec_command(&launch_cmd, 30_000, None, None, None) .await .unwrap(); - eprintln!("Browser launch exit_code={}", launch_result.exit_code); + eprintln!("Browser launch exit_code={:?}", launch_result.exit_code); // 5. Wait for the page to load, then check if browser is running tokio::time::sleep(std::time::Duration::from_secs(8)).await; @@ -2085,7 +2106,7 @@ async fn daytona_playwright_mcp_sandbox_transport() { .await .unwrap(); eprintln!( - "Install exit_code={}, last_lines:\n{}", + "Install exit_code={:?}, last_lines:\n{}", install.exit_code, install .stdout @@ -2098,7 +2119,7 @@ async fn daytona_playwright_mcp_sandbox_transport() { .collect::>() .join("\n") ); - assert_eq!(install.exit_code, 0, "Playwright install failed"); + assert_eq!(install.exit_code, Some(0), "Playwright install failed"); // 2. Start the Playwright MCP server via the sandbox transport resolution path let mcp_port = 3100u16; diff --git a/lib/crates/fabro-workflow/tests/it/integration.rs b/lib/crates/fabro-workflow/tests/it/integration.rs index aa7bb7513..edfce5503 100644 --- a/lib/crates/fabro-workflow/tests/it/integration.rs +++ b/lib/crates/fabro-workflow/tests/it/integration.rs @@ -32,7 +32,7 @@ use fabro_interview::{ }; use fabro_llm::provider::Provider; use fabro_store::{ArtifactStore, Database}; -use fabro_types::{RunEvent, RunId, StageId, WorkflowSettings, parse_blob_ref}; +use fabro_types::{CommandTermination, RunEvent, RunId, StageId, WorkflowSettings, parse_blob_ref}; use fabro_validate::{Severity, validate, validate_or_raise}; use fabro_workflow::context::Context; use fabro_workflow::error::{Error, FailureSignatureExt}; @@ -9554,8 +9554,9 @@ impl fabro_agent::Sandbox for CliTestEnv { return Ok(fabro_agent::ExecResult { stdout, stderr: String::new(), - exit_code: 0, - timed_out: false, + exit_code: Some(0), + + termination: CommandTermination::Exited, duration_ms: 5, }); } @@ -9563,10 +9564,11 @@ impl fabro_agent::Sandbox for CliTestEnv { // Background launch: return PID if command.contains("echo $!") { return Ok(fabro_agent::ExecResult { - stdout: "12345\n".into(), - stderr: String::new(), - exit_code: 0, - timed_out: false, + stdout: "12345\n".into(), + stderr: String::new(), + exit_code: Some(0), + + termination: CommandTermination::Exited, duration_ms: 1, }); } @@ -9574,10 +9576,11 @@ impl fabro_agent::Sandbox for CliTestEnv { // Poll for completion: return exit code 0 immediately if command.contains("exit_code") && command.contains("echo running") { return Ok(fabro_agent::ExecResult { - stdout: "0\n".into(), - stderr: String::new(), - exit_code: 0, - timed_out: false, + stdout: "0\n".into(), + stderr: String::new(), + exit_code: Some(0), + + termination: CommandTermination::Exited, duration_ms: 1, }); } @@ -9585,10 +9588,11 @@ impl fabro_agent::Sandbox for CliTestEnv { // Read stdout file if command.starts_with("cat") && command.contains("stdout.log") { return Ok(fabro_agent::ExecResult { - stdout: self.cli_stdout.clone(), - stderr: String::new(), - exit_code: 0, - timed_out: false, + stdout: self.cli_stdout.clone(), + stderr: String::new(), + exit_code: Some(0), + + termination: CommandTermination::Exited, duration_ms: 1, }); } @@ -9596,10 +9600,11 @@ impl fabro_agent::Sandbox for CliTestEnv { // Read stderr file if command.starts_with("cat") && command.contains("stderr.log") { return Ok(fabro_agent::ExecResult { - stdout: String::new(), - stderr: String::new(), - exit_code: 0, - timed_out: false, + stdout: String::new(), + stderr: String::new(), + exit_code: Some(0), + + termination: CommandTermination::Exited, duration_ms: 1, }); } @@ -9607,20 +9612,22 @@ impl fabro_agent::Sandbox for CliTestEnv { // Cleanup temp files if command.starts_with("rm -f") { return Ok(fabro_agent::ExecResult { - stdout: String::new(), - stderr: String::new(), - exit_code: 0, - timed_out: false, + stdout: String::new(), + stderr: String::new(), + exit_code: Some(0), + + termination: CommandTermination::Exited, duration_ms: 1, }); } // Fallback Ok(fabro_agent::ExecResult { - stdout: self.cli_stdout.clone(), - stderr: String::new(), - exit_code: 0, - timed_out: false, + stdout: self.cli_stdout.clone(), + stderr: String::new(), + exit_code: Some(0), + + termination: CommandTermination::Exited, duration_ms: 100, }) } @@ -9868,48 +9875,53 @@ async fn cli_backend_run_fails_on_nonzero_exit() { ) -> fabro_sandbox::Result { if command.starts_with("git") { return Ok(fabro_agent::ExecResult { - stdout: String::new(), - stderr: String::new(), - exit_code: 0, - timed_out: false, + stdout: String::new(), + stderr: String::new(), + exit_code: Some(0), + + termination: CommandTermination::Exited, duration_ms: 0, }); } // Background launch: return PID if command.contains("echo $!") { return Ok(fabro_agent::ExecResult { - stdout: "12345\n".into(), - stderr: String::new(), - exit_code: 0, - timed_out: false, + stdout: "12345\n".into(), + stderr: String::new(), + exit_code: Some(0), + + termination: CommandTermination::Exited, duration_ms: 0, }); } // Poll: return non-zero exit code if command.contains("exit_code") && command.contains("echo running") { return Ok(fabro_agent::ExecResult { - stdout: "127\n".into(), - stderr: String::new(), - exit_code: 0, - timed_out: false, + stdout: "127\n".into(), + stderr: String::new(), + exit_code: Some(0), + + termination: CommandTermination::Exited, duration_ms: 0, }); } // Read stderr file if command.starts_with("cat") && command.contains("stderr.log") { return Ok(fabro_agent::ExecResult { - stdout: "command not found: claude".into(), - stderr: String::new(), - exit_code: 0, - timed_out: false, + stdout: "command not found: claude".into(), + stderr: String::new(), + exit_code: Some(0), + + termination: CommandTermination::Exited, duration_ms: 0, }); } Ok(fabro_agent::ExecResult { - stdout: String::new(), - stderr: String::new(), - exit_code: 0, - timed_out: false, + stdout: String::new(), + stderr: String::new(), + exit_code: Some(0), + + termination: CommandTermination::Exited, duration_ms: 0, }) } diff --git a/lib/packages/fabro-api-client/src/.openapi-generator/FILES b/lib/packages/fabro-api-client/src/.openapi-generator/FILES index 7bd14c09c..b84e0dcea 100644 --- a/lib/packages/fabro-api-client/src/.openapi-generator/FILES +++ b/lib/packages/fabro-api-client/src/.openapi-generator/FILES @@ -45,6 +45,7 @@ models/close-run-pull-request-response.ts models/code-location.ts models/command-log-response.ts models/command-output-stream.ts +models/command-termination.ts models/completion-content-part.ts models/completion-message.ts models/completion-response.ts diff --git a/lib/packages/fabro-api-client/src/api/models-api.ts b/lib/packages/fabro-api-client/src/api/models-api.ts index 707e1eef8..719edcb41 100644 --- a/lib/packages/fabro-api-client/src/api/models-api.ts +++ b/lib/packages/fabro-api-client/src/api/models-api.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -242,3 +242,4 @@ export class ModelsApi extends BaseAPI { return ModelsApiFp(this.configuration).testModel(id, mode, options).then((request) => request(this.axios, this.basePath)); } } + diff --git a/lib/packages/fabro-api-client/src/api/run-internals-api.ts b/lib/packages/fabro-api-client/src/api/run-internals-api.ts index b8325989a..6bcf36c3a 100644 --- a/lib/packages/fabro-api-client/src/api/run-internals-api.ts +++ b/lib/packages/fabro-api-client/src/api/run-internals-api.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -60,7 +60,7 @@ export const RunInternalsApiAxiosParamCreator = function (configuration?: Config * Appends a validated event to the run event log. Intended for trusted internal callers. * @summary Append Run Event * @param {string} id Unique run identifier (ULID). - * @param {RunEvent} runEvent + * @param {RunEvent} runEvent * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -574,11 +574,11 @@ export const RunInternalsApiAxiosParamCreator = function (configuration?: Config }; }, /** - * Uploads one or more artifacts for a stage. Intended for trusted internal callers. The server accepts both: - `application/octet-stream` for single-file uploads with the `filename` query parameter - strict manifest-first `multipart/form-data` uploads documented by `ArtifactBatchUploadManifest` The generated Rust client currently exposes the octet-stream variant because the OpenAPI code generator in this repo does not support multiple request media types on one operation. + * Uploads one or more artifacts for a stage. Intended for trusted internal callers. The server accepts both: - `application/octet-stream` for single-file uploads with the `filename` query parameter - strict manifest-first `multipart/form-data` uploads documented by `ArtifactBatchUploadManifest` The generated Rust client currently exposes the octet-stream variant because the OpenAPI code generator in this repo does not support multiple request media types on one operation. * @summary Put Stage Artifact * @param {string} id Unique run identifier (ULID). * @param {string} stageId Identifier of a stage within a run\'s workflow graph, serialized as `node_id@visit`. - * @param {File} body + * @param {File} body * @param {string} [filename] Relative artifact path for `application/octet-stream` uploads. Ignored for multipart uploads. * @param {*} [options] Override http request option. * @throws {RequiredError} @@ -755,7 +755,7 @@ export const RunInternalsApiAxiosParamCreator = function (configuration?: Config * Writes an opaque binary blob and returns its content-addressed blob identifier. * @summary Write Run Blob * @param {string} id Unique run identifier (ULID). - * @param {File} body + * @param {File} body * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -809,7 +809,7 @@ export const RunInternalsApiFp = function(configuration?: Configuration) { * Appends a validated event to the run event log. Intended for trusted internal callers. * @summary Append Run Event * @param {string} id Unique run identifier (ULID). - * @param {RunEvent} runEvent + * @param {RunEvent} runEvent * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -965,11 +965,11 @@ export const RunInternalsApiFp = function(configuration?: Configuration) { return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); }, /** - * Uploads one or more artifacts for a stage. Intended for trusted internal callers. The server accepts both: - `application/octet-stream` for single-file uploads with the `filename` query parameter - strict manifest-first `multipart/form-data` uploads documented by `ArtifactBatchUploadManifest` The generated Rust client currently exposes the octet-stream variant because the OpenAPI code generator in this repo does not support multiple request media types on one operation. + * Uploads one or more artifacts for a stage. Intended for trusted internal callers. The server accepts both: - `application/octet-stream` for single-file uploads with the `filename` query parameter - strict manifest-first `multipart/form-data` uploads documented by `ArtifactBatchUploadManifest` The generated Rust client currently exposes the octet-stream variant because the OpenAPI code generator in this repo does not support multiple request media types on one operation. * @summary Put Stage Artifact * @param {string} id Unique run identifier (ULID). * @param {string} stageId Identifier of a stage within a run\'s workflow graph, serialized as `node_id@visit`. - * @param {File} body + * @param {File} body * @param {string} [filename] Relative artifact path for `application/octet-stream` uploads. Ignored for multipart uploads. * @param {*} [options] Override http request option. * @throws {RequiredError} @@ -1024,7 +1024,7 @@ export const RunInternalsApiFp = function(configuration?: Configuration) { * Writes an opaque binary blob and returns its content-addressed blob identifier. * @summary Write Run Blob * @param {string} id Unique run identifier (ULID). - * @param {File} body + * @param {File} body * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -1047,7 +1047,7 @@ export const RunInternalsApiFactory = function (configuration?: Configuration, b * Appends a validated event to the run event log. Intended for trusted internal callers. * @summary Append Run Event * @param {string} id Unique run identifier (ULID). - * @param {RunEvent} runEvent + * @param {RunEvent} runEvent * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -1170,11 +1170,11 @@ export const RunInternalsApiFactory = function (configuration?: Configuration, b return localVarFp.listStageTurns(id, stageId, pageLimit, pageOffset, options).then((request) => request(axios, basePath)); }, /** - * Uploads one or more artifacts for a stage. Intended for trusted internal callers. The server accepts both: - `application/octet-stream` for single-file uploads with the `filename` query parameter - strict manifest-first `multipart/form-data` uploads documented by `ArtifactBatchUploadManifest` The generated Rust client currently exposes the octet-stream variant because the OpenAPI code generator in this repo does not support multiple request media types on one operation. + * Uploads one or more artifacts for a stage. Intended for trusted internal callers. The server accepts both: - `application/octet-stream` for single-file uploads with the `filename` query parameter - strict manifest-first `multipart/form-data` uploads documented by `ArtifactBatchUploadManifest` The generated Rust client currently exposes the octet-stream variant because the OpenAPI code generator in this repo does not support multiple request media types on one operation. * @summary Put Stage Artifact * @param {string} id Unique run identifier (ULID). * @param {string} stageId Identifier of a stage within a run\'s workflow graph, serialized as `node_id@visit`. - * @param {File} body + * @param {File} body * @param {string} [filename] Relative artifact path for `application/octet-stream` uploads. Ignored for multipart uploads. * @param {*} [options] Override http request option. * @throws {RequiredError} @@ -1217,7 +1217,7 @@ export const RunInternalsApiFactory = function (configuration?: Configuration, b * Writes an opaque binary blob and returns its content-addressed blob identifier. * @summary Write Run Blob * @param {string} id Unique run identifier (ULID). - * @param {File} body + * @param {File} body * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -1235,7 +1235,7 @@ export class RunInternalsApi extends BaseAPI { * Appends a validated event to the run event log. Intended for trusted internal callers. * @summary Append Run Event * @param {string} id Unique run identifier (ULID). - * @param {RunEvent} runEvent + * @param {RunEvent} runEvent * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -1369,11 +1369,11 @@ export class RunInternalsApi extends BaseAPI { } /** - * Uploads one or more artifacts for a stage. Intended for trusted internal callers. The server accepts both: - `application/octet-stream` for single-file uploads with the `filename` query parameter - strict manifest-first `multipart/form-data` uploads documented by `ArtifactBatchUploadManifest` The generated Rust client currently exposes the octet-stream variant because the OpenAPI code generator in this repo does not support multiple request media types on one operation. + * Uploads one or more artifacts for a stage. Intended for trusted internal callers. The server accepts both: - `application/octet-stream` for single-file uploads with the `filename` query parameter - strict manifest-first `multipart/form-data` uploads documented by `ArtifactBatchUploadManifest` The generated Rust client currently exposes the octet-stream variant because the OpenAPI code generator in this repo does not support multiple request media types on one operation. * @summary Put Stage Artifact * @param {string} id Unique run identifier (ULID). * @param {string} stageId Identifier of a stage within a run\'s workflow graph, serialized as `node_id@visit`. - * @param {File} body + * @param {File} body * @param {string} [filename] Relative artifact path for `application/octet-stream` uploads. Ignored for multipart uploads. * @param {*} [options] Override http request option. * @throws {RequiredError} @@ -1420,7 +1420,7 @@ export class RunInternalsApi extends BaseAPI { * Writes an opaque binary blob and returns its content-addressed blob identifier. * @summary Write Run Blob * @param {string} id Unique run identifier (ULID). - * @param {File} body + * @param {File} body * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -1428,3 +1428,4 @@ export class RunInternalsApi extends BaseAPI { return RunInternalsApiFp(this.configuration).writeRunBlob(id, body, options).then((request) => request(this.axios, this.basePath)); } } + diff --git a/lib/packages/fabro-api-client/src/api/run-outputs-api.ts b/lib/packages/fabro-api-client/src/api/run-outputs-api.ts index 879a73773..a92d29e73 100644 --- a/lib/packages/fabro-api-client/src/api/run-outputs-api.ts +++ b/lib/packages/fabro-api-client/src/api/run-outputs-api.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -33,7 +33,7 @@ import type { RunBilling } from '../models'; export const RunOutputsApiAxiosParamCreator = function (configuration?: Configuration) { return { /** - * Returns the set of file changes produced by a run as a list of before/after diffs. While the run\'s sandbox is reachable, diffs are resolved live against the sandbox working tree at the current HEAD. Degraded responses keep the same `data: FileDiff[]` shape. File contents are null on every entry; non-sensitive non-flagged entries include `unified_patch`, while sensitive / binary / symlink / submodule / truncated entries render through the same placeholder flags used by the live path. Responses are bounded by per-file (256 KiB / 20k lines), per-run aggregate (5 MiB), and per-request (200 files) caps. Files exceeding a cap are returned with `truncated: true` and empty `contents`. Sensitive paths (credentials, keys) are elided with `sensitive: true` and empty `contents`. + * Returns the set of file changes produced by a run as a list of before/after diffs. While the run\'s sandbox is reachable, diffs are resolved live against the sandbox working tree at the current HEAD. Degraded responses keep the same `data: FileDiff[]` shape. File contents are null on every entry; non-sensitive non-flagged entries include `unified_patch`, while sensitive / binary / symlink / submodule / truncated entries render through the same placeholder flags used by the live path. Responses are bounded by per-file (256 KiB / 20k lines), per-run aggregate (5 MiB), and per-request (200 files) caps. Files exceeding a cap are returned with `truncated: true` and empty `contents`. Sensitive paths (credentials, keys) are elided with `sensitive: true` and empty `contents`. * @summary List Run Files Changed * @param {string} id Unique run identifier (ULID). * @param {number} [pageLimit] Maximum number of items to return per page. @@ -142,7 +142,7 @@ export const RunOutputsApiFp = function(configuration?: Configuration) { const localVarAxiosParamCreator = RunOutputsApiAxiosParamCreator(configuration) return { /** - * Returns the set of file changes produced by a run as a list of before/after diffs. While the run\'s sandbox is reachable, diffs are resolved live against the sandbox working tree at the current HEAD. Degraded responses keep the same `data: FileDiff[]` shape. File contents are null on every entry; non-sensitive non-flagged entries include `unified_patch`, while sensitive / binary / symlink / submodule / truncated entries render through the same placeholder flags used by the live path. Responses are bounded by per-file (256 KiB / 20k lines), per-run aggregate (5 MiB), and per-request (200 files) caps. Files exceeding a cap are returned with `truncated: true` and empty `contents`. Sensitive paths (credentials, keys) are elided with `sensitive: true` and empty `contents`. + * Returns the set of file changes produced by a run as a list of before/after diffs. While the run\'s sandbox is reachable, diffs are resolved live against the sandbox working tree at the current HEAD. Degraded responses keep the same `data: FileDiff[]` shape. File contents are null on every entry; non-sensitive non-flagged entries include `unified_patch`, while sensitive / binary / symlink / submodule / truncated entries render through the same placeholder flags used by the live path. Responses are bounded by per-file (256 KiB / 20k lines), per-run aggregate (5 MiB), and per-request (200 files) caps. Files exceeding a cap are returned with `truncated: true` and empty `contents`. Sensitive paths (credentials, keys) are elided with `sensitive: true` and empty `contents`. * @summary List Run Files Changed * @param {string} id Unique run identifier (ULID). * @param {number} [pageLimit] Maximum number of items to return per page. @@ -181,7 +181,7 @@ export const RunOutputsApiFactory = function (configuration?: Configuration, bas const localVarFp = RunOutputsApiFp(configuration) return { /** - * Returns the set of file changes produced by a run as a list of before/after diffs. While the run\'s sandbox is reachable, diffs are resolved live against the sandbox working tree at the current HEAD. Degraded responses keep the same `data: FileDiff[]` shape. File contents are null on every entry; non-sensitive non-flagged entries include `unified_patch`, while sensitive / binary / symlink / submodule / truncated entries render through the same placeholder flags used by the live path. Responses are bounded by per-file (256 KiB / 20k lines), per-run aggregate (5 MiB), and per-request (200 files) caps. Files exceeding a cap are returned with `truncated: true` and empty `contents`. Sensitive paths (credentials, keys) are elided with `sensitive: true` and empty `contents`. + * Returns the set of file changes produced by a run as a list of before/after diffs. While the run\'s sandbox is reachable, diffs are resolved live against the sandbox working tree at the current HEAD. Degraded responses keep the same `data: FileDiff[]` shape. File contents are null on every entry; non-sensitive non-flagged entries include `unified_patch`, while sensitive / binary / symlink / submodule / truncated entries render through the same placeholder flags used by the live path. Responses are bounded by per-file (256 KiB / 20k lines), per-run aggregate (5 MiB), and per-request (200 files) caps. Files exceeding a cap are returned with `truncated: true` and empty `contents`. Sensitive paths (credentials, keys) are elided with `sensitive: true` and empty `contents`. * @summary List Run Files Changed * @param {string} id Unique run identifier (ULID). * @param {number} [pageLimit] Maximum number of items to return per page. @@ -212,7 +212,7 @@ export const RunOutputsApiFactory = function (configuration?: Configuration, bas */ export class RunOutputsApi extends BaseAPI { /** - * Returns the set of file changes produced by a run as a list of before/after diffs. While the run\'s sandbox is reachable, diffs are resolved live against the sandbox working tree at the current HEAD. Degraded responses keep the same `data: FileDiff[]` shape. File contents are null on every entry; non-sensitive non-flagged entries include `unified_patch`, while sensitive / binary / symlink / submodule / truncated entries render through the same placeholder flags used by the live path. Responses are bounded by per-file (256 KiB / 20k lines), per-run aggregate (5 MiB), and per-request (200 files) caps. Files exceeding a cap are returned with `truncated: true` and empty `contents`. Sensitive paths (credentials, keys) are elided with `sensitive: true` and empty `contents`. + * Returns the set of file changes produced by a run as a list of before/after diffs. While the run\'s sandbox is reachable, diffs are resolved live against the sandbox working tree at the current HEAD. Degraded responses keep the same `data: FileDiff[]` shape. File contents are null on every entry; non-sensitive non-flagged entries include `unified_patch`, while sensitive / binary / symlink / submodule / truncated entries render through the same placeholder flags used by the live path. Responses are bounded by per-file (256 KiB / 20k lines), per-run aggregate (5 MiB), and per-request (200 files) caps. Files exceeding a cap are returned with `truncated: true` and empty `contents`. Sensitive paths (credentials, keys) are elided with `sensitive: true` and empty `contents`. * @summary List Run Files Changed * @param {string} id Unique run identifier (ULID). * @param {number} [pageLimit] Maximum number of items to return per page. diff --git a/lib/packages/fabro-api-client/src/api/runs-api.ts b/lib/packages/fabro-api-client/src/api/runs-api.ts index 28d5eb3f9..3fd0c71e0 100644 --- a/lib/packages/fabro-api-client/src/api/runs-api.ts +++ b/lib/packages/fabro-api-client/src/api/runs-api.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -69,7 +69,7 @@ import type { ValidateResponse } from '../models'; export const RunsApiAxiosParamCreator = function (configuration?: Configuration) { return { /** - * Marks a terminal run (`succeeded`, `failed`, or `dead`) as `archived`. Archived runs are hidden from default listings and are read-only until unarchived. Idempotent on already-archived runs. Returns 409 if the run is not terminal. + * Marks a terminal run (`succeeded`, `failed`, or `dead`) as `archived`. Archived runs are hidden from default listings and are read-only until unarchived. Idempotent on already-archived runs. Returns 409 if the run is not terminal. * @summary Archive Run * @param {string} id Unique run identifier (ULID). * @param {*} [options] Override http request option. @@ -191,7 +191,7 @@ export const RunsApiAxiosParamCreator = function (configuration?: Configuration) /** * Creates a new workflow run in `submitted` status from a self-contained manifest. * @summary Create Run - * @param {RunManifest} runManifest + * @param {RunManifest} runManifest * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -233,7 +233,7 @@ export const RunsApiAxiosParamCreator = function (configuration?: Configuration) * Creates a pull request for a completed run on GitHub and persists the record on the server. * @summary Create Run Pull Request * @param {string} id Unique run identifier (ULID). - * @param {CreateRunPullRequestRequest} createRunPullRequestRequest + * @param {CreateRunPullRequestRequest} createRunPullRequestRequest * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -320,10 +320,10 @@ export const RunsApiAxiosParamCreator = function (configuration?: Configuration) }; }, /** - * Creates a new run from a checkpoint of the source run. The source run is left untouched. + * Creates a new run from a checkpoint of the source run. The source run is left untouched. * @summary Fork Run * @param {string} id Unique run identifier (ULID). - * @param {ForkRequest} [forkRequest] + * @param {ForkRequest} [forkRequest] * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -403,7 +403,7 @@ export const RunsApiAxiosParamCreator = function (configuration?: Configuration) }; }, /** - * Returns checkpoint timeline entries from durable run-store checkpoints. Metadata branches are write-only archives and are not read by this endpoint. + * Returns checkpoint timeline entries from durable run-store checkpoints. Metadata branches are write-only archives and are not read by this endpoint. * @summary Get Run Timeline * @param {string} id Unique run identifier (ULID). * @param {*} [options] Override http request option. @@ -543,7 +543,7 @@ export const RunsApiAxiosParamCreator = function (configuration?: Configuration) * Merges the stored pull request for a run on GitHub. * @summary Merge Run Pull Request * @param {string} id Unique run identifier (ULID). - * @param {MergeRunPullRequestRequest} mergeRunPullRequestRequest + * @param {MergeRunPullRequestRequest} mergeRunPullRequestRequest * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -627,7 +627,7 @@ export const RunsApiAxiosParamCreator = function (configuration?: Configuration) /** * Validates and renders a workflow manifest as SVG without creating a run. * @summary Render Workflow Graph - * @param {RenderWorkflowGraphRequest} renderWorkflowGraphRequest + * @param {RenderWorkflowGraphRequest} renderWorkflowGraphRequest * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -829,10 +829,10 @@ export const RunsApiAxiosParamCreator = function (configuration?: Configuration) }; }, /** - * Creates a new run from an earlier checkpoint of a terminal source run, archives the source run, and records `run.superseded_by` on the source after archive succeeds. Returns 207 when the new run was created but the source archive step failed. + * Creates a new run from an earlier checkpoint of a terminal source run, archives the source run, and records `run.superseded_by` on the source after archive succeeds. Returns 207 when the new run was created but the source archive step failed. * @summary Rewind Run * @param {string} id Unique run identifier (ULID). - * @param {RewindRequest} [rewindRequest] + * @param {RewindRequest} [rewindRequest] * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -874,7 +874,7 @@ export const RunsApiAxiosParamCreator = function (configuration?: Configuration) /** * Validates runtime readiness for a workflow manifest without creating a run. * @summary Validate Workflow Manifest - * @param {RunManifest} runManifest + * @param {RunManifest} runManifest * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -916,7 +916,7 @@ export const RunsApiAxiosParamCreator = function (configuration?: Configuration) * Starts a submitted run, queuing it for execution. Provide `resume=true` to resume an interrupted run from checkpoint. Returns 409 if the run is not startable. * @summary Start Run * @param {string} id Unique run identifier (ULID). - * @param {StartRunRequest} [startRunRequest] + * @param {StartRunRequest} [startRunRequest] * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -956,7 +956,7 @@ export const RunsApiAxiosParamCreator = function (configuration?: Configuration) }; }, /** - * Restores an archived run to its prior terminal status. Idempotent on runs that are terminal but not archived (returns the current status without emitting an event). Returns 409 if the run is active. + * Restores an archived run to its prior terminal status. Idempotent on runs that are terminal but not archived (returns the current status without emitting an event). Returns 409 if the run is active. * @summary Unarchive Run * @param {string} id Unique run identifier (ULID). * @param {*} [options] Override http request option. @@ -1038,7 +1038,7 @@ export const RunsApiAxiosParamCreator = function (configuration?: Configuration) /** * Validates workflow structure and diagnostics without runtime readiness checks. * @summary Validate Workflow Manifest - * @param {RunManifest} runManifest + * @param {RunManifest} runManifest * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -1086,7 +1086,7 @@ export const RunsApiFp = function(configuration?: Configuration) { const localVarAxiosParamCreator = RunsApiAxiosParamCreator(configuration) return { /** - * Marks a terminal run (`succeeded`, `failed`, or `dead`) as `archived`. Archived runs are hidden from default listings and are read-only until unarchived. Idempotent on already-archived runs. Returns 409 if the run is not terminal. + * Marks a terminal run (`succeeded`, `failed`, or `dead`) as `archived`. Archived runs are hidden from default listings and are read-only until unarchived. Idempotent on already-archived runs. Returns 409 if the run is not terminal. * @summary Archive Run * @param {string} id Unique run identifier (ULID). * @param {*} [options] Override http request option. @@ -1127,7 +1127,7 @@ export const RunsApiFp = function(configuration?: Configuration) { /** * Creates a new workflow run in `submitted` status from a self-contained manifest. * @summary Create Run - * @param {RunManifest} runManifest + * @param {RunManifest} runManifest * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -1141,7 +1141,7 @@ export const RunsApiFp = function(configuration?: Configuration) { * Creates a pull request for a completed run on GitHub and persists the record on the server. * @summary Create Run Pull Request * @param {string} id Unique run identifier (ULID). - * @param {CreateRunPullRequestRequest} createRunPullRequestRequest + * @param {CreateRunPullRequestRequest} createRunPullRequestRequest * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -1166,10 +1166,10 @@ export const RunsApiFp = function(configuration?: Configuration) { return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); }, /** - * Creates a new run from a checkpoint of the source run. The source run is left untouched. + * Creates a new run from a checkpoint of the source run. The source run is left untouched. * @summary Fork Run * @param {string} id Unique run identifier (ULID). - * @param {ForkRequest} [forkRequest] + * @param {ForkRequest} [forkRequest] * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -1193,7 +1193,7 @@ export const RunsApiFp = function(configuration?: Configuration) { return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); }, /** - * Returns checkpoint timeline entries from durable run-store checkpoints. Metadata branches are write-only archives and are not read by this endpoint. + * Returns checkpoint timeline entries from durable run-store checkpoints. Metadata branches are write-only archives and are not read by this endpoint. * @summary Get Run Timeline * @param {string} id Unique run identifier (ULID). * @param {*} [options] Override http request option. @@ -1238,7 +1238,7 @@ export const RunsApiFp = function(configuration?: Configuration) { * Merges the stored pull request for a run on GitHub. * @summary Merge Run Pull Request * @param {string} id Unique run identifier (ULID). - * @param {MergeRunPullRequestRequest} mergeRunPullRequestRequest + * @param {MergeRunPullRequestRequest} mergeRunPullRequestRequest * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -1264,7 +1264,7 @@ export const RunsApiFp = function(configuration?: Configuration) { /** * Validates and renders a workflow manifest as SVG without creating a run. * @summary Render Workflow Graph - * @param {RenderWorkflowGraphRequest} renderWorkflowGraphRequest + * @param {RenderWorkflowGraphRequest} renderWorkflowGraphRequest * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -1327,10 +1327,10 @@ export const RunsApiFp = function(configuration?: Configuration) { return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); }, /** - * Creates a new run from an earlier checkpoint of a terminal source run, archives the source run, and records `run.superseded_by` on the source after archive succeeds. Returns 207 when the new run was created but the source archive step failed. + * Creates a new run from an earlier checkpoint of a terminal source run, archives the source run, and records `run.superseded_by` on the source after archive succeeds. Returns 207 when the new run was created but the source archive step failed. * @summary Rewind Run * @param {string} id Unique run identifier (ULID). - * @param {RewindRequest} [rewindRequest] + * @param {RewindRequest} [rewindRequest] * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -1343,7 +1343,7 @@ export const RunsApiFp = function(configuration?: Configuration) { /** * Validates runtime readiness for a workflow manifest without creating a run. * @summary Validate Workflow Manifest - * @param {RunManifest} runManifest + * @param {RunManifest} runManifest * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -1357,7 +1357,7 @@ export const RunsApiFp = function(configuration?: Configuration) { * Starts a submitted run, queuing it for execution. Provide `resume=true` to resume an interrupted run from checkpoint. Returns 409 if the run is not startable. * @summary Start Run * @param {string} id Unique run identifier (ULID). - * @param {StartRunRequest} [startRunRequest] + * @param {StartRunRequest} [startRunRequest] * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -1368,7 +1368,7 @@ export const RunsApiFp = function(configuration?: Configuration) { return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); }, /** - * Restores an archived run to its prior terminal status. Idempotent on runs that are terminal but not archived (returns the current status without emitting an event). Returns 409 if the run is active. + * Restores an archived run to its prior terminal status. Idempotent on runs that are terminal but not archived (returns the current status without emitting an event). Returns 409 if the run is active. * @summary Unarchive Run * @param {string} id Unique run identifier (ULID). * @param {*} [options] Override http request option. @@ -1396,7 +1396,7 @@ export const RunsApiFp = function(configuration?: Configuration) { /** * Validates workflow structure and diagnostics without runtime readiness checks. * @summary Validate Workflow Manifest - * @param {RunManifest} runManifest + * @param {RunManifest} runManifest * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -1416,7 +1416,7 @@ export const RunsApiFactory = function (configuration?: Configuration, basePath? const localVarFp = RunsApiFp(configuration) return { /** - * Marks a terminal run (`succeeded`, `failed`, or `dead`) as `archived`. Archived runs are hidden from default listings and are read-only until unarchived. Idempotent on already-archived runs. Returns 409 if the run is not terminal. + * Marks a terminal run (`succeeded`, `failed`, or `dead`) as `archived`. Archived runs are hidden from default listings and are read-only until unarchived. Idempotent on already-archived runs. Returns 409 if the run is not terminal. * @summary Archive Run * @param {string} id Unique run identifier (ULID). * @param {*} [options] Override http request option. @@ -1448,7 +1448,7 @@ export const RunsApiFactory = function (configuration?: Configuration, basePath? /** * Creates a new workflow run in `submitted` status from a self-contained manifest. * @summary Create Run - * @param {RunManifest} runManifest + * @param {RunManifest} runManifest * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -1459,7 +1459,7 @@ export const RunsApiFactory = function (configuration?: Configuration, basePath? * Creates a pull request for a completed run on GitHub and persists the record on the server. * @summary Create Run Pull Request * @param {string} id Unique run identifier (ULID). - * @param {CreateRunPullRequestRequest} createRunPullRequestRequest + * @param {CreateRunPullRequestRequest} createRunPullRequestRequest * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -1478,10 +1478,10 @@ export const RunsApiFactory = function (configuration?: Configuration, basePath? return localVarFp.deleteRun(id, force, options).then((request) => request(axios, basePath)); }, /** - * Creates a new run from a checkpoint of the source run. The source run is left untouched. + * Creates a new run from a checkpoint of the source run. The source run is left untouched. * @summary Fork Run * @param {string} id Unique run identifier (ULID). - * @param {ForkRequest} [forkRequest] + * @param {ForkRequest} [forkRequest] * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -1499,7 +1499,7 @@ export const RunsApiFactory = function (configuration?: Configuration, basePath? return localVarFp.getRunPullRequest(id, options).then((request) => request(axios, basePath)); }, /** - * Returns checkpoint timeline entries from durable run-store checkpoints. Metadata branches are write-only archives and are not read by this endpoint. + * Returns checkpoint timeline entries from durable run-store checkpoints. Metadata branches are write-only archives and are not read by this endpoint. * @summary Get Run Timeline * @param {string} id Unique run identifier (ULID). * @param {*} [options] Override http request option. @@ -1535,7 +1535,7 @@ export const RunsApiFactory = function (configuration?: Configuration, basePath? * Merges the stored pull request for a run on GitHub. * @summary Merge Run Pull Request * @param {string} id Unique run identifier (ULID). - * @param {MergeRunPullRequestRequest} mergeRunPullRequestRequest + * @param {MergeRunPullRequestRequest} mergeRunPullRequestRequest * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -1555,7 +1555,7 @@ export const RunsApiFactory = function (configuration?: Configuration, basePath? /** * Validates and renders a workflow manifest as SVG without creating a run. * @summary Render Workflow Graph - * @param {RenderWorkflowGraphRequest} renderWorkflowGraphRequest + * @param {RenderWorkflowGraphRequest} renderWorkflowGraphRequest * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -1603,10 +1603,10 @@ export const RunsApiFactory = function (configuration?: Configuration, basePath? return localVarFp.retrieveRunGraphSource(id, options).then((request) => request(axios, basePath)); }, /** - * Creates a new run from an earlier checkpoint of a terminal source run, archives the source run, and records `run.superseded_by` on the source after archive succeeds. Returns 207 when the new run was created but the source archive step failed. + * Creates a new run from an earlier checkpoint of a terminal source run, archives the source run, and records `run.superseded_by` on the source after archive succeeds. Returns 207 when the new run was created but the source archive step failed. * @summary Rewind Run * @param {string} id Unique run identifier (ULID). - * @param {RewindRequest} [rewindRequest] + * @param {RewindRequest} [rewindRequest] * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -1616,7 +1616,7 @@ export const RunsApiFactory = function (configuration?: Configuration, basePath? /** * Validates runtime readiness for a workflow manifest without creating a run. * @summary Validate Workflow Manifest - * @param {RunManifest} runManifest + * @param {RunManifest} runManifest * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -1627,7 +1627,7 @@ export const RunsApiFactory = function (configuration?: Configuration, basePath? * Starts a submitted run, queuing it for execution. Provide `resume=true` to resume an interrupted run from checkpoint. Returns 409 if the run is not startable. * @summary Start Run * @param {string} id Unique run identifier (ULID). - * @param {StartRunRequest} [startRunRequest] + * @param {StartRunRequest} [startRunRequest] * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -1635,7 +1635,7 @@ export const RunsApiFactory = function (configuration?: Configuration, basePath? return localVarFp.startRun(id, startRunRequest, options).then((request) => request(axios, basePath)); }, /** - * Restores an archived run to its prior terminal status. Idempotent on runs that are terminal but not archived (returns the current status without emitting an event). Returns 409 if the run is active. + * Restores an archived run to its prior terminal status. Idempotent on runs that are terminal but not archived (returns the current status without emitting an event). Returns 409 if the run is active. * @summary Unarchive Run * @param {string} id Unique run identifier (ULID). * @param {*} [options] Override http request option. @@ -1657,7 +1657,7 @@ export const RunsApiFactory = function (configuration?: Configuration, basePath? /** * Validates workflow structure and diagnostics without runtime readiness checks. * @summary Validate Workflow Manifest - * @param {RunManifest} runManifest + * @param {RunManifest} runManifest * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -1672,7 +1672,7 @@ export const RunsApiFactory = function (configuration?: Configuration, basePath? */ export class RunsApi extends BaseAPI { /** - * Marks a terminal run (`succeeded`, `failed`, or `dead`) as `archived`. Archived runs are hidden from default listings and are read-only until unarchived. Idempotent on already-archived runs. Returns 409 if the run is not terminal. + * Marks a terminal run (`succeeded`, `failed`, or `dead`) as `archived`. Archived runs are hidden from default listings and are read-only until unarchived. Idempotent on already-archived runs. Returns 409 if the run is not terminal. * @summary Archive Run * @param {string} id Unique run identifier (ULID). * @param {*} [options] Override http request option. @@ -1707,7 +1707,7 @@ export class RunsApi extends BaseAPI { /** * Creates a new workflow run in `submitted` status from a self-contained manifest. * @summary Create Run - * @param {RunManifest} runManifest + * @param {RunManifest} runManifest * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -1719,7 +1719,7 @@ export class RunsApi extends BaseAPI { * Creates a pull request for a completed run on GitHub and persists the record on the server. * @summary Create Run Pull Request * @param {string} id Unique run identifier (ULID). - * @param {CreateRunPullRequestRequest} createRunPullRequestRequest + * @param {CreateRunPullRequestRequest} createRunPullRequestRequest * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -1740,10 +1740,10 @@ export class RunsApi extends BaseAPI { } /** - * Creates a new run from a checkpoint of the source run. The source run is left untouched. + * Creates a new run from a checkpoint of the source run. The source run is left untouched. * @summary Fork Run * @param {string} id Unique run identifier (ULID). - * @param {ForkRequest} [forkRequest] + * @param {ForkRequest} [forkRequest] * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -1763,7 +1763,7 @@ export class RunsApi extends BaseAPI { } /** - * Returns checkpoint timeline entries from durable run-store checkpoints. Metadata branches are write-only archives and are not read by this endpoint. + * Returns checkpoint timeline entries from durable run-store checkpoints. Metadata branches are write-only archives and are not read by this endpoint. * @summary Get Run Timeline * @param {string} id Unique run identifier (ULID). * @param {*} [options] Override http request option. @@ -1802,7 +1802,7 @@ export class RunsApi extends BaseAPI { * Merges the stored pull request for a run on GitHub. * @summary Merge Run Pull Request * @param {string} id Unique run identifier (ULID). - * @param {MergeRunPullRequestRequest} mergeRunPullRequestRequest + * @param {MergeRunPullRequestRequest} mergeRunPullRequestRequest * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -1824,7 +1824,7 @@ export class RunsApi extends BaseAPI { /** * Validates and renders a workflow manifest as SVG without creating a run. * @summary Render Workflow Graph - * @param {RenderWorkflowGraphRequest} renderWorkflowGraphRequest + * @param {RenderWorkflowGraphRequest} renderWorkflowGraphRequest * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -1877,10 +1877,10 @@ export class RunsApi extends BaseAPI { } /** - * Creates a new run from an earlier checkpoint of a terminal source run, archives the source run, and records `run.superseded_by` on the source after archive succeeds. Returns 207 when the new run was created but the source archive step failed. + * Creates a new run from an earlier checkpoint of a terminal source run, archives the source run, and records `run.superseded_by` on the source after archive succeeds. Returns 207 when the new run was created but the source archive step failed. * @summary Rewind Run * @param {string} id Unique run identifier (ULID). - * @param {RewindRequest} [rewindRequest] + * @param {RewindRequest} [rewindRequest] * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -1891,7 +1891,7 @@ export class RunsApi extends BaseAPI { /** * Validates runtime readiness for a workflow manifest without creating a run. * @summary Validate Workflow Manifest - * @param {RunManifest} runManifest + * @param {RunManifest} runManifest * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -1903,7 +1903,7 @@ export class RunsApi extends BaseAPI { * Starts a submitted run, queuing it for execution. Provide `resume=true` to resume an interrupted run from checkpoint. Returns 409 if the run is not startable. * @summary Start Run * @param {string} id Unique run identifier (ULID). - * @param {StartRunRequest} [startRunRequest] + * @param {StartRunRequest} [startRunRequest] * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -1912,7 +1912,7 @@ export class RunsApi extends BaseAPI { } /** - * Restores an archived run to its prior terminal status. Idempotent on runs that are terminal but not archived (returns the current status without emitting an event). Returns 409 if the run is active. + * Restores an archived run to its prior terminal status. Idempotent on runs that are terminal but not archived (returns the current status without emitting an event). Returns 409 if the run is active. * @summary Unarchive Run * @param {string} id Unique run identifier (ULID). * @param {*} [options] Override http request option. @@ -1936,7 +1936,7 @@ export class RunsApi extends BaseAPI { /** * Validates workflow structure and diagnostics without runtime readiness checks. * @summary Validate Workflow Manifest - * @param {RunManifest} runManifest + * @param {RunManifest} runManifest * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -1944,3 +1944,4 @@ export class RunsApi extends BaseAPI { return RunsApiFp(this.configuration).validateRunManifest(runManifest, options).then((request) => request(this.axios, this.basePath)); } } + diff --git a/lib/packages/fabro-api-client/src/models/agent-permissions.ts b/lib/packages/fabro-api-client/src/models/agent-permissions.ts index 3880585a3..d333f0d9e 100644 --- a/lib/packages/fabro-api-client/src/models/agent-permissions.ts +++ b/lib/packages/fabro-api-client/src/models/agent-permissions.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -22,3 +22,6 @@ export const AgentPermissions = { } as const; export type AgentPermissions = typeof AgentPermissions[keyof typeof AgentPermissions]; + + + diff --git a/lib/packages/fabro-api-client/src/models/aggregate-billing-totals.ts b/lib/packages/fabro-api-client/src/models/aggregate-billing-totals.ts index bed219431..cebaf2c1e 100644 --- a/lib/packages/fabro-api-client/src/models/aggregate-billing-totals.ts +++ b/lib/packages/fabro-api-client/src/models/aggregate-billing-totals.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -55,3 +55,4 @@ export interface AggregateBillingTotals { */ 'runtime_secs': number; } + diff --git a/lib/packages/fabro-api-client/src/models/approval-mode.ts b/lib/packages/fabro-api-client/src/models/approval-mode.ts index 7684e7600..f1f206666 100644 --- a/lib/packages/fabro-api-client/src/models/approval-mode.ts +++ b/lib/packages/fabro-api-client/src/models/approval-mode.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -21,3 +21,6 @@ export const ApprovalMode = { } as const; export type ApprovalMode = typeof ApprovalMode[keyof typeof ApprovalMode]; + + + diff --git a/lib/packages/fabro-api-client/src/models/artifacts-settings.ts b/lib/packages/fabro-api-client/src/models/artifacts-settings.ts index d9e7798db..4de6e7669 100644 --- a/lib/packages/fabro-api-client/src/models/artifacts-settings.ts +++ b/lib/packages/fabro-api-client/src/models/artifacts-settings.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -17,3 +17,4 @@ export interface ArtifactsSettings { 'include': Array; } + diff --git a/lib/packages/fabro-api-client/src/models/billed-token-counts.ts b/lib/packages/fabro-api-client/src/models/billed-token-counts.ts index 1c945f4d3..21d0c7354 100644 --- a/lib/packages/fabro-api-client/src/models/billed-token-counts.ts +++ b/lib/packages/fabro-api-client/src/models/billed-token-counts.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -47,3 +47,4 @@ export interface BilledTokenCounts { */ 'total_usd_micros'?: number | null; } + diff --git a/lib/packages/fabro-api-client/src/models/command-termination.ts b/lib/packages/fabro-api-client/src/models/command-termination.ts new file mode 100644 index 000000000..5cda05fae --- /dev/null +++ b/lib/packages/fabro-api-client/src/models/command-termination.ts @@ -0,0 +1,30 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Fabro Run API + * HTTP API for managing Fabro workflow run executions. + * + * The version of the OpenAPI document: 0.1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + + +/** + * Terminal state for a command execution. + */ + +export const CommandTermination = { + EXITED: 'exited', + TIMED_OUT: 'timed_out', + CANCELLED: 'cancelled' +} as const; + +export type CommandTermination = typeof CommandTermination[keyof typeof CommandTermination]; + + + diff --git a/lib/packages/fabro-api-client/src/models/create-run-pull-request-request.ts b/lib/packages/fabro-api-client/src/models/create-run-pull-request-request.ts index d029261b2..117f8ab17 100644 --- a/lib/packages/fabro-api-client/src/models/create-run-pull-request-request.ts +++ b/lib/packages/fabro-api-client/src/models/create-run-pull-request-request.ts @@ -27,3 +27,4 @@ export interface CreateRunPullRequestRequest { */ 'model'?: string | null; } + diff --git a/lib/packages/fabro-api-client/src/models/daytona-network-layer-one-of-allow-list.ts b/lib/packages/fabro-api-client/src/models/daytona-network-layer-one-of-allow-list.ts index b35ab95b5..4f25a90b8 100644 --- a/lib/packages/fabro-api-client/src/models/daytona-network-layer-one-of-allow-list.ts +++ b/lib/packages/fabro-api-client/src/models/daytona-network-layer-one-of-allow-list.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -17,3 +17,4 @@ export interface DaytonaNetworkLayerOneOfAllowList { 'allow_list': Array; } + diff --git a/lib/packages/fabro-api-client/src/models/daytona-network-layer-one-of.ts b/lib/packages/fabro-api-client/src/models/daytona-network-layer-one-of.ts index 0d3aee6d5..4024a873a 100644 --- a/lib/packages/fabro-api-client/src/models/daytona-network-layer-one-of.ts +++ b/lib/packages/fabro-api-client/src/models/daytona-network-layer-one-of.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -20,3 +20,4 @@ import type { DaytonaNetworkLayerOneOfAllowList } from './daytona-network-layer- export interface DaytonaNetworkLayerOneOf { 'allow_list': DaytonaNetworkLayerOneOfAllowList; } + diff --git a/lib/packages/fabro-api-client/src/models/daytona-network-layer.ts b/lib/packages/fabro-api-client/src/models/daytona-network-layer.ts index ec2c9342d..6a4d616c3 100644 --- a/lib/packages/fabro-api-client/src/models/daytona-network-layer.ts +++ b/lib/packages/fabro-api-client/src/models/daytona-network-layer.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -25,3 +25,5 @@ import type { DaytonaNetworkLayerOneOfAllowList } from './daytona-network-layer- * Daytona network access policy. */ export type DaytonaNetworkLayer = DaytonaNetworkLayerOneOf | string; + + diff --git a/lib/packages/fabro-api-client/src/models/daytona-settings.ts b/lib/packages/fabro-api-client/src/models/daytona-settings.ts index fff243d47..dae48daa5 100644 --- a/lib/packages/fabro-api-client/src/models/daytona-settings.ts +++ b/lib/packages/fabro-api-client/src/models/daytona-settings.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -27,3 +27,4 @@ export interface DaytonaSettings { 'network': DaytonaNetworkLayer | null; 'skip_clone': boolean; } + diff --git a/lib/packages/fabro-api-client/src/models/daytona-snapshot-settings.ts b/lib/packages/fabro-api-client/src/models/daytona-snapshot-settings.ts index b067a5a93..e512bcb3f 100644 --- a/lib/packages/fabro-api-client/src/models/daytona-snapshot-settings.ts +++ b/lib/packages/fabro-api-client/src/models/daytona-snapshot-settings.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -24,3 +24,4 @@ export interface DaytonaSnapshotSettings { 'disk_gb': number | null; 'dockerfile': DockerfileSource | null; } + diff --git a/lib/packages/fabro-api-client/src/models/diff-stats.ts b/lib/packages/fabro-api-client/src/models/diff-stats.ts index 89e90db65..f0747f978 100644 --- a/lib/packages/fabro-api-client/src/models/diff-stats.ts +++ b/lib/packages/fabro-api-client/src/models/diff-stats.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -15,7 +15,7 @@ /** - * Aggregate `+/-` line counts across all files in a diff. Binary, sensitive, symlink, and submodule files contribute 0/0 since they have no line-level diff. Both fields are 0 for empty / pre-start envelopes. + * Aggregate `+/-` line counts across all files in a diff. Binary, sensitive, symlink, and submodule files contribute 0/0 since they have no line-level diff. Both fields are 0 for empty / pre-start envelopes. */ export interface DiffStats { /** diff --git a/lib/packages/fabro-api-client/src/models/docker-settings.ts b/lib/packages/fabro-api-client/src/models/docker-settings.ts index 4fc87ddea..000a6deea 100644 --- a/lib/packages/fabro-api-client/src/models/docker-settings.ts +++ b/lib/packages/fabro-api-client/src/models/docker-settings.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -22,3 +22,4 @@ export interface DockerSettings { 'env_vars': { [key: string]: string; }; 'skip_clone': boolean; } + diff --git a/lib/packages/fabro-api-client/src/models/dockerfile-source-inline.ts b/lib/packages/fabro-api-client/src/models/dockerfile-source-inline.ts index 296eb3d94..c62baac22 100644 --- a/lib/packages/fabro-api-client/src/models/dockerfile-source-inline.ts +++ b/lib/packages/fabro-api-client/src/models/dockerfile-source-inline.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -24,3 +24,5 @@ export const DockerfileSourceInlineTypeEnum = { } as const; export type DockerfileSourceInlineTypeEnum = typeof DockerfileSourceInlineTypeEnum[keyof typeof DockerfileSourceInlineTypeEnum]; + + diff --git a/lib/packages/fabro-api-client/src/models/dockerfile-source-path.ts b/lib/packages/fabro-api-client/src/models/dockerfile-source-path.ts index 2510b3634..6399be3a5 100644 --- a/lib/packages/fabro-api-client/src/models/dockerfile-source-path.ts +++ b/lib/packages/fabro-api-client/src/models/dockerfile-source-path.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -24,3 +24,5 @@ export const DockerfileSourcePathTypeEnum = { } as const; export type DockerfileSourcePathTypeEnum = typeof DockerfileSourcePathTypeEnum[keyof typeof DockerfileSourcePathTypeEnum]; + + diff --git a/lib/packages/fabro-api-client/src/models/dockerfile-source.ts b/lib/packages/fabro-api-client/src/models/dockerfile-source.ts index dfb2ef280..2ac58bd7e 100644 --- a/lib/packages/fabro-api-client/src/models/dockerfile-source.ts +++ b/lib/packages/fabro-api-client/src/models/dockerfile-source.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -24,3 +24,5 @@ import type { DockerfileSourcePath } from './dockerfile-source-path'; * @type DockerfileSource */ export type DockerfileSource = DockerfileSourceInline | DockerfileSourcePath; + + diff --git a/lib/packages/fabro-api-client/src/models/file-diff.ts b/lib/packages/fabro-api-client/src/models/file-diff.ts index 0965eb3e9..6989b3fc2 100644 --- a/lib/packages/fabro-api-client/src/models/file-diff.ts +++ b/lib/packages/fabro-api-client/src/models/file-diff.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -18,7 +18,7 @@ import type { DiffFile } from './diff-file'; /** - * A before/after pair showing changes to a single file. Contents conventions for non-modify cases: - Added: `old_file.contents` is empty string; `new_file` holds the added contents. - Deleted: `new_file.contents` is empty string; `old_file` holds the removed contents. - Renamed (no content change): both sides hold identical contents; `old_file.name != new_file.name`. - Symlink / submodule / binary / sensitive / truncated: contents are empty strings; consumers must render a placeholder based on the flag set. - Degraded responses: contents are null on every entry; regular text diffs include `unified_patch`. + * A before/after pair showing changes to a single file. Contents conventions for non-modify cases: - Added: `old_file.contents` is empty string; `new_file` holds the added contents. - Deleted: `new_file.contents` is empty string; `old_file` holds the removed contents. - Renamed (no content change): both sides hold identical contents; `old_file.name != new_file.name`. - Symlink / submodule / binary / sensitive / truncated: contents are empty strings; consumers must render a placeholder based on the flag set. - Degraded responses: contents are null on every entry; regular text diffs include `unified_patch`. */ export interface FileDiff { 'old_file': DiffFile; diff --git a/lib/packages/fabro-api-client/src/models/git-author-settings.ts b/lib/packages/fabro-api-client/src/models/git-author-settings.ts index 619c72dda..a62d11d53 100644 --- a/lib/packages/fabro-api-client/src/models/git-author-settings.ts +++ b/lib/packages/fabro-api-client/src/models/git-author-settings.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -18,3 +18,4 @@ export interface GitAuthorSettings { 'name': string | null; 'email': string | null; } + diff --git a/lib/packages/fabro-api-client/src/models/hook-definition.ts b/lib/packages/fabro-api-client/src/models/hook-definition.ts index a7d2f7cf1..26a3b1fb1 100644 --- a/lib/packages/fabro-api-client/src/models/hook-definition.ts +++ b/lib/packages/fabro-api-client/src/models/hook-definition.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -46,3 +46,5 @@ export const HookDefinitionTypeEnum = { } as const; export type HookDefinitionTypeEnum = typeof HookDefinitionTypeEnum[keyof typeof HookDefinitionTypeEnum]; + + diff --git a/lib/packages/fabro-api-client/src/models/hook-event.ts b/lib/packages/fabro-api-client/src/models/hook-event.ts index aabc20ecd..b9dcb029f 100644 --- a/lib/packages/fabro-api-client/src/models/hook-event.ts +++ b/lib/packages/fabro-api-client/src/models/hook-event.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -35,3 +35,6 @@ export const HookEvent = { } as const; export type HookEvent = typeof HookEvent[keyof typeof HookEvent]; + + + diff --git a/lib/packages/fabro-api-client/src/models/index.ts b/lib/packages/fabro-api-client/src/models/index.ts index ce2e6a927..85bc80c70 100644 --- a/lib/packages/fabro-api-client/src/models/index.ts +++ b/lib/packages/fabro-api-client/src/models/index.ts @@ -25,6 +25,7 @@ export * from './close-run-pull-request-response'; export * from './code-location'; export * from './command-log-response'; export * from './command-output-stream'; +export * from './command-termination'; export * from './completion-content-part'; export * from './completion-message'; export * from './completion-response'; diff --git a/lib/packages/fabro-api-client/src/models/interview-provider-settings.ts b/lib/packages/fabro-api-client/src/models/interview-provider-settings.ts index 34d8d5c0e..c778574a3 100644 --- a/lib/packages/fabro-api-client/src/models/interview-provider-settings.ts +++ b/lib/packages/fabro-api-client/src/models/interview-provider-settings.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -17,3 +17,4 @@ export interface InterviewProviderSettings { 'channel': string | null; } + diff --git a/lib/packages/fabro-api-client/src/models/local-sandbox-settings.ts b/lib/packages/fabro-api-client/src/models/local-sandbox-settings.ts index 8a8864bd4..dd9f96f60 100644 --- a/lib/packages/fabro-api-client/src/models/local-sandbox-settings.ts +++ b/lib/packages/fabro-api-client/src/models/local-sandbox-settings.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -20,3 +20,6 @@ import type { WorktreeMode } from './worktree-mode'; export interface LocalSandboxSettings { 'worktree_mode': WorktreeMode; } + + + diff --git a/lib/packages/fabro-api-client/src/models/mcp-server-settings.ts b/lib/packages/fabro-api-client/src/models/mcp-server-settings.ts index b84ea5675..cd7c82dce 100644 --- a/lib/packages/fabro-api-client/src/models/mcp-server-settings.ts +++ b/lib/packages/fabro-api-client/src/models/mcp-server-settings.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -23,3 +23,4 @@ export interface McpServerSettings { 'startup_timeout_secs': number; 'tool_timeout_secs': number; } + diff --git a/lib/packages/fabro-api-client/src/models/mcp-transport-http.ts b/lib/packages/fabro-api-client/src/models/mcp-transport-http.ts index 341b4617d..e27637780 100644 --- a/lib/packages/fabro-api-client/src/models/mcp-transport-http.ts +++ b/lib/packages/fabro-api-client/src/models/mcp-transport-http.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -25,3 +25,5 @@ export const McpTransportHttpTypeEnum = { } as const; export type McpTransportHttpTypeEnum = typeof McpTransportHttpTypeEnum[keyof typeof McpTransportHttpTypeEnum]; + + diff --git a/lib/packages/fabro-api-client/src/models/mcp-transport-sandbox.ts b/lib/packages/fabro-api-client/src/models/mcp-transport-sandbox.ts index c96f48dc4..154d7ec00 100644 --- a/lib/packages/fabro-api-client/src/models/mcp-transport-sandbox.ts +++ b/lib/packages/fabro-api-client/src/models/mcp-transport-sandbox.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -26,3 +26,5 @@ export const McpTransportSandboxTypeEnum = { } as const; export type McpTransportSandboxTypeEnum = typeof McpTransportSandboxTypeEnum[keyof typeof McpTransportSandboxTypeEnum]; + + diff --git a/lib/packages/fabro-api-client/src/models/mcp-transport-stdio.ts b/lib/packages/fabro-api-client/src/models/mcp-transport-stdio.ts index 4c7039879..396e1b767 100644 --- a/lib/packages/fabro-api-client/src/models/mcp-transport-stdio.ts +++ b/lib/packages/fabro-api-client/src/models/mcp-transport-stdio.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -25,3 +25,5 @@ export const McpTransportStdioTypeEnum = { } as const; export type McpTransportStdioTypeEnum = typeof McpTransportStdioTypeEnum[keyof typeof McpTransportStdioTypeEnum]; + + diff --git a/lib/packages/fabro-api-client/src/models/mcp-transport.ts b/lib/packages/fabro-api-client/src/models/mcp-transport.ts index 7191c0125..834a83f13 100644 --- a/lib/packages/fabro-api-client/src/models/mcp-transport.ts +++ b/lib/packages/fabro-api-client/src/models/mcp-transport.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -27,3 +27,5 @@ import type { McpTransportStdio } from './mcp-transport-stdio'; * @type McpTransport */ export type McpTransport = McpTransportHttp | McpTransportSandbox | McpTransportStdio; + + diff --git a/lib/packages/fabro-api-client/src/models/model-costs.ts b/lib/packages/fabro-api-client/src/models/model-costs.ts index 97ab64e48..c7f5a43d1 100644 --- a/lib/packages/fabro-api-client/src/models/model-costs.ts +++ b/lib/packages/fabro-api-client/src/models/model-costs.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -31,3 +31,4 @@ export interface ModelCosts { */ 'cache_input_cost_per_mtok': number | null; } + diff --git a/lib/packages/fabro-api-client/src/models/model-features.ts b/lib/packages/fabro-api-client/src/models/model-features.ts index c25ac9032..5c522e435 100644 --- a/lib/packages/fabro-api-client/src/models/model-features.ts +++ b/lib/packages/fabro-api-client/src/models/model-features.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -35,3 +35,4 @@ export interface ModelFeatures { */ 'effort': boolean; } + diff --git a/lib/packages/fabro-api-client/src/models/model-limits.ts b/lib/packages/fabro-api-client/src/models/model-limits.ts index c915d36b5..efc35f7ac 100644 --- a/lib/packages/fabro-api-client/src/models/model-limits.ts +++ b/lib/packages/fabro-api-client/src/models/model-limits.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -27,3 +27,4 @@ export interface ModelLimits { */ 'max_output': number | null; } + diff --git a/lib/packages/fabro-api-client/src/models/model.ts b/lib/packages/fabro-api-client/src/models/model.ts index db637c046..834661c41 100644 --- a/lib/packages/fabro-api-client/src/models/model.ts +++ b/lib/packages/fabro-api-client/src/models/model.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -71,3 +71,6 @@ export interface Model { */ 'configured': boolean; } + + + diff --git a/lib/packages/fabro-api-client/src/models/node-state.ts b/lib/packages/fabro-api-client/src/models/node-state.ts index 31f37ca91..df5eb16a1 100644 --- a/lib/packages/fabro-api-client/src/models/node-state.ts +++ b/lib/packages/fabro-api-client/src/models/node-state.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -13,6 +13,9 @@ */ +// May contain unused imports in some cases +// @ts-ignore +import type { CommandTermination } from './command-termination'; // May contain unused imports in some cases // @ts-ignore import type { NodeStatusRecord } from './node-status-record'; @@ -35,4 +38,8 @@ export interface NodeState { 'stderr_bytes'?: number | null; 'streams_separated'?: boolean | null; 'live_streaming'?: boolean | null; + 'termination'?: CommandTermination | null; } + + + diff --git a/lib/packages/fabro-api-client/src/models/node-status-record.ts b/lib/packages/fabro-api-client/src/models/node-status-record.ts index 03bed6202..6bfb568ac 100644 --- a/lib/packages/fabro-api-client/src/models/node-status-record.ts +++ b/lib/packages/fabro-api-client/src/models/node-status-record.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -26,3 +26,6 @@ export interface NodeStatusRecord { 'failure_reason'?: string | null; 'timestamp': string; } + + + diff --git a/lib/packages/fabro-api-client/src/models/notification-provider-settings.ts b/lib/packages/fabro-api-client/src/models/notification-provider-settings.ts index 9149b9342..2bbcff698 100644 --- a/lib/packages/fabro-api-client/src/models/notification-provider-settings.ts +++ b/lib/packages/fabro-api-client/src/models/notification-provider-settings.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -17,3 +17,4 @@ export interface NotificationProviderSettings { 'channel': string | null; } + diff --git a/lib/packages/fabro-api-client/src/models/notification-route-settings.ts b/lib/packages/fabro-api-client/src/models/notification-route-settings.ts index c41e4cccf..7acc95c38 100644 --- a/lib/packages/fabro-api-client/src/models/notification-route-settings.ts +++ b/lib/packages/fabro-api-client/src/models/notification-route-settings.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -25,3 +25,4 @@ export interface NotificationRouteSettings { 'discord': NotificationProviderSettings | null; 'teams': NotificationProviderSettings | null; } + diff --git a/lib/packages/fabro-api-client/src/models/project-namespace.ts b/lib/packages/fabro-api-client/src/models/project-namespace.ts index 687bccb74..c9a062096 100644 --- a/lib/packages/fabro-api-client/src/models/project-namespace.ts +++ b/lib/packages/fabro-api-client/src/models/project-namespace.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -20,3 +20,4 @@ export interface ProjectNamespace { 'directory': string; 'metadata': { [key: string]: string; }; } + diff --git a/lib/packages/fabro-api-client/src/models/pull-request-settings.ts b/lib/packages/fabro-api-client/src/models/pull-request-settings.ts index 55a41c8ba..9075f2814 100644 --- a/lib/packages/fabro-api-client/src/models/pull-request-settings.ts +++ b/lib/packages/fabro-api-client/src/models/pull-request-settings.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -23,3 +23,6 @@ export interface PullRequestSettings { 'auto_merge': boolean; 'merge_strategy': MergeMethod; } + + + diff --git a/lib/packages/fabro-api-client/src/models/run-agent-settings.ts b/lib/packages/fabro-api-client/src/models/run-agent-settings.ts index 69b9672bc..6c6b7b548 100644 --- a/lib/packages/fabro-api-client/src/models/run-agent-settings.ts +++ b/lib/packages/fabro-api-client/src/models/run-agent-settings.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -24,3 +24,6 @@ export interface RunAgentSettings { 'permissions': AgentPermissions | null; 'mcps': { [key: string]: McpServerSettings; }; } + + + diff --git a/lib/packages/fabro-api-client/src/models/run-billing-totals.ts b/lib/packages/fabro-api-client/src/models/run-billing-totals.ts index f38594452..2b0a84e58 100644 --- a/lib/packages/fabro-api-client/src/models/run-billing-totals.ts +++ b/lib/packages/fabro-api-client/src/models/run-billing-totals.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -51,3 +51,4 @@ export interface RunBillingTotals { */ 'total_usd_micros'?: number | null; } + diff --git a/lib/packages/fabro-api-client/src/models/run-checkpoint-settings.ts b/lib/packages/fabro-api-client/src/models/run-checkpoint-settings.ts index b2f784466..cf998de6c 100644 --- a/lib/packages/fabro-api-client/src/models/run-checkpoint-settings.ts +++ b/lib/packages/fabro-api-client/src/models/run-checkpoint-settings.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -17,3 +17,4 @@ export interface RunCheckpointSettings { 'exclude_globs': Array; } + diff --git a/lib/packages/fabro-api-client/src/models/run-execution-settings.ts b/lib/packages/fabro-api-client/src/models/run-execution-settings.ts index 9ffbf9d54..d78f4d0af 100644 --- a/lib/packages/fabro-api-client/src/models/run-execution-settings.ts +++ b/lib/packages/fabro-api-client/src/models/run-execution-settings.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -25,3 +25,6 @@ export interface RunExecutionSettings { 'approval': ApprovalMode; 'retros': boolean; } + + + diff --git a/lib/packages/fabro-api-client/src/models/run-git-settings.ts b/lib/packages/fabro-api-client/src/models/run-git-settings.ts index 2d390957b..31cbd1d2c 100644 --- a/lib/packages/fabro-api-client/src/models/run-git-settings.ts +++ b/lib/packages/fabro-api-client/src/models/run-git-settings.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -20,3 +20,4 @@ import type { GitAuthorSettings } from './git-author-settings'; export interface RunGitSettings { 'author': GitAuthorSettings | null; } + diff --git a/lib/packages/fabro-api-client/src/models/run-goal-file.ts b/lib/packages/fabro-api-client/src/models/run-goal-file.ts index 096ca898b..e4dbb18cc 100644 --- a/lib/packages/fabro-api-client/src/models/run-goal-file.ts +++ b/lib/packages/fabro-api-client/src/models/run-goal-file.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -27,3 +27,5 @@ export const RunGoalFileTypeEnum = { } as const; export type RunGoalFileTypeEnum = typeof RunGoalFileTypeEnum[keyof typeof RunGoalFileTypeEnum]; + + diff --git a/lib/packages/fabro-api-client/src/models/run-goal-inline.ts b/lib/packages/fabro-api-client/src/models/run-goal-inline.ts index 257d415f9..d3cd8a015 100644 --- a/lib/packages/fabro-api-client/src/models/run-goal-inline.ts +++ b/lib/packages/fabro-api-client/src/models/run-goal-inline.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -27,3 +27,5 @@ export const RunGoalInlineTypeEnum = { } as const; export type RunGoalInlineTypeEnum = typeof RunGoalInlineTypeEnum[keyof typeof RunGoalInlineTypeEnum]; + + diff --git a/lib/packages/fabro-api-client/src/models/run-goal.ts b/lib/packages/fabro-api-client/src/models/run-goal.ts index 560389840..b4743fded 100644 --- a/lib/packages/fabro-api-client/src/models/run-goal.ts +++ b/lib/packages/fabro-api-client/src/models/run-goal.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -24,3 +24,5 @@ import type { RunGoalInline } from './run-goal-inline'; * @type RunGoal */ export type RunGoal = RunGoalFile | RunGoalInline; + + diff --git a/lib/packages/fabro-api-client/src/models/run-interviews-settings.ts b/lib/packages/fabro-api-client/src/models/run-interviews-settings.ts index f571a1812..2e935d62e 100644 --- a/lib/packages/fabro-api-client/src/models/run-interviews-settings.ts +++ b/lib/packages/fabro-api-client/src/models/run-interviews-settings.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -23,3 +23,4 @@ export interface RunInterviewsSettings { 'discord': InterviewProviderSettings | null; 'teams': InterviewProviderSettings | null; } + diff --git a/lib/packages/fabro-api-client/src/models/run-mode.ts b/lib/packages/fabro-api-client/src/models/run-mode.ts index 4a8ab07e2..c2391069c 100644 --- a/lib/packages/fabro-api-client/src/models/run-mode.ts +++ b/lib/packages/fabro-api-client/src/models/run-mode.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -21,3 +21,6 @@ export const RunMode = { } as const; export type RunMode = typeof RunMode[keyof typeof RunMode]; + + + diff --git a/lib/packages/fabro-api-client/src/models/run-model-settings.ts b/lib/packages/fabro-api-client/src/models/run-model-settings.ts index d6bbf1994..88a32b1f5 100644 --- a/lib/packages/fabro-api-client/src/models/run-model-settings.ts +++ b/lib/packages/fabro-api-client/src/models/run-model-settings.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -19,3 +19,4 @@ export interface RunModelSettings { 'name': string | null; 'fallbacks': Array; } + diff --git a/lib/packages/fabro-api-client/src/models/run-namespace.ts b/lib/packages/fabro-api-client/src/models/run-namespace.ts index 48b386111..cb8ec4e8d 100644 --- a/lib/packages/fabro-api-client/src/models/run-namespace.ts +++ b/lib/packages/fabro-api-client/src/models/run-namespace.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -78,3 +78,4 @@ export interface RunNamespace { 'pull_request': PullRequestSettings | null; 'artifacts': ArtifactsSettings; } + diff --git a/lib/packages/fabro-api-client/src/models/run-prepare-settings.ts b/lib/packages/fabro-api-client/src/models/run-prepare-settings.ts index d1f3a1911..aacc1677f 100644 --- a/lib/packages/fabro-api-client/src/models/run-prepare-settings.ts +++ b/lib/packages/fabro-api-client/src/models/run-prepare-settings.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -18,3 +18,4 @@ export interface RunPrepareSettings { 'commands': Array; 'timeout_ms': number; } + diff --git a/lib/packages/fabro-api-client/src/models/run-projection.ts b/lib/packages/fabro-api-client/src/models/run-projection.ts index 93b2c5815..fed006842 100644 --- a/lib/packages/fabro-api-client/src/models/run-projection.ts +++ b/lib/packages/fabro-api-client/src/models/run-projection.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -64,3 +64,6 @@ export interface RunProjection { */ 'nodes': { [key: string]: NodeState; }; } + + + diff --git a/lib/packages/fabro-api-client/src/models/run-sandbox-settings.ts b/lib/packages/fabro-api-client/src/models/run-sandbox-settings.ts index 5cb285cbd..749170056 100644 --- a/lib/packages/fabro-api-client/src/models/run-sandbox-settings.ts +++ b/lib/packages/fabro-api-client/src/models/run-sandbox-settings.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -32,3 +32,4 @@ export interface RunSandboxSettings { 'docker': DockerSettings | null; 'daytona': DaytonaSettings | null; } + diff --git a/lib/packages/fabro-api-client/src/models/run-scm-settings.ts b/lib/packages/fabro-api-client/src/models/run-scm-settings.ts index f0970dcec..8258b5fda 100644 --- a/lib/packages/fabro-api-client/src/models/run-scm-settings.ts +++ b/lib/packages/fabro-api-client/src/models/run-scm-settings.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -20,3 +20,4 @@ export interface RunScmSettings { 'repository': string | null; 'github': object | null; } + diff --git a/lib/packages/fabro-api-client/src/models/run-spec.ts b/lib/packages/fabro-api-client/src/models/run-spec.ts index 5968d1bc2..1c28f71f0 100644 --- a/lib/packages/fabro-api-client/src/models/run-spec.ts +++ b/lib/packages/fabro-api-client/src/models/run-spec.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -40,3 +40,4 @@ export interface RunSpec { 'fork_source_ref'?: ForkSourceRef | null; 'in_place': boolean; } + diff --git a/lib/packages/fabro-api-client/src/models/run-stage.ts b/lib/packages/fabro-api-client/src/models/run-stage.ts index 2d2959ffa..c98ec9bee 100644 --- a/lib/packages/fabro-api-client/src/models/run-stage.ts +++ b/lib/packages/fabro-api-client/src/models/run-stage.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -39,3 +39,6 @@ export interface RunStage { */ 'dot_id'?: string; } + + + diff --git a/lib/packages/fabro-api-client/src/models/stage-outcome.ts b/lib/packages/fabro-api-client/src/models/stage-outcome.ts index 1295b7ec4..2c7a1f894 100644 --- a/lib/packages/fabro-api-client/src/models/stage-outcome.ts +++ b/lib/packages/fabro-api-client/src/models/stage-outcome.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -26,3 +26,6 @@ export const StageOutcome = { } as const; export type StageOutcome = typeof StageOutcome[keyof typeof StageOutcome]; + + + diff --git a/lib/packages/fabro-api-client/src/models/tls-mode.ts b/lib/packages/fabro-api-client/src/models/tls-mode.ts index 302a16f11..07b36b5d1 100644 --- a/lib/packages/fabro-api-client/src/models/tls-mode.ts +++ b/lib/packages/fabro-api-client/src/models/tls-mode.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -22,3 +22,6 @@ export const TlsMode = { } as const; export type TlsMode = typeof TlsMode[keyof typeof TlsMode]; + + + diff --git a/lib/packages/fabro-api-client/src/models/workflow-namespace.ts b/lib/packages/fabro-api-client/src/models/workflow-namespace.ts index 5501b5b21..17705c189 100644 --- a/lib/packages/fabro-api-client/src/models/workflow-namespace.ts +++ b/lib/packages/fabro-api-client/src/models/workflow-namespace.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -20,3 +20,4 @@ export interface WorkflowNamespace { 'graph': string; 'metadata': { [key: string]: string; }; } + diff --git a/lib/packages/fabro-api-client/src/models/workflow-settings.ts b/lib/packages/fabro-api-client/src/models/workflow-settings.ts index 7f1756c5f..c33c911d1 100644 --- a/lib/packages/fabro-api-client/src/models/workflow-settings.ts +++ b/lib/packages/fabro-api-client/src/models/workflow-settings.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -24,10 +24,11 @@ import type { RunNamespace } from './run-namespace'; import type { WorkflowNamespace } from './workflow-namespace'; /** - * The persisted dense `WorkflowSettings` snapshot used for a specific run. This matches the resolved run settings recorded at launch time. + * The persisted dense `WorkflowSettings` snapshot used for a specific run. This matches the resolved run settings recorded at launch time. */ export interface WorkflowSettings { 'project': ProjectNamespace; 'workflow': WorkflowNamespace; 'run': RunNamespace; } + diff --git a/lib/packages/fabro-api-client/src/models/worktree-mode.ts b/lib/packages/fabro-api-client/src/models/worktree-mode.ts index 94a93c9ae..96589a739 100644 --- a/lib/packages/fabro-api-client/src/models/worktree-mode.ts +++ b/lib/packages/fabro-api-client/src/models/worktree-mode.ts @@ -5,7 +5,7 @@ * HTTP API for managing Fabro workflow run executions. * * The version of the OpenAPI document: 0.1.0 - * + * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). * https://openapi-generator.tech @@ -23,3 +23,6 @@ export const WorktreeMode = { } as const; export type WorktreeMode = typeof WorktreeMode[keyof typeof WorktreeMode]; + + +