atomr_agents_coding_cli_harness/
session.rs1use std::sync::Arc;
9
10use parking_lot::RwLock;
11use tokio::sync::{broadcast, mpsc};
12
13use atomr_agents_coding_cli_core::{CliRequest, CliSessionId, CliVendorKind};
14
15#[derive(Debug, Clone)]
17pub enum SessionEvent {
18 Bytes(Vec<u8>),
20 Exited { code: Option<i32> },
22}
23
24#[derive(Debug, Clone)]
26pub enum SessionTransport {
27 Stdin(Vec<u8>),
28 Resize { cols: u16, rows: u16 },
29 Detach,
30}
31
32pub struct InteractiveSessionHandle {
33 pub id: CliSessionId,
34 pub vendor: CliVendorKind,
35 pub tmux_session: String,
36 pub started_at: chrono::DateTime<chrono::Utc>,
37 pub request: CliRequest,
38
39 pub events: broadcast::Sender<SessionEvent>,
41 pub input: mpsc::Sender<SessionTransport>,
43
44 pub closed: Arc<parking_lot::Mutex<bool>>,
47}
48
49impl InteractiveSessionHandle {
50 pub fn subscribe(&self) -> broadcast::Receiver<SessionEvent> {
51 self.events.subscribe()
52 }
53
54 pub async fn send_stdin(&self, bytes: Vec<u8>) -> bool {
55 self.input.send(SessionTransport::Stdin(bytes)).await.is_ok()
56 }
57
58 pub async fn resize(&self, cols: u16, rows: u16) -> bool {
59 self.input
60 .send(SessionTransport::Resize { cols, rows })
61 .await
62 .is_ok()
63 }
64
65 pub async fn detach(&self) -> bool {
66 self.input.send(SessionTransport::Detach).await.is_ok()
67 }
68}
69
70#[derive(Default, Clone)]
73pub struct SessionRegistry {
74 inner: Arc<RwLock<Vec<Arc<InteractiveSessionHandle>>>>,
75}
76
77impl SessionRegistry {
78 pub fn new() -> Self {
79 Self::default()
80 }
81
82 pub fn insert(&self, h: Arc<InteractiveSessionHandle>) {
83 self.inner.write().push(h);
84 }
85
86 pub fn get(&self, id: &CliSessionId) -> Option<Arc<InteractiveSessionHandle>> {
87 self.inner.read().iter().find(|s| &s.id == id).cloned()
88 }
89
90 pub fn list(&self) -> Vec<Arc<InteractiveSessionHandle>> {
91 self.inner.read().clone()
92 }
93
94 pub fn remove(&self, id: &CliSessionId) {
95 self.inner.write().retain(|s| &s.id != id);
96 }
97
98 pub fn len(&self) -> usize {
99 self.inner.read().len()
100 }
101 pub fn is_empty(&self) -> bool {
102 self.inner.read().is_empty()
103 }
104}