use leviath_core::Blueprint;
use leviath_core::blueprint::{StageMode, UnattendedPolicy};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Held {
pub stage: String,
pub name: String,
}
pub fn held_points(blueprint: &Blueprint) -> Vec<Held> {
blueprint
.stages
.iter()
.flat_map(|stage| {
let points = match &stage.mode {
StageMode::InteractivePoints { points } => points.as_slice(),
_ => &[],
};
points
.iter()
.filter(|p| p.unattended == UnattendedPolicy::Ask)
.map(|p| Held {
stage: stage.name.clone(),
name: p.name.clone(),
})
})
.collect()
}
pub fn held_tools(blueprint: &Blueprint) -> Vec<Held> {
blueprint
.stages
.iter()
.flat_map(|stage| {
stage
.required_tools
.iter()
.filter(|t| {
leviath_runtime::dynamic_interaction::BLOCKING_INTERACTION_TOOLS
.contains(&leviath_tools::canonical_tool_name(t))
})
.map(|tool| Held {
stage: stage.name.clone(),
name: tool.clone(),
})
})
.collect()
}
fn human_timeout(secs: u64) -> String {
match secs {
0 => "indefinitely".to_string(),
s if s % 3600 == 0 => format!("{}h", s / 3600),
s if s % 60 == 0 => format!("{}m", s / 60),
s => format!("{s}s"),
}
}
pub fn preflight_lines(blueprint: &Blueprint, timeout_secs: u64) -> Vec<String> {
let points = held_points(blueprint);
let tools = held_tools(blueprint);
if points.is_empty() && tools.is_empty() {
return Vec::new();
}
let mut lines = Vec::new();
let total = points.len() + tools.len();
let plural = if total == 1 { "" } else { "s" };
lines.push(format!(
"--yolo will still stop for a person at {total} checkpoint{plural}:"
));
for p in &points {
lines.push(format!(" {}: {}", p.stage, p.name));
}
for t in &tools {
lines.push(format!(" {}: {} (if the model calls it)", t.stage, t.name));
}
lines.push(match timeout_secs {
0 => " nothing expires these; the run waits until somebody answers".to_string(),
secs => format!(
" unanswered after {}, the run stops with an error; `lev respond` lists them",
human_timeout(secs)
),
});
lines
}
#[cfg(test)]
mod tests;