diff --git a/hive-c0re/src/socket_server/lifecycle_handlers.rs b/hive-c0re/src/socket_server/lifecycle_handlers.rs index 92a9312f..29182a69 100644 --- a/hive-c0re/src/socket_server/lifecycle_handlers.rs +++ b/hive-c0re/src/socket_server/lifecycle_handlers.rs @@ -158,8 +158,23 @@ pub(super) fn handle_update(coord: &Arc, agent: &str, name: &str) - /// `ListDescendants` — every topological descendant of `agent` with /// its running/stopped state, parents before children. -pub(super) async fn handle_list_descendants(coord: &Arc, agent: &str) -> Response { +pub(super) async fn handle_list_descendants(agent: &str) -> Response { tracing::debug!(%agent, "agent: list descendants"); + // All containers known to nixos-container (running only). + let running_set: std::collections::HashSet = match crate::lifecycle::list().await { + Ok(names) => names + .into_iter() + .filter_map(|c| { + c.strip_prefix(crate::lifecycle::AGENT_PREFIX) + .map(str::to_owned) + }) + .collect(), + Err(e) => { + return Response::Err { + message: format!("list containers failed: {e:#}"), + }; + } + }; // Walk the full topology and collect every descendant. let topo = crate::topology::read(); let mut names: Vec = topo @@ -169,27 +184,10 @@ pub(super) async fn handle_list_descendants(coord: &Arc, agent: &st .collect(); // Parents before children, then alpha within each tier. crate::auto_update::topology_sort(&mut names, &topo); - // Read from the coordinator's cached container snapshot instead of - // live-querying each container's systemd unit state — the same - // `containers_snapshot()` the dashboard's `/api/state` cold-load path - // already uses, kept fresh by `rescan_containers_and_emit()` on every - // mutation plus the crash-watcher's periodic poll. Avoids N - // `systemctl is-active` subprocess spawns per `list_containers` call; - // per mara, daemons should do the expensive work themselves and serve - // clients a cheap cached read. - let snapshot = coord.containers_snapshot().await; - let running_by_name: std::collections::HashMap<&str, bool> = snapshot - .iter() - .map(|v| (v.name.as_str(), v.running)) - .collect(); let containers = names .into_iter() .map(|name| { - // A descendant absent from the snapshot (not yet scanned since - // its own registration, e.g. mid-spawn) reads as not running - // rather than erroring — matches the old membership-check's - // default-false behavior for an unknown name. - let running = running_by_name.get(name.as_str()).copied().unwrap_or(false); + let running = running_set.contains(&name); hive_sh4re::ContainerInfo { name, running } }) .collect(); diff --git a/hive-c0re/src/socket_server/mod.rs b/hive-c0re/src/socket_server/mod.rs index c2f65173..e9cfc3d0 100644 --- a/hive-c0re/src/socket_server/mod.rs +++ b/hive-c0re/src/socket_server/mod.rs @@ -574,7 +574,7 @@ async fn dispatch(req: &Request, agent: &str, coord: &Arc) -> Respo Request::Restart { name } => handle_restart(coord, agent, name).await, Request::Kill { name } => handle_kill(coord, agent, name).await, Request::Update { name } => handle_update(coord, agent, name), - Request::ListDescendants => handle_list_descendants(coord, agent).await, + Request::ListDescendants => handle_list_descendants(agent).await, Request::RequestInitConfig { name, description } => { handle_request_init_config(coord, agent, name, description.clone()) }