use super::screen::Screen;
use super::transcript::Transcript;
use crate::protocol::{ExitReason, RecordedCommand, ResourceLimits};
use crate::pty::PtyProcess;
use std::time::Instant;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AgentState {
Running,
Exited { code: i32 },
}
pub struct Agent {
pub id: String,
pub command: Vec<String>,
pub labels: Vec<String>,
pub pty: PtyProcess,
pub state: AgentState,
pub exit_reason: Option<ExitReason>,
pub started_at: Instant,
pub transcript: Transcript,
pub screen: Screen,
pub attached: bool,
pub limits: Option<ResourceLimits>,
pub sigterm_sent: bool,
pub sigterm_sent_at: Option<Instant>,
pub screen_cleared_at: Option<Instant>,
pub no_resize: bool,
pub recording: bool,
pub recorded_commands: Vec<RecordedCommand>,
}
impl Agent {
#[must_use]
pub fn new(
id: String,
command: Vec<String>,
labels: Vec<String>,
limits: Option<ResourceLimits>,
pty: PtyProcess,
rows: u16,
cols: u16,
no_resize: bool,
record: bool,
) -> Self {
let transcript_size = limits
.and_then(|l| l.max_output)
.map_or(1024 * 1024, |m| m as usize);
Self {
id,
command,
labels,
pty,
state: AgentState::Running,
exit_reason: None,
started_at: Instant::now(),
transcript: Transcript::new(transcript_size),
screen: Screen::new(rows, cols),
attached: false,
limits,
sigterm_sent: false,
sigterm_sent_at: None,
screen_cleared_at: None,
no_resize,
recording: record,
recorded_commands: Vec::new(),
}
}
pub fn record_command(&mut self, command: impl Into<String>, payload: impl Into<String>) {
if self.recording {
self.recorded_commands.push(RecordedCommand::new(command, payload));
}
}
#[must_use]
pub fn is_timed_out(&self) -> bool {
if let Some(limits) = self.limits {
if let Some(timeout_secs) = limits.timeout {
let timeout_millis = timeout_secs * 1000;
return self.started_at.elapsed().as_millis() as u64 >= timeout_millis;
}
}
false
}
#[must_use]
pub fn should_sigkill(&self) -> bool {
if let Some(sent_at) = self.sigterm_sent_at {
return sent_at.elapsed().as_millis() >= 5000;
}
false
}
#[must_use]
pub fn has_labels(&self, labels: &[String]) -> bool {
labels.iter().all(|l| self.labels.contains(l))
}
#[must_use]
#[allow(clippy::cast_sign_loss)] #[allow(clippy::missing_const_for_fn)] pub fn pid(&self) -> u32 {
self.pty.pid.as_raw() as u32
}
#[must_use]
pub const fn is_running(&self) -> bool {
matches!(self.state, AgentState::Running)
}
#[must_use]
pub const fn exit_code(&self) -> Option<i32> {
match self.state {
AgentState::Exited { code } => Some(code),
AgentState::Running => None,
}
}
}