opendev_tui/event/mod.rs
1//! Event types for the TUI application.
2//!
3//! Bridges crossterm terminal events with application-level events
4//! (agent messages, tool execution updates, etc.).
5//!
6//! This module is split into focused sub-modules:
7//! - [`handler`] — Crossterm event reader with scroll debouncing
8//! - [`recorder`] — Event recording/replay for debugging
9
10mod handler;
11mod recorder;
12
13pub use handler::EventHandler;
14pub use recorder::{EventRecorder, RecordedEvent, load_recorded_events};
15
16use crossterm::event::{Event as CrosstermEvent, KeyEvent};
17use opendev_models::message::ChatMessage;
18use opendev_runtime::InterruptToken;
19
20/// Application-level events consumed by the main event loop.
21#[derive(Debug)]
22#[allow(clippy::large_enum_variant)]
23pub enum AppEvent {
24 /// Raw terminal event from crossterm.
25 Terminal(CrosstermEvent),
26 /// Key press (extracted from terminal event for convenience).
27 Key(KeyEvent),
28 /// Terminal resize.
29 Resize(u16, u16),
30 /// Mouse-wheel scroll up.
31 ScrollUp,
32 /// Mouse-wheel scroll down.
33 ScrollDown,
34 /// Mouse button pressed at (col, row).
35 MouseDown { col: u16, row: u16 },
36 /// Mouse dragged to (col, row) while button held.
37 MouseDrag { col: u16, row: u16 },
38 /// Mouse button released at (col, row).
39 MouseUp { col: u16, row: u16 },
40 /// Terminal regained focus (user switched back to this tab/window).
41 FocusGained,
42 /// Tick for periodic UI updates (spinner animation, etc.).
43 Tick,
44
45 // -- Agent events --
46 /// Assistant started generating a response.
47 AgentStarted,
48 /// Streaming text chunk from the assistant.
49 AgentChunk(String),
50 /// Complete assistant message received.
51 AgentMessage(ChatMessage),
52 /// Agent finished the current turn.
53 AgentFinished,
54 /// Agent encountered an error.
55 AgentError(String),
56
57 // -- Tool events --
58 /// A tool execution started.
59 ToolStarted {
60 tool_id: String,
61 tool_name: String,
62 args: std::collections::HashMap<String, serde_json::Value>,
63 },
64 /// A tool produced output.
65 ToolOutput { tool_id: String, output: String },
66 /// A tool produced its final result.
67 ToolResult {
68 tool_id: String,
69 tool_name: String,
70 output: String,
71 success: bool,
72 args: std::collections::HashMap<String, serde_json::Value>,
73 },
74 /// A tool execution completed.
75 ToolFinished { tool_id: String, success: bool },
76 /// Tool requires user approval (legacy, no channel — kept for recording compatibility).
77 ToolApprovalRequired {
78 tool_id: String,
79 tool_name: String,
80 description: String,
81 },
82
83 /// Tool approval request with bidirectional channel.
84 ToolApprovalRequested {
85 command: String,
86 working_dir: String,
87 response_tx: tokio::sync::oneshot::Sender<opendev_runtime::ToolApprovalDecision>,
88 },
89
90 /// Ask-user request with bidirectional channel.
91 AskUserRequested {
92 question: String,
93 options: Vec<String>,
94 default: Option<String>,
95 response_tx: tokio::sync::oneshot::Sender<String>,
96 },
97
98 // -- Subagent events --
99 /// A subagent started executing.
100 SubagentStarted {
101 subagent_id: String,
102 subagent_name: String,
103 task: String,
104 cancel_token: Option<tokio_util::sync::CancellationToken>,
105 },
106 /// A subagent made a tool call (for nested display).
107 SubagentToolCall {
108 subagent_id: String,
109 subagent_name: String,
110 tool_name: String,
111 tool_id: String,
112 args: std::collections::HashMap<String, serde_json::Value>,
113 },
114 /// A subagent tool call completed.
115 SubagentToolComplete {
116 subagent_id: String,
117 subagent_name: String,
118 tool_name: String,
119 tool_id: String,
120 success: bool,
121 },
122 /// A subagent finished its task.
123 SubagentFinished {
124 subagent_id: String,
125 subagent_name: String,
126 success: bool,
127 result_summary: String,
128 tool_call_count: usize,
129 shallow_warning: Option<String>,
130 },
131 /// Token usage update from a subagent's LLM call.
132 SubagentTokenUpdate {
133 subagent_id: String,
134 subagent_name: String,
135 input_tokens: u64,
136 output_tokens: u64,
137 },
138 // -- Reasoning events --
139 /// Native reasoning content from LLM response (inline thinking).
140 ReasoningContent(String),
141 /// A new reasoning/thinking block started (separator between interleaved blocks).
142 ReasoningBlockStart,
143
144 // -- Task progress events --
145 /// Agent started working on a task (shows progress bar).
146 TaskProgressStarted { description: String },
147 /// Agent finished the current task (hides progress bar).
148 TaskProgressFinished,
149
150 // -- Budget events --
151 /// Session cost budget has been exhausted. The agent loop should pause.
152 BudgetExhausted { cost_usd: f64, budget_usd: f64 },
153
154 // -- File change events --
155 /// File change summary after a query completes.
156 FileChangeSummary {
157 files: usize,
158 additions: u64,
159 deletions: u64,
160 },
161
162 // -- Context events --
163 /// Context window usage percentage updated (0.0–100.0).
164 ContextUsage(f64),
165
166 // -- Compaction events --
167 /// Manual compaction started (shows compaction spinner).
168 CompactionStarted,
169 /// Manual compaction finished (hides compaction spinner, shows result).
170 CompactionFinished { success: bool, message: String },
171
172 // -- Plan events --
173 /// Plan approval request arrived from the PresentPlanTool.
174 /// Contains the plan content to display and the oneshot sender for the decision.
175 PlanApprovalRequested {
176 plan_content: String,
177 response_tx: tokio::sync::oneshot::Sender<opendev_runtime::PlanDecision>,
178 },
179
180 // -- UI events --
181 /// User submitted a message.
182 UserSubmit(String),
183 /// User requested interrupt (Escape).
184 Interrupt,
185 /// Set the interrupt token for the current query (sent by agent backend).
186 SetInterruptToken(InterruptToken),
187 /// Agent run was interrupted (sent by agent backend after cancellation).
188 AgentInterrupted,
189 /// Mode changed (normal/plan).
190 ModeChanged(String),
191 /// Kill a background task by ID.
192 KillTask(String),
193
194 // -- Background agent events --
195 /// An agent was moved to the background via Ctrl+B.
196 AgentBackgrounded {
197 task_id: String,
198 query_summary: String,
199 },
200 /// A background agent completed its work.
201 BackgroundAgentCompleted {
202 task_id: String,
203 success: bool,
204 result_summary: String,
205 full_result: String,
206 cost_usd: f64,
207 tool_call_count: usize,
208 },
209 /// Progress update from a background agent.
210 BackgroundAgentProgress {
211 task_id: String,
212 tool_name: String,
213 tool_count: usize,
214 },
215 /// A background agent was killed.
216 BackgroundAgentKilled { task_id: String },
217 /// LLM-generated nudge message after backgrounding agents.
218 BackgroundNudge { content: String },
219 /// Activity line from a background agent (tool call, reasoning, etc.).
220 BackgroundAgentActivity { task_id: String, line: String },
221 /// Register a background agent task with its interrupt token (sent from tui_runner).
222 SetBackgroundAgentToken {
223 task_id: String,
224 query: String,
225 session_id: String,
226 interrupt_token: InterruptToken,
227 },
228
229 // -- Undo/Redo events --
230 /// Snapshot was taken (stores tree hash for undo stack).
231 SnapshotTaken { hash: String },
232 /// Undo result from the runtime.
233 UndoResult { success: bool, message: String },
234 /// Redo result from the runtime.
235 RedoResult { success: bool, message: String },
236 /// Share result from the runtime.
237 ShareResult { path: String },
238 /// File watcher detected changes.
239 FileChanged { paths: Vec<String> },
240
241 /// Session title was auto-detected by the topic detector.
242 SessionTitleUpdated(String),
243
244 /// Quit the application.
245 Quit,
246}
247
248#[cfg(test)]
249mod tests;