use super::headless::HeadlessHandle;
use super::pty::PtyHandle;
use super::terminal::TerminalHandle;
use super::{AISession, SessionConfig, SessionStatus};
use anyhow::Result;
use portable_pty::CommandBuilder;
use std::io::ErrorKind;
pub async fn start_session(session: &AISession) -> Result<()> {
{
let mut status = session.status.write().await;
if *status != SessionStatus::Initializing {
return Err(anyhow::anyhow!("Session already started"));
}
*status = SessionStatus::Running;
}
let shell_env = std::env::var("SHELL").ok();
let shell = session
.config
.shell
.as_deref()
.or(shell_env.as_deref())
.unwrap_or("/bin/bash");
let terminal = if session.config.force_headless {
TerminalHandle::Headless(
HeadlessHandle::spawn_shell(
shell,
&session.config.working_directory,
session.config.environment.iter(),
)
.await?,
)
} else {
match spawn_pty(&session.config, shell).await {
Ok(pty) => TerminalHandle::Pty(pty),
Err(err) => {
if session.config.allow_headless_fallback && is_permission_denied(&err) {
tracing::warn!(
"PTY unavailable ({}). Falling back to headless shell for session {}",
err,
session.id
);
TerminalHandle::Headless(
HeadlessHandle::spawn_shell(
shell,
&session.config.working_directory,
session.config.environment.iter(),
)
.await?,
)
} else {
return Err(err);
}
}
}
};
{
let mut terminal_lock = session.terminal.write().await;
*terminal_lock = Some(terminal);
}
*session.last_activity.write().await = chrono::Utc::now();
Ok(())
}
pub async fn stop_session(session: &AISession) -> Result<()> {
{
let mut status = session.status.write().await;
if *status != SessionStatus::Running && *status != SessionStatus::Paused {
return Ok(()); }
*status = SessionStatus::Terminating;
}
{
let mut terminal_lock = session.terminal.write().await;
if let Some(terminal) = terminal_lock.take() {
terminal.shutdown().await?;
}
}
{
let mut process_lock = session.process.write().await;
if let Some(mut process) = process_lock.take() {
let _ = process.kill().await;
}
}
{
let mut status = session.status.write().await;
*status = SessionStatus::Terminated;
}
Ok(())
}
async fn spawn_pty(config: &SessionConfig, shell: &str) -> Result<PtyHandle> {
let pty = PtyHandle::new(config.pty_size.0, config.pty_size.1)?;
let mut cmd = CommandBuilder::new(shell);
cmd.cwd(&config.working_directory);
for (key, value) in &config.environment {
cmd.env(key, value);
}
pty.spawn_command(cmd).await?;
Ok(pty)
}
fn is_permission_denied(err: &anyhow::Error) -> bool {
err.chain().any(|cause| {
if let Some(io_err) = cause.downcast_ref::<std::io::Error>() {
io_err.kind() == ErrorKind::PermissionDenied
} else {
let msg = cause.to_string();
msg.contains("PermissionDenied") || msg.contains("Operation not permitted")
}
})
}
pub async fn pause_session(session: &AISession) -> Result<()> {
let mut status = session.status.write().await;
if *status != SessionStatus::Running {
return Err(anyhow::anyhow!("Session not running"));
}
*status = SessionStatus::Paused;
Ok(())
}
pub async fn resume_session(session: &AISession) -> Result<()> {
let mut status = session.status.write().await;
if *status != SessionStatus::Paused {
return Err(anyhow::anyhow!("Session not paused"));
}
*status = SessionStatus::Running;
*session.last_activity.write().await = chrono::Utc::now();
Ok(())
}