//! [`TerminalSessions`] — the active-terminal registry (application service). //! //! Maps a [`SessionId`] to the live [`PtyHandle`] and the [`TerminalSession`] //! snapshot. Thread-safe (behind a [`Mutex`]); a single instance is shared //! (as `Arc`) by all terminal use cases via the composition root. See the module //! docs in `terminal/mod.rs` for the rationale of keeping this in the //! application layer rather than the domain or the adapter. use std::collections::HashMap; use std::sync::Mutex; use domain::ports::PtyHandle; use domain::{AgentId, SessionId, SessionKind, TerminalSession}; /// A registered, live terminal: its PTY handle plus the domain snapshot. #[derive(Debug, Clone)] struct Entry { handle: PtyHandle, session: TerminalSession, } /// In-memory registry of active terminal sessions. #[derive(Default)] pub struct TerminalSessions { entries: Mutex>, } impl TerminalSessions { /// Creates an empty registry. #[must_use] pub fn new() -> Self { Self { entries: Mutex::new(HashMap::new()), } } /// Inserts a freshly-opened session. pub fn insert(&self, handle: PtyHandle, session: TerminalSession) { if let Ok(mut map) = self.entries.lock() { map.insert(session.id, Entry { handle, session }); } } /// Returns the [`PtyHandle`] for a session, if registered. #[must_use] pub fn handle(&self, id: &SessionId) -> Option { self.entries .lock() .ok() .and_then(|m| m.get(id).map(|e| e.handle.clone())) } /// Returns the [`TerminalSession`] snapshot for a session, if registered. #[must_use] pub fn session(&self, id: &SessionId) -> Option { self.entries .lock() .ok() .and_then(|m| m.get(id).map(|e| e.session.clone())) } /// Returns the [`SessionId`] of the live session hosting a given agent, if any. /// /// An agent runs in a session tagged [`SessionKind::Agent`]; this is the /// mapping the orchestrator's `stop_agent` uses to translate an agent id into /// the [`SessionId`] that `CloseTerminal` expects. Returns `None` when the /// agent has no live session (already stopped / never launched). #[must_use] pub fn session_for_agent(&self, agent_id: &AgentId) -> Option { self.entries.lock().ok().and_then(|m| { m.values() .find(|e| matches!(e.session.kind, SessionKind::Agent { agent_id: a } if &a == agent_id)) .map(|e| e.session.id) }) } /// Returns the [`PtyHandle`]s of every currently-registered session. /// /// Used at application shutdown to kill all live PTYs cleanly (the /// `CloseRequested` hook), independently of the frontend's per-view lifecycle. #[must_use] pub fn handles(&self) -> Vec { self.entries .lock() .map(|m| m.values().map(|e| e.handle.clone()).collect()) .unwrap_or_default() } /// Removes a session from the registry, returning its handle if present. pub fn remove(&self, id: &SessionId) -> Option { self.entries .lock() .ok() .and_then(|mut m| m.remove(id).map(|e| e.handle)) } /// Number of currently-registered sessions. #[must_use] pub fn len(&self) -> usize { self.entries.lock().map(|m| m.len()).unwrap_or(0) } /// Whether the registry is empty. #[must_use] pub fn is_empty(&self) -> bool { self.len() == 0 } }