//! watch: supervise one agent and optionally restart it on crash. use super::*; use crate::cli::StartArgs; use crate::process; use std::time::Duration; /// One supervision check. Returns whether the agent is running afterwards. /// Restarts are background starts (event + session recorded as usual). pub fn check_once(app: &App, agent_name: &str, restart: bool, notify: bool) -> bool { let entry = app.state.get(agent_name).ok().flatten(); let running = entry .as_ref() .and_then(|e| e.pid) .map(process::is_running) .unwrap_or(false); if running { return true; } if !restart { app.log.warn(&format!("'{agent_name}' is not running")); return false; } let agent = match app.catalog.resolve(agent_name) { Some(a) => a, None => { app.log.error(&format!("unknown agent '{agent_name}'")); return false; } }; app.log.warn(&format!("'{agent_name}' is not running — restarting")); let opts = StartArgs { agent: Some(agent_name.to_string()), background: true, ..Default::default() }; match run_cmd::start(app, &opts) { Ok(_) => { if notify { process::desktop_notify("agent-manager", &format!("{} restarted", agent.title())); } true } Err(e) => { app.log.error(&format!("restart failed: {e:#}")); false } } } pub fn run(app: &App, agent: &str, restart: bool, notify: bool, interval: u64) -> Result { require_agent(app, agent)?; let interval = interval.max(1); app.log.info(&format!("watching '{agent}' every {interval}s (Ctrl-C to stop)")); let mut backoff = 1u64; loop { let ok = check_once(app, agent, restart, notify); if ok { backoff = 1; std::thread::sleep(Duration::from_secs(interval)); } else if restart { app.log.warn(&format!("retrying in {backoff}s")); std::thread::sleep(Duration::from_secs(backoff)); backoff = (backoff * 2).min(60); } else { std::thread::sleep(Duration::from_secs(interval)); } } } #[cfg(test)] mod tests { use super::*; use clap::Parser; #[test] fn check_once_reports_dead_agent_without_restart() { let guard = tempfile::tempdir().unwrap(); let dir = guard.path().to_path_buf(); std::mem::forget(guard); let cfg = dir.join("config.yaml"); std::fs::write( &cfg, concat!( "version: \"1.0\"\n", "settings:\n", " auto_install_deps: false\n", "agents: []\n", ), ) .unwrap(); let cli = crate::cli::Cli::parse_from(["am", "--config", cfg.to_str().unwrap()]); let mut app = App::from_cli(cli).unwrap(); let mut p = app.paths.clone(); p.install_dir = dir.join("agents"); p.bin_dir = dir.join("agents").join("bin"); p.log_dir = dir.join("logs"); p.state_file = dir.join("state.json"); p.probe_cache_file = dir.join("probe-cache.json"); p.config_dir = Some(dir.join("config")); app.paths = p; // Agent absent de l état : pas running, pas de restart -> false. assert!(!check_once(&app, "claude-code", false, false)); // Agent inconnu avec restart : erreur loguée, false. assert!(!check_once(&app, "inconnu", true, false)); } }