orchestral_runtime/exec_process/
lifecycle.rs1use 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#[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#[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#[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}