use super::*;
use crate::git_tree::{Agent, AgentState, GitTree, Stream};
use crate::opslog::{DETACHED_EXIT, OpEntry, Origin};
const WS: &str = "/ws/cobalt";
const MINTED: &str = "vanished-heron";
const NOW: i64 = 10_000;
const GRACE: Duration = Duration::from_secs(20);
fn agent(id: &str, name: Option<&str>, state: AgentState, last_action: i64) -> Agent {
Agent {
branch_name: format!("agents/{id}"),
agent_id: id.to_owned(),
tip_oid: format!("tip-{id}"),
tip_short_oid: "tip".into(),
tip_timestamp_unix: 0,
last_action_unix: last_action,
messages: 0,
call_start_unix: None,
steps: vec![],
preview: None,
stream: Stream::default(),
tool_calls: vec![],
state,
truncated: false,
failure: None,
flagged: None,
state_uncertain: false,
pending: vec![],
conflicted_oid: None,
budget_oid: None,
abandoned_oid: None,
notify_oid: None,
held: None,
goal_ball: None,
name: name.map(str::to_owned),
goal_name: None,
}
}
fn forest(agents: Vec<Agent>) -> HashMap<PathBuf, GitTree> {
HashMap::from([(
PathBuf::from(WS),
GitTree {
commits: vec![],
agents,
},
)])
}
fn nothing() -> HashMap<PathBuf, GitTree> {
HashMap::new()
}
fn entry(ts: &str, argv: &[&str], exit: i32) -> OpEntry {
OpEntry {
ts: ts.into(),
argv: argv.iter().map(|a| (*a).to_owned()).collect(),
cwd: WS.into(),
exit,
stdout: String::new(),
stderr: String::new(),
origin: Origin::Conversation,
}
}
fn prompt(ts: &str, name: &str) -> OpEntry {
entry(
ts,
&["litany", PROMPT, NAME_FLAG, name, WS, "the goal"],
DETACHED_EXIT,
)
}
fn advance(ts: &str, agent: &str) -> OpEntry {
entry(ts, &["litany", ADVANCE, WS, agent], DETACHED_EXIT)
}
#[test]
fn a_start_that_left_no_conversation_is_stillborn() {
assert!(stillborn(
&forest(vec![agent(
"c-1",
Some("other-name"),
AgentState::Quiescent,
1
)]),
&prompt("100", MINTED),
NOW,
GRACE
));
}
#[test]
fn a_start_whose_conversation_is_there_is_not() {
assert!(!stillborn(
&forest(vec![agent("c-1", Some(MINTED), AgentState::Quiescent, 200)]),
&prompt("100", MINTED),
NOW,
GRACE
));
}
#[test]
fn a_driven_target_is_never_stillborn() {
for state in [AgentState::Live, AgentState::InFlight] {
assert!(
!stillborn(
&forest(vec![agent("c-1", Some(MINTED), state, 1)]),
&prompt("100", MINTED),
NOW,
GRACE
),
"{state:?} is a driver at work"
);
}
}
#[test]
fn a_quiet_target_that_has_not_acted_since_the_launch_is_stillborn() {
assert!(stillborn(
&forest(vec![agent("c-1", Some(MINTED), AgentState::Stopped, 99)]),
&prompt("100", MINTED),
NOW,
GRACE
));
}
#[test]
fn a_resume_is_judged_on_the_agent_it_named() {
let dead = forest(vec![agent("c-1", None, AgentState::Stopped, 99)]);
assert!(stillborn(&dead, &advance("100", "c-1"), NOW, GRACE));
let moved = forest(vec![agent("c-1", None, AgentState::Stopped, 101)]);
assert!(!stillborn(&moved, &advance("100", "c-1"), NOW, GRACE));
assert!(stillborn(&moved, &advance("100", "c-9"), NOW, GRACE));
}
#[test]
fn a_launch_inside_the_grace_window_has_no_verdict() {
let world = forest(vec![]);
assert!(!stillborn(&world, &prompt("100", MINTED), 119, GRACE));
assert!(stillborn(&world, &prompt("100", MINTED), 120, GRACE));
}
#[test]
fn a_world_yog_cannot_read_answers_nothing() {
assert!(!stillborn(¬hing(), &prompt("100", MINTED), NOW, GRACE));
}
#[test]
fn only_a_detached_line_is_asked() {
let piped = entry("100", &["litany", PROMPT, NAME_FLAG, MINTED, WS, "g"], 0);
assert!(!stillborn(&forest(vec![]), &piped, NOW, GRACE));
}
#[test]
fn a_row_that_names_no_target_answers_nothing() {
let world = forest(vec![]);
for argv in [
vec!["litany", "prime", WS, "x"],
vec!["litany", PROMPT, WS, "the goal"],
vec!["litany", PROMPT, NAME_FLAG],
vec!["litany", ADVANCE, WS],
vec!["litany"],
] {
let e = entry("100", &argv, DETACHED_EXIT);
assert!(!stillborn(&world, &e, NOW, GRACE), "{argv:?}");
}
assert!(!stillborn(&world, &prompt("TS", MINTED), NOW, GRACE));
}