use std::collections::HashSet;
use std::thread;
use std::time::{Duration, Instant};
use anyhow::{Result, anyhow};
use crate::git;
use crate::multiplexer::{AgentStatus, create_backend, detect_backend};
use crate::state::StateStore;
use crate::util;
use crate::workflow;
fn resolve_worktree_path(
name: &str,
mux: &dyn crate::multiplexer::Multiplexer,
) -> Result<std::path::PathBuf> {
if git::is_git_repo().unwrap_or(false) {
match git::find_worktree(name) {
Ok((path, _branch)) => return Ok(path),
Err(e) if e.downcast_ref::<git::WorktreeNotFound>().is_some() => {}
Err(e) => return Err(e),
}
}
let (path, _agents) = workflow::resolve_worktree_agents(name, mux)?;
Ok(path)
}
fn parse_status(s: &str) -> Result<AgentStatus> {
match s {
"working" => Ok(AgentStatus::Working),
"waiting" => Ok(AgentStatus::Waiting),
"done" => Ok(AgentStatus::Done),
_ => Err(anyhow!(
"Invalid status '{}'. Must be: working, waiting, done",
s
)),
}
}
pub fn run(
worktree_names: &[String],
target_status: &str,
timeout_secs: Option<u64>,
any: bool,
) -> Result<()> {
let target = parse_status(target_status)?;
let mux = create_backend(detect_backend());
let start = Instant::now();
let worktree_paths: Vec<_> = worktree_names
.iter()
.map(|name| {
let path = resolve_worktree_path(name, mux.as_ref())?;
Ok((name.clone(), path))
})
.collect::<Result<Vec<_>>>()?;
let mut reached: HashSet<String> = HashSet::new();
let mut seen_agent: HashSet<String> = HashSet::new();
loop {
if let Some(timeout) = timeout_secs
&& start.elapsed() > Duration::from_secs(timeout)
{
let remaining: Vec<_> = worktree_names
.iter()
.filter(|n| !reached.contains(n.as_str()))
.collect();
eprintln!(
"Timeout waiting for: {}",
remaining
.iter()
.map(|s| s.as_str())
.collect::<Vec<_>>()
.join(", ")
);
std::process::exit(1);
}
let agent_panes =
StateStore::new().and_then(|store| store.load_reconciled_agents(mux.as_ref()))?;
for (name, wt_path) in &worktree_paths {
if reached.contains(name) {
continue;
}
let matching = workflow::match_agents_to_worktree(&agent_panes, wt_path);
if !matching.is_empty() {
seen_agent.insert(name.clone());
let has_target = matching.iter().any(|a| a.status == Some(target));
if has_target {
let elapsed = util::format_elapsed_duration(start.elapsed());
eprintln!("{}: {} ({})", name, target_status, elapsed);
reached.insert(name.clone());
if any {
return Ok(());
}
}
} else if seen_agent.contains(name) {
if !wt_path.exists() {
let elapsed = util::format_elapsed_duration(start.elapsed());
eprintln!("{}: merged ({})", name, elapsed);
reached.insert(name.clone());
if any {
return Ok(());
}
} else {
eprintln!("{}: agent exited unexpectedly", name);
std::process::exit(3);
}
}
}
if reached.len() == worktree_paths.len() {
return Ok(());
}
thread::sleep(Duration::from_secs(2));
}
}