//! `hive-matrix-daemon` binary — long-running matrix-sdk Client + sync //! loop per agent. Bridges incoming room events to hyperhive wake //! signals and serves the unix socket the stdio MCP bridge talks to. //! //! Lifecycle: //! 1. Read access token from `paths::token_file()` (fail clean if absent). //! 2. Whoami probe → recover `user_id` + `device_id` → restore //! matrix-sdk session (no login flow). //! 3. Install the message-event handler that fires hyperhive wakes. //! 4. Spawn the unix socket listener for the MCP bridge. //! 5. Run sync forever. //! //! Standalone-degraded boot: missing token file → exit 0 cleanly so //! systemd's `ConditionPathExists=` doesn't have to be perfectly //! synced with hive-c0re's token-provisioning timing. use anyhow::{Context, Result}; use matrix_sdk::config::SyncSettings; mod client; mod handlers; mod paths; mod protocol; mod socket; mod timeline; mod wake; #[tokio::main] async fn main() -> Result<()> { tracing_subscriber::fmt() .with_env_filter( tracing_subscriber::EnvFilter::try_from_default_env() .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")), ) .with_writer(std::io::stderr) .init(); let homeserver = paths::homeserver_url(); let token_file = paths::token_file(); let state_dir = paths::matrix_state_dir(); let mcp_socket = paths::daemon_socket(); let hyperhive_socket = paths::hyperhive_socket(); if !tokio::fs::try_exists(&token_file).await.unwrap_or(false) { tracing::warn!( path = %token_file.display(), "matrix token file absent; exiting cleanly (hive-c0re will provision \ it on first agent registration, then systemd restarts us)" ); return Ok(()); } tracing::info!( homeserver, token_file = %token_file.display(), state_dir = %state_dir.display(), "hive-matrix-daemon starting" ); let matrix_client = client::build_and_restore(&homeserver, &token_file, &state_dir) .await .context("build matrix client")?; timeline::install_message_handler(&matrix_client, hyperhive_socket); // Spawn the unix socket server before sync starts so the MCP // bridge can connect as soon as the first claude turn fires. The // socket dispatches against the same `Client` we sync on, so any // tool call benefits from the sync state-cache. let socket_client = matrix_client.clone(); let socket_listener = mcp_socket.clone(); tokio::spawn(async move { if let Err(e) = socket::serve(&socket_listener, socket_client).await { tracing::error!(error = %e, "mcp socket server exited"); } }); // Sync forever; matrix-sdk handles reconnection internally. let sync_settings = SyncSettings::default(); matrix_client .sync(sync_settings) .await .context("matrix-sdk sync loop exited")?; Ok(()) }