use super::{assert_prefixed, noop_editor, with_fx};
use crate::cmd::{Outcome, dispatch, message, prompt};
use crate::prompt::inbox::{inbox_dir, try_acquire};
use crate::template::{GitRunner, RealGit};
use crate::workspace::{agent_ids, agent_name, fixture};
use std::path::{Path, PathBuf};
const PARENT: &str = "20260101T000000Z-aaaaaaaa";
fn dispatch_child(ws: &Path, name: Option<&str>) -> Result<Outcome, super::Error> {
with_fx("true", b"", &noop_editor, |fx| {
dispatch::run(
dispatch::Args {
role: "worker".into(),
repo: ws.to_path_buf(),
branch: PARENT.into(),
goal: Some("do the thing".into()),
from: None,
name: name.map(str::to_owned),
pin: vec![],
cwd: None,
},
fx,
)
})
.0
}
fn child_of(ws: &Path) -> String {
let mut ids = agent_ids(ws, &RealGit::new()).unwrap();
ids.retain(|id| id != PARENT);
assert_eq!(ids.len(), 1, "exactly one child was forked");
ids.remove(0)
}
#[test]
fn a_dispatched_child_wears_its_name_and_message_resolves_it() {
let (_h, ws) = fixture::workspace();
fixture::spawn_root(&ws, PARENT);
dispatch_child(&ws, Some("pale-otter")).unwrap();
let child = child_of(&ws);
assert_eq!(
agent_name::read(&ws, &child, &RealGit::new()).as_deref(),
Some("pale-otter"),
"the name's one home is the child's own dispatch commit",
);
let _held = try_acquire(&inbox_dir(&ws, &child))
.unwrap()
.expect("free lease");
let (r, ..) = with_fx("true", b"", &noop_editor, |fx| {
message::run(
message::Args {
workspace: ws.clone(),
agent: "pale-otter".into(),
content: "steering".into(),
},
fx,
)
});
assert!(matches!(r.unwrap(), Outcome::Quiet));
let ours = std::fs::read_dir(inbox_dir(&ws, &child))
.unwrap()
.flatten()
.filter(|e| e.file_name().to_string_lossy().starts_with("user-"))
.count();
assert_eq!(ours, 1, "the name addressed the child's own inbox");
}
#[test]
fn a_name_a_living_agent_wears_is_refused_at_both_creation_verbs_and_forks_nothing() {
let (_h, ws) = fixture::workspace();
fixture::spawn_root(&ws, PARENT);
dispatch_child(&ws, Some("pale-otter")).unwrap();
let before = agent_ids(&ws, &RealGit::new()).unwrap().len();
let err = dispatch_child(&ws, Some("pale-otter")).unwrap_err();
assert!(err.to_string().contains("already worn"), "{err}");
assert_prefixed(err, "dispatch worker");
let (r, ..) = with_fx("true", b"", &noop_editor, |fx| {
prompt::run(
prompt::Args {
repo: ws.clone(),
message: "hi".into(),
from: None,
config: None,
name: Some("pale-otter".into()),
pin: vec![],
cwd: None,
},
fx,
)
});
let err = r.unwrap_err();
assert!(err.to_string().contains("already worn"), "{err}");
assert_prefixed(err, "prompt");
assert_eq!(
agent_ids(&ws, &RealGit::new()).unwrap().len(),
before,
"a refused name leaves no branch behind",
);
}
#[test]
fn an_ambiguous_name_is_refused_with_its_candidates_and_deposits_nothing() {
let (_h, ws) = fixture::workspace();
let git = RealGit::new();
for id in [PARENT, "20260102T000000Z-bbbbbbbb"] {
let wt = fixture::spawn_root(&ws, id);
agent_name::settle(&wt, Some("pale-otter"), &git).unwrap();
git.run(&wt, &["commit", "-m", "settle name"]).unwrap();
}
let (r, ..) = with_fx("true", b"", &noop_editor, |fx| {
message::run(
message::Args {
workspace: ws.clone(),
agent: "pale-otter".into(),
content: "steering".into(),
},
fx,
)
});
let err = r.unwrap_err();
let msg = err.to_string();
assert!(msg.contains("ambiguous"), "{msg}");
assert!(msg.contains(PARENT), "{msg}");
assert!(msg.contains("20260102T000000Z-bbbbbbbb"), "{msg}");
assert_prefixed(err, "message");
assert!(
!inbox_dir(&ws, "pale-otter").exists(),
"an ambiguous needle deposits nothing"
);
}
#[test]
fn an_unknown_name_gets_the_shared_existence_decline_and_it_mentions_names() {
let (_h, ws) = fixture::workspace();
let (r, ..) = with_fx("true", b"", &noop_editor, |fx| {
message::run(
message::Args {
workspace: PathBuf::from(&ws),
agent: "grey-heron".into(),
content: "hi".into(),
},
fx,
)
});
let err = r.unwrap_err();
let msg = err.to_string();
assert!(msg.contains("no agent \"grey-heron\""), "{msg}");
assert!(msg.contains("by id or unique name"), "{msg}");
}
#[test]
fn a_child_of_a_named_parent_wears_its_own_mint_not_its_parents_namesake() {
let (_h, ws) = fixture::workspace();
fixture::spawn_root(&ws, PARENT);
dispatch_child(&ws, Some("pale-otter")).unwrap();
let named = child_of(&ws);
with_fx("true", b"", &noop_editor, |fx| {
dispatch::run(
dispatch::Args {
role: "worker".into(),
repo: ws.clone(),
branch: named.clone(),
goal: Some("go".into()),
from: None,
name: None,
pin: vec![],
cwd: None,
},
fx,
)
})
.0
.unwrap();
let git = RealGit::new();
let grandchild = agent_ids(&ws, &git)
.unwrap()
.into_iter()
.find(|id| id.starts_with(&format!("{named}-")))
.expect("the grandchild forked");
let minted = agent_name::read(&ws, &grandchild, &git)
.expect("an omitted name is minted — no fork ends nameless (yog bl-aca4)");
assert_ne!(
minted, "pale-otter",
"a child does not wear the name it inherited",
);
assert!(
minted.chars().all(|c| c.is_ascii_lowercase()),
"a minted name is one wordlist word, got {minted:?}",
);
assert_eq!(
agent_name::read(&ws, &named, &git).as_deref(),
Some("pale-otter"),
"and its parent keeps its own",
);
}