agent_tui/daemon/
repository.rs

1use std::collections::HashMap;
2use std::sync::Arc;
3use std::sync::Mutex;
4
5use crate::core::Component;
6use crate::core::CursorPosition;
7use crate::core::Element;
8
9use crate::daemon::error::SessionError;
10use crate::daemon::session::{Session, SessionId, SessionInfo, SessionManager};
11
12/// Operations on a session needed by use cases and algorithms.
13///
14/// This trait enables dependency inversion for modules like wait.rs and select_helpers.rs,
15/// allowing them to work with any type that provides session-like operations without
16/// depending directly on the Session concrete type.
17pub trait SessionOps {
18    /// Update terminal state from PTY.
19    fn update(&mut self) -> Result<(), SessionError>;
20
21    /// Get the current screen text.
22    fn screen_text(&self) -> String;
23
24    /// Run element detection and return detected elements.
25    fn detect_elements(&mut self) -> &[Element];
26
27    /// Find an element by reference (id, role, or text).
28    fn find_element(&self, element_ref: &str) -> Option<&Element>;
29
30    /// Write raw bytes to the PTY.
31    fn pty_write(&mut self, data: &[u8]) -> Result<(), SessionError>;
32
33    /// Analyze screen and return VOM components.
34    fn analyze_screen(&self) -> Vec<Component>;
35}
36
37/// Repository trait for session access and management.
38///
39/// This trait abstracts the session storage and retrieval operations,
40/// enabling use cases to be testable without a real SessionManager.
41#[allow(clippy::too_many_arguments)]
42pub trait SessionRepository: Send + Sync {
43    /// Spawn a new session with the given parameters.
44    fn spawn(
45        &self,
46        command: &str,
47        args: &[String],
48        cwd: Option<&str>,
49        env: Option<&HashMap<String, String>>,
50        session_id: Option<String>,
51        cols: u16,
52        rows: u16,
53    ) -> Result<(SessionId, u32), SessionError>;
54
55    /// Get a session by ID.
56    fn get(&self, session_id: &str) -> Result<Arc<Mutex<Session>>, SessionError>;
57
58    /// Get the active session.
59    fn active(&self) -> Result<Arc<Mutex<Session>>, SessionError>;
60
61    /// Resolve a session by ID, falling back to active session if None.
62    fn resolve(&self, session_id: Option<&str>) -> Result<Arc<Mutex<Session>>, SessionError>;
63
64    /// Set the active session.
65    fn set_active(&self, session_id: &str) -> Result<(), SessionError>;
66
67    /// List all sessions.
68    fn list(&self) -> Vec<SessionInfo>;
69
70    /// Kill a session by ID.
71    fn kill(&self, session_id: &str) -> Result<(), SessionError>;
72
73    /// Get the count of sessions.
74    fn session_count(&self) -> usize;
75
76    /// Get the active session ID.
77    fn active_session_id(&self) -> Option<SessionId>;
78}
79
80impl SessionRepository for SessionManager {
81    fn spawn(
82        &self,
83        command: &str,
84        args: &[String],
85        cwd: Option<&str>,
86        env: Option<&HashMap<String, String>>,
87        session_id: Option<String>,
88        cols: u16,
89        rows: u16,
90    ) -> Result<(SessionId, u32), SessionError> {
91        SessionManager::spawn(self, command, args, cwd, env, session_id, cols, rows)
92    }
93
94    fn get(&self, session_id: &str) -> Result<Arc<Mutex<Session>>, SessionError> {
95        SessionManager::get(self, session_id)
96    }
97
98    fn active(&self) -> Result<Arc<Mutex<Session>>, SessionError> {
99        SessionManager::active(self)
100    }
101
102    fn resolve(&self, session_id: Option<&str>) -> Result<Arc<Mutex<Session>>, SessionError> {
103        SessionManager::resolve(self, session_id)
104    }
105
106    fn set_active(&self, session_id: &str) -> Result<(), SessionError> {
107        SessionManager::set_active(self, session_id)
108    }
109
110    fn list(&self) -> Vec<SessionInfo> {
111        SessionManager::list(self)
112    }
113
114    fn kill(&self, session_id: &str) -> Result<(), SessionError> {
115        SessionManager::kill(self, session_id)
116    }
117
118    fn session_count(&self) -> usize {
119        SessionManager::session_count(self)
120    }
121
122    fn active_session_id(&self) -> Option<SessionId> {
123        SessionManager::active_session_id(self)
124    }
125}
126
127/// Snapshot of a session's current state.
128#[derive(Debug, Clone)]
129pub struct SessionSnapshot {
130    pub session_id: SessionId,
131    pub screen: String,
132    pub elements: Vec<Element>,
133    pub cursor: CursorPosition,
134}
135
136#[cfg(test)]
137mod tests {
138    use super::*;
139
140    #[test]
141    fn test_session_repository_trait_is_object_safe() {
142        fn assert_object_safe<T>(_: &T)
143        where
144            T: SessionRepository + ?Sized,
145        {
146        }
147
148        let manager = SessionManager::new();
149        assert_object_safe(&manager);
150    }
151
152    #[test]
153    fn test_session_ops_trait_is_usable_as_generic_bound() {
154        fn assert_generic_bound<S: SessionOps>(_session: &S) {}
155
156        // This test verifies that SessionOps can be used as a generic constraint.
157        // The actual usage is verified by wait.rs and select_helpers.rs compilation.
158        // The function signature is enough to prove the trait works as a bound.
159        let _ = assert_generic_bound::<Session>;
160    }
161}