118 lines
5.2 KiB
Rust
118 lines
5.2 KiB
Rust
//! Matrix bot context — shared state for the Matrix bot (rooms, history, permissions).
|
|
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<OwnedRoomId>,
|
|
pub project_root: PathBuf,
|
|
pub allowed_users: Vec<String>,
|
|
/// 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<TokioMutex<HashSet<OwnedEventId>>>,
|
|
/// 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<TokioMutex<mpsc::UnboundedReceiver<PermissionForward>>>,
|
|
/// 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<TokioMutex<HashMap<OwnedRoomId, oneshot::Sender<PermissionDecision>>>>,
|
|
/// 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<std::sync::Mutex<HashSet<String>>>,
|
|
/// Agent pool for checking agent availability.
|
|
pub agents: Arc<AgentPool>,
|
|
/// 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<dyn ChatTransport>,
|
|
/// Persistent store for pending deferred-start timers.
|
|
pub timer_store: Arc<TimerStore>,
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 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<T: Clone>() {}
|
|
assert_clone::<BotContext>();
|
|
}
|
|
|
|
#[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();
|
|
}
|
|
}
|