Skip to main content

oxios_kernel/kernel_handle/
pty_api.rs

1//! PtyApi — 14th typed facade alongside ExecApi (RFC-038 §8.1).
2use std::sync::Arc;
3
4use parking_lot::RwLock;
5
6use crate::config::PtyConfig;
7use crate::pty::{PtyManager, PtySessionInfo, PtySize};
8
9/// Shared, hot-reloadable PTY config.
10pub type SharedPtyConfig = Arc<RwLock<PtyConfig>>;
11
12/// Facade over [`PtyManager`] for kernel consumers and HTTP routes.
13pub struct PtyApi {
14    pub manager: Arc<PtyManager>,
15    pub config: SharedPtyConfig,
16}
17
18impl PtyApi {
19    pub fn new(config: SharedPtyConfig) -> Self {
20        let manager = Arc::new(PtyManager::new(Arc::clone(&config)));
21        Self { manager, config }
22    }
23
24    /// Snapshot of the current PTY config.
25    pub fn config_snapshot(&self) -> PtyConfig {
26        self.config.read().clone()
27    }
28
29    /// True if `[pty] enabled = true` in config.
30    pub fn is_enabled(&self) -> bool {
31        self.config.read().enabled
32    }
33
34    /// Open a new PTY session. Validates shell allowlist + per-principal cap.
35    pub fn open(
36        &self,
37        principal: &str,
38        shell: Option<String>,
39        size: PtySize,
40    ) -> Result<crate::pty::PtySessionId, crate::pty::PtyError> {
41        let s = self.manager.open(principal, shell, size)?;
42        Ok(s.id.clone())
43    }
44
45    /// Re-attach an existing session by id.
46    pub fn attach(
47        &self,
48        principal: &str,
49        session_id: &str,
50    ) -> Result<crate::pty::PtySessionId, crate::pty::PtyError> {
51        let s = self.manager.attach(principal, session_id)?;
52        Ok(s.id.clone())
53    }
54
55    /// Write bytes to a session's stdin.
56    pub fn write(&self, session_id: &str, bytes: &[u8]) -> Result<(), crate::pty::PtyError> {
57        self.manager.write(session_id, bytes)
58    }
59
60    /// Resize a session's PTY.
61    pub fn resize(
62        &self,
63        session_id: &str,
64        cols: u16,
65        rows: u16,
66    ) -> Result<(), crate::pty::PtyError> {
67        self.manager.resize(session_id, cols, rows)
68    }
69
70    /// Take a reader for streaming PTY stdout to a WS client.
71    pub fn try_clone_reader(
72        &self,
73        session_id: &str,
74    ) -> Result<Box<dyn std::io::Read + Send>, crate::pty::PtyError> {
75        self.manager.try_clone_reader(session_id)
76    }
77
78    /// Mark session as Attached.
79    pub fn mark_attached(&self, session_id: &str) -> bool {
80        self.manager.mark_attached(session_id)
81    }
82
83    /// Mark session as Detached.
84    pub fn mark_detached(&self, session_id: &str) -> bool {
85        self.manager.mark_detached(session_id)
86    }
87
88    /// Close a session (SIGTERM via Drop).
89    pub fn close(&self, session_id: &str) -> Result<(), crate::pty::PtyError> {
90        self.manager.close(session_id)
91    }
92
93    /// List sessions for a principal (UI listing endpoint).
94    pub fn list_sessions(&self, principal: &str) -> Vec<PtySessionInfo> {
95        self.manager.list_sessions(principal)
96    }
97
98    /// Spawn the GC tick task.
99    pub fn start_gc(&self) -> tokio::task::JoinHandle<()> {
100        Arc::clone(&self.manager).start_gc()
101    }
102}