use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::fmt;
use std::path::PathBuf;
use thiserror::Error;
use uuid::Uuid;
use super::injection_status::InjectionStatus;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ManagedSessionId(pub Uuid);
impl ManagedSessionId {
pub fn new() -> Self {
Self(Uuid::new_v4())
}
pub fn as_uuid(&self) -> &Uuid {
&self.0
}
}
impl Default for ManagedSessionId {
fn default() -> Self {
Self::new()
}
}
impl fmt::Display for ManagedSessionId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl From<Uuid> for ManagedSessionId {
fn from(uuid: Uuid) -> Self {
Self(uuid)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ManagedSessionState {
Provisioning,
Active,
Stopped,
Errored,
Decommissioned,
Deleted,
}
impl ManagedSessionState {
pub fn is_terminal(&self) -> bool {
matches!(self, Self::Decommissioned | Self::Deleted)
}
pub fn from_wire(token: &str) -> Option<Self> {
match token {
"provisioning" => Some(Self::Provisioning),
"active" => Some(Self::Active),
"stopped" => Some(Self::Stopped),
"errored" => Some(Self::Errored),
"decommissioned" => Some(Self::Decommissioned),
"deleted" => Some(Self::Deleted),
_ => None,
}
}
}
impl fmt::Display for ManagedSessionState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
Self::Provisioning => "provisioning",
Self::Active => "active",
Self::Stopped => "stopped",
Self::Errored => "errored",
Self::Decommissioned => "decommissioned",
Self::Deleted => "deleted",
};
write!(f, "{s}")
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionRecord {
pub id: ManagedSessionId,
pub tmux_name: String,
pub cwd: PathBuf,
pub task: String,
pub state: ManagedSessionState,
pub created_at: DateTime<Utc>,
pub last_activity_at: Option<DateTime<Utc>>,
pub workspace_path: Option<PathBuf>,
pub repo_url: Option<String>,
pub branch: Option<String>,
pub pending_decision: Option<String>,
pub proposed_default: Option<String>,
#[serde(default)]
pub correlation: crate::driver::SessionCorrelation,
#[serde(default)]
pub runtime: crate::runtime::RuntimeKind,
#[serde(default)]
pub ephemeral: bool,
#[serde(default)]
pub workspace_owned: bool,
#[serde(default)]
pub source_id: Option<String>,
#[serde(default)]
pub claude_session_id: Option<String>,
#[serde(default)]
pub scrollback_path: Option<PathBuf>,
#[serde(default)]
pub last_cwd: Option<PathBuf>,
#[serde(default)]
pub deliverable_id: Option<crate::deliverable::DeliverableId>,
#[serde(default)]
pub pane_id: Option<String>,
#[serde(default)]
pub injection_status: InjectionStatus,
#[serde(default)]
pub worktree_owner: Option<ManagedSessionId>,
}
#[derive(Debug, Error)]
pub enum RecordError {
#[error("invalid session record: {0}")]
Invalid(String),
}
#[cfg(test)]
#[path = "record_tests.rs"]
mod tests;