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 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}