agent_tui_usecases/usecases/
session.rs1use std::sync::Arc;
4
5use crate::domain::AssertConditionType;
6use crate::domain::AssertInput;
7use crate::domain::AssertOutput;
8use crate::domain::AttachInput;
9use crate::domain::AttachOutput;
10use crate::domain::CleanupFailure;
11use crate::domain::CleanupInput;
12use crate::domain::CleanupOutput;
13use crate::domain::KillOutput;
14use crate::domain::ResizeInput;
15use crate::domain::ResizeOutput;
16use crate::domain::RestartOutput;
17use crate::domain::SessionInput;
18use crate::domain::SessionsOutput;
19use crate::domain::SpawnInput;
20use crate::domain::SpawnOutput;
21use crate::usecases::SpawnError;
22use crate::usecases::ports::SessionError;
23use crate::usecases::ports::SessionRepository;
24use crate::usecases::ports::SpawnErrorKind;
25use crate::usecases::ports::TerminalError;
26
27pub trait SpawnUseCase: Send + Sync {
28 fn execute(&self, input: SpawnInput) -> Result<SpawnOutput, SpawnError>;
29}
30
31pub struct SpawnUseCaseImpl<R: SessionRepository> {
32 repository: Arc<R>,
33}
34
35impl<R: SessionRepository> SpawnUseCaseImpl<R> {
36 pub fn new(repository: Arc<R>) -> Self {
37 Self { repository }
38 }
39}
40
41impl<R: SessionRepository> SpawnUseCase for SpawnUseCaseImpl<R> {
42 fn execute(&self, input: SpawnInput) -> Result<SpawnOutput, SpawnError> {
43 let SpawnInput {
44 command,
45 args,
46 cwd,
47 env,
48 session_id,
49 size,
50 } = input;
51
52 match self.repository.spawn(
53 &command,
54 &args,
55 cwd.as_deref(),
56 env.as_ref(),
57 session_id,
58 size,
59 ) {
60 Ok((session_id, pid)) => Ok(SpawnOutput { session_id, pid }),
61 Err(SessionError::LimitReached(max)) => Err(SpawnError::SessionLimitReached { max }),
62 Err(SessionError::AlreadyExists(session_id)) => {
63 Err(SpawnError::SessionAlreadyExists { session_id })
64 }
65 Err(SessionError::Terminal(TerminalError::Spawn { kind, reason })) => match kind {
66 SpawnErrorKind::NotFound => Err(SpawnError::CommandNotFound { command }),
67 SpawnErrorKind::PermissionDenied => Err(SpawnError::PermissionDenied { command }),
68 SpawnErrorKind::Other => Err(SpawnError::TerminalError {
69 operation: "spawn".to_string(),
70 reason,
71 }),
72 },
73 Err(SessionError::Terminal(term_err)) => Err(SpawnError::TerminalError {
74 operation: term_err.operation().to_string(),
75 reason: term_err.reason().to_string(),
76 }),
77 Err(e) => Err(SpawnError::TerminalError {
78 operation: "spawn".to_string(),
79 reason: e.to_string(),
80 }),
81 }
82 }
83}
84
85pub trait SessionsUseCase: Send + Sync {
86 fn execute(&self) -> SessionsOutput;
87}
88
89pub struct SessionsUseCaseImpl<R: SessionRepository> {
90 repository: Arc<R>,
91}
92
93impl<R: SessionRepository> SessionsUseCaseImpl<R> {
94 pub fn new(repository: Arc<R>) -> Self {
95 Self { repository }
96 }
97}
98
99impl<R: SessionRepository> SessionsUseCase for SessionsUseCaseImpl<R> {
100 fn execute(&self) -> SessionsOutput {
101 let sessions = self.repository.list();
102 let active_session = self.repository.active_session_id();
103
104 SessionsOutput {
105 sessions,
106 active_session,
107 }
108 }
109}
110
111pub trait KillUseCase: Send + Sync {
112 fn execute(&self, input: SessionInput) -> Result<KillOutput, SessionError>;
113}
114
115pub struct KillUseCaseImpl<R: SessionRepository> {
116 repository: Arc<R>,
117}
118
119impl<R: SessionRepository> KillUseCaseImpl<R> {
120 pub fn new(repository: Arc<R>) -> Self {
121 Self { repository }
122 }
123}
124
125impl<R: SessionRepository> KillUseCase for KillUseCaseImpl<R> {
126 fn execute(&self, input: SessionInput) -> Result<KillOutput, SessionError> {
127 let session = self.repository.resolve(input.session_id.as_ref())?;
128 let session_id = session.session_id();
129
130 self.repository.kill(&session_id)?;
131
132 Ok(KillOutput {
133 session_id,
134 success: true,
135 })
136 }
137}
138
139pub trait RestartUseCase: Send + Sync {
140 fn execute(&self, input: SessionInput) -> Result<RestartOutput, SessionError>;
141}
142
143pub struct RestartUseCaseImpl<R: SessionRepository> {
144 repository: Arc<R>,
145}
146
147impl<R: SessionRepository> RestartUseCaseImpl<R> {
148 pub fn new(repository: Arc<R>) -> Self {
149 Self { repository }
150 }
151}
152
153impl<R: SessionRepository> RestartUseCase for RestartUseCaseImpl<R> {
154 fn execute(&self, input: SessionInput) -> Result<RestartOutput, SessionError> {
155 self.repository.restart(input.session_id.as_ref())
156 }
157}
158
159pub trait AttachUseCase: Send + Sync {
160 fn execute(&self, input: AttachInput) -> Result<AttachOutput, SessionError>;
161}
162
163pub struct AttachUseCaseImpl<R: SessionRepository> {
164 repository: Arc<R>,
165}
166
167impl<R: SessionRepository> AttachUseCaseImpl<R> {
168 pub fn new(repository: Arc<R>) -> Self {
169 Self { repository }
170 }
171}
172
173impl<R: SessionRepository> AttachUseCase for AttachUseCaseImpl<R> {
174 fn execute(&self, input: AttachInput) -> Result<AttachOutput, SessionError> {
175 let session = self.repository.resolve(Some(&input.session_id))?;
176
177 if !session.is_running() {
178 return Err(SessionError::NotRunning {
179 session_id: input.session_id.to_string(),
180 });
181 }
182
183 self.repository.set_active(&input.session_id)?;
184
185 Ok(AttachOutput {
186 session_id: input.session_id,
187 success: true,
188 })
189 }
190}
191
192pub trait ResizeUseCase: Send + Sync {
193 fn execute(&self, input: ResizeInput) -> Result<ResizeOutput, SessionError>;
194}
195
196pub struct ResizeUseCaseImpl<R: SessionRepository> {
197 repository: Arc<R>,
198}
199
200impl<R: SessionRepository> ResizeUseCaseImpl<R> {
201 pub fn new(repository: Arc<R>) -> Self {
202 Self { repository }
203 }
204}
205
206impl<R: SessionRepository> ResizeUseCase for ResizeUseCaseImpl<R> {
207 fn execute(&self, input: ResizeInput) -> Result<ResizeOutput, SessionError> {
208 let session = self.repository.resolve(input.session_id.as_ref())?;
209 session.resize(input.size)?;
210
211 Ok(ResizeOutput {
212 session_id: session.session_id(),
213 success: true,
214 size: input.size,
215 })
216 }
217}
218
219pub trait CleanupUseCase: Send + Sync {
220 fn execute(&self, input: CleanupInput) -> CleanupOutput;
221}
222
223pub struct CleanupUseCaseImpl<R: SessionRepository> {
224 repository: Arc<R>,
225}
226
227impl<R: SessionRepository> CleanupUseCaseImpl<R> {
228 pub fn new(repository: Arc<R>) -> Self {
229 Self { repository }
230 }
231}
232
233impl<R: SessionRepository> CleanupUseCase for CleanupUseCaseImpl<R> {
234 fn execute(&self, input: CleanupInput) -> CleanupOutput {
235 let sessions = self.repository.list();
236 let mut cleaned = 0;
237 let mut failures = Vec::new();
238
239 for session in sessions {
240 let should_cleanup = input.all || !session.running;
241 if !should_cleanup {
242 continue;
243 }
244
245 match self.repository.kill(&session.id) {
246 Ok(()) => cleaned += 1,
247 Err(err) => failures.push(CleanupFailure {
248 session_id: session.id,
249 error: err.to_string(),
250 }),
251 }
252 }
253
254 CleanupOutput { cleaned, failures }
255 }
256}
257
258pub trait AssertUseCase: Send + Sync {
259 fn execute(&self, input: AssertInput) -> Result<AssertOutput, SessionError>;
260}
261
262pub struct AssertUseCaseImpl<R: SessionRepository> {
263 repository: Arc<R>,
264}
265
266impl<R: SessionRepository> AssertUseCaseImpl<R> {
267 pub fn new(repository: Arc<R>) -> Self {
268 Self { repository }
269 }
270}
271
272impl<R: SessionRepository> AssertUseCase for AssertUseCaseImpl<R> {
273 fn execute(&self, input: AssertInput) -> Result<AssertOutput, SessionError> {
274 let condition = format!("{}:{}", input.condition_type.as_str(), input.value);
275
276 let passed = match input.condition_type {
277 AssertConditionType::Text => {
278 let session = self.repository.resolve(input.session_id.as_ref())?;
279 session.update()?;
280 let screen = session.screen_text();
281 screen.contains(&input.value)
282 }
283 AssertConditionType::Session => {
284 let sessions = self.repository.list();
285 sessions
286 .iter()
287 .any(|s| s.id.as_str() == input.value && s.is_active())
288 }
289 other => {
290 return Err(SessionError::InvalidKey(format!(
291 "Unsupported assert condition: {}",
292 other.as_str()
293 )));
294 }
295 };
296
297 Ok(AssertOutput { passed, condition })
298 }
299}
300
301#[cfg(test)]
302#[path = "session_tests.rs"]
303mod tests;