Skip to main content
Glama
manager.rs8 kB
//! Session manager for coordinating multiple terminal sessions. use std::collections::HashMap; use std::sync::{Arc, RwLock}; use terminal_mcp_core::{Dimensions, Error, Result, SessionId}; use crate::session::{Session, SessionStatus}; /// Configuration for session manager. #[derive(Debug, Clone)] pub struct SessionManagerConfig { /// Maximum number of concurrent sessions pub max_sessions: usize, /// Default terminal rows pub default_rows: u16, /// Default terminal columns pub default_cols: u16, } impl Default for SessionManagerConfig { fn default() -> Self { Self { max_sessions: 10, default_rows: 24, default_cols: 80, } } } /// Session manager for coordinating multiple terminal sessions. pub struct SessionManager { sessions: Arc<RwLock<HashMap<SessionId, Arc<Session>>>>, config: SessionManagerConfig, } impl SessionManager { /// Create a new session manager with default configuration. pub fn new() -> Self { Self::with_config(SessionManagerConfig::default()) } /// Create a new session manager with custom configuration. pub fn with_config(config: SessionManagerConfig) -> Self { Self { sessions: Arc::new(RwLock::new(HashMap::new())), config, } } /// Create a new terminal session. pub fn create_session( &self, command: String, args: Vec<String>, dimensions: Option<Dimensions>, ) -> Result<Arc<Session>> { // Check session limit let sessions = self.sessions.read().unwrap(); if sessions.len() >= self.config.max_sessions { return Err(Error::SessionLimitReached(self.config.max_sessions)); } drop(sessions); // Use provided dimensions or defaults let dims = dimensions .unwrap_or_else(|| Dimensions::new(self.config.default_rows, self.config.default_cols)); // Create session let session = Session::create(command, args, dims)?; let session_id = *session.id(); let session = Arc::new(session); // Store session let mut sessions = self.sessions.write().unwrap(); sessions.insert(session_id, Arc::clone(&session)); Ok(session) } /// Get a session by ID. pub fn get_session(&self, session_id: &SessionId) -> Result<Arc<Session>> { let sessions = self.sessions.read().unwrap(); sessions .get(session_id) .cloned() .ok_or(Error::SessionNotFound(*session_id)) } /// List all active sessions. pub fn list_sessions(&self) -> Vec<SessionInfo> { let sessions = self.sessions.read().unwrap(); sessions .values() .map(|session| SessionInfo { session_id: *session.id(), command: session.command().to_string(), status: session.status(), created_at: session.created_at(), }) .collect() } /// Close a session by ID. pub fn close_session(&self, session_id: &SessionId) -> Result<()> { // Get and terminate the session let session = self.get_session(session_id)?; session.terminate()?; // Remove from registry let mut sessions = self.sessions.write().unwrap(); sessions.remove(session_id); Ok(()) } /// Close all sessions. pub fn close_all(&self) -> Result<()> { let session_ids: Vec<SessionId> = { let sessions = self.sessions.read().unwrap(); sessions.keys().copied().collect() }; for session_id in session_ids { let _ = self.close_session(&session_id); } Ok(()) } /// Get the number of active sessions. pub fn session_count(&self) -> usize { let sessions = self.sessions.read().unwrap(); sessions.len() } } impl Default for SessionManager { fn default() -> Self { Self::new() } } /// Information about a session. #[derive(Debug, Clone)] pub struct SessionInfo { /// Session ID pub session_id: SessionId, /// Command pub command: String, /// Session status pub status: SessionStatus, /// Creation time pub created_at: std::time::SystemTime, } #[cfg(test)] mod tests { use super::*; #[test] fn test_session_manager_create() { let manager = SessionManager::new(); assert_eq!(manager.session_count(), 0); } #[test] fn test_session_manager_create_session() { let manager = SessionManager::new(); let result = manager.create_session("echo".to_string(), vec!["test".to_string()], None); assert!(result.is_ok()); assert_eq!(manager.session_count(), 1); } #[test] fn test_session_manager_get_session() { let manager = SessionManager::new(); let session = manager .create_session("echo".to_string(), vec![], None) .unwrap(); let session_id = *session.id(); let retrieved = manager.get_session(&session_id); assert!(retrieved.is_ok()); assert_eq!(retrieved.unwrap().id(), &session_id); } #[test] fn test_session_manager_get_nonexistent_session() { let manager = SessionManager::new(); let fake_id = SessionId::new(); let result = manager.get_session(&fake_id); assert!(result.is_err()); assert!(matches!(result.unwrap_err(), Error::SessionNotFound(_))); } #[test] fn test_session_manager_list_sessions() { let manager = SessionManager::new(); manager .create_session("echo".to_string(), vec!["1".to_string()], None) .unwrap(); manager .create_session("echo".to_string(), vec!["2".to_string()], None) .unwrap(); let sessions = manager.list_sessions(); assert_eq!(sessions.len(), 2); } #[test] fn test_session_manager_close_session() { let manager = SessionManager::new(); let session = manager .create_session( if cfg!(windows) { "cmd.exe" } else { "sh" }.to_string(), vec![], None, ) .unwrap(); let session_id = *session.id(); assert_eq!(manager.session_count(), 1); let result = manager.close_session(&session_id); assert!(result.is_ok()); assert_eq!(manager.session_count(), 0); } #[test] fn test_session_manager_session_limit() { let config = SessionManagerConfig { max_sessions: 2, ..Default::default() }; let manager = SessionManager::with_config(config); // Create 2 sessions (should succeed) manager .create_session("echo".to_string(), vec![], None) .unwrap(); manager .create_session("echo".to_string(), vec![], None) .unwrap(); // Try to create 3rd session (should fail) let result = manager.create_session("echo".to_string(), vec![], None); assert!(result.is_err()); assert!(matches!(result.unwrap_err(), Error::SessionLimitReached(_))); } #[test] fn test_session_manager_close_all() { let manager = SessionManager::new(); manager .create_session( if cfg!(windows) { "cmd.exe" } else { "sh" }.to_string(), vec![], None, ) .unwrap(); manager .create_session( if cfg!(windows) { "cmd.exe" } else { "sh" }.to_string(), vec![], None, ) .unwrap(); assert_eq!(manager.session_count(), 2); let result = manager.close_all(); assert!(result.is_ok()); assert_eq!(manager.session_count(), 0); } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/aybelatchane/mcp-server-terminal'

If you have feedback or need assistance with the MCP directory API, please join our Discord server