pub(super) use super::streams::{
Block, error_stream, happy_response_bytes, parse_jsonl, stream_of,
};
pub(super) use super::stubs::{
StubAdapter, StubGit, StubSleeper, unreachable_adapter, version_line,
};
pub(super) use super::tool_stub::StubToolExecutor;
use crate::prompt::{Clock, Deps, IdGen};
use std::cell::RefCell;
use std::path::{Path, PathBuf};
use tempfile::TempDir;
#[derive(Default)]
pub(super) struct FixedClock {
iso_calls: RefCell<u32>,
compact_calls: RefCell<u32>,
}
impl Clock for FixedClock {
fn now_iso8601(&self) -> String {
*self.iso_calls.borrow_mut() += 1;
format!("iso-{}", self.iso_calls.borrow())
}
fn now_compact(&self) -> String {
*self.compact_calls.borrow_mut() += 1;
format!("ct-{}", self.compact_calls.borrow())
}
}
pub(super) struct FixedIdGen;
impl IdGen for FixedIdGen {
fn short(&self) -> String {
"deadbeef".into()
}
}
pub(super) const VALID_GLOBAL_MODELS_YAML: &str = r#"
models:
claude-sonnet-5:
provider: anthropic
model_id: claude-sonnet-5
capabilities: [tool_use_native]
context_window: 200000
"#;
pub(super) const VALID_PER_REPO_PROVIDERS_YAML: &str = r#"
roles:
worker:
provider: anthropic
model: claude-sonnet-5
tools: [bash, read_file]
"#;
pub(super) const VALID_WORKFLOW_YAML: &str = "events: {}\n";
pub(super) const VALID_VERSION: &str = "1\n";
pub(super) const VALID_MANIFEST_YAML: &str = "roles: {}\n";
pub(super) fn scaffold_repo(per_repo_yaml: &str, worker_soul: Option<&str>) -> TempDir {
scaffold_repo_with_workflow(per_repo_yaml, VALID_WORKFLOW_YAML, worker_soul)
}
pub(super) fn scaffold_repo_with_workflow(
per_repo_yaml: &str,
workflow_yaml: &str,
worker_soul: Option<&str>,
) -> TempDir {
let tmp = TempDir::new().unwrap();
std::fs::create_dir_all(tmp.path().join(crate::workspace::REPO_DIR)).unwrap();
std::fs::write(tmp.path().join("version"), VALID_VERSION).unwrap();
std::fs::write(tmp.path().join("providers.yaml"), per_repo_yaml).unwrap();
std::fs::write(tmp.path().join("workflow.yaml"), workflow_yaml).unwrap();
std::fs::write(tmp.path().join("manifest.yaml"), VALID_MANIFEST_YAML).unwrap();
if let Some(body) = worker_soul {
let souls = tmp.path().join("souls");
std::fs::create_dir_all(&souls).unwrap();
std::fs::write(souls.join("worker.md"), body).unwrap();
}
tmp
}
pub(super) fn scaffold_harness_root() -> TempDir {
let tmp = TempDir::new().unwrap();
std::fs::write(tmp.path().join("models.yaml"), VALID_GLOBAL_MODELS_YAML).unwrap();
tmp
}
pub(super) fn scaffold_harness_root_with_adapter(adapter: &str) -> TempDir {
let tmp = TempDir::new().unwrap();
let yaml = format!("adapter: {adapter}\n{VALID_GLOBAL_MODELS_YAML}");
std::fs::write(tmp.path().join("models.yaml"), yaml).unwrap();
tmp
}
#[allow(clippy::too_many_arguments)]
pub(super) fn valid_deps<'a>(
adapter: &'a StubAdapter,
sleeper: &'a StubSleeper,
git: &'a StubGit,
clock: &'a FixedClock,
id: &'a FixedIdGen,
tool_executor: &'a StubToolExecutor,
config_root: &'a Path,
) -> Deps<'a> {
Deps {
adapter,
sleeper,
git,
clock,
id_gen: id,
tool_executor,
config_root,
adapter_target: None,
stop: never_stopped(),
launcher: no_launch(),
}
}
pub(super) fn no_launch() -> &'static NoLaunch {
static NO_LAUNCH: NoLaunch = NoLaunch;
&NO_LAUNCH
}
pub(super) struct NoLaunch;
impl crate::prompt::inbox::Launcher for NoLaunch {
fn launch(&self, _workspace: &Path, _agent_id: &str) -> std::io::Result<()> {
Ok(())
}
}
pub(super) fn never_stopped() -> &'static std::sync::atomic::AtomicBool {
static NEVER: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
&NEVER
}
pub(super) fn run_with_stubs(
repo: &Path,
msg: &str,
adapter: &StubAdapter,
git: &StubGit,
) -> Result<String, crate::prompt::Error> {
let harness = scaffold_harness_root();
let (clock, id) = (FixedClock::default(), FixedIdGen);
let (sleeper, tool_executor) = (StubSleeper::default(), StubToolExecutor::ok());
crate::prompt::run(
repo,
msg,
&valid_deps(
adapter,
&sleeper,
git,
&clock,
&id,
&tool_executor,
harness.path(),
),
)
}
pub(super) fn worktree_path(repo: &Path) -> PathBuf {
crate::workspace::agent_worktree(repo, "ct-1-deadbeef")
}