Skip to main content

orchestral_runtime/exec_process/
lifecycle.rs

1use std::sync::{Arc, Mutex};
2
3use orchestral_core::agent_protocol::wire::RunId;
4use orchestral_core::tool_protocol::ToolOperationPlan;
5use tokio::sync::Notify;
6
7use super::{ExecProcessError, ExecSessionId};
8
9/// Monotonic lifecycle of one supervised execution resource.
10#[derive(Debug, Clone, PartialEq, Eq)]
11#[non_exhaustive]
12pub enum ExecSessionStatus {
13    Running,
14    Exited { exit_code: i32 },
15    Terminated,
16    Failed { message: String },
17}
18
19impl ExecSessionStatus {
20    pub fn is_terminal(&self) -> bool {
21        !matches!(self, Self::Running)
22    }
23}
24
25/// Authoritative point-in-time view used by UI and lifecycle reducers.
26#[derive(Debug, Clone, PartialEq)]
27pub struct ExecSessionSnapshot {
28    pub run_id: RunId,
29    pub session_id: ExecSessionId,
30    pub tty: bool,
31    pub status: ExecSessionStatus,
32    pub operation: ToolOperationPlan,
33    pub wall_time_seconds: f64,
34}
35
36/// Lossy process activity notification. `snapshot` remains authoritative when
37/// a receiver lags.
38#[derive(Debug, Clone, PartialEq)]
39pub struct ExecSessionEvent {
40    pub snapshot: ExecSessionSnapshot,
41}
42
43pub(super) struct SessionLifecycle {
44    status: Mutex<ExecSessionStatus>,
45    pub(super) changed: Notify,
46}
47
48impl SessionLifecycle {
49    pub(super) fn running() -> Arc<Self> {
50        Arc::new(Self {
51            status: Mutex::new(ExecSessionStatus::Running),
52            changed: Notify::new(),
53        })
54    }
55
56    pub(super) fn status(&self) -> Result<ExecSessionStatus, ExecProcessError> {
57        self.status
58            .lock()
59            .map(|status| status.clone())
60            .map_err(|_| ExecProcessError::Unavailable)
61    }
62
63    pub(super) fn transition(&self, status: ExecSessionStatus) -> Result<bool, ExecProcessError> {
64        let mut current = self
65            .status
66            .lock()
67            .map_err(|_| ExecProcessError::Unavailable)?;
68        if current.is_terminal() {
69            return Ok(false);
70        }
71        *current = status;
72        drop(current);
73        self.changed.notify_waiters();
74        Ok(true)
75    }
76}