use car_registry::supervisor::{AgentSpec, RestartPolicy, Supervisor};
use std::collections::BTreeMap;
use std::path::Path;
pub const ASSISTANT_AGENT_ID: &str = "car-assistant";
const ASSISTANT_CAPABILITIES: &[&str] = &[
"chat",
"verified-answers",
"safety-checks",
"memory-routines",
"background-work",
"trust-control",
"approvals",
"approval-channels",
"a2ui",
"continuity",
"notifications",
"connected-services",
"conversation-channels",
"workspace-files",
"personal-context",
"documents",
"creative-output",
];
fn spec(command: String) -> AgentSpec {
AgentSpec {
id: ASSISTANT_AGENT_ID.to_string(),
name: "Parslee Core".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: assistant_capabilities(),
}
}
fn assistant_capabilities() -> Vec<String> {
ASSISTANT_CAPABILITIES
.iter()
.map(|capability| capability.to_string())
.collect()
}
fn merge_assistant_capabilities(existing: &[String]) -> Vec<String> {
let mut merged = existing.to_vec();
for capability in ASSISTANT_CAPABILITIES {
if !merged.iter().any(|value| value == capability) {
merged.push((*capability).to_string());
}
}
merged
}
pub async fn ensure_registered_in(sup: &Supervisor, car_binary: &Path) -> Result<bool, String> {
if let Some(existing) = sup
.list()
.await
.iter()
.find(|a| a.spec.id == ASSISTANT_AGENT_ID)
{
let merged_capabilities = merge_assistant_capabilities(&existing.spec.capabilities);
if merged_capabilities != existing.spec.capabilities {
let mut updated = existing.spec.clone();
updated.name = "Parslee Core".to_string();
updated.capabilities = merged_capabilities;
sup.upsert(updated).await.map_err(|e| e.to_string())?;
}
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::*;
use std::path::PathBuf;
fn stand_in_binary() -> PathBuf {
if cfg!(windows) {
let root = std::env::var("SystemRoot").unwrap_or_else(|_| r"C:\Windows".to_string());
PathBuf::from(format!(r"{root}\System32\cmd.exe"))
} else {
PathBuf::from("/bin/sh")
}
}
fn other_binary() -> PathBuf {
if cfg!(windows) {
let root = std::env::var("SystemRoot").unwrap_or_else(|_| r"C:\Windows".to_string());
PathBuf::from(format!(r"{root}\System32\where.exe"))
} else {
PathBuf::from("/bin/echo")
}
}
#[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 = stand_in_binary();
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, car.to_string_lossy());
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.capabilities.len() > 10,
"assistant must advertise the full Parslee Core capability surface"
);
for expected in [
"verified-answers",
"memory-routines",
"approvals",
"approval-channels",
"a2ui",
"continuity",
"notifications",
"connected-services",
"documents",
"creative-output",
] {
assert!(
entry.spec.capabilities.contains(&expected.to_string()),
"assistant capability metadata missing {expected}"
);
}
assert!(!entry.spec.token.is_empty());
}
#[tokio::test]
async fn register_refreshes_stale_builtin_capabilities_without_overwriting_operator_settings() {
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 operator_cmd = stand_in_binary();
sup.upsert(AgentSpec {
id: ASSISTANT_AGENT_ID.to_string(),
name: "CAR Assistant".to_string(),
command: operator_cmd.to_string_lossy().into_owned(),
args: vec!["-c".to_string(), "sleep 1".to_string()],
cwd: None,
env: BTreeMap::new(),
restart: RestartPolicy::Never,
max_restarts: 1,
backoff_secs: 9,
auto_start: false,
token: "operator-token".to_string(),
capabilities: vec!["chat".to_string()],
})
.await
.unwrap();
assert!(!ensure_registered_in(&sup, &other_binary()).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.name, "Parslee Core");
assert_eq!(entry.spec.command, operator_cmd.to_string_lossy());
assert_eq!(
entry.spec.args,
vec!["-c".to_string(), "sleep 1".to_string()]
);
assert_eq!(entry.spec.restart, RestartPolicy::Never);
assert_eq!(entry.spec.max_restarts, 1);
assert_eq!(entry.spec.backoff_secs, 9);
assert!(!entry.spec.auto_start);
assert_eq!(entry.spec.token, "operator-token");
assert!(entry
.spec
.capabilities
.contains(&"verified-answers".to_string()));
assert!(entry.spec.capabilities.contains(&"a2ui".to_string()));
assert!(entry
.spec
.capabilities
.contains(&"connected-services".to_string()));
assert!(entry
.spec
.capabilities
.contains(&"creative-output".to_string()));
}
}