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.
This commit is contained in:
Bryan Helmkamp 2026-04-30 15:11:34 -04:00
parent a31a7295f2
commit e50df2b58b
No known key found for this signature in database
106 changed files with 842 additions and 478 deletions

View file

@ -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<TurnType, { kind: "command" }>;
}) {
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({
<div className="ml-auto flex items-center gap-2">
{turn.running ? (
<StatusPill tone="running">Running</StatusPill>
) : turn.timedOut ? (
) : turn.termination === "timed_out" ? (
<StatusPill tone="failed">Timed out</StatusPill>
) : turn.termination === "cancelled" ? (
<StatusPill tone="failed">Cancelled</StatusPill>
) : (
<>
<StatusPill tone={failed ? "failed" : "success"}>

View file

@ -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.

View file

@ -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 {

View file

@ -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<WebFetchSummarizer>) -> 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: "<html><body><h1>hello</h1></body></html>".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: "<html><body><p>Lots of content about Rust...</p></body></html>"
.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 {
"<html><body><p>Rust is a systems programming language.</p></body></html>"
.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: "<html><body><p>Page content</p></body></html>".into(),
stderr: String::new(),
exit_code: 0,
timed_out: false,
exit_code: Some(0),
termination: CommandTermination::Exited,
duration_ms: 100,
},
..Default::default()

View file

@ -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", &[]),

View file

@ -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,
};

View file

@ -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::<ApiCommandTermination, CommandTermination>();
}
#[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::<ApiCommandTermination>(json!("exited")).unwrap(),
CommandTermination::Exited
);
assert_eq!(
serde_json::from_value::<ApiCommandTermination>(json!("timed_out")).unwrap(),
CommandTermination::TimedOut
);
assert_eq!(
serde_json::from_value::<ApiCommandTermination>(json!("cancelled")).unwrap(),
CommandTermination::Cancelled
);
}
fn assert_same_type<T: 'static, U: 'static>() {
assert_eq!(
TypeId::of::<T>(),
TypeId::of::<U>(),
"{} should be the same type as {}",
type_name::<T>(),
type_name::<U>()
);
}

View file

@ -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();

View file

@ -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}")),
},

View file

@ -560,7 +560,7 @@ async fn ensure_remote_dir(sandbox: &Arc<dyn Sandbox>, 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(

View file

@ -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
)));
}

View file

@ -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"

View file

@ -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<i32>,
termination: CommandTermination,
duration_ms: u64,
stderr: String,
stdout: String,
@ -70,8 +72,8 @@ impl Error {
pub fn exec(
label: impl Into<String>,
exit_code: i32,
timed_out: bool,
exit_code: Option<i32>,
termination: CommandTermination,
duration_ms: u64,
stderr: impl Into<String>,
stdout: impl Into<String>,
@ -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<i32>) -> String {
exit_code.map_or_else(|| "none".to_string(), |code| code.to_string())
}
pub type Result<T> = std::result::Result<T, Error>;
#[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:"));
}

View file

@ -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();
}

View file

@ -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<usize>, limit: Option
pub struct ExecResult {
pub stdout: String,
pub stderr: String,
pub exit_code: i32,
pub timed_out: bool,
pub exit_code: Option<i32>,
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<String>) -> 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| {

View file

@ -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,
})
}

View file

@ -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()

View file

@ -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,
}
}

View file

@ -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,

View file

@ -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}"))
})?);

View file

@ -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))

View file

@ -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 {

View file

@ -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;

View file

@ -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<i32>,
pub duration_ms: u64,
pub timed_out: bool,
pub termination: CommandTermination,
#[serde(default)]
pub stdout_bytes: u64,
#[serde(default)]

View file

@ -57,6 +57,8 @@ pub struct NodeState {
pub streams_separated: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub live_streaming: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub termination: Option<crate::CommandTermination>,
}
impl RunProjection {

View file

@ -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",

View file

@ -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,
})
}

View file

@ -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<i32>,
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,

View file

@ -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,

View file

@ -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);
}
}

View file

@ -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,
}));

View file

@ -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<String> = 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,
}
}

View file

@ -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 {

View file

@ -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,
});
}

View file

@ -542,6 +542,7 @@ mod tests {
stderr_bytes: None,
streams_separated: None,
live_streaming: None,
termination: None,
});
let dump = RunDump::from_projection(&projection);

View file

@ -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,
}
}

View file

@ -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()
)
}
}

View file

@ -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::<Vec<_>>()
.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;

View file

@ -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<fabro_agent::ExecResult> {
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,
})
}

View file

@ -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

View file

@ -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));
}
}

View file

@ -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\&#39;s workflow graph, serialized as &#x60;node_id@visit&#x60;.
* @param {File} body
* @param {File} body
* @param {string} [filename] Relative artifact path for &#x60;application/octet-stream&#x60; 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\&#39;s workflow graph, serialized as &#x60;node_id@visit&#x60;.
* @param {File} body
* @param {File} body
* @param {string} [filename] Relative artifact path for &#x60;application/octet-stream&#x60; 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\&#39;s workflow graph, serialized as &#x60;node_id@visit&#x60;.
* @param {File} body
* @param {File} body
* @param {string} [filename] Relative artifact path for &#x60;application/octet-stream&#x60; 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\&#39;s workflow graph, serialized as &#x60;node_id@visit&#x60;.
* @param {File} body
* @param {File} body
* @param {string} [filename] Relative artifact path for &#x60;application/octet-stream&#x60; 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));
}
}

View file

@ -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.

View file

@ -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));
}
}

View file

@ -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];

View file

@ -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;
}

View file

@ -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];

View file

@ -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<string>;
}

View file

@ -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;
}

View file

@ -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];

View file

@ -27,3 +27,4 @@ export interface CreateRunPullRequestRequest {
*/
'model'?: string | null;
}

View file

@ -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<string>;
}

View file

@ -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;
}

View file

@ -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;

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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 {
/**

View file

@ -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;
}

View file

@ -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];

View file

@ -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];

View file

@ -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;

View file

@ -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;

View file

@ -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;
}

View file

@ -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];

View file

@ -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];

View file

@ -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';

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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];

View file

@ -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];

View file

@ -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];

View file

@ -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;

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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; };
}

View file

@ -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;
}

View file

@ -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; };
}

View file

@ -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;
}

View file

@ -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<string>;
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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];

View file

@ -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];

View file

@ -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;

View file

@ -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;
}

View file

@ -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];

View file

@ -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<string>;
}

View file

@ -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;
}

View file

@ -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<string>;
'timeout_ms': number;
}

View file

@ -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; };
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}

Some files were not shown because too many files have changed in this diff Show more