use std::sync::{Arc, OnceLock};
use car_registry::supervisor::{AgentSpec, StopSignal, Supervisor, SupervisorError};
use crate::proxy::DaemonClient;
fn daemon_singleton() -> Arc<DaemonClient> {
static CLIENT: OnceLock<Arc<DaemonClient>> = OnceLock::new();
CLIENT.get_or_init(DaemonClient::new).clone()
}
async fn ws_call(method: &str, params: serde_json::Value) -> Result<String, String> {
let v = daemon_singleton().call(method, params).await?;
serde_json::to_string(&v).map_err(|e| e.to_string())
}
fn handle_or_locked() -> Result<&'static Supervisor, SupervisorError> {
static SUP: OnceLock<Supervisor> = OnceLock::new();
if let Some(s) = SUP.get() {
return Ok(s);
}
let s = Supervisor::user_default()?;
let _ = SUP.set(s);
SUP.get().ok_or_else(|| {
SupervisorError::Other("supervisor singleton not set after init".to_string())
})
}
pub async fn list() -> Result<String, String> {
match handle_or_locked() {
Ok(s) => serde_json::to_string(&s.list().await).map_err(|e| e.to_string()),
Err(SupervisorError::AlreadyRunning(_)) => match ws_call("agents.list", serde_json::json!({})).await {
Ok(s) => Ok(s),
Err(_) => {
let path = Supervisor::user_default_manifest_path().map_err(|e| e.to_string())?;
let agents = Supervisor::list_from_manifest(&path).map_err(|e| e.to_string())?;
serde_json::to_string(&agents).map_err(|e| e.to_string())
}
},
Err(e) => Err(e.to_string()),
}
}
pub async fn upsert(spec_json: &str) -> Result<String, String> {
let mut value: serde_json::Value =
serde_json::from_str(spec_json).map_err(|e| format!("invalid AgentSpec JSON: {e}"))?;
if let Some(name) = value
.get("interpreter")
.and_then(|v| v.as_str())
.map(str::to_string)
{
let resolved =
car_registry::supervisor::resolve_interpreter(&name).map_err(|e| e.to_string())?;
value["command"] = serde_json::Value::String(resolved.to_string_lossy().into_owned());
}
match handle_or_locked() {
Ok(s) => {
let spec: AgentSpec =
serde_json::from_value(value).map_err(|e| format!("invalid AgentSpec JSON: {e}"))?;
let agent = s.upsert(spec).await.map_err(|e| e.to_string())?;
serde_json::to_string(&agent).map_err(|e| e.to_string())
}
Err(SupervisorError::AlreadyRunning(_)) => ws_call("agents.upsert", value).await,
Err(e) => Err(e.to_string()),
}
}
pub async fn health() -> Result<String, String> {
match handle_or_locked() {
Ok(s) => serde_json::to_string(&s.health().await).map_err(|e| e.to_string()),
Err(SupervisorError::AlreadyRunning(_)) => match ws_call("agents.health", serde_json::json!({})).await {
Ok(s) => Ok(s),
Err(_) => {
let path = Supervisor::user_default_manifest_path().map_err(|e| e.to_string())?;
let health = Supervisor::health_from_manifest(&path).map_err(|e| e.to_string())?;
serde_json::to_string(&health).map_err(|e| e.to_string())
}
},
Err(e) => Err(e.to_string()),
}
}
pub async fn install(manifest_json: &str) -> Result<String, String> {
let manifest: car_registry::manifest::AgentManifest = serde_json::from_str(manifest_json)
.map_err(|e| format!("invalid AgentManifest JSON: {e}"))?;
let host = car_registry::install::HostCapabilities::daemon_default(env!("CARGO_PKG_VERSION"));
let s = match handle_or_locked() {
Ok(s) => s,
Err(SupervisorError::AlreadyRunning(_)) => {
return ws_call(
"agents.install",
serde_json::from_str(manifest_json)
.map_err(|e| format!("invalid AgentManifest JSON: {e}"))?,
)
.await;
}
Err(e) => return Err(e.to_string()),
};
let (report, managed) = s
.install_manifest(manifest, &host)
.await
.map_err(|e| e.to_string())?;
Ok(serde_json::json!({
"report": {
"missingOptional": report
.missing_optional
.iter()
.map(|(ns, feat)| serde_json::json!({ "namespace": ns, "feature": feat }))
.collect::<Vec<_>>(),
},
"agent": managed,
})
.to_string())
}
pub async fn remove(id: &str) -> Result<String, String> {
match handle_or_locked() {
Ok(s) => {
let removed = s.remove(id).await.map_err(|e| e.to_string())?;
serde_json::to_string(&serde_json::json!({ "removed": removed }))
.map_err(|e| e.to_string())
}
Err(SupervisorError::AlreadyRunning(_)) => {
ws_call("agents.remove", serde_json::json!({ "id": id })).await
}
Err(e) => Err(e.to_string()),
}
}
pub async fn start(id: &str) -> Result<String, String> {
match handle_or_locked() {
Ok(s) => {
let agent = s.start(id).await.map_err(|e| e.to_string())?;
serde_json::to_string(&agent).map_err(|e| e.to_string())
}
Err(SupervisorError::AlreadyRunning(_)) => {
ws_call("agents.start", serde_json::json!({ "id": id })).await
}
Err(e) => Err(e.to_string()),
}
}
pub async fn stop(id: &str, signal: Option<&str>) -> Result<String, String> {
match handle_or_locked() {
Ok(s) => {
let signal = signal
.map(|raw| {
serde_json::from_value::<StopSignal>(serde_json::Value::String(raw.to_string()))
.map_err(|e| format!("invalid signal `{raw}`: {e}"))
})
.transpose()?
.unwrap_or_default();
let agent = s.stop(id, signal).await.map_err(|e| e.to_string())?;
serde_json::to_string(&agent).map_err(|e| e.to_string())
}
Err(SupervisorError::AlreadyRunning(_)) => {
let mut params = serde_json::json!({ "id": id });
if let Some(raw) = signal {
params["signal"] = serde_json::Value::String(raw.to_string());
}
ws_call("agents.stop", params).await
}
Err(e) => Err(e.to_string()),
}
}
pub async fn restart(id: &str) -> Result<String, String> {
match handle_or_locked() {
Ok(s) => {
let agent = s.restart(id).await.map_err(|e| e.to_string())?;
serde_json::to_string(&agent).map_err(|e| e.to_string())
}
Err(SupervisorError::AlreadyRunning(_)) => {
ws_call("agents.restart", serde_json::json!({ "id": id })).await
}
Err(e) => Err(e.to_string()),
}
}
pub async fn tail_log(id: &str, n: Option<usize>) -> Result<String, String> {
match handle_or_locked() {
Ok(s) => {
let lines = s
.tail_log(id, n.unwrap_or(100))
.await
.map_err(|e| e.to_string())?;
serde_json::to_string(&serde_json::json!({ "lines": lines }))
.map_err(|e| e.to_string())
}
Err(SupervisorError::AlreadyRunning(_)) => {
let mut params = serde_json::json!({ "id": id });
if let Some(n) = n {
params["n"] = serde_json::Value::Number(serde_json::Number::from(n));
}
ws_call("agents.tail_log", params).await
}
Err(e) => Err(e.to_string()),
}
}