use super::*;
use serde_json::json;
use tempfile::TempDir;
const PARENT: &str = "20260101-a1";
const CHILD: &str = "20260101-a1-20260102-b2";
const SIBLING: &str = "20260101-a1-20260102-c3";
fn transcript(entries: &[(&str, &str)]) -> TempDir {
let wt = TempDir::new().unwrap();
std::fs::create_dir_all(wt.path().join("messages")).unwrap();
for (name, body) in entries {
std::fs::write(wt.path().join("messages").join(name), body).unwrap();
}
wt
}
fn prompt(from: &str) -> String {
format!("---\nfrom: {from}\ndeposited_at: t\n---\ndo it\n")
}
fn result(from: &str) -> String {
format!(
"---\nfrom: {from}\ndeposited_at: t\nepitaph: final-response\nterminal_ref: sha\n---\nok\n"
)
}
fn reply_to(wt: &TempDir, agent: &str) -> Option<String> {
recipient(wt.path(), agent, Epitaph::FinalResponse).unwrap()
}
#[test]
fn terminal_text_joins_text_blocks_and_skips_non_text() {
let blocks = vec![
Content::Text("hello ".into()),
Content::Thinking {
text: "ignored".into(),
signature: None,
id: None,
encrypted_content: None,
},
Content::ToolUse {
id: "t1".into(),
name: "bash".into(),
input: json!({}),
signature: None,
},
Content::Text("world".into()),
];
assert_eq!(terminal_text(&blocks).as_deref(), Some("hello world"));
}
#[test]
fn terminal_text_is_none_when_agent_did_not_speak() {
let blocks = vec![Content::ToolUse {
id: "t1".into(),
name: "bash".into(),
input: json!({}),
signature: None,
}];
assert_eq!(terminal_text(&blocks), None);
assert_eq!(terminal_text(&[]), None);
}
#[test]
fn a_reply_answers_the_dispatcher_when_the_dispatch_is_the_last_prompt() {
let wt = transcript(&[
("001-20260101-a1.md", &prompt(PARENT)),
("002-claude-sonnet-5.json", "[]"),
("003-tool.json", "[]"),
]);
assert_eq!(reply_to(&wt, CHILD).as_deref(), Some(PARENT));
}
#[test]
fn a_reply_answers_the_operator_by_depositing_nothing() {
let wt = transcript(&[
("001-20260101-a1.md", &prompt(PARENT)),
("002-user.md", &prompt("user")),
]);
assert_eq!(reply_to(&wt, CHILD), None);
}
#[test]
fn a_reply_answers_a_sibling_that_prompted_it() {
let wt = transcript(&[
("001-20260101-a1.md", &prompt(PARENT)),
("002-20260101-a1-20260102-c3.md", &prompt(SIBLING)),
]);
assert_eq!(reply_to(&wt, CHILD).as_deref(), Some(SIBLING));
}
#[test]
fn a_returning_childs_result_is_not_a_prompt() {
let wt = transcript(&[
("001-user.md", &prompt("user")),
("002-20260101-a1-20260102-b2.md", &result(CHILD)),
]);
assert_eq!(reply_to(&wt, PARENT), None);
}
#[test]
fn an_agents_own_note_to_itself_is_not_a_prompt() {
let wt = transcript(&[
("001-20260101-a1.md", &prompt(PARENT)),
("002-20260101-a1-20260102-b2.md", &prompt(CHILD)),
]);
assert_eq!(reply_to(&wt, CHILD).as_deref(), Some(PARENT));
}
#[test]
fn a_reply_with_no_surviving_prompt_falls_back_to_the_dispatcher() {
let empty = transcript(&[
("001-claude-sonnet-5.json", "[]"),
("noseq.md", "x"),
("abc-user.md", "x"),
("001-.md", "x"),
]);
assert_eq!(reply_to(&empty, CHILD).as_deref(), Some(PARENT));
assert_eq!(reply_to(&empty, PARENT), None);
let bare = TempDir::new().unwrap();
assert_eq!(
recipient(bare.path(), CHILD, Epitaph::FinalResponse).unwrap(),
Some(PARENT.to_string())
);
}
#[test]
fn an_obituary_reports_to_the_dispatcher_whoever_prompted_last() {
let wt = transcript(&[("001-user.md", &prompt("user"))]);
for epitaph in [Epitaph::Stopped, Epitaph::BudgetExhausted, Epitaph::Died] {
assert_eq!(
recipient(wt.path(), CHILD, epitaph).unwrap().as_deref(),
Some(PARENT),
"{epitaph:?} is an obituary"
);
assert_eq!(recipient(wt.path(), PARENT, epitaph).unwrap(), None);
}
}
#[test]
fn a_messages_path_that_is_not_a_directory_surfaces() {
let wt = TempDir::new().unwrap();
std::fs::write(wt.path().join("messages"), "not a directory").unwrap();
assert!(recipient(wt.path(), CHILD, Epitaph::FinalResponse).is_err());
}
#[test]
fn an_unreadable_transcript_entry_surfaces_rather_than_misaddressing() {
let wt = transcript(&[]);
std::fs::create_dir(wt.path().join("messages/001-user.md")).unwrap();
assert!(recipient(wt.path(), CHILD, Epitaph::FinalResponse).is_err());
}