Skip to main content

tmai_core/runtime/
mod.rs

1//! Runtime adapter abstraction for tmux decoupling.
2//!
3//! Provides [`RuntimeAdapter`] trait that abstracts agent discovery, observation,
4//! and control. Two implementations:
5//! - [`TmuxAdapter`]: wraps `TmuxClient` for traditional tmux-based operation
6//! - [`StandaloneAdapter`]: hook/IPC-only mode, no tmux required
7
8mod standalone;
9mod tmux_adapter;
10
11pub use standalone::StandaloneAdapter;
12pub use tmux_adapter::TmuxAdapter;
13
14use anyhow::Result;
15
16use crate::tmux::PaneInfo;
17
18/// Abstraction over the terminal multiplexer / process runtime.
19///
20/// Implementors provide agent discovery, screen observation, key control,
21/// and optional session management. Methods are sync because `TmuxClient`
22/// is subprocess-based (Poller already runs in a tokio blocking task).
23pub trait RuntimeAdapter: Send + Sync {
24    // =========================================================
25    // Discovery
26    // =========================================================
27
28    /// List all panes/agents across all sessions (including detached).
29    fn list_all_panes(&self) -> Result<Vec<PaneInfo>>;
30
31    /// List panes from attached sessions only.
32    fn list_panes(&self) -> Result<Vec<PaneInfo>>;
33
34    /// List session names.
35    fn list_sessions(&self) -> Result<Vec<String>>;
36
37    /// Check if the runtime backend is available and operational.
38    fn is_available(&self) -> bool;
39
40    // =========================================================
41    // Observation
42    // =========================================================
43
44    /// Capture pane content with ANSI escape codes (for preview rendering).
45    fn capture_pane(&self, target: &str) -> Result<String>;
46
47    /// Capture full scrollback with ANSI escape codes (all history).
48    fn capture_pane_full(&self, target: &str) -> Result<String> {
49        self.capture_pane(target)
50    }
51
52    /// Capture pane content as plain text (for detection analysis).
53    fn capture_pane_plain(&self, target: &str) -> Result<String>;
54
55    /// Get the title of a pane.
56    fn get_pane_title(&self, target: &str) -> Result<String>;
57
58    /// Get terminal cursor position (col, row) for a pane, both 0-indexed.
59    /// Returns None if the runtime does not support cursor queries.
60    fn get_cursor_position(&self, _target: &str) -> Result<Option<(u32, u32)>> {
61        Ok(None)
62    }
63
64    // =========================================================
65    // Control
66    // =========================================================
67
68    /// Send interpreted keys to a pane (e.g., "Enter", "C-c").
69    fn send_keys(&self, target: &str, keys: &str) -> Result<()>;
70
71    /// Send literal text to a pane (no key interpretation).
72    fn send_keys_literal(&self, target: &str, keys: &str) -> Result<()>;
73
74    /// Send literal text followed by Enter.
75    fn send_text_and_enter(&self, target: &str, text: &str) -> Result<()>;
76
77    // =========================================================
78    // Focus / Lifecycle
79    // =========================================================
80
81    /// Focus on a pane (bring to foreground in the runtime).
82    fn focus_pane(&self, target: &str) -> Result<()>;
83
84    /// Terminate a pane / agent process.
85    fn kill_pane(&self, target: &str) -> Result<()>;
86
87    // =========================================================
88    // Session Management (optional capabilities)
89    // =========================================================
90
91    /// Create a new session. Not all runtimes support this.
92    fn create_session(&self, _name: &str, _cwd: &str, _window_name: Option<&str>) -> Result<()> {
93        anyhow::bail!("session creation not supported by {} runtime", self.name())
94    }
95
96    /// Create a new window in a session. Returns the new pane target.
97    fn new_window(&self, _session: &str, _cwd: &str, _window_name: Option<&str>) -> Result<String> {
98        anyhow::bail!("window creation not supported by {} runtime", self.name())
99    }
100
101    /// Split a window to create a new pane. Returns the new pane target.
102    fn split_window(&self, _session: &str, _cwd: &str) -> Result<String> {
103        anyhow::bail!("window splitting not supported by {} runtime", self.name())
104    }
105
106    /// Split a window and apply tiled layout for balanced pane sizes.
107    fn split_window_tiled(&self, session: &str, cwd: &str) -> Result<String> {
108        // Default: fall back to regular split_window
109        self.split_window(session, cwd)
110    }
111
112    /// Apply a layout to the window containing the target (e.g. "tiled").
113    fn select_layout(&self, _target: &str, _layout: &str) -> Result<()> {
114        Ok(()) // no-op for runtimes that don't support layouts
115    }
116
117    /// Count panes in the window containing the target.
118    fn count_panes(&self, _target: &str) -> Result<usize> {
119        Ok(0)
120    }
121
122    /// Run a command in a pane (send text + Enter).
123    fn run_command(&self, _target: &str, _command: &str) -> Result<()> {
124        anyhow::bail!("command execution not supported by {} runtime", self.name())
125    }
126
127    /// Run a command wrapped with `tmai wrap` for PTY monitoring.
128    fn run_command_wrapped(&self, _target: &str, _command: &str) -> Result<()> {
129        anyhow::bail!("wrapped command not supported by {} runtime", self.name())
130    }
131
132    /// Get the user's current location (session name, window index).
133    fn get_current_location(&self) -> Result<(String, u32)> {
134        anyhow::bail!("current location not available in {} runtime", self.name())
135    }
136
137    // =========================================================
138    // Metadata
139    // =========================================================
140
141    /// Runtime name for logging and display (e.g., "tmux", "standalone").
142    fn name(&self) -> &str;
143}