use super::scripted::{Fixture, Scripted};
use super::{Resolution, branch_with_step};
use crate::prompt::dispatch::tool_step::{ToolWindow, run_tool_calls};
use brazen::{Content, Role};
use serde_json::json;
use std::sync::atomic::AtomicBool;
use tempfile::TempDir;
fn tool_use(id: &str, name: &str) -> Content {
Content::ToolUse {
id: id.into(),
name: name.into(),
input: json!({}),
signature: None,
}
}
fn entry(worktree: &std::path::Path, file: &str) -> Vec<Content> {
let bytes = std::fs::read(worktree.join("messages").join(file)).expect("entry committed");
serde_json::from_slice(&bytes).expect("entry is canonical blocks")
}
#[test]
fn a_stop_mid_window_settles_every_unanswered_block() {
let agent_id = "agent-b98d";
let ws = TempDir::new().unwrap();
let fx = Fixture::new();
let (worktree, step_dir_rel) = branch_with_step(&ws, agent_id, &fx.git);
let exec = Scripted {
kill: &["boom"],
..Scripted::new()
};
let stop = AtomicBool::new(true);
let deps = fx.deps(&exec, &stop);
let content = vec![
Content::Text("working on it".into()),
tool_use("t1", "alpha"),
tool_use("t2", "boom"),
tool_use("t3", "gamma"),
];
let grant = ["alpha".to_string(), "boom".to_string(), "gamma".to_string()];
let resolution = Resolution::new();
let window = run_tool_calls(
ws.path(),
&worktree,
agent_id,
&resolution.of(crate::prompt::WORKER_ROLE, &grant),
&step_dir_rel,
&content,
&deps,
)
.unwrap();
assert!(matches!(window, ToolWindow::Stopped), "the stop ceases");
assert_eq!(exec.log.borrow().len(), 2, "`gamma` was never entered");
let alpha = entry(&worktree, "002-tool.json");
let [
Content::ToolResult {
tool_use_id,
is_error,
..
},
] = alpha.as_slice()
else {
panic!("one result block: {alpha:?}");
};
assert_eq!(tool_use_id, "t1");
assert!(!is_error);
for (file, id) in [("003-tool.json", "t2"), ("004-tool.json", "t3")] {
let blocks = entry(&worktree, file);
let [
Content::ToolResult {
tool_use_id,
content,
is_error,
},
] = blocks.as_slice()
else {
panic!("one result block: {blocks:?}");
};
assert_eq!(tool_use_id, id);
assert!(is_error, "the settlement is in-band `is_error`");
let [Content::Text(text)] = content.as_slice() else {
panic!("one text block: {content:?}");
};
assert!(text.contains("did not return"), "{text}");
assert!(text.contains("§2.9"), "it names the stop: {text}");
}
assert!(
!worktree.join("messages/005-tool.json").exists(),
"the prose block is not an invocation and settles nothing"
);
let tail = crate::prompt::dispatch::assembler::transcript(&worktree).unwrap();
assert_eq!(tail.last().unwrap().role, Role::Tool);
}