use super::{Channel, IncomingMessage, OutgoingMessage};
use crate::cmd::CommandErrorContext;
use async_trait::async_trait;
use std::sync::Arc;
pub struct TmuxChannel {
transport: Option<Arc<crate::channels::transport::Transport>>,
}
impl TmuxChannel {
pub fn with_transport(transport: Arc<crate::channels::transport::Transport>) -> Self {
Self {
transport: Some(transport),
}
}
}
#[async_trait]
impl Channel for TmuxChannel {
fn name(&self) -> &str {
"tmux"
}
async fn start(&self) -> anyhow::Result<tokio::sync::mpsc::Receiver<IncomingMessage>> {
let (_tx, rx) = tokio::sync::mpsc::channel(64);
tracing::info!("tmux channel started");
if self.transport.is_none() {
tracing::warn!("tmux channel started without transport - output streaming disabled");
}
Ok(rx)
}
async fn send(&self, msg: &OutgoingMessage) -> anyhow::Result<()> {
let session = &msg.thread_id; send_keys(session, &msg.body).await
}
async fn health_check(&self) -> anyhow::Result<()> {
let output = tokio::process::Command::new("tmux")
.args(["list-sessions"])
.output_with_context()
.await?;
if !output.status.success() {
anyhow::bail!("tmux server not running");
}
Ok(())
}
}
pub async fn send_keys(session: &str, text: &str) -> anyhow::Result<()> {
let output = tokio::process::Command::new("tmux")
.args(["send-keys", "-t", session, text, "Enter"])
.output_with_context()
.await?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
anyhow::bail!("tmux send-keys failed: {stderr}");
}
Ok(())
}
pub async fn capture_pane(session: &str) -> anyhow::Result<String> {
let output = tokio::process::Command::new("tmux")
.args(["capture-pane", "-t", session, "-p", "-S", "-"])
.output_with_context()
.await?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
anyhow::bail!("tmux capture-pane failed: {stderr}");
}
Ok(String::from_utf8_lossy(&output.stdout).to_string())
}
pub async fn list_orch_sessions() -> anyhow::Result<Vec<String>> {
let output = tokio::process::Command::new("tmux")
.args(["list-sessions", "-F", "#{session_name}"])
.output_with_context()
.await?;
if !output.status.success() {
return Ok(vec![]);
}
Ok(String::from_utf8_lossy(&output.stdout)
.lines()
.filter(|s| s.starts_with("orch-"))
.map(String::from)
.collect())
}
pub async fn is_session_dead(session: &str) -> bool {
let output = tokio::process::Command::new("tmux")
.args(["has-session", "-t", session])
.output_with_context()
.await;
match output {
Ok(output) => !output.status.success(),
Err(_) => true,
}
}