use super::transcript::commit_tool;
use crate::prompt::clock::SystemClock;
use crate::prompt::tool::spawn::PathLookup;
use crate::prompt::tool::{SpawnTool, ToolCall, ToolExecutor};
use crate::template::{GitRunner, RealGit};
use brazen::Content;
use serde_json::json;
use std::path::PathBuf;
use std::sync::atomic::AtomicBool;
use tempfile::TempDir;
struct NoPath;
impl PathLookup for NoPath {
fn which_on_path(&self, _prefixed_name: &str) -> Option<PathBuf> {
None
}
}
#[test]
fn a_bash_write_lands_in_the_worktree_and_rides_the_tool_commit() {
let agent_id = "agent-2503";
let ws = TempDir::new().unwrap();
let worktree = crate::workspace::agent_worktree(ws.path(), agent_id);
std::fs::create_dir_all(&worktree).unwrap();
let git = RealGit::new();
let branch = crate::workspace::agent_ref(agent_id);
git.run(&worktree, &["init", "-b", &branch]).unwrap();
git.run(&worktree, &["config", "user.email", "t@t"])
.unwrap();
git.run(&worktree, &["config", "user.name", "t"]).unwrap();
std::fs::create_dir_all(worktree.join("messages")).unwrap();
std::fs::write(worktree.join("messages/001-model.json"), b"[]").unwrap();
git.run(&worktree, &["add", "-A"]).unwrap();
git.run(&worktree, &["commit", "-m", "dispatch"]).unwrap();
let step_dir = ws.path().join("steps").join(agent_id).join("001");
std::fs::create_dir_all(&step_dir).unwrap();
let empty_data_root = TempDir::new().unwrap();
let lernie = crate::test_support::lernie_binary();
let clock = SystemClock;
let exec =
SpawnTool::new(empty_data_root.path(), &clock, &lernie).with_path_lookup(Box::new(NoPath));
let outcome = exec
.execute(
ToolCall {
id: "toolu_write",
name: "bash",
input: &json!({ "command": "echo hello > out.txt" }),
},
&step_dir,
&AtomicBool::new(false),
)
.expect("bash executes");
assert!(!outcome.is_error, "write should succeed: {outcome:?}");
assert_eq!(
std::fs::read_to_string(worktree.join("out.txt")).unwrap(),
"hello\n"
);
let tool_result = Content::ToolResult {
tool_use_id: "toolu_write".into(),
content: vec![Content::Text(String::new())],
is_error: false,
};
commit_tool(&worktree, agent_id, &tool_result, &git).unwrap();
let committed = git
.run_capture(&worktree, &["show", "--name-only", "--pretty=", "HEAD"])
.unwrap();
let names: Vec<&str> = committed.lines().collect();
assert!(
names.contains(&"out.txt"),
"the tool's worktree side effect rides the tool commit: {names:?}"
);
assert!(
names.contains(&"messages/002-tool.json"),
"the result entry is in the same commit: {names:?}"
);
assert!(
git.run_capture(&worktree, &["status", "--porcelain"])
.unwrap()
.is_empty(),
"nothing is left dirty between sibling tool calls (§3.3)"
);
}
struct Recorder(std::cell::RefCell<Vec<String>>);
impl ToolExecutor for Recorder {
fn execute(
&self,
call: ToolCall<'_>,
_step_dir: &std::path::Path,
_stop: &AtomicBool,
) -> Result<crate::prompt::tool::ToolOutcome, crate::prompt::tool::ExecError> {
self.0.borrow_mut().push(call.name.to_string());
Ok(crate::prompt::tool::ToolOutcome {
content: b"ok".to_vec(),
is_error: false,
})
}
}
#[test]
fn run_tool_calls_executes_only_the_tool_use_blocks() {
struct NoAdapter;
impl crate::prompt::adapter::AdapterRunner for NoAdapter {
fn run(
&self,
_b: &std::ffi::OsString,
_a: &[&str],
_s: &[u8],
_o: &mut dyn FnMut(&[u8]) -> std::io::Result<()>,
) -> std::io::Result<Vec<u8>> {
unreachable!("adapter is never reached")
}
}
struct NoSleeper;
impl crate::prompt::dispatch::Sleeper for NoSleeper {
fn sleep(&self, _d: std::time::Duration) {
unreachable!("sleeper is never reached")
}
}
struct NoLauncher;
impl crate::prompt::inbox::Launcher for NoLauncher {
fn launch(&self, _ws: &std::path::Path, _agent: &str) -> std::io::Result<()> {
unreachable!("launcher is never reached")
}
}
let agent_id = "agent-6f1b";
let ws = TempDir::new().unwrap();
let worktree = crate::workspace::agent_worktree(ws.path(), agent_id);
std::fs::create_dir_all(&worktree).unwrap();
let git = RealGit::new();
let branch = crate::workspace::agent_ref(agent_id);
git.run(&worktree, &["init", "-b", &branch]).unwrap();
git.run(&worktree, &["config", "user.email", "t@t"])
.unwrap();
git.run(&worktree, &["config", "user.name", "t"]).unwrap();
std::fs::create_dir_all(worktree.join("messages")).unwrap();
std::fs::write(worktree.join("messages/001-model.json"), b"[]").unwrap();
git.run(&worktree, &["add", "-A"]).unwrap();
git.run(&worktree, &["commit", "-m", "dispatch"]).unwrap();
let step_dir_rel = format!("steps/{agent_id}/001");
std::fs::create_dir_all(ws.path().join(&step_dir_rel)).unwrap();
let recorder = Recorder(std::cell::RefCell::new(Vec::new()));
let stop = AtomicBool::new(false);
let clock = SystemClock;
let id_gen = crate::prompt::NanoIdGen;
let cfg = TempDir::new().unwrap();
let deps = crate::prompt::Deps {
adapter: &NoAdapter,
sleeper: &NoSleeper,
git: &git,
clock: &clock,
id_gen: &id_gen,
tool_executor: &recorder,
config_root: cfg.path(),
adapter_target: None,
stop: &stop,
launcher: &NoLauncher,
};
let content = vec![
Content::Text("running the check".into()),
Content::ToolUse {
id: "t1".into(),
name: "bash".into(),
input: json!({"command": "true"}),
signature: None,
},
];
let stopped = super::run_tool_calls(
ws.path(),
&worktree,
agent_id,
crate::prompt::WORKER_ROLE,
&step_dir_rel,
&content,
&deps,
)
.unwrap();
assert!(!stopped, "no stop was requested");
assert_eq!(*recorder.0.borrow(), vec!["bash".to_string()]);
assert!(worktree.join("messages/002-tool.json").exists());
}