//! Application-level events used to coordinate UI actions. //! //! `AppEvent` is the internal message bus between UI components and the top-level `App` loop. //! Widgets emit events to request actions that must be handled at the app layer (like opening //! pickers, persisting configuration, or shutting down the agent), without needing direct access to //! `App` internals. //! //! Exit is modelled explicitly via `AppEvent::Exit(ExitMode)` so callers can request shutdown-first //! quits without reaching into the app loop or coupling to shutdown/exit sequencing. use std::path::PathBuf; use codex_chatgpt::connectors::AppInfo; use codex_file_search::FileMatch; use codex_protocol::ThreadId; use codex_protocol::openai_models::ModelPreset; use codex_protocol::protocol::Event; use codex_protocol::protocol::RateLimitSnapshot; use codex_utils_approval_presets::ApprovalPreset; use crate::bottom_pane::ApprovalRequest; use crate::bottom_pane::StatusLineItem; use crate::history_cell::HistoryCell; use codex_core::features::Feature; use codex_protocol::config_types::CollaborationModeMask; use codex_protocol::config_types::Personality; use codex_protocol::config_types::ServiceTier; use codex_protocol::openai_models::ReasoningEffort; use codex_protocol::protocol::AskForApproval; use codex_protocol::protocol::SandboxPolicy; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(crate) enum RealtimeAudioDeviceKind { Microphone, Speaker, } impl RealtimeAudioDeviceKind { pub(crate) fn title(self) -> &'static str { match self { Self::Microphone => "Microphone", Self::Speaker => "Speaker", } } pub(crate) fn noun(self) -> &'static str { match self { Self::Microphone => "microphone", Self::Speaker => "speaker", } } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(not(target_os = "windows"), allow(dead_code))] pub(crate) enum WindowsSandboxEnableMode { Elevated, Legacy, } #[derive(Debug, Clone)] #[cfg_attr(not(target_os = "windows"), allow(dead_code))] pub(crate) struct ConnectorsSnapshot { pub(crate) connectors: Vec, } #[allow(clippy::large_enum_variant)] #[derive(Debug)] pub(crate) enum AppEvent { CodexEvent(Event), /// Open the agent picker for switching active threads. OpenAgentPicker, /// Switch the active thread to the selected agent. SelectAgentThread(ThreadId), /// Submit an op to the specified thread, regardless of current focus. SubmitThreadOp { thread_id: ThreadId, op: codex_protocol::protocol::Op, }, /// Forward an event from a non-primary thread into the app-level thread router. ThreadEvent { thread_id: ThreadId, event: Event, }, /// Start a new session. NewSession, /// Clear the terminal UI (screen + scrollback), start a fresh session, and keep the /// previous chat resumable. ClearUi, /// Open the resume picker inside the running TUI session. OpenResumePicker, /// Fork the current session into a new thread. ForkCurrentSession, /// Request to exit the application. /// /// Use `ShutdownFirst` for user-initiated quits so core cleanup runs and the /// UI exits only after `ShutdownComplete`. `Immediate` is a last-resort /// escape hatch that skips shutdown and may drop in-flight work (e.g., /// background tasks, rollout flush, or child process cleanup). Exit(ExitMode), /// Request to exit the application due to a fatal error. FatalExitRequest(String), /// Forward an `Op` to the Agent. Using an `AppEvent` for this avoids /// bubbling channels through layers of widgets. CodexOp(codex_protocol::protocol::Op), /// Kick off an asynchronous file search for the given query (text after /// the `@`). Previous searches may be cancelled by the app layer so there /// is at most one in-flight search. StartFileSearch(String), /// Result of a completed asynchronous file search. The `query` echoes the /// original search term so the UI can decide whether the results are /// still relevant. FileSearchResult { query: String, matches: Vec, }, /// Result of refreshing rate limits RateLimitSnapshotFetched(RateLimitSnapshot), /// Result of prefetching connectors. ConnectorsLoaded { result: Result, is_final: bool, }, /// Result of computing a `/diff` command. DiffResult(String), /// Open the app link view in the bottom pane. OpenAppLink { app_id: String, title: String, description: Option, instructions: String, url: String, is_installed: bool, is_enabled: bool, }, /// Open the provided URL in the user's browser. OpenUrlInBrowser { url: String, }, /// Refresh app connector state and mention bindings. RefreshConnectors { force_refetch: bool, }, InsertHistoryCell(Box), /// Apply rollback semantics to local transcript cells. /// /// This is emitted when rollback was not initiated by the current /// backtrack flow so trimming occurs in AppEvent queue order relative to /// inserted history cells. ApplyThreadRollback { num_turns: u32, }, StartCommitAnimation, StopCommitAnimation, CommitTick, /// Update the current reasoning effort in the running app and widget. UpdateReasoningEffort(Option), /// Update the current model slug in the running app and widget. UpdateModel(String), /// Update the active collaboration mask in the running app and widget. UpdateCollaborationMode(CollaborationModeMask), /// Update the current personality in the running app and widget. UpdatePersonality(Personality), /// Persist the selected model and reasoning effort to the appropriate config. PersistModelSelection { model: String, effort: Option, }, /// Persist the selected personality to the appropriate config. PersistPersonalitySelection { personality: Personality, }, /// Persist the selected service tier to the appropriate config. PersistServiceTierSelection { service_tier: Option, }, /// Open the device picker for a realtime microphone or speaker. OpenRealtimeAudioDeviceSelection { kind: RealtimeAudioDeviceKind, }, /// Persist the selected realtime microphone or speaker to top-level config. #[cfg_attr( any(target_os = "linux", not(feature = "voice-input")), allow(dead_code) )] PersistRealtimeAudioDeviceSelection { kind: RealtimeAudioDeviceKind, name: Option, }, /// Restart the selected realtime microphone or speaker locally. RestartRealtimeAudioDevice { kind: RealtimeAudioDeviceKind, }, /// Open the reasoning selection popup after picking a model. OpenReasoningPopup { model: ModelPreset, }, /// Open the Plan-mode reasoning scope prompt for the selected model/effort. OpenPlanReasoningScopePrompt { model: String, effort: Option, }, /// Open the full model picker (non-auto models). OpenAllModelsPopup { models: Vec, }, /// Open the confirmation prompt before enabling full access mode. OpenFullAccessConfirmation { preset: ApprovalPreset, return_to_permissions: bool, }, /// Open the Windows world-writable directories warning. /// If `preset` is `Some`, the confirmation will apply the provided /// approval/sandbox configuration on Continue; if `None`, it performs no /// policy change and only acknowledges/dismisses the warning. #[cfg_attr(not(target_os = "windows"), allow(dead_code))] OpenWorldWritableWarningConfirmation { preset: Option, /// Up to 3 sample world-writable directories to display in the warning. sample_paths: Vec, /// If there are more than `sample_paths`, this carries the remaining count. extra_count: usize, /// True when the scan failed (e.g. ACL query error) and protections could not be verified. failed_scan: bool, }, /// Prompt to enable the Windows sandbox feature before using Agent mode. #[cfg_attr(not(target_os = "windows"), allow(dead_code))] OpenWindowsSandboxEnablePrompt { preset: ApprovalPreset, }, /// Open the Windows sandbox fallback prompt after declining or failing elevation. #[cfg_attr(not(target_os = "windows"), allow(dead_code))] OpenWindowsSandboxFallbackPrompt { preset: ApprovalPreset, }, /// Begin the elevated Windows sandbox setup flow. #[cfg_attr(not(target_os = "windows"), allow(dead_code))] BeginWindowsSandboxElevatedSetup { preset: ApprovalPreset, }, /// Begin the non-elevated Windows sandbox setup flow. #[cfg_attr(not(target_os = "windows"), allow(dead_code))] BeginWindowsSandboxLegacySetup { preset: ApprovalPreset, }, /// Begin a non-elevated grant of read access for an additional directory. #[cfg_attr(not(target_os = "windows"), allow(dead_code))] BeginWindowsSandboxGrantReadRoot { path: String, }, /// Result of attempting to grant read access for an additional directory. #[cfg_attr(not(target_os = "windows"), allow(dead_code))] WindowsSandboxGrantReadRootCompleted { path: PathBuf, error: Option, }, /// Enable the Windows sandbox feature and switch to Agent mode. #[cfg_attr(not(target_os = "windows"), allow(dead_code))] EnableWindowsSandboxForAgentMode { preset: ApprovalPreset, mode: WindowsSandboxEnableMode, }, /// Update the Windows sandbox feature mode without changing approval presets. #[cfg_attr(not(target_os = "windows"), allow(dead_code))] /// Update the current approval policy in the running app and widget. UpdateAskForApprovalPolicy(AskForApproval), /// Update the current sandbox policy in the running app and widget. UpdateSandboxPolicy(SandboxPolicy), /// Update feature flags and persist them to the top-level config. UpdateFeatureFlags { updates: Vec<(Feature, bool)>, }, /// Update whether the full access warning prompt has been acknowledged. UpdateFullAccessWarningAcknowledged(bool), /// Update whether the world-writable directories warning has been acknowledged. #[cfg_attr(not(target_os = "windows"), allow(dead_code))] UpdateWorldWritableWarningAcknowledged(bool), /// Update whether the rate limit switch prompt has been acknowledged for the session. UpdateRateLimitSwitchPromptHidden(bool), /// Update the Plan-mode-specific reasoning effort in memory. UpdatePlanModeReasoningEffort(Option), /// Persist the acknowledgement flag for the full access warning prompt. PersistFullAccessWarningAcknowledged, /// Persist the acknowledgement flag for the world-writable directories warning. #[cfg_attr(not(target_os = "windows"), allow(dead_code))] PersistWorldWritableWarningAcknowledged, /// Persist the acknowledgement flag for the rate limit switch prompt. PersistRateLimitSwitchPromptHidden, /// Persist the Plan-mode-specific reasoning effort. PersistPlanModeReasoningEffort(Option), /// Persist the acknowledgement flag for the model migration prompt. PersistModelMigrationPromptAcknowledged { from_model: String, to_model: String, }, /// Skip the next world-writable scan (one-shot) after a user-confirmed continue. #[cfg_attr(not(target_os = "windows"), allow(dead_code))] SkipNextWorldWritableScan, /// Re-open the approval presets popup. OpenApprovalsPopup, /// Open the skills list popup. OpenSkillsList, /// Open the skills enable/disable picker. OpenManageSkillsPopup, /// Enable or disable a skill by path. SetSkillEnabled { path: PathBuf, enabled: bool, }, /// Enable or disable an app by connector ID. SetAppEnabled { id: String, enabled: bool, }, /// Notify that the manage skills popup was closed. ManageSkillsClosed, /// Re-open the permissions presets popup. OpenPermissionsPopup, /// Live update for the in-progress voice recording placeholder. Carries /// the placeholder `id` and the text to display (e.g., an ASCII meter). #[cfg(not(target_os = "linux"))] UpdateRecordingMeter { id: String, text: String, }, /// Voice transcription finished for the given placeholder id. #[cfg(not(target_os = "linux"))] TranscriptionComplete { id: String, text: String, }, /// Voice transcription failed; remove the placeholder identified by `id`. #[cfg(not(target_os = "linux"))] TranscriptionFailed { id: String, #[allow(dead_code)] error: String, }, /// Open the branch picker option from the review popup. OpenReviewBranchPicker(PathBuf), /// Open the commit picker option from the review popup. OpenReviewCommitPicker(PathBuf), /// Open the custom prompt option from the review popup. OpenReviewCustomPrompt, /// Submit a user message with an explicit collaboration mask. SubmitUserMessageWithMode { text: String, collaboration_mode: CollaborationModeMask, }, /// Open the approval popup. FullScreenApprovalRequest(ApprovalRequest), /// Open the feedback note entry overlay after the user selects a category. OpenFeedbackNote { category: FeedbackCategory, include_logs: bool, }, /// Open the upload consent popup for feedback after selecting a category. OpenFeedbackConsent { category: FeedbackCategory, }, /// Launch the external editor after a normal draw has completed. LaunchExternalEditor, /// Async update of the current git branch for status line rendering. StatusLineBranchUpdated { cwd: PathBuf, branch: Option, }, /// Apply a user-confirmed status-line item ordering/selection. StatusLineSetup { items: Vec, }, /// Dismiss the status-line setup UI without changing config. StatusLineSetupCancelled, /// Apply a user-confirmed syntax theme selection. SyntaxThemeSelected { name: String, }, } /// The exit strategy requested by the UI layer. /// /// Most user-initiated exits should use `ShutdownFirst` so core cleanup runs and the UI exits only /// after core acknowledges completion. `Immediate` is an escape hatch for cases where shutdown has /// already completed (or is being bypassed) and the UI loop should terminate right away. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(crate) enum ExitMode { /// Shutdown core and exit after completion. ShutdownFirst, /// Exit the UI loop immediately without waiting for shutdown. /// /// This skips `Op::Shutdown`, so any in-flight work may be dropped and /// cleanup that normally runs before `ShutdownComplete` can be missed. Immediate, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(crate) enum FeedbackCategory { BadResult, GoodResult, Bug, SafetyCheck, Other, }