pub mod cli;
mod hop;
#[cfg(test)]
mod tests;
use super::{assembler, child_result, driver, terminal};
use crate::prompt::inbox::{self, ExecutorLock};
use crate::prompt::resolve::WorkerConfig;
use crate::prompt::{Deps, Error};
use brazen::{Content, Message, Role};
use std::path::Path;
#[derive(Debug)]
pub enum AdvanceOutcome {
AlreadyDriven,
NothingToDo,
Terminal,
ToolsPending(ExecutorLock),
}
#[derive(Debug, PartialEq, Eq)]
enum Warrant {
ModelCallDue,
NothingDue,
Unpaired,
}
fn warrant(messages: &[Message]) -> Warrant {
match messages.last() {
None => Warrant::NothingDue,
Some(m) if matches!(m.role, Role::User | Role::Tool) => Warrant::ModelCallDue,
Some(m)
if m.content
.iter()
.any(|b| matches!(b, Content::ToolUse { .. })) =>
{
Warrant::Unpaired
}
Some(_) => Warrant::NothingDue,
}
}
pub(in crate::prompt) fn run(
workspace: &Path,
agent_id: &str,
lease: Option<ExecutorLock>,
deps: &Deps<'_>,
resolve: &mut dyn FnMut() -> Result<WorkerConfig, Error>,
) -> Result<AdvanceOutcome, Error> {
let lock = match lease {
Some(lock) => lock,
None => {
let inbox_dir = inbox::inbox_dir(workspace, agent_id);
match inbox::try_acquire(&inbox_dir).map_err(|source| Error::ExecutorLock {
path: inbox_dir.clone(),
source,
})? {
Some(lock) => lock,
None => return Ok(AdvanceOutcome::AlreadyDriven),
}
}
};
let delivery = driver::deliver(workspace, agent_id, deps.git)?;
let seen = delivery.left;
let worktree = crate::workspace::agent_worktree(workspace, agent_id);
if !worktree.exists() {
driver::release_then_reprobe(lock, workspace, agent_id, &seen, deps.launcher);
return Ok(AdvanceOutcome::NothingToDo);
}
let mut cfg = None;
if child_result::has_pending_result(workspace, agent_id)? {
let resolved = resolve()?;
child_result::interpret_pending(workspace, agent_id, &worktree, &resolved.workflow, deps)?;
cfg = Some(resolved);
}
match warrant(&assembler::transcript(&worktree)?) {
Warrant::NothingDue => {
driver::release_then_reprobe(lock, workspace, agent_id, &seen, deps.launcher);
Ok(AdvanceOutcome::NothingToDo)
}
Warrant::Unpaired => Err(Error::UnpairedToolUse {
branch: agent_id.to_string(),
}),
Warrant::ModelCallDue => {
let cfg = match cfg {
Some(cfg) => cfg,
None => resolve()?,
};
match hop::step(workspace, agent_id, &worktree, &cfg, deps)? {
hop::StepOutcome::ToolsRan => Ok(AdvanceOutcome::ToolsPending(lock)),
hop::StepOutcome::Terminal(epitaph) => {
terminal::conclude(
workspace,
agent_id,
epitaph,
&cfg.workflow,
lock,
&seen,
deps,
)?;
Ok(AdvanceOutcome::Terminal)
}
}
}
}
}