//! Terminal session entity and supporting value objects. use serde::{Deserialize, Serialize}; use crate::error::DomainError; use crate::ids::{AgentId, NodeId, SessionId}; use crate::project::ProjectPath; /// Dimensions of a pseudo-terminal, in character cells. /// /// Invariant: `rows > 0 && cols > 0`. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PtySize { /// Number of rows. pub rows: u16, /// Number of columns. pub cols: u16, } impl PtySize { /// Builds a validated PTY size. /// /// # Errors /// Returns [`DomainError::InvalidPtySize`] if either dimension is zero. pub fn new(rows: u16, cols: u16) -> Result { if rows == 0 || cols == 0 { return Err(DomainError::InvalidPtySize { rows, cols }); } Ok(Self { rows, cols }) } } /// What a terminal session is running. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase", tag = "type")] pub enum SessionKind { /// A plain shell terminal. Plain, /// A terminal launched for a specific agent. #[serde(rename_all = "camelCase")] Agent { /// The agent driving this session. agent_id: AgentId, }, } /// Lifecycle status of a terminal session. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase", tag = "state")] pub enum SessionStatus { /// Spawn requested, not yet confirmed running. Starting, /// Running. Running, /// Exited with a code. Exited { /// Process exit code. code: i32, }, } /// A terminal session hosted by a layout leaf cell. /// /// Invariants: /// - `pty_size` is valid (`rows>0 && cols>0`), /// - "at most one active session per leaf" is a *layout* invariant enforced in /// [`crate::layout`], not here (a session does not own the leaf). #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct TerminalSession { /// Stable identifier. pub id: SessionId, /// Layout leaf hosting this session. pub node_id: NodeId, /// Working directory. pub cwd: ProjectPath, /// What the session runs. pub kind: SessionKind, /// Current terminal size. pub pty_size: PtySize, /// Lifecycle status. pub status: SessionStatus, } impl TerminalSession { /// Builds a terminal session (in `Starting` state). #[must_use] pub fn starting( id: SessionId, node_id: NodeId, cwd: ProjectPath, kind: SessionKind, pty_size: PtySize, ) -> Self { Self { id, node_id, cwd, kind, pty_size, status: SessionStatus::Starting, } } }