agent-offload 0.1.2

Launch coding agents in tmux panes and wait for completion
use crate::config;
use crate::headless;
use crate::launcher;
use crate::prompt;
use crate::run_dir;
use crate::tmux;
use crate::{ConfigArgs, RunArgs};
use anyhow::{Context, Result, bail};
use std::fs;
use std::path::Path;
use std::thread;
use std::time::Duration;

pub fn run(args: RunArgs) -> Result<()> {
    let (config, config_path) = config::load_config(args.config.as_deref())?;
    let (profile_name, profile) = config.resolve_profile(args.profile.as_deref())?;
    let prompt = prompt::load_prompt(&args.prompt)?;
    let headless_mode = args.headless || config.headless || profile.headless;

    if headless_mode {
        eprintln!("profile: {profile_name} (headless)");
        eprintln!("config: {}", config_path.display());
        let exit_code = headless::run_headless(profile, &prompt)?;
        std::process::exit(exit_code);
    }

    let run_dir = run_dir::create()?;
    let augmented_prompt = prompt::augment_prompt(&prompt, &run_dir.done_file);
    fs::write(&run_dir.prompt_file, &augmented_prompt).context("could not write prompt file")?;
    launcher::write_launcher(profile, &run_dir.prompt_file, &run_dir.launcher_file)?;

    let cwd = std::env::current_dir().context("could not read current directory")?;
    let pane_id = tmux::split_window(&run_dir.launcher_file, &cwd)?;

    eprintln!("profile: {profile_name}");
    eprintln!("config: {}", config_path.display());
    eprintln!("pane: {pane_id}");
    eprintln!("run dir: {}", run_dir.path.display());
    eprintln!("waiting for: {}", run_dir.done_file.display());

    wait_for_done(&run_dir.done_file, &pane_id)?;

    // Kill the delegated agent's pane -- it stays running after writing done.md
    let _ = tmux::kill_pane(&pane_id);

    let summary = fs::read_to_string(&run_dir.done_file).unwrap_or_default();
    if summary.trim().is_empty() {
        println!("done: {}", run_dir.done_file.display());
    } else {
        println!("{}", summary.trim());
    }

    Ok(())
}

pub fn profiles(args: ConfigArgs) -> Result<()> {
    let (config, config_path) = config::load_config(args.config.as_deref())?;
    println!("config: {}", config_path.display());

    for name in config.profiles.keys() {
        let marker = if name == &config.default_profile {
            " default"
        } else {
            ""
        };
        println!("{name}{marker}");
    }

    Ok(())
}

fn wait_for_done(done_file: &Path, pane_id: &str) -> Result<()> {
    loop {
        if done_file.exists() {
            return Ok(());
        }

        if !tmux::pane_exists(pane_id)? {
            bail!(
                "tmux pane {pane_id} closed before writing {}",
                done_file.display()
            );
        }

        thread::sleep(Duration::from_millis(500));
    }
}