Skip to main content

stakpak_tui/app/
events.rs

1use ratatui::style::Color;
2use stakai::Model;
3use stakpak_api::models::ListRuleBook;
4use stakpak_shared::models::{
5    integrations::openai::{ToolCall, ToolCallResult, ToolCallResultProgress, ToolCallStreamInfo},
6    llm::LLMTokenUsage,
7};
8use uuid::Uuid;
9
10use crate::app::{ExistingPlanPrompt, LoadingOperation, SessionInfo};
11use crate::services::banner::BannerStyle;
12use crate::services::board_tasks::FetchTasksResult;
13
14#[derive(Debug)]
15pub enum InputEvent {
16    AssistantMessage(String),
17    AddUserMessage(String),
18    StreamAssistantMessage(Uuid, String),
19    RunToolCall(ToolCall),
20    ToolResult(ToolCallResult),
21    StreamToolResult(ToolCallResultProgress),
22    /// Progress update while tool calls are being streamed/generated by the LLM
23    StreamToolCallProgress(Vec<ToolCallStreamInfo>),
24    StartLoadingOperation(LoadingOperation),
25    EndLoadingOperation(LoadingOperation),
26    InputChanged(char),
27    ShellMode,
28    RunShellCommand(String),
29    /// Spawn the user's shell and then execute a command in it (for interactive stall recovery)
30    RunShellWithCommand(String),
31    GetStatus(String),
32    BillingInfoLoaded(stakpak_shared::models::billing::BillingResponse),
33    Error(String),
34    SetSessions(Vec<SessionInfo>),
35    SetBannerMessage(String, BannerStyle),
36    InputBackspace,
37    InputChangedNewline,
38    InputSubmitted,
39    InputSubmittedWith(String),
40    InputSubmittedWithColor(String, Color),
41    MessageToolCalls(Vec<ToolCall>),
42    ScrollUp,
43    ScrollDown,
44    PageUp,
45    PageDown,
46    DropdownUp,
47    DropdownDown,
48    Up,
49    Down,
50    Quit,
51    HandleEsc,
52    HandleReject(Option<String>, bool, Option<Color>),
53    CursorLeft,
54    CursorRight,
55    ToggleCursorVisible,
56    Resized(u16, u16),
57    ShowConfirmationDialog(ToolCall),
58    HasUserMessage,
59    Tab,
60    ToggleApprovalStatus,
61    ShellOutput(String),
62    ShellError(String),
63    ShellWaitingForInput,
64    ShellCompleted(i32),
65    ShellClear,
66    ShellKill,
67    HandlePaste(String),
68    /// Ctrl+V clipboard image paste (non-text, via system clipboard).
69    HandleClipboardImagePaste,
70    InputDelete,
71    InputDeleteWord,
72    InputCursorStart,
73    InputCursorEnd,
74    InputCursorPrevWord,
75    InputCursorNextWord,
76    ToggleAutoApprove,
77    AutoApproveCurrentTool,
78    ToggleDialogFocus,
79    RetryLastToolCall,
80    /// Interactive stall detected - automatically switch to shell mode and fire the command
81    InteractiveStallDetected(String),
82    AttemptQuit,
83    ToggleCollapsedMessages,
84    ShowFileChangesPopup,
85    // Auto-approve popup events
86    ShowAutoApprovePopup,
87    FileChangesRevertFile,
88    FileChangesRevertAll,
89    FileChangesOpenEditor,
90    EmergencyClearTerminal,
91    ToggleMouseCapture,
92    OpenFileInEditor,
93    // Approval popup events
94    ApprovalPopupNextTab,
95    ApprovalPopupPrevTab,
96    ApprovalPopupToggleApproval,
97    ApprovalPopupSubmit,
98    ApprovalPopupEscape,
99    // Approval bar events (inline approval)
100    ApprovalBarApproveAll,
101    ApprovalBarRejectAll,
102    ApprovalBarSelectAction(usize),
103    ApprovalBarApproveSelected,
104    ApprovalBarRejectSelected,
105    ApprovalBarNextAction,
106    ApprovalBarPrevAction,
107    ApprovalBarCollapse,
108    // Profile switcher events
109    ShowProfileSwitcher,
110    ProfilesLoaded(Vec<String>, String),
111    ProfileSwitchRequested(String),
112    ProfileSwitchProgress(String),
113    ProfileSwitchComplete(String),
114    ProfileSwitchFailed(String),
115    // Command palette events
116    ShowCommandPalette,
117    CommandPaletteSearchInputChanged(char),
118    CommandPaletteSearchBackspace,
119    ProfileSwitcherSelect,
120    ProfileSwitcherCancel,
121    // Shortcuts popup events
122    ShowShortcuts,
123    ShortcutsCancel,
124
125    // Rulebook switcher events
126    ShowRulebookSwitcher,
127    RulebooksLoaded(Vec<ListRuleBook>),
128    CurrentRulebooksLoaded(Vec<String>),
129    RulebookSwitcherSelect,
130    RulebookSwitcherToggle,
131    RulebookSwitcherCancel,
132    RulebookSwitcherConfirm,
133    RulebookSwitcherSelectAll,
134    RulebookSwitcherDeselectAll,
135    RulebookSearchInputChanged(char),
136    RulebookSearchBackspace,
137    HandleCtrlS,
138    ToggleMoreShortcuts,
139    // Usage tracking events
140    StreamUsage(LLMTokenUsage),
141    RequestTotalUsage,
142    TotalUsage(LLMTokenUsage),
143
144    // Model events
145    StreamModel(Model),
146
147    // Model switcher events
148    ShowModelSwitcher,
149    AvailableModelsLoaded(Vec<Model>),
150    ModelSwitcherSelect,
151    ModelSwitcherCancel,
152    ModelSwitcherSearchInputChanged(char),
153    ModelSwitcherSearchBackspace,
154    /// Update recent models list (sent after model switch is saved)
155    RecentModelsUpdated(Vec<String>),
156
157    // Side panel events
158    ToggleSidePanel,
159    SidePanelNextSection,
160    SidePanelToggleSection,
161
162    // Mouse events
163    MouseClick(u16, u16),
164    MouseDragStart(u16, u16),
165    MouseDrag(u16, u16),
166    MouseDragEnd(u16, u16),
167    MouseMove(u16, u16),
168
169    // Board tasks events
170    RefreshBoardTasks,
171    BoardTasksLoaded(FetchTasksResult),
172    BoardTasksError(String),
173
174    // Plan mode events
175    /// Plan mode toggled on/off (sent from backend to TUI to sync state)
176    PlanModeChanged(bool),
177    /// Existing plan found at startup — show the modal with metadata and stashed prompt
178    ExistingPlanFound(ExistingPlanPrompt),
179    /// Toggle plan review overlay (Ctrl+P in plan mode)
180    TogglePlanReview,
181    /// Close plan review overlay
182    PlanReviewClose,
183    /// Move cursor up in plan review
184    PlanReviewCursorUp,
185    /// Move cursor down in plan review
186    PlanReviewCursorDown,
187    /// Open comment modal for current line
188    PlanReviewComment,
189    /// Approve the plan
190    PlanReviewApprove,
191    /// Submit feedback (unresolved comments)
192    PlanReviewFeedback,
193    /// Jump to next commented line
194    PlanReviewNextComment,
195    /// Jump to previous commented line
196    PlanReviewPrevComment,
197    /// Toggle resolve on selected comment
198    PlanReviewResolve,
199    /// Scroll plan review up by a page
200    PlanReviewPageUp,
201    /// Scroll plan review down by a page
202    PlanReviewPageDown,
203
204    // Ask User popup events
205    ShowAskUserPopup(
206        ToolCall,
207        Vec<stakpak_shared::models::integrations::openai::AskUserQuestion>,
208    ),
209    AskUserNextTab,
210    AskUserPrevTab,
211    AskUserNextOption,
212    AskUserPrevOption,
213    AskUserSelectOption,
214    AskUserConfirmQuestion,
215    AskUserCustomInputChanged(char),
216    AskUserCustomInputBackspace,
217    AskUserCustomInputDelete,
218    AskUserSubmit,
219    AskUserCancel,
220
221    // Clipboard events
222    CopySessionId,
223
224    // Session events
225    SetSessionId(String),
226
227    // Background task status
228    RunningBackgroundTasksCount(usize),
229    // Approval settings persistence modal events
230    ShowApprovalSettingsPersistenceModal,
231    ApprovalSettingsPersistenceNavigate(i32),
232    ApprovalSettingsPersistenceConfirm,
233    ApprovalSettingsPersistenceCancel,
234}
235
236impl InputEvent {
237    /// Returns true for events sent by the backend (stream, CLI) that must never be
238    /// consumed by popup interceptors. These events drive the message pipeline and
239    /// blocking them causes the TUI to get stuck in a loading state.
240    ///
241    /// **Important:** When adding new `InputEvent` variants that originate from the
242    /// backend (mode_interactive, stream, helpers, etc.), add them here too —
243    /// otherwise they'll be silently dropped when any popup is open.
244    pub fn is_backend_event(&self) -> bool {
245        matches!(
246            self,
247            InputEvent::StreamAssistantMessage(_, _)
248                | InputEvent::AssistantMessage(_)
249                | InputEvent::StartLoadingOperation(_)
250                | InputEvent::EndLoadingOperation(_)
251                | InputEvent::StreamUsage(_)
252                | InputEvent::StreamModel(_)
253                | InputEvent::StreamToolCallProgress(_)
254                | InputEvent::StreamToolResult(_)
255                | InputEvent::HasUserMessage
256                | InputEvent::Error(_)
257                | InputEvent::RunToolCall(_)
258                | InputEvent::ToolResult(_)
259                | InputEvent::MessageToolCalls(_)
260                | InputEvent::ShowConfirmationDialog(_)
261                | InputEvent::AddUserMessage(_)
262                | InputEvent::PlanModeChanged(_)
263                | InputEvent::BoardTasksLoaded(_)
264                | InputEvent::BoardTasksError(_)
265                | InputEvent::ShowAskUserPopup(_, _)
266                | InputEvent::ExistingPlanFound(_)
267                | InputEvent::SetSessions(_)
268                | InputEvent::SetBannerMessage(_, _)
269                | InputEvent::GetStatus(_)
270                | InputEvent::BillingInfoLoaded(_)
271                | InputEvent::TotalUsage(_)
272                | InputEvent::ProfileSwitchProgress(_)
273                | InputEvent::ProfileSwitchComplete(_)
274                | InputEvent::ProfileSwitchFailed(_)
275                | InputEvent::ProfilesLoaded(_, _)
276                | InputEvent::AvailableModelsLoaded(_)
277                | InputEvent::RecentModelsUpdated(_)
278                | InputEvent::RulebooksLoaded(_)
279                | InputEvent::CurrentRulebooksLoaded(_)
280                | InputEvent::RunningBackgroundTasksCount(_)
281        )
282    }
283}
284
285#[derive(Debug)]
286pub enum OutputEvent {
287    /// User message with optional tool call results, image parts, and revert index
288    /// The revert index (if Some) indicates that messages should be truncated to that user message index
289    UserMessage(
290        String,
291        Option<Vec<ToolCallResult>>,
292        Vec<stakpak_shared::models::integrations::openai::ContentPart>,
293        Option<usize>, // revert_to_user_message_index
294    ),
295    AcceptTool(ToolCall),
296    RejectTool(ToolCall, bool),
297    ListSessions,
298    SwitchToSession(String),
299    NewSession,
300    SendToolResult(ToolCallResult, bool, Vec<ToolCall>),
301    ResumeSession,
302    RequestProfileSwitch(String),
303    RequestRulebookUpdate(Vec<String>),
304    RequestCurrentRulebooks,
305    RequestTotalUsage,
306    RequestAvailableModels,
307    SwitchToModel(Model),
308    /// Save recent models list to config (used when initial model is added)
309    SaveRecentModels(Vec<String>),
310    /// Plan mode activated via /plan command. Contains optional inline prompt.
311    PlanModeActivated(Option<String>),
312    /// Feedback on the plan — formatted unresolved comments injected as user message.
313    PlanFeedback(String),
314    /// Plan approved — transition to Executing phase.
315    PlanApproved,
316    /// A slash command was invoked.
317    CommandCalled(String),
318    /// Response from ask_user popup with the tool call and result
319    AskUserResponse(ToolCallResult),
320    /// Save auto-approve settings to the profile config (tool names set to Auto)
321    SaveAutoApproveToProfile(Vec<String>),
322}