pub mod pty;
pub mod pty_io;
pub mod tmux;
use anyhow::{Result, anyhow};
use tmux::TmuxEngine;
use crate::agent::state::AgentSnapshot;
use crate::agent::watch::AgentWatcher;
use crate::models::session::RuntimeKind;
#[derive(Debug, Clone, serde::Serialize)]
pub struct EngineSessionInfo {
pub name: String,
pub attached: bool,
pub windows: u32,
pub created: String,
pub cwd: Option<String>,
pub agent_kind: Option<String>,
pub agent_state: Option<String>,
pub attention_reason: Option<String>,
pub agent_event: Option<String>,
pub agent_nonce: Option<String>,
}
pub struct WatchTarget {
pub kind: RuntimeKind,
pub session: String,
pub pane_pid: u32,
pub activity: String,
pub title: String,
}
pub trait SessionEngine: Send + Sync {
async fn create_session(&self, name: &str, cwd: &str, command: Option<&str>) -> Result<bool>;
async fn kill_session(&self, name: &str) -> Result<()>;
async fn session_exists(&self, name: &str) -> bool;
async fn list_sessions(&self) -> Result<Vec<EngineSessionInfo>>;
async fn current_cwd(&self, name: &str) -> Result<String>;
async fn capture_screen(&self, name: &str) -> Result<String>;
async fn agent_snapshot(&self, name: &str) -> Result<Option<AgentSnapshot>>;
async fn is_active(&self, name: &str) -> bool;
async fn track_session(&self, name: &str) -> Result<()>;
async fn untrack_session(&self, name: &str);
async fn watch_targets(&self) -> Vec<WatchTarget>;
}
#[derive(Clone)]
pub struct EngineRegistry {
mux: TmuxEngine,
watcher: AgentWatcher,
}
impl Default for EngineRegistry {
fn default() -> Self {
Self::new()
}
}
impl EngineRegistry {
pub fn new() -> Self {
Self { mux: TmuxEngine::new(), watcher: AgentWatcher::default() }
}
pub fn watcher(&self) -> &AgentWatcher {
&self.watcher
}
pub async fn create_session(
&self,
kind: RuntimeKind,
name: &str,
cwd: &str,
command: Option<&str>,
) -> Result<bool> {
match kind {
RuntimeKind::Tmux => self.mux.create_session(name, cwd, command).await,
_ => Err(anyhow!("no session engine registered for runtime kind")),
}
}
pub async fn kill_session(&self, kind: RuntimeKind, name: &str) -> Result<()> {
match kind {
RuntimeKind::Tmux => self.mux.kill_session(name).await,
_ => Err(anyhow!("no session engine registered for runtime kind")),
}
}
pub async fn session_exists(&self, kind: RuntimeKind, name: &str) -> bool {
match kind {
RuntimeKind::Tmux => self.mux.session_exists(name).await,
_ => false,
}
}
pub async fn list_sessions(&self) -> Result<Vec<EngineSessionInfo>> {
self.mux.list_sessions().await
}
pub async fn current_cwd(&self, kind: RuntimeKind, name: &str) -> Result<String> {
match kind {
RuntimeKind::Tmux => self.mux.current_cwd(name).await,
_ => Err(anyhow!("no session engine registered for runtime kind")),
}
}
pub async fn capture_screen(&self, kind: RuntimeKind, name: &str) -> Result<String> {
match kind {
RuntimeKind::Tmux => self.mux.capture_screen(name).await,
_ => Err(anyhow!("no session engine registered for runtime kind")),
}
}
pub async fn agent_snapshot(
&self,
kind: RuntimeKind,
name: &str,
) -> Result<Option<AgentSnapshot>> {
match kind {
RuntimeKind::Tmux => self.mux.agent_snapshot(name).await,
_ => Ok(None),
}
}
pub async fn is_active(&self, kind: RuntimeKind, name: &str) -> bool {
match kind {
RuntimeKind::Tmux => self.mux.is_active(name).await,
_ => false,
}
}
pub async fn track_session(&self, kind: RuntimeKind, name: &str) -> Result<()> {
match kind {
RuntimeKind::Tmux => self.mux.track_session(name).await,
_ => Ok(()),
}
}
pub async fn untrack_session(&self, kind: RuntimeKind, name: &str) {
if kind == RuntimeKind::Tmux {
self.mux.untrack_session(name).await;
}
}
pub async fn watch_targets(&self) -> Vec<WatchTarget> {
self.mux.watch_targets().await
}
pub fn multiplexer_name(&self) -> &'static str {
self.mux.multiplexer_name()
}
pub fn multiplexer_install_hints(&self) -> &'static [&'static str] {
self.mux.multiplexer_install_hints()
}
pub fn check_multiplexer(&self) -> Result<()> {
self.mux.check_available()
}
pub async fn mouse_enabled(&self) -> Result<bool> {
self.mux.mouse_enabled().await
}
pub async fn set_mouse_enabled(&self, enabled: bool) -> Result<()> {
self.mux.set_mouse_enabled(enabled).await
}
}
#[cfg(windows)]
pub async fn apply_multiplexer_escape_time_workaround() {
tmux::terminal_ws::apply_escape_time_workaround().await
}
pub async fn run_terminal_session(
kind: Option<String>,
ws: axum::extract::ws::WebSocket,
session_id: String,
query: crate::ws::terminal::TerminalQuery,
state: crate::AppState,
) {
match kind.as_deref() {
Some("pty") => pty::terminal_ws::handle_pty_terminal(ws, session_id, query, state).await,
_ => tmux::terminal_ws::handle_terminal(ws, session_id, query, state).await,
}
}
pub async fn run_external_terminal_session(
ws: axum::extract::ws::WebSocket,
session_name: String,
query: crate::ws::terminal::TerminalQuery,
state: crate::AppState,
) {
tmux::terminal_ws::handle_external_terminal(ws, session_name, query, state).await
}