use crate::agents::AgentPool; use crate::chat::timer::TimerStore; use crate::chat::ChatTransport; use crate::http::context::{PermissionDecision, PermissionForward}; use matrix_sdk::ruma::{OwnedEventId, OwnedRoomId, OwnedUserId}; use std::collections::{HashMap, HashSet}; use std::path::PathBuf; use std::sync::Arc; use tokio::sync::Mutex as TokioMutex; use tokio::sync::{mpsc, oneshot}; use super::history::ConversationHistory; /// Shared context injected into Matrix event handlers. #[derive(Clone)] pub struct BotContext { pub bot_user_id: OwnedUserId, /// All room IDs the bot listens in. pub target_room_ids: Vec, pub project_root: PathBuf, pub allowed_users: Vec, /// Shared, per-room rolling conversation history. pub history: ConversationHistory, /// Maximum number of entries to keep per room before trimming the oldest. pub history_size: usize, /// Event IDs of messages the bot has sent. Used to detect replies to the /// bot so it can continue a conversation thread without requiring an /// explicit `@mention` on every follow-up. pub bot_sent_event_ids: Arc>>, /// Receiver for permission requests from the MCP `prompt_permission` tool. /// During an active chat the bot locks this to poll for incoming requests. pub perm_rx: Arc>>, /// Per-room pending permission reply senders. When a permission prompt is /// posted to a room the oneshot sender is stored here; when the user /// replies (yes/no) the event handler resolves it. pub pending_perm_replies: Arc>>>, /// How long to wait for a user to respond to a permission prompt before /// denying (fail-closed). pub permission_timeout_secs: u64, /// The name the bot uses to refer to itself. Derived from `display_name` /// in bot.toml; defaults to "Assistant" when unset. pub bot_name: String, /// Set of room IDs where ambient mode is active. In ambient mode the bot /// responds to all messages rather than only addressed ones. /// Uses a sync mutex since locks are never held across await points. /// Room IDs are stored as plain strings (platform-agnostic). pub ambient_rooms: Arc>>, /// Agent pool for checking agent availability. pub agents: Arc, /// Per-room htop monitoring sessions. Keyed by room ID; each entry holds /// a stop-signal sender that the background task watches. pub htop_sessions: super::super::htop::HtopSessions, /// Chat transport used for sending and editing messages. /// /// All message I/O goes through this abstraction so the bot logic works /// with any platform, not just Matrix. pub transport: Arc, /// Persistent store for pending deferred-start timers. pub timer_store: Arc, } // --------------------------------------------------------------------------- // Tests // --------------------------------------------------------------------------- #[cfg(test)] mod tests { use super::*; use std::path::PathBuf; use tokio::sync::mpsc; fn make_user_id(s: &str) -> OwnedUserId { s.parse().unwrap() } #[test] fn bot_context_is_clone() { // BotContext must be Clone for the Matrix event handler injection. fn assert_clone() {} assert_clone::(); } #[test] fn bot_context_has_no_require_verified_devices_field() { // Verification is always on — BotContext no longer has a toggle field. // This test verifies the struct can be constructed and cloned without it. let (_perm_tx, perm_rx) = mpsc::unbounded_channel(); let ctx = BotContext { bot_user_id: make_user_id("@bot:example.com"), target_room_ids: vec![], project_root: PathBuf::from("/tmp"), allowed_users: vec![], history: Arc::new(TokioMutex::new(HashMap::new())), history_size: 20, bot_sent_event_ids: Arc::new(TokioMutex::new(HashSet::new())), perm_rx: Arc::new(TokioMutex::new(perm_rx)), pending_perm_replies: Arc::new(TokioMutex::new(HashMap::new())), permission_timeout_secs: 120, bot_name: "Assistant".to_string(), ambient_rooms: Arc::new(std::sync::Mutex::new(HashSet::new())), agents: Arc::new(crate::agents::AgentPool::new_test(3000)), htop_sessions: Arc::new(TokioMutex::new(HashMap::new())), transport: Arc::new(crate::chat::transport::whatsapp::WhatsAppTransport::new( "test-phone".to_string(), "test-token".to_string(), "pipeline_notification".to_string(), )), timer_store: Arc::new(crate::chat::timer::TimerStore::load( std::path::PathBuf::from("/tmp/timers.json"), )), }; // Clone must work (required by Matrix SDK event handler injection). let _cloned = ctx.clone(); } }