Files
agent-manager/src/commands/watch_cmd.rs
T

108 lines
3.5 KiB
Rust

//! 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<i32> {
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));
}
}