use car_registry::supervisor::{AgentSpec, RestartPolicy, Supervisor};
use std::collections::BTreeMap;
use std::path::Path;
pub const ASSISTANT_AGENT_ID: &str = "car-assistant";
fn spec(command: String) -> AgentSpec {
AgentSpec {
id: ASSISTANT_AGENT_ID.to_string(),
name: "CAR Assistant".to_string(),
command,
args: vec!["do".to_string(), "--serve".to_string()],
cwd: None,
env: BTreeMap::new(),
restart: RestartPolicy::OnFailure,
max_restarts: 5,
backoff_secs: 2,
auto_start: true,
token: String::new(),
capabilities: vec!["chat".to_string()],
}
}
pub async fn ensure_registered_in(
sup: &Supervisor,
car_binary: &Path,
) -> Result<bool, String> {
if sup
.list()
.await
.iter()
.any(|a| a.spec.id == ASSISTANT_AGENT_ID)
{
return Ok(false);
}
sup.upsert(spec(car_binary.to_string_lossy().into_owned()))
.await
.map(|_| true)
.map_err(|e| e.to_string())
}
pub async fn ensure_registered() -> Result<bool, String> {
let exe = std::env::current_exe()
.map_err(|e| format!("cannot resolve current executable: {e}"))?;
let sup = Supervisor::user_default().map_err(|e| e.to_string())?;
ensure_registered_in(&sup, &exe).await
}
pub fn is_manifest_locked(err: &str) -> bool {
err.contains("another supervisor already owns this manifest")
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn register_is_idempotent_and_uses_the_given_binary() {
let dir = tempfile::tempdir().unwrap();
let manifest = dir.path().join("agents.json");
let logs = dir.path().join("logs");
let sup = Supervisor::with_paths(manifest, logs).unwrap();
let car = Path::new("/bin/sh");
assert!(ensure_registered_in(&sup, car).await.unwrap());
assert!(!ensure_registered_in(&sup, car).await.unwrap());
let listed = sup.list().await;
let entry = listed
.iter()
.find(|a| a.spec.id == ASSISTANT_AGENT_ID)
.expect("assistant registered");
assert_eq!(entry.spec.command, "/bin/sh");
assert_eq!(entry.spec.args, vec!["do".to_string(), "--serve".to_string()]);
assert!(entry.spec.auto_start);
assert!(
entry.spec.capabilities.contains(&"chat".to_string()),
"assistant must advertise the chat capability"
);
assert!(!entry.spec.token.is_empty());
}
}