Skip to main content

handshake_core/
event.rs

1use std::collections::VecDeque;
2
3use thiserror::Error;
4
5use crate::application::RequestId;
6use crate::folder::FolderState;
7use crate::terminal::{TerminalExit, TerminalId};
8use crate::workflow::{SessionId, Workflow, WorkflowId, WorkflowState};
9
10#[derive(Clone, Debug, Eq, PartialEq)]
11pub enum StateEvent {
12    ApplicationReady,
13    ConfigChanged(Box<crate::config::Config>),
14    WorkflowTypesChanged(Vec<crate::WorkflowType>),
15    TraceChanged(crate::TraceSummary),
16    WorkflowsChanged(WorkflowState),
17    WorkflowChanged(Workflow),
18    /// The open folder or the recent folders changed. Carries the complete new folder state.
19    FoldersChanged(FolderState),
20    TerminalClosed {
21        terminal_id: TerminalId,
22    },
23    TerminalExited {
24        terminal_id: TerminalId,
25        exit: TerminalExit,
26    },
27    TerminalFailed {
28        terminal_id: TerminalId,
29        message: String,
30    },
31}
32
33#[derive(Clone, Debug, Eq, PartialEq)]
34pub enum CommandResult {
35    WorkflowTypeValidated { issues: Vec<crate::ValidationIssue> },
36    WorkflowTypeSaved { reference: crate::WorkflowTypeRef },
37    WorkflowTypeDeleted { reference: crate::WorkflowTypeRef },
38    SessionCreated { session_id: SessionId },
39    SessionRenamed { session_id: SessionId },
40    SessionSelected { session_id: SessionId },
41    SessionDeleted { session_id: SessionId },
42    Pong,
43    WorkflowCreated { workflow_id: WorkflowId },
44    WorkflowActivated { workflow_id: WorkflowId },
45    WorkflowReopened { workflow_id: WorkflowId },
46    WorkflowClosed { workflow_id: WorkflowId },
47    AgentStarted { workflow_id: WorkflowId },
48    AgentCancelled { workflow_id: WorkflowId },
49    TerminalStarted { terminal_id: TerminalId },
50    TerminalClosed { terminal_id: TerminalId },
51}
52
53#[derive(Clone, Debug, Eq, PartialEq)]
54pub enum EventKind {
55    State(StateEvent),
56    CommandCompleted {
57        request_id: RequestId,
58        result: CommandResult,
59    },
60}
61
62#[derive(Clone, Debug, Eq, PartialEq)]
63pub struct Event {
64    pub sequence: u64,
65    pub kind: EventKind,
66}
67
68#[derive(Debug)]
69pub(crate) struct EventJournal {
70    capacity: usize,
71    events: VecDeque<Event>,
72    latest_sequence: u64,
73}
74
75impl EventJournal {
76    pub(crate) fn new(capacity: usize) -> Result<Self, EventError> {
77        if capacity == 0 {
78            return Err(EventError::ZeroCapacity);
79        }
80
81        Ok(Self {
82            capacity,
83            events: VecDeque::with_capacity(capacity),
84            latest_sequence: 0,
85        })
86    }
87
88    pub(crate) fn append(&mut self, kind: EventKind) -> Result<Event, EventError> {
89        let sequence = self
90            .latest_sequence
91            .checked_add(1)
92            .ok_or(EventError::SequenceOverflow)?;
93        let event = Event { sequence, kind };
94        self.events.push_back(event.clone());
95        self.latest_sequence = sequence;
96
97        if self.events.len() > self.capacity {
98            self.events.pop_front();
99        }
100
101        Ok(event)
102    }
103
104    pub(crate) const fn latest_sequence(&self) -> u64 {
105        self.latest_sequence
106    }
107
108    pub(crate) fn after(&self, sequence: u64, limit: usize) -> Result<Vec<Event>, EventError> {
109        if limit == 0 {
110            return Err(EventError::ZeroLimit);
111        }
112
113        if let Some(oldest) = self.events.front() {
114            let oldest_cursor = oldest.sequence.saturating_sub(1);
115            if sequence < oldest_cursor {
116                return Err(EventError::CursorExpired {
117                    requested: sequence,
118                    oldest_available: oldest.sequence,
119                });
120            }
121        }
122
123        Ok(self
124            .events
125            .iter()
126            .filter(|event| event.sequence > sequence)
127            .take(limit)
128            .cloned()
129            .collect())
130    }
131}
132
133#[derive(Debug, Error)]
134pub enum EventError {
135    #[error(
136        "event cursor {requested} has expired; the oldest available event is {oldest_available}"
137    )]
138    CursorExpired {
139        requested: u64,
140        oldest_available: u64,
141    },
142    #[error("event sequence overflowed")]
143    SequenceOverflow,
144    #[error("event journal capacity must be greater than zero")]
145    ZeroCapacity,
146    #[error("event batch limit must be greater than zero")]
147    ZeroLimit,
148}