use crate::prompt::child_dispatch::{ChildDispatchRequest, run};
use crate::prompt::dispatch::MESSAGES_DIR;
use crate::prompt::dispatch::assembler::assemble;
use crate::prompt::inbox::Launcher;
use crate::template::{GitRunner, RealGit};
use crate::workspace::{self, fixture};
use std::io;
use std::path::{Path, PathBuf};
const PARENT: &str = "20260101-p1";
const USER_TAIL: &str = "spawn a subagent to analyze the files under the XDG location";
const CHILD_GOAL: &str = "Analyze the files under the XDG location and report.\n";
struct NoopLauncher;
impl Launcher for NoopLauncher {
fn launch(&self, _ws: &Path, _agent: &str) -> io::Result<()> {
Ok(())
}
}
fn parent_with_dialog(ws: &Path) -> PathBuf {
let g = RealGit::new();
let parent_wt = fixture::spawn_root(ws, PARENT);
std::fs::create_dir_all(parent_wt.join(MESSAGES_DIR)).unwrap();
std::fs::write(parent_wt.join(MESSAGES_DIR).join("001-user.md"), USER_TAIL).unwrap();
std::fs::create_dir_all(parent_wt.join("summary")).unwrap();
std::fs::write(parent_wt.join("summary/001.md"), "earlier context\n").unwrap();
std::fs::create_dir_all(parent_wt.join("skills")).unwrap();
std::fs::write(parent_wt.join("skills/loaded.md"), "a spent body\n").unwrap();
g.run(&parent_wt, &["add", "-A"]).unwrap();
g.run(&parent_wt, &["commit", "-m", "transcript [p1]"])
.unwrap();
parent_wt
}
fn dispatch(ws: &Path, parent_wt: &Path, role: &str) -> String {
run(
&ChildDispatchRequest {
repo: ws,
parent_branch: PARENT,
parent_worktree: parent_wt,
role,
goal: CHILD_GOAL,
name: None,
fork_point: None,
pins: crate::prompt::PinnedDocs::none(),
},
&RealGit::new(),
&crate::prompt::SystemClock,
&crate::prompt::NanoIdGen,
&NoopLauncher,
)
.unwrap()
}
fn tree_of(ws: &Path, id: &str) -> String {
RealGit::new()
.run_capture(
&workspace::repo_git(ws),
&["ls-tree", "-r", "--name-only", &workspace::agent_ref(id)],
)
.unwrap()
}
#[test]
fn a_child_never_opens_on_its_dispatchers_dialog() {
let (_h, ws) = fixture::workspace();
let parent_wt = parent_with_dialog(&ws);
let child = dispatch(&ws, &parent_wt, crate::prompt::WORKER_ROLE);
let listing = tree_of(&ws, &child);
for gone in ["messages/001-user.md", "summary/001.md", "skills/loaded.md"] {
assert!(!listing.lines().any(|l| l == gone), "{gone} in {listing}");
}
let child_wt = workspace::agent_worktree(&ws, &child);
let history = assemble(&child_wt, None).unwrap();
assert!(history.is_empty(), "got {history:?}");
let deposited = std::fs::read_dir(crate::prompt::inbox::inbox_dir(&ws, &child))
.unwrap()
.map(|e| std::fs::read_to_string(e.unwrap().path()).unwrap())
.collect::<String>();
assert!(deposited.contains(CHILD_GOAL.trim()), "got {deposited:?}");
assert!(!deposited.contains(USER_TAIL), "got {deposited:?}");
let parent_listing = tree_of(&ws, PARENT);
assert!(
parent_listing.lines().any(|l| l == "messages/001-user.md"),
"{parent_listing}"
);
}
#[test]
fn a_compactor_keeps_the_dialog_it_exists_to_compact() {
let (_h, ws) = fixture::workspace();
let parent_wt = parent_with_dialog(&ws);
let child = dispatch(&ws, &parent_wt, crate::prompt::compactor::COMPACTOR_ROLE);
let listing = tree_of(&ws, &child);
for kept in ["messages/001-user.md", "summary/001.md", "skills/loaded.md"] {
assert!(
listing.lines().any(|l| l == kept),
"{kept} not in {listing}"
);
}
}