mod join;
mod outcome;
mod wire;
use std::collections::HashMap;
use std::path::Path;
use crate::app::Snapshot;
use crate::budgets::{BudgetSpend, StepBill};
use crate::git_tree::{Agent, AgentState, GitTree};
use crate::opslog::OpEntry;
use crate::workdiff::tests::{Project, ball, snap as ws_snap, xdg};
pub(super) const NAME: &str = "lab";
pub(super) const BALL: &str = "bl-1";
pub(super) const CONV: &str = "otter-one";
pub(super) const AGENT: &str = "20260815T101112Z-abcd1234";
pub(super) fn trail(ws: &Path, project: &Path, bindings: &[(&str, &Path)]) -> Vec<OpEntry> {
let mut entries = vec![OpEntry {
argv: ["bl", "claim", BALL, "--as", NAME]
.map(str::to_owned)
.to_vec(),
cwd: project.to_string_lossy().into_owned(),
..OpEntry::default()
}];
for (conv, binding) in bindings {
entries.push(OpEntry {
argv: [
"litany",
"prompt",
"--name",
conv,
"--cwd",
&binding.to_string_lossy(),
"--pin",
"instructions/00-AGENTS.md=/p/AGENTS.md",
"--pin",
"instructions/01-AGENTS.md=/p/src/AGENTS.md",
"/ws",
"the goal",
]
.map(str::to_owned)
.to_vec(),
cwd: ws.to_string_lossy().into_owned(),
..OpEntry::default()
});
}
entries
}
pub(super) fn snap(
ws: &Path,
project: &Path,
agents: Vec<Agent>,
bills: Vec<StepBill>,
) -> Snapshot {
let mut snap = ws_snap(ws, NAME, project, vec![ball(BALL, Some(NAME), None)]);
snap.trees = HashMap::from([(
ws.to_path_buf(),
GitTree {
agents,
..GitTree::default()
},
)]);
snap.bills = HashMap::from([(ws.to_path_buf(), bills)]);
snap
}
pub(super) fn named_agent() -> Agent {
let mut agent = crate::nav::convs::tests::agent(AGENT, AgentState::Quiescent, 1);
agent.name = Some(CONV.to_owned());
agent
}
pub(super) fn bill(conv: &str, seq: &str, input: u64, wall_secs: u64) -> StepBill {
StepBill {
conv: conv.to_owned(),
seq: seq.to_owned(),
model: Some("opus".to_owned()),
provider: None,
spend: BudgetSpend {
input_tokens: input,
..BudgetSpend::default()
},
last_usage: BudgetSpend::default(),
window: None,
wall_secs,
}
}
pub(super) fn layout(root: &Path) -> (balls::layout::Xdg, std::path::PathBuf) {
(xdg(root), root.join("state").join("balls"))
}
pub(super) fn world(root: &Path) -> crate::xdg::Env {
crate::workdiff::tests::world(root)
}
pub(super) fn worktree(ws: &Path, agent: &str, goal: &str, messages: &[(&str, &str)]) {
let dir = ws.join("agents").join(agent);
std::fs::create_dir_all(dir.join("messages")).unwrap();
std::fs::write(dir.join("goal.md"), goal).unwrap();
for (i, (name, body)) in messages.iter().enumerate() {
let file = format!("{:03}-{name}", i + 1);
std::fs::write(dir.join("messages").join(file), body).unwrap();
}
}
pub(super) fn claimed_project() -> Project {
let project = Project::new();
project.switch(&balls::delivery_path::work_branch(BALL));
project.commit("src/a.rs", "fn a() {}\n");
project.checkout(crate::workdiff::tests::MAIN);
project
}