use tokio::process::Command as AsyncCommand;
use crate::engine;
const DEFAULT_TIMEOUT_SECS: u64 = 30;
async fn discover_sandbox_vms() -> Vec<String> {
let output = AsyncCommand::new("limactl")
.args(["ls", "-q"])
.output()
.await
.ok()
.filter(|o| o.status.success());
match output {
Some(out) => String::from_utf8_lossy(&out.stdout)
.lines()
.filter(|v| v.ends_with("-sandbox"))
.map(|v| v.to_string())
.collect(),
None => Vec::new(),
}
}
async fn is_vm_running(vm_name: &str) -> bool {
let output = AsyncCommand::new("limactl")
.args(["ls", "-f", "{{.Status}}", vm_name])
.output()
.await
.ok();
match output {
Some(out) if out.status.success() => {
let status = String::from_utf8_lossy(&out.stdout);
status.contains("Running")
}
_ => false,
}
}
async fn stop_vm(name: String, timeout_secs: u64, verbose: bool) {
if verbose {
println!("[PROC] Stopping VM '{}'...", name);
}
if !is_vm_running(&name).await {
if verbose {
println!("[OK] VM '{}' is not running.", name);
}
return;
}
let status = AsyncCommand::new("limactl")
.arg("stop")
.arg(&name)
.status()
.await;
if !matches!(status, Ok(s) if s.success()) {
eprintln!("[WARN] Failed to stop VM '{}' via limactl.", name);
}
let start = std::time::Instant::now();
while start.elapsed().as_secs() < timeout_secs {
if !is_vm_running(&name).await {
println!("[OK] VM '{}' stopped gracefully.", name);
return;
}
tokio::time::sleep(std::time::Duration::from_millis(500)).await;
}
eprintln!(
"[WARN] VM '{}' timed out after {}s. Escalating to force termination...",
name, timeout_secs
);
let _ = AsyncCommand::new("limactl")
.args(["stop", "--force", &name])
.status()
.await;
tokio::time::sleep(std::time::Duration::from_millis(1000)).await;
}
pub async fn run(verbose: bool, timeout_secs: Option<u64>) {
let default_timeout = DEFAULT_TIMEOUT_SECS;
let timeout = timeout_secs.unwrap_or(default_timeout);
if verbose {
println!("[PROC] Scanning runtime hypervisor layers...");
}
let sandboxes = discover_sandbox_vms().await;
let mut tasks = tokio::task::JoinSet::new();
for vm in sandboxes {
tasks.spawn(stop_vm(vm.clone(), timeout, verbose));
}
while tasks.join_next().await.is_some() {}
stop_vm("mcp-services-vm".to_string(), timeout, verbose).await;
if verbose {
println!("[PROC] Tearing down local hardware inference loops...");
}
let _ = engine::stop().await;
if verbose {
println!("[OK] Global context engine shutdown complete.");
}
}