//! MCP socket listener boot sync. //! //! On hive-c0re startup, any agent containers that survived the daemon restart //! still have their bind-mount source dirs but no live MCP listener (the //! `Coordinator` is freshly empty). `sync_on_start` does a one-shot sweep to //! re-register all running agents. //! //! After startup, listeners are managed event-driven: //! - `run_create` calls `register_agent` eagerly on first-spawn. //! - `run_reconcile` calls `register_agent` immediately after `start_with_fallback`. //! - `kill`/`destroy` paths call `unregister_agent`. //! //! No recurring poll is needed because c0re owns the listener lifecycle — //! a listener can only disappear when c0re itself restarts, which is exactly //! the case `sync_on_start` covers. use std::sync::Arc; use crate::coordinator::Coordinator; /// One-shot MCP listener sync run at daemon startup. /// /// Iterates all currently-running agent containers and calls `register_agent` /// for any that have no live listener in the `Coordinator`. Safe to call /// concurrently with the rest of startup — `register_agent` is idempotent /// (drops and rebinds) and the coordinator lock serialises concurrent calls. pub async fn sync_on_start(coord: Arc) { let running = match crate::lifecycle::list().await { Ok(names) => names, Err(e) => { tracing::warn!(error = ?e, "mcp_sockets: startup sync failed to list agents; MCP listeners may be missing until next start"); return; } }; let registered = coord.list_agents(); for container in running { let Some(name) = container.strip_prefix(crate::lifecycle::AGENT_PREFIX) else { continue; }; if !registered.contains(&name.to_owned()) { if let Err(e) = coord.register_agent(name) { tracing::warn!( agent = %name, error = ?e, "mcp_sockets: startup register_agent failed" ); } else { tracing::debug!(agent = %name, "mcp_sockets: registered listener on startup"); } } } }