//! World-loaded subagents and a SIGKILL at the reporter phase boundary. use sausage::{harness::run_module, world::World}; use std::{ path::Path, process::Command, time::{Duration, Instant}, }; const BUNDLE: &str = include_str!("../../js/subagents-test-bundle.js"); fn prepare(dir: &Path) -> anyhow::Result<()> { let disk = dir.join("world"); let repo = disk.join("src/sausage"); if repo.join(".git").exists() { return Ok(()); } let source = Path::new(env!("CARGO_MANIFEST_DIR")).join(".."); let target = crate::common::build_git(); std::fs::create_dir_all(disk.join("bin"))?; for binary in ["git", "tar"] { std::fs::copy( target.join(format!("wasm32-wasip2/release/{binary}.wasm")), disk.join(format!("bin/{binary}.wasm")), )?; } for directory in [ "extensions/subagents", "extensions/models", "extensions/core", "agents", ] { std::fs::create_dir_all(repo.join(directory))?; } for extension in ["subagents", "models"] { for entry in std::fs::read_dir(source.join("extensions").join(extension))? { let entry = entry?; if entry.path().extension().is_some_and(|ext| ext == "ts") { std::fs::copy( entry.path(), repo.join("extensions") .join(extension) .join(entry.file_name()), )?; } } } std::fs::create_dir_all(repo.join("extensions/probe"))?; std::fs::write( repo.join("extensions/probe/server.ts"), "export default host => { globalThis.subagentService = host.need({name:'subagents'}); host.implement({name:'models.pin'}, {pin:async()=>globalThis.childPin}); return {name:'probe'}; }", )?; std::fs::copy( source.join("extensions/core/agents.ts"), repo.join("extensions/core/agents.ts"), )?; crate::common::write_config( dir, "scout", serde_json::json!([ { "name": "scout", "description": "Search", "resident": true, "denyTools": [ "subagent" ], "isolate": "none", "model": "faux/faux-1", "prompt": "Scout instructions" } ]), )?; for args in [ vec!["init", "-b", "main"], vec!["add", "."], vec!["commit", "--no-gpg-sign", "-m", "subagents fixture"], ] { assert!( Command::new("git") .args(args) .current_dir(&repo) .env("GIT_AUTHOR_NAME", "Test") .env("GIT_AUTHOR_EMAIL", "test@example.com") .env("GIT_COMMITTER_NAME", "Test") .env("GIT_COMMITTER_EMAIL", "test@example.com") .status()? .success() ); } Ok(()) } async fn run(dir: &Path, mode: &str) -> anyhow::Result<()> { let world = World::open(dir)?; prepare(dir)?; let exec = sausage::process::exec(World { root: world.root.clone(), host_dir: world.host_dir.clone(), processes: world.processes.clone(), })?; run_module( world, exec, &format!("globalThis.subagentsMode={mode:?};\n{BUNDLE}"), ) .await } #[tokio::test(flavor = "current_thread")] async fn coordination() -> anyhow::Result<()> { let dir = tempfile::tempdir()?; run(dir.path(), "normal").await } #[tokio::test(flavor = "current_thread")] async fn restart_child() -> anyhow::Result<()> { if let Ok(dir) = std::env::var("SUBAGENTS_DIR") { run(Path::new(&dir), &std::env::var("SUBAGENTS_MODE")?).await?; } Ok(()) } #[test] fn sigkill_between_deliver_and_report() -> anyhow::Result<()> { let dir = tempfile::tempdir()?; World::open(dir.path())?; prepare(dir.path())?; let spawn = |mode: &str| { Command::new(std::env::current_exe().unwrap()) .args(["--exact", "subagents::restart_child", "--nocapture"]) .env("SUBAGENTS_DIR", dir.path()) .env("SUBAGENTS_MODE", mode) .spawn() }; let mut child = spawn("kill")?; let deadline = Instant::now() + Duration::from_secs(30); while !dir.path().join("world/report-ready").exists() { if let Some(status) = child.try_wait()? { anyhow::bail!("Child exited before report: {status}"); } if Instant::now() > deadline { child.kill()?; child.wait()?; anyhow::bail!("Reporter did not reach report"); } std::thread::yield_now(); } child.kill()?; child.wait()?; for _ in 0..2 { let mut child = spawn("resume")?; let deadline = Instant::now() + Duration::from_secs(30); loop { if let Some(status) = child.try_wait()? { assert!(status.success()); break; } if Instant::now() > deadline { child.kill()?; child.wait()?; anyhow::bail!("Report recovery timed out"); } std::thread::yield_now(); } } Ok(()) }