Codex Handbook
tui/src/app_event.rs 1076 lines
//! 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_app_server_protocol::AddCreditsNudgeCreditType;use codex_app_server_protocol::AddCreditsNudgeEmailStatus;use codex_app_server_protocol::AppInfo;use codex_app_server_protocol::ConsumeAccountRateLimitResetCreditResponse;use codex_app_server_protocol::GetAccountRateLimitsResponse;use codex_app_server_protocol::GetAccountTokenUsageResponse;use codex_app_server_protocol::MarketplaceAddResponse;use codex_app_server_protocol::MarketplaceRemoveResponse;use codex_app_server_protocol::MarketplaceUpgradeResponse;use codex_app_server_protocol::McpServerStatus;use codex_app_server_protocol::McpServerStatusDetail;use codex_app_server_protocol::PluginInstallResponse;use codex_app_server_protocol::PluginListResponse;use codex_app_server_protocol::PluginMarketplaceEntry;use codex_app_server_protocol::PluginReadParams;use codex_app_server_protocol::PluginReadResponse;use codex_app_server_protocol::PluginUninstallResponse;use codex_app_server_protocol::RateLimitResetCreditsSummary;use codex_app_server_protocol::SkillsListResponse;use codex_app_server_protocol::ThreadGoalStatus;use codex_file_search::FileMatch;use codex_protocol::ThreadId;use codex_protocol::openai_models::ModelPreset;use codex_utils_absolute_path::AbsolutePathBuf;use codex_utils_approval_presets::ApprovalPreset;use crate::app_command::AppCommand;use crate::app_server_session::AppServerStartedThread;use crate::bottom_pane::ApprovalRequest;use crate::bottom_pane::StatusLineItem;use crate::bottom_pane::TerminalTitleItem;use crate::chatwidget::UserMessage;use crate::goal_files::GoalDraft;use codex_app_server_protocol::AskForApproval;use codex_config::types::ApprovalsReviewer;use codex_features::Feature;use codex_plugin::PluginCapabilitySummary;use codex_protocol::config_types::CollaborationModeMask;use codex_protocol::config_types::Personality;use codex_protocol::models::ActivePermissionProfile;use codex_protocol::openai_models::ReasoningEffort;use crate::history_cell::HistoryCell;#[derive(Debug, Clone, Copy, PartialEq, Eq)]pub(crate) enum ThreadGoalSetMode {    ConfirmIfExists,    ReplaceExisting,    UpdateExisting {        status: ThreadGoalStatus,        token_budget: Option<i64>,    },}#[derive(Debug, Clone)]pub(crate) struct HistoryLookupResponse {    pub(crate) offset: usize,    pub(crate) log_id: u64,    pub(crate) entry: Option<String>,}#[derive(Debug, Clone, Copy, PartialEq, Eq)]pub(crate) enum ConsolidationScrollbackReflow {    IfResizeReflowRan,    Required,}#[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<AppInfo>,}#[derive(Debug, Clone, PartialEq, Eq)]pub(crate) enum PluginLocation {    Local { marketplace_path: AbsolutePathBuf },    Remote { marketplace_name: String },}impl PluginLocation {    pub(crate) fn into_request_params(self) -> (Option<AbsolutePathBuf>, Option<String>) {        match self {            PluginLocation::Local { marketplace_path } => (Some(marketplace_path), None),            PluginLocation::Remote { marketplace_name } => (None, Some(marketplace_name)),        }    }}#[derive(Debug, Clone, PartialEq, Eq)]pub(crate) struct PluginRemoteSectionError {    pub(crate) section_id: String,    pub(crate) label: String,    pub(crate) message: String,}/// Distinguishes why a rate-limit refresh was requested so the completion/// handler can route the result correctly.////// A `StartupPrefetch` fires once, concurrently with the rest of TUI init, and/// updates the cached snapshots and any available reset-credit notice (no/// status card to finalize). A `StatusCommand` is tied to a specific `/status`/// invocation and must call `finish_status_rate_limit_refresh` when done so the/// card stops showing a "refreshing" state.#[derive(Debug, Clone, Copy, PartialEq, Eq)]pub(crate) enum RateLimitRefreshOrigin {    /// Eagerly fetched after bootstrap for `/status` data and reset availability.    StartupPrefetch { reset_hint_request_id: u64 },    /// User-initiated via `/status`; the `request_id` correlates with the    /// status card that should be updated when the fetch completes.    StatusCommand { request_id: u64 },    /// Refresh requested after a reset credit was successfully consumed.    ResetConsume { request_id: u64 },}#[derive(Debug, Clone, PartialEq, Eq)]pub(crate) enum KeymapEditIntent {    ReplaceAll,    AddAlternate,    ReplaceOne { old_key: String },}#[allow(clippy::large_enum_variant)]#[derive(Debug)]pub(crate) enum AppEvent {    /// Open the agent picker for switching active threads.    OpenAgentPicker,    /// Switch the active thread to the selected agent.    SelectAgentThread(ThreadId),    /// Fork the current thread into a transient side conversation.    StartSide {        parent_thread_id: ThreadId,        user_message: Option<UserMessage>,    },    /// Submit an op to the specified thread, regardless of current focus.    SubmitThreadOp {        thread_id: ThreadId,        op: AppCommand,    },    /// Deliver a synthetic history lookup response to a specific thread channel.    ThreadHistoryEntryResponse {        thread_id: ThreadId,        event: HistoryLookupResponse,    },    /// Persist a submitted prompt in the cross-session message history.    AppendMessageHistoryEntry {        thread_id: ThreadId,        text: String,    },    /// Persist a branch discovered from an App git-action directive into thread metadata.    SyncThreadGitBranch {        thread_id: ThreadId,        branch: String,    },    /// Fetch a persistent cross-session message history entry by offset.    LookupMessageHistoryEntry {        thread_id: ThreadId,        offset: usize,        log_id: u64,    },    /// Start a new session.    NewSession,    /// Result of the fresh startup thread that is attached after the input UI is live.    StartupThreadStarted {        result: Result<AppServerStartedThread, String>,    },    /// Clear the terminal UI (screen + scrollback), start a fresh session, and keep the    /// previous chat resumable.    ClearUi,    /// Re-render the transcript using the selected scrollback rendering mode.    RawOutputModeChanged {        enabled: bool,    },    /// Clear the current context, start a fresh session, and submit an initial user message.    ///    /// This is the Plan Mode handoff path: the previous thread remains resumable, but the model    /// sees only the explicit prompt carried in `text` once the new session is configured.    ClearUiAndSubmitUserMessage {        text: String,    },    /// Open the resume picker inside the running TUI session.    OpenResumePicker,    /// Open the Claude Code migration picker inside the running TUI session.    OpenExternalAgentConfigMigration,    /// Resume a thread by UUID or thread name inside the running TUI session.    ResumeSessionByIdOrName(String),    /// Archive the current active main thread and exit after it succeeds.    ArchiveCurrentThread,    /// Permanently delete the current active main thread and exit after it succeeds.    DeleteCurrentThread,    /// 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 app-server account logout, then exit after it succeeds.    Logout,    /// Request to exit the application due to a fatal error.    #[allow(dead_code)]    FatalExitRequest(String),    /// Forward a command to the Agent. Using an `AppEvent` for this avoids    /// bubbling channels through layers of widgets.    CodexOp(AppCommand),    /// Restore an output-free interrupted turn into the composer and roll it back.    RestoreCancelledTurn(UserMessage),    /// Approve one retry of a recent auto-review denial selected in the TUI.    ApproveRecentAutoReviewDenial {        thread_id: ThreadId,        id: String,    },    /// 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<FileMatch>,    },    /// Refresh account rate limits in the background.    RefreshRateLimits {        origin: RateLimitRefreshOrigin,    },    /// Open the current thread goal summary/action menu.    OpenThreadGoalMenu {        thread_id: ThreadId,    },    /// Open an editor for the current thread goal objective.    OpenThreadGoalEditor {        thread_id: Option<ThreadId>,    },    /// Materialize and set or replace the current thread goal objective.    SetThreadGoalDraft {        thread_id: ThreadId,        draft: GoalDraft,        mode: ThreadGoalSetMode,    },    /// Pause or resume the current thread goal.    SetThreadGoalStatus {        thread_id: ThreadId,        status: ThreadGoalStatus,    },    /// Clear the current thread goal.    ClearThreadGoal {        thread_id: ThreadId,    },    /// Result of refreshing rate limits.    RateLimitsLoaded {        origin: RateLimitRefreshOrigin,        result: Result<GetAccountRateLimitsResponse, String>,    },    /// Open the default token-activity view selected from the `/usage` menu.    OpenTokenActivity,    /// Open the reset-credit flow selected from the `/usage` menu.    OpenRateLimitResetCredits,    /// Result of reading the current reset-credit balance.    RateLimitResetCreditsLoaded {        request_id: u64,        result: Result<RateLimitResetCreditsSummary, String>,    },    /// Consume one reset credit using a stable idempotency key.    ConsumeRateLimitResetCredit {        idempotency_key: String,    },    /// Result of consuming one reset credit.    RateLimitResetCreditConsumed {        request_id: u64,        idempotency_key: String,        result: Result<ConsumeAccountRateLimitResetCreditResponse, String>,    },    /// Fetch account-wide token activity for a `/usage` history card.    RefreshTokenActivity {        request_id: u64,    },    /// Result of fetching account-wide token activity.    TokenActivityLoaded {        request_id: u64,        result: Result<GetAccountTokenUsageResponse, String>,    },    /// Commit settled asynchronous usage output after active-output barriers clear.    CommitPendingUsageOutput,    /// Commit settled asynchronous usage output after stream shutdown.    CommitPendingUsageOutputAfterStreamShutdown,    /// Send a user-confirmed request to notify the workspace owner.    SendAddCreditsNudgeEmail {        credit_type: AddCreditsNudgeCreditType,    },    /// Result of notifying the workspace owner.    AddCreditsNudgeEmailFinished {        result: Result<AddCreditsNudgeEmailStatus, String>,    },    /// Result of prefetching connectors.    ConnectorsLoaded {        result: Result<ConnectorsSnapshot, String>,        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<String>,        instructions: String,        url: String,        is_installed: bool,        is_enabled: bool,    },    /// Open the provided URL in the user's browser.    OpenUrlInBrowser {        url: String,    },    /// Open the current thread in Codex Desktop.    OpenDesktopThread {        thread_id: ThreadId,    },    /// Persist a pet selection and reload the ambient pet.    PetSelected {        pet_id: String,    },    /// Persist terminal pets as disabled and remove the ambient pet.    PetDisabled,    /// Start loading the side preview for the pet picker.    PetPreviewRequested {        pet_id: String,    },    /// Result of loading the side preview for the pet picker.    PetPreviewLoaded {        request_id: u64,        result: Result<crate::pets::AmbientPet, String>,    },    /// Result of loading the selected ambient pet before config persistence.    PetSelectionLoaded {        request_id: u64,        pet_id: String,        result: Result<Option<crate::pets::AmbientPet>, String>,    },    /// Result of restoring the configured ambient pet during startup.    ConfiguredPetLoaded {        pet_id: String,        result: Result<Option<crate::pets::AmbientPet>, String>,    },    /// Refresh app connector state and mention bindings.    RefreshConnectors {        force_refetch: bool,    },    /// Fetch app connector state from the app server after the widget accepts a refresh request.    FetchConnectorsList {        force_refetch: bool,    },    /// Fetch plugin marketplace state for the provided working directory.    FetchPluginsList {        cwd: PathBuf,    },    /// Fetch lifecycle hook inventory for the provided working directory.    FetchHooksList {        cwd: PathBuf,    },    /// Result of fetching plugin marketplace state.    PluginsLoaded {        cwd: PathBuf,        result: Result<PluginListResponse, String>,    },    /// Result of explicitly fetching remote-backed plugin sections.    PluginRemoteSectionsLoaded {        cwd: PathBuf,        marketplaces: Vec<PluginMarketplaceEntry>,        section_errors: Vec<PluginRemoteSectionError>,    },    /// Result of fetching lifecycle hook inventory.    HooksLoaded {        cwd: PathBuf,        result: Result<codex_app_server_protocol::HooksListResponse, String>,    },    /// Open the prompt for adding a marketplace source.    OpenMarketplaceAddPrompt,    /// Replace the plugins popup with a marketplace-add loading state.    OpenMarketplaceAddLoading {        source: String,    },    /// Add a marketplace from the provided source.    FetchMarketplaceAdd {        cwd: PathBuf,        source: String,    },    /// Result of adding a marketplace.    MarketplaceAddLoaded {        cwd: PathBuf,        source: String,        result: Result<MarketplaceAddResponse, String>,    },    /// Open the confirmation prompt for removing a marketplace.    OpenMarketplaceRemoveConfirm {        marketplace_name: String,        marketplace_display_name: String,    },    /// Replace the plugins popup with a marketplace-remove loading state.    OpenMarketplaceRemoveLoading {        marketplace_display_name: String,    },    /// Remove a marketplace by name.    FetchMarketplaceRemove {        cwd: PathBuf,        marketplace_name: String,        marketplace_display_name: String,    },    /// Result of removing a marketplace.    MarketplaceRemoveLoaded {        cwd: PathBuf,        marketplace_name: String,        marketplace_display_name: String,        result: Result<MarketplaceRemoveResponse, String>,    },    /// Replace the plugins popup with a marketplace-upgrade loading state.    OpenMarketplaceUpgradeLoading {        marketplace_name: Option<String>,    },    /// Upgrade configured Git marketplaces.    FetchMarketplaceUpgrade {        cwd: PathBuf,        marketplace_name: Option<String>,    },    /// Result of upgrading configured Git marketplaces.    MarketplaceUpgradeLoaded {        cwd: PathBuf,        result: Result<MarketplaceUpgradeResponse, String>,    },    /// Replace the plugins popup with a plugin-detail loading state.    OpenPluginDetailLoading {        plugin_display_name: String,    },    /// Fetch detail for a specific plugin from a marketplace.    FetchPluginDetail {        cwd: PathBuf,        params: PluginReadParams,    },    /// Result of fetching plugin detail.    PluginDetailLoaded {        cwd: PathBuf,        result: Result<PluginReadResponse, String>,    },    /// Replace the plugins popup with an install loading state.    OpenPluginInstallLoading {        plugin_display_name: String,    },    /// Replace the plugins popup with an uninstall loading state.    OpenPluginUninstallLoading {        plugin_display_name: String,    },    /// Install a specific plugin from a marketplace.    FetchPluginInstall {        cwd: PathBuf,        location: PluginLocation,        plugin_name: String,        plugin_display_name: String,    },    /// Result of installing a plugin.    PluginInstallLoaded {        cwd: PathBuf,        location: PluginLocation,        plugin_name: String,        plugin_display_name: String,        result: Result<PluginInstallResponse, String>,    },    /// Uninstall a specific plugin by canonical plugin id.    FetchPluginUninstall {        cwd: PathBuf,        plugin_id: String,        plugin_display_name: String,    },    /// Result of uninstalling a plugin.    PluginUninstallLoaded {        cwd: PathBuf,        plugin_id: String,        plugin_display_name: String,        result: Result<PluginUninstallResponse, String>,    },    /// Enable or disable an installed plugin.    SetPluginEnabled {        cwd: PathBuf,        plugin_id: String,        enabled: bool,    },    /// Result of enabling or disabling a plugin.    PluginEnabledSet {        cwd: PathBuf,        plugin_id: String,        enabled: bool,        result: Result<(), String>,    },    /// Refresh plugin mention bindings from the current config.    RefreshPluginMentions,    /// Result of refreshing plugin mention bindings.    PluginMentionsLoaded {        plugins: Option<Vec<PluginCapabilitySummary>>,    },    /// Advance the post-install plugin app-auth flow.    PluginInstallAuthAdvance {        refresh_connectors: bool,    },    /// Abandon the post-install plugin app-auth flow.    PluginInstallAuthAbandon,    /// Fetch MCP inventory via app-server RPCs and render it into history.    FetchMcpInventory {        detail: McpServerStatusDetail,        thread_id: Option<ThreadId>,    },    /// Result of fetching MCP inventory via app-server RPCs.    McpInventoryLoaded {        result: Result<Vec<McpServerStatus>, String>,        detail: McpServerStatusDetail,        thread_id: Option<ThreadId>,    },    /// Result of the startup skills refresh that runs after the first frame is scheduled.    ///    /// This event is startup-only. Interactive skills refreshes are handled synchronously through the app    /// command path because those callers expect the visible skill state to be current when their command    /// completes.    SkillsListLoaded {        result: Result<SkillsListResponse, String>,    },    /// Begin buffering initial resume replay rows before they are written to scrollback.    BeginInitialHistoryReplayBuffer,    /// Begin buffering thread-switch replay cells so the final scrollback write can reuse the    /// resize-reflow tail renderer.    BeginThreadSwitchHistoryReplayBuffer,    InsertHistoryCell(Box<dyn HistoryCell>),    /// Finish buffering initial resume replay after all replay events have been queued.    EndInitialHistoryReplayBuffer,    /// Replace the contiguous run of streaming `AgentMessageCell`s at the end of    /// the transcript with a single `AgentMarkdownCell` that stores the raw    /// markdown source and re-renders from it on resize.    ///    /// Emitted by `ChatWidget::flush_answer_stream_with_separator` after stream    /// finalization. The `App` handler walks backward through `transcript_cells`    /// to find the `AgentMessageCell` run and splices in the consolidated cell.    /// The `cwd` keeps local file-link display stable across the final re-render.    /// `scrollback_reflow` lets table-tail finalization force the already-emitted    /// terminal scrollback to be rebuilt from the consolidated source-backed cell.    /// `deferred_history_cell` lets callers add the final stream tail to the    /// transcript without first writing its provisional render to scrollback.    ConsolidateAgentMessage {        source: String,        cwd: PathBuf,        scrollback_reflow: ConsolidationScrollbackReflow,        deferred_history_cell: Option<Box<dyn HistoryCell>>,    },    /// Replace the contiguous run of streaming `ProposedPlanStreamCell`s at the    /// end of the transcript with a single source-backed `ProposedPlanCell`.    ///    /// Emitted by `ChatWidget::on_plan_item_completed` after plan stream    /// finalization.    ConsolidateProposedPlan(String),    /// 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<ReasoningEffort>),    /// Update the current model slug in the running app and widget.    UpdateModel(String),    /// 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<ReasoningEffort>,    },    /// Persist the selected personality to the appropriate config.    PersistPersonalitySelection {        personality: Personality,    },    /// Persist the selected service tier to the appropriate config.    PersistServiceTierSelection {        service_tier: Option<String>,    },    /// 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<ReasoningEffort>,    },    /// Open the full model picker (non-auto models).    OpenAllModelsPopup {        models: Vec<ModelPreset>,    },    /// Open the confirmation prompt before enabling full access mode.    OpenFullAccessConfirmation {        preset: ApprovalPreset,        return_to_permissions: bool,        profile_selection: Option<PermissionProfileSelection>,    },    /// 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<ApprovalPreset>,        profile_selection: Option<PermissionProfileSelection>,        /// Up to 3 sample world-writable directories to display in the warning.        sample_paths: Vec<String>,        /// 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,        profile_selection: Option<PermissionProfileSelection>,    },    /// Open the Windows sandbox fallback prompt after declining or failing elevation.    #[cfg_attr(not(target_os = "windows"), allow(dead_code))]    OpenWindowsSandboxFallbackPrompt {        preset: ApprovalPreset,        profile_selection: Option<PermissionProfileSelection>,    },    /// Begin the elevated Windows sandbox setup flow.    #[cfg_attr(not(target_os = "windows"), allow(dead_code))]    BeginWindowsSandboxElevatedSetup {        preset: ApprovalPreset,        profile_selection: Option<PermissionProfileSelection>,    },    /// Begin the non-elevated Windows sandbox setup flow.    #[cfg_attr(not(target_os = "windows"), allow(dead_code))]    BeginWindowsSandboxLegacySetup {        preset: ApprovalPreset,        profile_selection: Option<PermissionProfileSelection>,    },    /// 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<String>,    },    /// Enable the Windows sandbox feature and switch to Agent mode.    #[cfg_attr(not(target_os = "windows"), allow(dead_code))]    EnableWindowsSandboxForAgentMode {        preset: ApprovalPreset,        mode: WindowsSandboxEnableMode,        profile_selection: Option<PermissionProfileSelection>,    },    /// 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 built-in active permission profile in the running app and widget.    UpdateActivePermissionProfile(ActivePermissionProfile),    /// Select a named permission profile, optionally applying built-in mode settings too.    SelectPermissionProfile(PermissionProfileSelection),    /// Update the current approvals reviewer in the running app and widget.    UpdateApprovalsReviewer(ApprovalsReviewer),    /// Update feature flags and persist them to the top-level config.    UpdateFeatureFlags {        updates: Vec<(Feature, bool)>,    },    /// Update memory settings and persist them to config.toml.    UpdateMemorySettings {        use_memories: bool,        generate_memories: bool,    },    /// Clear all persisted local memory artifacts via the app-server.    ResetMemories,    /// 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<ReasoningEffort>),    /// 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<ReasoningEffort>),    /// 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: AbsolutePathBuf,        enabled: bool,    },    /// Enable or disable an app by connector ID.    SetAppEnabled {        id: String,        enabled: bool,    },    /// Enable or disable a hook by stable hook key.    SetHookEnabled {        key: String,        enabled: bool,    },    /// Trust the current definition for a hook by stable hook key.    TrustHook {        key: String,        current_hash: String,    },    /// Trust the current definitions for one or more hooks by stable hook key.    TrustHooks {        updates: Vec<crate::hooks_rpc::HookTrustUpdate>,    },    /// Result of persisting hook enabled state.    HookEnabledSet {        key: String,        enabled: bool,        result: Result<(), String>,    },    /// Result of persisting hook trust state.    HookTrusted {        result: Result<(), String>,    },    /// Notify that the manage skills popup was closed.    ManageSkillsClosed,    /// Re-open the permissions presets popup.    OpenPermissionsPopup,    /// 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,    },    /// Submit feedback for the current thread via the app-server feedback RPC.    SubmitFeedback {        category: FeedbackCategory,        reason: Option<String>,        turn_id: Option<String>,        include_logs: bool,    },    /// Result of a feedback upload request initiated by the TUI.    FeedbackSubmitted {        origin_thread_id: Option<ThreadId>,        category: FeedbackCategory,        include_logs: bool,        result: Result<String, String>,    },    /// 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<String>,    },    /// Async update of Git summary fields for status line rendering.    StatusLineGitSummaryUpdated {        cwd: PathBuf,        summary: crate::chatwidget::StatusLineGitSummary,    },    /// Apply a user-confirmed status-line item ordering/selection.    StatusLineSetup {        items: Vec<StatusLineItem>,        use_theme_colors: bool,    },    /// Dismiss the status-line setup UI without changing config.    StatusLineSetupCancelled,    /// Apply a user-confirmed terminal-title item ordering/selection.    TerminalTitleSetup {        items: Vec<TerminalTitleItem>,    },    /// Apply a temporary terminal-title preview while the setup UI is open.    TerminalTitleSetupPreview {        items: Vec<TerminalTitleItem>,    },    /// Dismiss the terminal-title setup UI without changing config.    TerminalTitleSetupCancelled,    /// Apply a user-confirmed syntax theme selection.    SyntaxThemeSelected {        name: String,    },    /// Runtime syntax theme preview changed; refresh theme-derived UI colors.    SyntaxThemePreviewed,    /// Open set/remove actions for the selected keymap action.    OpenKeymapActionMenu {        context: String,        action: String,    },    /// Open binding selection before replacing one binding for an action.    OpenKeymapReplaceBindingMenu {        context: String,        action: String,    },    /// Open key capture for the selected keymap action.    OpenKeymapCapture {        context: String,        action: String,        intent: KeymapEditIntent,    },    /// Open the keymap keypress inspector.    OpenKeymapDebug,    /// Apply a captured key to the selected keymap action.    KeymapCaptured {        context: String,        action: String,        key: String,        intent: KeymapEditIntent,    },    /// Remove the custom root binding for the selected keymap action.    KeymapCleared {        context: String,        action: String,    },}/// Named profile selection to apply after any required UI guardrails complete.#[derive(Debug, Clone)]pub(crate) struct PermissionProfileSelection {    pub profile_id: String,    pub approval_policy: Option<AskForApproval>,    pub approvals_reviewer: Option<ApprovalsReviewer>,    pub display_label: 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,}