//! Shared services bundle — common state threaded through HTTP handlers and chat transports. //! //! `Services` bundles the fields that every transport (Matrix, Slack, Discord, //! WhatsApp) and the HTTP/MCP layer need. A single `Arc` is //! constructed once in `main.rs` and cloned into `AppContext` and each //! transport's context struct. use crate::agents::AgentPool; use crate::http::context::{PermissionDecision, PermissionForward}; use crate::service::status::StatusBroadcaster; use std::collections::{HashMap, HashSet}; use std::path::PathBuf; use std::sync::Arc; use tokio::sync::{Mutex as TokioMutex, mpsc, oneshot}; /// Shared state bundle constructed once at startup and cloned (via `Arc`) into /// every context that needs access to the project root, agent pool, bot /// identity, ambient-room set, or permission plumbing. pub struct Services { /// Absolute path to the project root directory. pub project_root: PathBuf, /// Agent pool for starting, stopping, and querying coding agents. pub agents: Arc, /// Display name the bot uses to identify itself (from `bot.toml`). pub bot_name: String, /// String representation of the bot's user ID (e.g. `"@timmy:hs.local"` /// for Matrix, `"slack-bot"` for Slack). pub bot_user_id: String, /// Set of room/channel IDs where ambient mode is active. pub ambient_rooms: Arc>>, /// Receiver for permission requests from the MCP `prompt_permission` tool. pub perm_rx: Arc>>, /// Per-room pending permission reply senders, keyed by room/channel ID /// as a plain string. pub pending_perm_replies: Arc>>>, /// Seconds to wait for a user to respond to a permission prompt before /// auto-denying (fail-closed). pub permission_timeout_secs: u64, /// Project-scoped status broadcaster. /// /// Consumers (chat transports, Web UI, agent context) call /// [`StatusBroadcaster::subscribe`] to receive pipeline status events. /// The broadcaster is project-scoped: events published here are delivered /// only to subscribers of this instance, providing natural multi-project /// isolation. pub status: Arc, } #[cfg(test)] impl Services { /// Build a minimal `Services` for testing with the given project root and /// bot display name. pub fn new_test(project_root: std::path::PathBuf, bot_name: String) -> std::sync::Arc { let (_perm_tx, perm_rx) = mpsc::unbounded_channel(); std::sync::Arc::new(Self { project_root, agents: std::sync::Arc::new(crate::agents::AgentPool::new_test(3000)), bot_name, bot_user_id: String::new(), ambient_rooms: std::sync::Arc::new(std::sync::Mutex::new(HashSet::new())), perm_rx: std::sync::Arc::new(TokioMutex::new(perm_rx)), pending_perm_replies: std::sync::Arc::new(TokioMutex::new(HashMap::new())), permission_timeout_secs: 120, status: std::sync::Arc::new(StatusBroadcaster::new()), }) } }