import { jsx as _jsx } from "react/jsx-runtime";
/**
 * @license
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */
import { useMemo, useState, useCallback, useEffect, useRef, useLayoutEffect, } from 'react';
import { measureElement } from 'ink';
import { App } from './App.js';
import { AppContext } from './contexts/AppContext.js';
import { UIStateContext } from './contexts/UIStateContext.js';
import { UIActionsContext, } from './contexts/UIActionsContext.js';
import { ConfigContext } from './contexts/ConfigContext.js';
import { ToolCallStatus, } from './types.js';
import { MessageType, StreamingState } from './types.js';
import { IdeClient, ideContextStore, createDebugLogger, getErrorMessage, getAllGeminiMdFilenames, ShellExecutionService, Storage, SessionEndReason, SessionStartSource, generatePromptSuggestion, logPromptSuggestion, PromptSuggestionEvent, logSpeculation, SpeculationEvent, startSpeculation, acceptSpeculation, abortSpeculation, IDLE_SPECULATION, ApprovalMode, ConditionalRulesRegistry, ToolConfirmationOutcome, } from '@hoptrendy/hopcode-core';
import { buildResumedHistoryItems } from './utils/resumeHistoryUtils.js';
import { getStickyTodos } from './utils/todoSnapshot.js';
import { validateAuthMethod } from '../config/auth.js';
import { loadHierarchicalContextMemory } from '../config/config.js';
import process from 'node:process';
import { useHistory } from './hooks/useHistoryManager.js';
import { useMemoryMonitor } from './hooks/useMemoryMonitor.js';
import { useThemeCommand } from './hooks/useThemeCommand.js';
import { useFeedbackDialog } from './hooks/useFeedbackDialog.js';
import { useAuthCommand } from './auth/useAuth.js';
import { useEditorSettings } from './hooks/useEditorSettings.js';
import { useSettingsCommand } from './hooks/useSettingsCommand.js';
import { useModelCommand } from './hooks/useModelCommand.js';
import { useManageModelsCommand } from './hooks/useManageModelsCommand.js';
import { useArenaCommand } from './hooks/useArenaCommand.js';
import { useApprovalModeCommand } from './hooks/useApprovalModeCommand.js';
import { useResumeCommand } from './hooks/useResumeCommand.js';
import { useDeleteCommand } from './hooks/useDeleteCommand.js';
import { useSlashCommandProcessor } from './hooks/slashCommandProcessor.js';
import { useDoublePress } from './hooks/useDoublePress.js';
import { computeApiTruncationIndex, isRealUserTurn, } from './utils/historyMapping.js';
import { useVimMode } from './contexts/VimModeContext.js';
import { CompactModeProvider } from './contexts/CompactModeContext.js';
import { useTerminalSize } from './hooks/useTerminalSize.js';
import { calculatePromptWidths } from './components/InputPrompt.js';
import { useStdin, useStdout } from 'ink';
import ansiEscapes from 'ansi-escapes';
import * as fs from 'node:fs';
import { basename } from 'node:path';
import { computeWindowTitle } from '../utils/windowTitle.js';
import { clearScreen } from '../utils/stdioHelpers.js';
import { useTextBuffer } from './components/shared/text-buffer.js';
import { useLogger } from './hooks/useLogger.js';
import { useGeminiStream } from './hooks/useGeminiStream.js';
import { useVim } from './hooks/vim.js';
import { isBtwCommand, isSlashCommand } from './utils/commandUtils.js';
import { SettingScope } from '../config/settings.js';
import {} from '../core/initializer.js';
import { useFocus } from './hooks/useFocus.js';
import { useAwaySummary } from './hooks/useAwaySummary.js';
import { useBracketedPaste } from './hooks/useBracketedPaste.js';
import { useKeypress } from './hooks/useKeypress.js';
import { keyMatchers, Command } from './keyMatchers.js';
import { useLoadingIndicator } from './hooks/useLoadingIndicator.js';
import { useTerminalProgress } from './hooks/useTerminalProgress.js';
import { useFolderTrust } from './hooks/useFolderTrust.js';
import { useIdeTrustListener } from './hooks/useIdeTrustListener.js';
import {} from './IdeIntegrationNudge.js';
import {} from './CommandFormatMigrationNudge.js';
import { useCommandMigration } from './hooks/useCommandMigration.js';
import { migrateTomlCommands } from '../services/command-migration-tool.js';
import {} from './utils/updateCheck.js';
import { setUpdateHandler } from '../utils/handleAutoUpdate.js';
import { registerCleanup, runExitCleanup } from '../utils/cleanup.js';
import { useMessageQueue } from './hooks/useMessageQueue.js';
import { useAutoAcceptIndicator } from './hooks/useAutoAcceptIndicator.js';
import { useSessionStats } from './contexts/SessionContext.js';
import { useGitBranchName } from './hooks/useGitBranchName.js';
import { useExtensionUpdates, useConfirmUpdateRequests, useSettingInputRequests, usePluginChoiceRequests, } from './hooks/useExtensionUpdates.js';
import { useCodingPlanUpdates } from './hooks/useCodingPlanUpdates.js';
import { ShellFocusContext } from './contexts/ShellFocusContext.js';
import { useAgentViewState } from './contexts/AgentViewContext.js';
import { t } from '../i18n/index.js';
import { useWelcomeBack } from './hooks/useWelcomeBack.js';
import { useDialogClose } from './hooks/useDialogClose.js';
import { useInitializationAuthError } from './hooks/useInitializationAuthError.js';
import { useSubagentCreateDialog } from './hooks/useSubagentCreateDialog.js';
import { useAgentsManagerDialog } from './hooks/useAgentsManagerDialog.js';
import { useExtensionsManagerDialog } from './hooks/useExtensionsManagerDialog.js';
import { useMcpDialog } from './hooks/useMcpDialog.js';
import { useHooksDialog } from './hooks/useHooksDialog.js';
import { useMemoryDialog } from './hooks/useMemoryDialog.js';
import { useAttentionNotifications } from './hooks/useAttentionNotifications.js';
import { useContextualTips } from './hooks/useContextualTips.js';
import { getTipHistory } from '../services/tips/index.js';
import { useRemoteInput } from '../remoteInput/RemoteInputContext.js';
import { useDualOutput } from '../dualOutput/DualOutputContext.js';
import { requestConsentInteractive, requestConsentOrFail, } from '../commands/extensions/consent.js';
const CTRL_EXIT_PROMPT_DURATION_MS = 1000;
const debugLogger = createDebugLogger('APP_CONTAINER');
function isToolExecuting(pendingHistoryItems) {
    return pendingHistoryItems.some((item) => {
        if (item && item.type === 'tool_group') {
            return item.tools.some((tool) => ToolCallStatus.Executing === tool.status);
        }
        return false;
    });
}
// Exported for tests. Given a newest-first list of messages, return a list
// with duplicates removed, keeping the first (newest) occurrence of each.
export function dedupeNewestFirst(messages) {
    const seen = new Set();
    const result = [];
    for (const msg of messages) {
        if (!seen.has(msg)) {
            seen.add(msg);
            result.push(msg);
        }
    }
    return result;
}
/**
 * The fraction of the terminal width to allocate to the shell.
 * This provides horizontal padding.
 */
const SHELL_WIDTH_FRACTION = 0.89;
/**
 * The number of lines to subtract from the available terminal height
 * for the shell. This provides vertical padding and space for other UI elements.
 */
const SHELL_HEIGHT_PADDING = 10;
export const AppContainer = (props) => {
    const { settings, config, initializationResult } = props;
    const historyManager = useHistory();
    useMemoryMonitor(historyManager);
    const [debugMessage, setDebugMessage] = useState('');
    const [quittingMessages, setQuittingMessages] = useState(null);
    const [themeError, setThemeError] = useState(initializationResult.themeError);
    const [isProcessing, setIsProcessing] = useState(false);
    const [embeddedShellFocused, setEmbeddedShellFocused] = useState(false);
    const [contextMdFileCount, setContextMdFileCount] = useState(initializationResult.contextMdFileCount);
    const [shellModeActive, setShellModeActive] = useState(false);
    const [modelSwitchedFromQuotaError, setModelSwitchedFromQuotaError] = useState(false);
    const [historyRemountKey, setHistoryRemountKey] = useState(0);
    const [updateInfo, setUpdateInfo] = useState(null);
    const [isTrustedFolder, setIsTrustedFolder] = useState(config.isTrustedFolder());
    const extensionManager = config.getExtensionManager();
    const { addConfirmUpdateExtensionRequest, confirmUpdateExtensionRequests } = useConfirmUpdateRequests();
    const { addSettingInputRequest, settingInputRequests } = useSettingInputRequests();
    const { addPluginChoiceRequest, pluginChoiceRequests } = usePluginChoiceRequests();
    extensionManager.setRequestConsent(requestConsentOrFail.bind(null, (description) => requestConsentInteractive(description, addConfirmUpdateExtensionRequest)));
    extensionManager.setRequestChoicePlugin((marketplace) => new Promise((resolve, reject) => {
        addPluginChoiceRequest({
            marketplaceName: marketplace.name,
            plugins: marketplace.plugins.map((p) => ({
                name: p.name,
                description: p.description,
            })),
            onSelect: (pluginName) => {
                resolve(pluginName);
            },
            onCancel: () => {
                reject(new Error('Plugin selection cancelled'));
            },
        });
    }));
    extensionManager.setRequestSetting((setting) => new Promise((resolve, reject) => {
        addSettingInputRequest({
            settingName: setting.name,
            settingDescription: setting.description,
            sensitive: setting.sensitive ?? false,
            onSubmit: (value) => {
                resolve(value);
            },
            onCancel: () => {
                reject(new Error('Setting input cancelled'));
            },
        });
    }));
    const { extensionsUpdateState, extensionsUpdateStateInternal, dispatchExtensionStateUpdate, } = useExtensionUpdates(extensionManager, historyManager.addItem, config.getWorkingDir());
    const { codingPlanUpdateRequest, dismissCodingPlanUpdate } = useCodingPlanUpdates(settings, config, historyManager.addItem);
    const [isTrustDialogOpen, setTrustDialogOpen] = useState(false);
    const openTrustDialog = useCallback(() => setTrustDialogOpen(true), []);
    const closeTrustDialog = useCallback(() => setTrustDialogOpen(false), []);
    const [isPermissionsDialogOpen, setPermissionsDialogOpen] = useState(false);
    const openPermissionsDialog = useCallback(() => setPermissionsDialogOpen(true), []);
    const closePermissionsDialog = useCallback(() => setPermissionsDialogOpen(false), []);
    // Helper to determine the current model (polled, since Config has no model-change event).
    const getCurrentModel = useCallback(() => config.getModel(), [config]);
    const [currentModel, setCurrentModel] = useState(getCurrentModel());
    const [isConfigInitialized, setConfigInitialized] = useState(false);
    const [userMessages, setUserMessages] = useState([]);
    // Terminal and layout hooks
    const { columns: terminalWidth, rows: terminalHeight } = useTerminalSize();
    const { stdin, setRawMode } = useStdin();
    const { stdout } = useStdout();
    // Additional hooks moved from App.tsx
    const { stats: sessionStats, startNewSession } = useSessionStats();
    const logger = useLogger(config.storage, sessionStats.sessionId);
    const branchName = useGitBranchName(config.getTargetDir());
    // Layout measurements
    const mainControlsRef = useRef(null);
    const originalTitleRef = useRef(computeWindowTitle(basename(config.getTargetDir())));
    const lastTitleRef = useRef(null);
    const staticExtraHeight = 3;
    // Initialize config (runs once on mount)
    useEffect(() => {
        (async () => {
            // Note: the program will not work if this fails so let errors be
            // handled by the global catch.
            await config.initialize();
            setConfigInitialized(true);
            const resumedSessionData = config.getResumedSessionData();
            if (resumedSessionData) {
                const historyItems = buildResumedHistoryItems(resumedSessionData, config);
                historyManager.loadHistory(historyItems);
                // Restore session name tag from custom title
                const title = config
                    .getSessionService()
                    .getSessionTitle(config.getSessionId());
                if (title) {
                    setSessionName(title);
                }
            }
            // Fire SessionStart event after config is initialized
            const sessionStartSource = resumedSessionData
                ? SessionStartSource.Resume
                : SessionStartSource.Startup;
            const hookSystem = config.getHookSystem();
            if (hookSystem) {
                hookSystem
                    .fireSessionStartEvent(sessionStartSource, config.getModel() ?? '', String(config.getApprovalMode()))
                    .then(() => {
                    debugLogger.debug('SessionStart event completed successfully');
                })
                    .catch((err) => {
                    debugLogger.warn(`SessionStart hook failed: ${err}`);
                });
            }
            else {
                debugLogger.debug('SessionStart: HookSystem not available, skipping event');
            }
        })();
        // Register SessionEnd cleanup for process exit
        registerCleanup(async () => {
            try {
                await config
                    .getHookSystem()
                    ?.fireSessionEndEvent(SessionEndReason.PromptInputExit);
                debugLogger.debug('SessionEnd event completed successfully!!!');
            }
            catch (err) {
                debugLogger.error(`SessionEnd hook failed: ${err}`);
            }
        });
        registerCleanup(async () => {
            const ideClient = await IdeClient.getInstance();
            await ideClient.disconnect();
        });
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [config]);
    // Track idle state via ref so the update handler can defer notifications
    // while the model is streaming, without triggering re-renders.
    // Note: isIdleRef.current is assigned after streamingState becomes available
    // (see the assignment below useGeminiStream).
    const isIdleRef = useRef(true);
    const updateHandlerRef = useRef(null);
    useEffect(() => {
        const handler = setUpdateHandler(historyManager.addItem, setUpdateInfo, isIdleRef);
        updateHandlerRef.current = handler;
        return () => handler?.cleanup();
    }, [historyManager.addItem]);
    // Watch for model changes (e.g., user switches model via /model)
    useEffect(() => {
        const checkModelChange = () => {
            const model = getCurrentModel();
            if (model !== currentModel) {
                setCurrentModel(model);
            }
        };
        checkModelChange();
        const interval = setInterval(checkModelChange, 1000); // Check every second
        return () => clearInterval(interval);
    }, [config, currentModel, getCurrentModel]);
    // Derive widths for InputPrompt using shared helper
    const { inputWidth, suggestionsWidth } = useMemo(() => {
        const { inputWidth, suggestionsWidth } = calculatePromptWidths(terminalWidth);
        return { inputWidth, suggestionsWidth };
    }, [terminalWidth]);
    // Uniform width for bordered box components: accounts for margins and caps at 100
    const mainAreaWidth = Math.min(terminalWidth - 4, 100);
    const staticAreaMaxItemHeight = Math.max(terminalHeight * 4, 100);
    const isValidPath = useCallback((filePath) => {
        try {
            return fs.existsSync(filePath) && fs.statSync(filePath).isFile();
        }
        catch (_e) {
            return false;
        }
    }, []);
    const buffer = useTextBuffer({
        initialText: '',
        viewport: { height: 10, width: inputWidth },
        stdin,
        setRawMode,
        isValidPath,
        shellModeActive,
    });
    useEffect(() => {
        const fetchUserMessages = async () => {
            const pastMessagesRaw = (await logger?.getPreviousUserMessages()) || [];
            const currentSessionUserMessages = historyManager.history
                .filter((item) => item.type === 'user' &&
                typeof item.text === 'string' &&
                item.text.trim() !== '')
                .map((item) => item.text)
                .reverse();
            // Current-session messages are already newest-first; combining with past
            // messages gives a newest-first list. dedupeNewestFirst keeps the first
            // (newest) occurrence so resubmitting an old prompt promotes it to
            // "most recent" rather than leaving a stale copy at an older position.
            const combinedMessages = [
                ...currentSessionUserMessages,
                ...pastMessagesRaw,
            ];
            setUserMessages(dedupeNewestFirst(combinedMessages).reverse());
        };
        fetchUserMessages();
    }, [historyManager.history, logger]);
    const remountStaticHistory = useCallback(() => {
        setHistoryRemountKey((prev) => prev + 1);
    }, []);
    const refreshStatic = useCallback(() => {
        stdout.write(ansiEscapes.clearTerminal);
        remountStaticHistory();
    }, [remountStaticHistory, stdout]);
    const { isThemeDialogOpen, openThemeDialog, handleThemeSelect, handleThemeHighlight, } = useThemeCommand(settings, setThemeError, historyManager.addItem, initializationResult.themeError);
    const { isApprovalModeDialogOpen, openApprovalModeDialog, handleApprovalModeSelect, } = useApprovalModeCommand(settings, config);
    const { setAuthState, authError, onAuthError, isAuthDialogOpen, isAuthenticating, pendingAuthType, externalAuthState, qwenAuthState, handleAuthSelect, handleCodingPlanSubmit, handleAlibabaStandardSubmit, handleOpenRouterSubmit, handleCustomApiKeySubmit, openAuthDialog, cancelAuthentication, } = useAuthCommand(settings, config, historyManager.addItem, refreshStatic);
    useInitializationAuthError(initializationResult.authError, onAuthError);
    // Sync user tier from config when authentication changes
    // TODO: Implement getUserTier() method on Config if needed
    // useEffect(() => {
    //   if (authState === AuthState.Authenticated) {
    //     setUserTier(config.getUserTier());
    //   }
    // }, [config, authState]);
    // Check for enforced auth type mismatch
    useEffect(() => {
        // Check for initialization error first
        const currentAuthType = config.getModelsConfig().getCurrentAuthType();
        if (settings.merged.security?.auth?.enforcedType &&
            currentAuthType &&
            settings.merged.security?.auth.enforcedType !== currentAuthType) {
            onAuthError(t('Authentication is enforced to be {{enforcedType}}, but you are currently using {{currentType}}.', {
                enforcedType: String(settings.merged.security?.auth.enforcedType),
                currentType: String(currentAuthType),
            }));
        }
        else if (!settings.merged.security?.auth?.useExternal) {
            // If no authType is selected yet, allow the auth UI flow to prompt the user.
            // Only validate credentials once a concrete authType exists.
            if (currentAuthType) {
                const error = validateAuthMethod(currentAuthType, config);
                if (error) {
                    onAuthError(error);
                }
            }
        }
    }, [
        settings.merged.security?.auth?.enforcedType,
        settings.merged.security?.auth?.useExternal,
        config,
        onAuthError,
    ]);
    const [editorError, setEditorError] = useState(null);
    const { isEditorDialogOpen, openEditorDialog, handleEditorSelect, exitEditorDialog, } = useEditorSettings(settings, setEditorError, historyManager.addItem);
    const { isSettingsDialogOpen, openSettingsDialog, closeSettingsDialog } = useSettingsCommand();
    const { isMemoryDialogOpen, openMemoryDialog, closeMemoryDialog } = useMemoryDialog();
    const { isModelDialogOpen, isFastModelMode, openModelDialog, closeModelDialog, } = useModelCommand();
    const { isManageModelsDialogOpen, openManageModelsDialog, closeManageModelsDialog, } = useManageModelsCommand();
    const { activeArenaDialog, openArenaDialog, closeArenaDialog } = useArenaCommand();
    // Session name state (set via /rename, restored on /resume)
    const [sessionName, setSessionName] = useState(null);
    const { isResumeDialogOpen, resumeMatchedSessions, openResumeDialog, closeResumeDialog, handleResume, } = useResumeCommand({
        config,
        historyManager,
        startNewSession,
        setSessionName,
        remount: refreshStatic,
    });
    const { isDeleteDialogOpen, openDeleteDialog, closeDeleteDialog, handleDelete, } = useDeleteCommand({
        config,
        addItem: historyManager.addItem,
    });
    const { toggleVimEnabled } = useVimMode();
    const { isSubagentCreateDialogOpen, openSubagentCreateDialog, closeSubagentCreateDialog, } = useSubagentCreateDialog();
    const { isAgentsManagerDialogOpen, openAgentsManagerDialog, closeAgentsManagerDialog, } = useAgentsManagerDialog();
    const { isExtensionsManagerDialogOpen, openExtensionsManagerDialog, closeExtensionsManagerDialog, } = useExtensionsManagerDialog();
    const { isMcpDialogOpen, openMcpDialog, closeMcpDialog } = useMcpDialog();
    const { isHooksDialogOpen, openHooksDialog, closeHooksDialog } = useHooksDialog();
    // Ref bridge: the guarded openRewindSelector callback is defined later
    // (after useDoublePress), but slashCommandActions needs it now. The ref
    // lets the useMemo capture a stable function pointer whose implementation
    // is swapped in once the real callback exists.
    const openRewindSelectorRef = useRef(() => { });
    const slashCommandActions = useMemo(() => ({
        openAuthDialog,
        openThemeDialog,
        openEditorDialog,
        openMemoryDialog,
        openSettingsDialog,
        openModelDialog,
        openManageModelsDialog,
        openTrustDialog,
        openArenaDialog,
        openPermissionsDialog,
        openApprovalModeDialog,
        quit: (messages) => {
            setQuittingMessages(messages);
            setTimeout(async () => {
                await runExitCleanup();
                process.exit(0);
            }, 100);
        },
        setDebugMessage,
        dispatchExtensionStateUpdate,
        addConfirmUpdateExtensionRequest,
        openSubagentCreateDialog,
        openAgentsManagerDialog,
        openExtensionsManagerDialog,
        openMcpDialog,
        openHooksDialog,
        openResumeDialog,
        openRewindSelector: () => openRewindSelectorRef.current(),
        handleResume,
        openDeleteDialog,
    }), [
        openAuthDialog,
        openThemeDialog,
        openEditorDialog,
        openMemoryDialog,
        openSettingsDialog,
        openModelDialog,
        openManageModelsDialog,
        openArenaDialog,
        setDebugMessage,
        dispatchExtensionStateUpdate,
        openTrustDialog,
        openPermissionsDialog,
        openApprovalModeDialog,
        addConfirmUpdateExtensionRequest,
        openSubagentCreateDialog,
        openAgentsManagerDialog,
        openExtensionsManagerDialog,
        openMcpDialog,
        openHooksDialog,
        openResumeDialog,
        handleResume,
        openDeleteDialog,
    ]);
    const { handleSlashCommand, slashCommands, pendingHistoryItems: pendingSlashCommandHistoryItems, btwItem, setBtwItem, cancelBtw, commandContext, shellConfirmationRequest, confirmationRequest, } = useSlashCommandProcessor(config, settings, historyManager.addItem, historyManager.clearItems, historyManager.loadHistory, remountStaticHistory, toggleVimEnabled, isProcessing, setIsProcessing, isIdleRef, setContextMdFileCount, slashCommandActions, extensionsUpdateStateInternal, isConfigInitialized, logger, setSessionName);
    // onDebugMessage should log to debug logfile, not update footer debugMessage
    const onDebugMessage = useCallback((message) => {
        config.getDebugLogger().debug(message);
    }, [config]);
    const performMemoryRefresh = useCallback(async () => {
        historyManager.addItem({
            type: MessageType.INFO,
            text: 'Refreshing hierarchical memory (QWEN.md or other context files)...',
        }, Date.now());
        try {
            const { memoryContent, fileCount, conditionalRules, projectRoot } = await loadHierarchicalContextMemory(process.cwd(), settings.merged.context?.loadFromIncludeDirectories
                ? config.getWorkspaceContext().getDirectories()
                : [], config.getFileService(), config.getExtensionContextFilePaths(), config.isTrustedFolder(), settings.merged.context?.importFormat || 'tree', // Use setting or default to 'tree'
            config.getContextRuleExcludes());
            config.setUserMemory(memoryContent);
            config.setContextMdFileCount(fileCount);
            config.setConditionalRulesRegistry(new ConditionalRulesRegistry(conditionalRules, projectRoot));
            setContextMdFileCount(fileCount);
            historyManager.addItem({
                type: MessageType.INFO,
                text: `Memory refreshed successfully. ${memoryContent.length > 0
                    ? `Loaded ${memoryContent.length} characters from ${fileCount} file(s).`
                    : 'No memory content found.'}`,
            }, Date.now());
            debugLogger.debug(`[DEBUG] Refreshed memory content in config: ${memoryContent.substring(0, 200)}...`);
        }
        catch (error) {
            const errorMessage = getErrorMessage(error);
            historyManager.addItem({
                type: MessageType.ERROR,
                text: `Error refreshing memory: ${errorMessage}`,
            }, Date.now());
            debugLogger.error('Error refreshing memory:', error);
        }
    }, [config, historyManager, settings.merged]);
    const cancelHandlerRef = useRef(() => { });
    const midTurnDrainRef = useRef(null);
    const { streamingState, submitQuery, initError, pendingGeminiHistoryItems, thought, cancelOngoingRequest, retryLastPrompt, handleApprovalModeChange, activePtyId, loopDetectionConfirmationRequest, pendingToolCalls, streamingResponseLengthRef, isReceivingContent, } = useGeminiStream(config.getGeminiClient(), historyManager.history, historyManager.addItem, config, settings, onDebugMessage, handleSlashCommand, shellModeActive, () => settings.merged.general?.preferredEditor, onAuthError, performMemoryRefresh, modelSwitchedFromQuotaError, setModelSwitchedFromQuotaError, refreshStatic, () => cancelHandlerRef.current(), setEmbeddedShellFocused, terminalWidth, terminalHeight, midTurnDrainRef);
    // Now that streamingState is available, keep isIdleRef in sync and
    // flush any deferred update notifications when the model finishes responding.
    isIdleRef.current = streamingState === StreamingState.Idle;
    useEffect(() => {
        if (streamingState === StreamingState.Idle) {
            updateHandlerRef.current?.flush();
        }
    }, [streamingState]);
    // Contextual tips — show tips based on context usage after model responses
    // Defer TipHistory loading when tips are disabled to avoid side effects
    // (sessionCount increment + disk write) when the user has opted out.
    const tipsDisabled = !!(settings.merged.ui?.hideTips || config.getScreenReader());
    const tipHistory = useMemo(() => (tipsDisabled ? null : getTipHistory()), [tipsDisabled]);
    useContextualTips({
        streamingState,
        lastPromptTokenCount: sessionStats.lastPromptTokenCount,
        sessionPromptCount: sessionStats.promptCount,
        config,
        tipHistory,
        addItem: historyManager.addItem,
        hideTips: tipsDisabled,
    });
    // Track whether suggestions are visible for Tab key handling
    const [hasSuggestionsVisible, setHasSuggestionsVisible] = useState(false);
    const agentViewState = useAgentViewState();
    // Prompt suggestion state
    const [promptSuggestion, setPromptSuggestion] = useState(null);
    const prevStreamingStateRef = useRef(StreamingState.Idle);
    const speculationRef = useRef(IDLE_SPECULATION);
    const suggestionAbortRef = useRef(null);
    // Dismiss callback — clears suggestion + aborts in-flight generation/speculation
    const dismissPromptSuggestion = useCallback(() => {
        setPromptSuggestion(null);
        suggestionAbortRef.current?.abort();
        suggestionAbortRef.current = null;
    }, []);
    // Auto-accept indicator — disabled on agent tabs (agents handle their own)
    const geminiClient = config.getGeminiClient();
    const showAutoAcceptIndicator = useAutoAcceptIndicator({
        config,
        addItem: historyManager.addItem,
        onApprovalModeChange: handleApprovalModeChange,
        shouldBlockTab: () => hasSuggestionsVisible,
        disabled: agentViewState.activeView !== 'main',
    });
    const { messageQueue, addMessage, clearQueue, popAllMessages, drainQueue, popNextSegment, } = useMessageQueue();
    // Bridge message queue to mid-turn drain via ref.
    // drainQueue reads the synchronous queueRef inside the hook, so it
    // stays consistent with popNextSegment even before React re-renders.
    midTurnDrainRef.current = drainQueue;
    // Connect remote input watcher to submitQuery for bidirectional sync.
    // When an external process writes a command to the input-file,
    // the watcher calls submitQuery as if the user typed it in the TUI.
    const remoteInput = useRemoteInput();
    useEffect(() => {
        if (!remoteInput)
            return;
        remoteInput.setSubmitFn((text) => submitQuery(text));
    }, [remoteInput, submitQuery]);
    // Notify remote input watcher when TUI becomes idle so it can
    // retry queued commands that were deferred while TUI was busy.
    useEffect(() => {
        if (!remoteInput)
            return;
        if (streamingState === StreamingState.Idle) {
            remoteInput.notifyIdle();
        }
    }, [remoteInput, streamingState]);
    // Dual-output tool-confirmation bridge.
    //
    // When a tool call enters awaiting_approval we emit a `control_request`
    // (subtype: can_use_tool) on the dual-output channel so an external
    // consumer can decide. Whichever side resolves first (TUI native UI or
    // `confirmation_response` written to --input-file) wins; we always emit
    // a `control_response` mirroring the final decision so observers stay in
    // sync.
    const dualOutput = useDualOutput();
    const confirmRequestMap = useRef(new Map()); // requestId → callId
    const confirmCallIdMap = useRef(new Map()); // callId → requestId
    const confirmEmitted = useRef(new Set());
    useEffect(() => {
        if (!dualOutput || !dualOutput.isConnected)
            return;
        for (const tc of pendingToolCalls) {
            if (tc.status === 'awaiting_approval' &&
                !confirmEmitted.current.has(tc.request.callId)) {
                const requestId = crypto.randomUUID();
                confirmRequestMap.current.set(requestId, tc.request.callId);
                confirmCallIdMap.current.set(tc.request.callId, requestId);
                confirmEmitted.current.add(tc.request.callId);
                dualOutput.emitPermissionRequest(requestId, tc.request.name, tc.request.callId, tc.request.args);
            }
        }
        // Detect tools that left awaiting_approval (TUI-native resolution) so we
        // can emit a matching control_response back to external consumers.
        for (const [callId, requestId] of confirmCallIdMap.current) {
            const tc = pendingToolCalls.find((t) => t.request.callId === callId);
            if (tc &&
                tc.status !== 'awaiting_approval' &&
                confirmEmitted.current.has(callId)) {
                const allowed = tc.status !== 'cancelled' && tc.status !== 'error';
                dualOutput.emitControlResponse(requestId, allowed);
                confirmRequestMap.current.delete(requestId);
                confirmCallIdMap.current.delete(callId);
                confirmEmitted.current.delete(callId);
            }
        }
    }, [dualOutput, pendingToolCalls]);
    // Keep latest state in refs so the confirmation handler (registered once)
    // always reads current values without needing re-registration.
    const pendingToolCallsRef = useRef(pendingToolCalls);
    pendingToolCallsRef.current = pendingToolCalls;
    const dualOutputRef = useRef(dualOutput);
    dualOutputRef.current = dualOutput;
    // Route confirmation_response commands written to --input-file back into
    // the tool's onConfirm handler. Registered once (deps: [remoteInput]) to
    // avoid teardown/re-registration churn on every pendingToolCalls change.
    useEffect(() => {
        if (!remoteInput)
            return;
        remoteInput.setConfirmationHandler((requestId, allowed) => {
            const callId = confirmRequestMap.current.get(requestId);
            if (!callId) {
                dualOutputRef.current?.emitControlError(requestId, 'unknown request_id (already resolved, cancelled, or never issued)');
                return;
            }
            const tc = pendingToolCallsRef.current.find((t) => t.request.callId === callId && t.status === 'awaiting_approval');
            if (!tc) {
                dualOutputRef.current?.emitControlError(requestId, 'tool call is no longer awaiting approval');
                return;
            }
            const waitingTc = tc;
            if (!waitingTc.confirmationDetails?.onConfirm) {
                dualOutputRef.current?.emitControlError(requestId, 'tool call has no onConfirm handler');
                return;
            }
            void waitingTc.confirmationDetails.onConfirm(allowed
                ? ToolConfirmationOutcome.ProceedOnce
                : ToolConfirmationOutcome.Cancel);
            // Do NOT clean up maps here — let the mirror useEffect (line ~870)
            // detect the state transition and emit control_response + clean up,
            // keeping the emission path symmetric for both TUI-native and
            // external-initiated resolutions.
        });
        return () => {
            remoteInput.setConfirmationHandler(() => { });
        };
    }, [remoteInput]);
    // Callback for handling final submit (must be after addMessage from useMessageQueue)
    const handleFinalSubmit = useCallback((submittedValue) => {
        // Route to active in-process agent if viewing a sub-agent tab.
        if (agentViewState.activeView !== 'main') {
            const agent = agentViewState.agents.get(agentViewState.activeView);
            if (agent) {
                agent.interactiveAgent.enqueueMessage(submittedValue.trim());
                return;
            }
        }
        if (streamingState === StreamingState.Responding &&
            isBtwCommand(submittedValue)) {
            void submitQuery(submittedValue);
            return;
        }
        // Handle bare exit/quit commands (without the / prefix)
        if (['exit', 'quit', ':q', ':q!', ':wq', ':wq!'].includes(submittedValue.trim())) {
            void handleSlashCommand('/quit');
            return;
        }
        // Check if speculation has results for this submission
        const spec = speculationRef.current;
        if (spec.status !== 'idle' &&
            spec.suggestion === submittedValue &&
            spec.status === 'completed') {
            // Accept completed speculation: inject messages and apply files
            acceptSpeculation(spec, geminiClient)
                .then((result) => {
                logSpeculation(config, new SpeculationEvent({
                    outcome: 'accepted',
                    turns_used: spec.messages.filter((m) => m.role === 'model')
                        .length,
                    files_written: result.filesApplied.length,
                    tool_use_count: spec.toolUseCount,
                    duration_ms: Date.now() - spec.startTime,
                    boundary_type: spec.boundary?.type,
                    had_pipelined_suggestion: !!result.nextSuggestion,
                }));
                // Speculation completed fully (no boundary) — render results in UI
                {
                    const now = Date.now();
                    // Render each speculated message as the appropriate HistoryItem
                    for (let mi = 0; mi < result.messages.length; mi++) {
                        const msg = result.messages[mi];
                        if (msg.role === 'user' && msg.parts) {
                            // Check if this is a tool result (functionResponse) or user text
                            const hasText = msg.parts.some((p) => p.text && !p.functionResponse);
                            if (hasText) {
                                const text = msg.parts
                                    .map((p) => p.text ?? '')
                                    .filter(Boolean)
                                    .join('');
                                if (text) {
                                    historyManager.addItem({ type: 'user', text }, now);
                                }
                            }
                            // functionResponse parts are rendered as part of the tool_group below
                        }
                        else if (msg.role === 'model' && msg.parts) {
                            // Extract text and tool calls separately
                            const textParts = msg.parts
                                .filter((p) => p.text && !p.functionCall)
                                .map((p) => p.text)
                                .join('');
                            const toolCalls = msg.parts.filter((p) => p.functionCall);
                            if (textParts) {
                                historyManager.addItem({ type: 'gemini', text: textParts }, now);
                            }
                            if (toolCalls.length > 0) {
                                // Find matching tool results from the next message
                                const nextMsg = result.messages[mi + 1];
                                const toolResults = nextMsg?.parts?.filter((p) => p.functionResponse) ?? [];
                                const tools = toolCalls.map((tc, i) => {
                                    const name = tc.functionCall?.name ?? 'unknown';
                                    const args = tc.functionCall?.args ?? {};
                                    const resp = toolResults[i]?.functionResponse?.response;
                                    const resultText = typeof resp === 'object' && resp
                                        ? (resp['output'] ??
                                            JSON.stringify(resp))
                                        : String(resp ?? '');
                                    return {
                                        callId: `spec-${name}-${i}`,
                                        name,
                                        description: Object.entries(args)
                                            .map(([k, v]) => `${k}: ${String(v).slice(0, 80)}`)
                                            .join(', ') || name,
                                        resultDisplay: String(resultText).slice(0, 500),
                                        status: ToolCallStatus.Success,
                                        confirmationDetails: undefined,
                                    };
                                });
                                const toolGroupItem = {
                                    type: 'tool_group',
                                    tools,
                                };
                                historyManager.addItem(toolGroupItem, now);
                            }
                        }
                    }
                }
                if (result.nextSuggestion) {
                    setPromptSuggestion(result.nextSuggestion);
                }
            })
                .catch(() => {
                // Fallback: submit normally
                addMessage(submittedValue);
            });
            speculationRef.current = IDLE_SPECULATION;
            return;
        }
        // Abort any running speculation since we're submitting something different
        if (spec.status === 'running') {
            abortSpeculation(spec).catch(() => { });
            speculationRef.current = IDLE_SPECULATION;
        }
        if (streamingState === StreamingState.Idle &&
            isSlashCommand(submittedValue)) {
            void submitQuery(submittedValue);
            return;
        }
        addMessage(submittedValue);
    }, [
        addMessage,
        agentViewState,
        streamingState,
        submitQuery,
        handleSlashCommand,
        config,
        geminiClient,
        historyManager,
    ]);
    const handleArenaModelsSelected = useCallback((models) => {
        const value = models.join(',');
        buffer.setText(`/arena start --models ${value} `);
        closeArenaDialog();
    }, [buffer, closeArenaDialog]);
    // Welcome back functionality (must be after handleFinalSubmit)
    const { welcomeBackInfo, showWelcomeBackDialog, welcomeBackChoice, handleWelcomeBackSelection, handleWelcomeBackClose, } = useWelcomeBack(config, handleFinalSubmit, buffer, settings.merged);
    const pendingHistoryItems = useMemo(() => [...pendingSlashCommandHistoryItems, ...pendingGeminiHistoryItems], [pendingSlashCommandHistoryItems, pendingGeminiHistoryItems]);
    const stickyTodos = useMemo(() => getStickyTodos(historyManager.history, pendingHistoryItems), [historyManager.history, pendingHistoryItems]);
    // Terminal tab progress bar (OSC 9;4) for iTerm2/Ghostty
    useTerminalProgress(streamingState, isToolExecuting(pendingHistoryItems));
    cancelHandlerRef.current = useCallback(() => {
        const pendingHistoryItems = [
            ...pendingSlashCommandHistoryItems,
            ...pendingGeminiHistoryItems,
        ];
        if (isToolExecuting(pendingHistoryItems)) {
            // Tool-cancel: drop both buffer and queue so nothing auto-fires later.
            buffer.setText('');
            clearQueue();
            return;
        }
        // Restore queued input joined into the buffer for editing.
        const popped = popAllMessages();
        if (popped) {
            const currentText = buffer.text;
            buffer.setText(currentText ? `${popped}\n${currentText}` : popped);
        }
    }, [
        buffer,
        popAllMessages,
        clearQueue,
        pendingSlashCommandHistoryItems,
        pendingGeminiHistoryItems,
    ]);
    const handleClearScreen = useCallback(() => {
        historyManager.clearItems();
        clearScreen();
        remountStaticHistory();
    }, [historyManager, remountStaticHistory]);
    const { handleInput: vimHandleInput } = useVim(buffer, handleFinalSubmit);
    /**
     * Determines if the input prompt should be active and accept user input.
     * Input is disabled during:
     * - Initialization errors
     * - Slash command processing
     * - Tool confirmations (WaitingForConfirmation state)
     * - Any future streaming states not explicitly allowed
     */
    const isInputActive = !initError &&
        !isProcessing &&
        (streamingState === StreamingState.Idle ||
            streamingState === StreamingState.Responding);
    const isFocused = useFocus();
    useBracketedPaste();
    useAwaySummary({
        enabled: settings.merged.general?.showSessionRecap ?? false,
        config,
        isFocused,
        isIdle: streamingState === StreamingState.Idle,
        addItem: historyManager.addItem,
        history: historyManager.history,
        awayThresholdMinutes: settings.merged.general?.sessionRecapAwayThresholdMinutes,
    });
    // Context file names computation
    const contextFileNames = useMemo(() => {
        const fromSettings = settings.merged.context?.fileName;
        return fromSettings
            ? Array.isArray(fromSettings)
                ? fromSettings
                : [fromSettings]
            : getAllGeminiMdFilenames();
    }, [settings.merged.context?.fileName]);
    // Initial prompt handling
    const initialPrompt = useMemo(() => config.getQuestion(), [config]);
    const initialPromptSubmitted = useRef(false);
    useEffect(() => {
        if (initialPrompt &&
            isConfigInitialized &&
            !initialPromptSubmitted.current &&
            !isAuthenticating &&
            !isAuthDialogOpen &&
            !isThemeDialogOpen &&
            !isEditorDialogOpen &&
            !showWelcomeBackDialog &&
            welcomeBackChoice !== 'restart' &&
            geminiClient?.isInitialized?.()) {
            handleFinalSubmit(initialPrompt);
            initialPromptSubmitted.current = true;
        }
    }, [
        initialPrompt,
        isConfigInitialized,
        handleFinalSubmit,
        isAuthenticating,
        isAuthDialogOpen,
        isThemeDialogOpen,
        isEditorDialogOpen,
        showWelcomeBackDialog,
        welcomeBackChoice,
        geminiClient,
    ]);
    // Generate prompt suggestions when streaming completes
    const followupSuggestionsEnabled = settings.merged.ui?.enableFollowupSuggestions === true;
    useEffect(() => {
        // Clear suggestion when feature is disabled at runtime
        if (!followupSuggestionsEnabled) {
            suggestionAbortRef.current?.abort();
            setPromptSuggestion(null);
            if (speculationRef.current.status === 'running') {
                abortSpeculation(speculationRef.current).catch(() => { });
                speculationRef.current = IDLE_SPECULATION;
            }
        }
        // Clear suggestion and abort pending generation/speculation when a new turn starts
        if (prevStreamingStateRef.current === StreamingState.Idle &&
            streamingState === StreamingState.Responding) {
            suggestionAbortRef.current?.abort();
            setPromptSuggestion(null);
            if (speculationRef.current.status !== 'idle') {
                abortSpeculation(speculationRef.current).catch(() => { });
                speculationRef.current = IDLE_SPECULATION;
            }
        }
        // Only trigger when transitioning from Responding to Idle (and enabled)
        // Skip when dialogs are active, in plan mode, elicitation pending, or last response was error
        if (followupSuggestionsEnabled &&
            config.isInteractive() &&
            !config.getSdkMode() &&
            prevStreamingStateRef.current === StreamingState.Responding &&
            streamingState === StreamingState.Idle &&
            // Check both committed history and pending items for errors
            // (API errors go to pendingGeminiHistoryItems, not historyManager.history)
            historyManager.history[historyManager.history.length - 1]?.type !==
                'error' &&
            !pendingGeminiHistoryItems.some((item) => item.type === 'error') &&
            !shellConfirmationRequest &&
            !confirmationRequest &&
            !loopDetectionConfirmationRequest &&
            !isPermissionsDialogOpen &&
            settingInputRequests.length === 0 &&
            config.getApprovalMode() !== ApprovalMode.PLAN) {
            const ac = new AbortController();
            suggestionAbortRef.current = ac;
            // Use curated history to avoid invalid/empty entries causing API errors
            const fullHistory = geminiClient.getChat().getHistory(true);
            const conversationHistory = fullHistory.length > 40 ? fullHistory.slice(-40) : fullHistory;
            const fastModel = config.getFastModel();
            generatePromptSuggestion(config, conversationHistory, ac.signal, {
                enableCacheSharing: settings.merged.ui?.enableCacheSharing === true,
                model: fastModel,
            })
                .then((result) => {
                if (ac.signal.aborted)
                    return;
                if (result.suggestion) {
                    setPromptSuggestion(result.suggestion);
                    // Start speculation if enabled (runs in background)
                    if (settings.merged.ui?.enableSpeculation) {
                        startSpeculation(config, result.suggestion, ac.signal, {
                            model: fastModel,
                        })
                            .then((state) => {
                            speculationRef.current = state;
                        })
                            .catch(() => {
                            // Speculation failure is non-blocking
                        });
                    }
                }
                else if (result.filterReason) {
                    // Log suppressed suggestion for analytics
                    logPromptSuggestion(config, new PromptSuggestionEvent({
                        outcome: 'suppressed',
                        reason: result.filterReason,
                    }));
                }
            })
                .catch(() => {
                // Silently degrade — don't disrupt the user experience
            });
        }
        // Only update prev ref when streamingState actually changes, so that
        // dialog-dependency re-runs don't cause us to miss a Responding→Idle transition.
        if (prevStreamingStateRef.current !== streamingState) {
            prevStreamingStateRef.current = streamingState;
        }
        return () => {
            suggestionAbortRef.current?.abort();
            // Cleanup speculation on unmount (#21)
            if (speculationRef.current.status !== 'idle') {
                abortSpeculation(speculationRef.current).catch(() => { });
                speculationRef.current = IDLE_SPECULATION;
            }
        };
        // eslint-disable-next-line react-hooks/exhaustive-deps -- guards may change independently
    }, [
        streamingState,
        followupSuggestionsEnabled,
        shellConfirmationRequest,
        confirmationRequest,
        loopDetectionConfirmationRequest,
        isPermissionsDialogOpen,
        settingInputRequests,
    ]);
    // Abort speculation when promptSuggestion is cleared (new turn, feature toggle, or
    // user-initiated dismiss via typing/paste). InputPrompt calls onPromptSuggestionDismiss
    // on user input, which clears promptSuggestion, triggering this effect to abort speculation.
    useEffect(() => {
        if (!promptSuggestion && speculationRef.current.status !== 'idle') {
            abortSpeculation(speculationRef.current).catch(() => { });
            speculationRef.current = IDLE_SPECULATION;
        }
    }, [promptSuggestion]);
    const [idePromptAnswered, setIdePromptAnswered] = useState(false);
    const [currentIDE, setCurrentIDE] = useState(null);
    useEffect(() => {
        const getIde = async () => {
            const ideClient = await IdeClient.getInstance();
            const currentIde = ideClient.getCurrentIde();
            setCurrentIDE(currentIde || null);
        };
        getIde();
    }, []);
    const shouldShowIdePrompt = Boolean(currentIDE &&
        !config.getIdeMode() &&
        !settings.merged.ide?.hasSeenNudge &&
        !idePromptAnswered);
    // Command migration nudge
    const { showMigrationNudge: shouldShowCommandMigrationNudge, tomlFiles: commandMigrationTomlFiles, setShowMigrationNudge: setShowCommandMigrationNudge, } = useCommandMigration(settings, config.storage);
    const [showToolDescriptions, setShowToolDescriptions] = useState(false);
    const [compactMode, setCompactMode] = useState(settings.merged.ui?.compactMode ?? false);
    const [ctrlCPressedOnce, setCtrlCPressedOnce] = useState(false);
    const ctrlCTimerRef = useRef(null);
    const [ctrlDPressedOnce, setCtrlDPressedOnce] = useState(false);
    const ctrlDTimerRef = useRef(null);
    const [escapePressedOnce, setEscapePressedOnce] = useState(false);
    const escapeTimerRef = useRef(null);
    const dialogsVisibleRef = useRef(false);
    const [isRewindSelectorOpen, setIsRewindSelectorOpen] = useState(false);
    const [rewindEscPending, setRewindEscPending] = useState(false);
    const [constrainHeight, setConstrainHeight] = useState(true);
    const [ideContextState, setIdeContextState] = useState();
    const [showEscapePrompt, setShowEscapePrompt] = useState(false);
    const [showIdeRestartPrompt, setShowIdeRestartPrompt] = useState(false);
    const { isFolderTrustDialogOpen, handleFolderTrustSelect, isRestarting } = useFolderTrust(settings, setIsTrustedFolder);
    const { needsRestart: ideNeedsRestart, restartReason: ideTrustRestartReason, } = useIdeTrustListener();
    const { isFeedbackDialogOpen, openFeedbackDialog, closeFeedbackDialog, temporaryCloseFeedbackDialog, submitFeedback, } = useFeedbackDialog({
        config,
        settings,
        streamingState,
        history: historyManager.history,
        sessionStats,
    });
    const dialogsVisible = showWelcomeBackDialog ||
        shouldShowIdePrompt ||
        shouldShowCommandMigrationNudge ||
        isFolderTrustDialogOpen ||
        !!shellConfirmationRequest ||
        !!confirmationRequest ||
        confirmUpdateExtensionRequests.length > 0 ||
        !!codingPlanUpdateRequest ||
        settingInputRequests.length > 0 ||
        pluginChoiceRequests.length > 0 ||
        !!loopDetectionConfirmationRequest ||
        isThemeDialogOpen ||
        isSettingsDialogOpen ||
        isMemoryDialogOpen ||
        isModelDialogOpen ||
        isManageModelsDialogOpen ||
        isTrustDialogOpen ||
        activeArenaDialog !== null ||
        isPermissionsDialogOpen ||
        isAuthDialogOpen ||
        isAuthenticating ||
        isEditorDialogOpen ||
        showIdeRestartPrompt ||
        isSubagentCreateDialogOpen ||
        isAgentsManagerDialogOpen ||
        isMcpDialogOpen ||
        isHooksDialogOpen ||
        isApprovalModeDialogOpen ||
        isResumeDialogOpen ||
        isDeleteDialogOpen ||
        isExtensionsManagerDialogOpen ||
        isRewindSelectorOpen;
    dialogsVisibleRef.current = dialogsVisible;
    const shouldShowStickyTodos = stickyTodos !== null &&
        !dialogsVisible &&
        !isFeedbackDialogOpen &&
        streamingState !== StreamingState.WaitingForConfirmation;
    const [controlsHeight, setControlsHeight] = useState(0);
    useLayoutEffect(() => {
        if (!mainControlsRef.current) {
            setControlsHeight(0);
            return;
        }
        const fullFooterMeasurement = measureElement(mainControlsRef.current);
        setControlsHeight(fullFooterMeasurement.height);
    }, [
        buffer,
        terminalWidth,
        terminalHeight,
        btwItem,
        dialogsVisible,
        shouldShowStickyTodos,
        stickyTodos,
    ]);
    // agentViewState is declared earlier (before handleFinalSubmit) so it
    // is available for input routing. Referenced here for layout computation.
    const tabBarHeight = agentViewState.agents.size > 0 ? 1 : 0;
    const availableTerminalHeight = Math.max(0, terminalHeight - controlsHeight - staticExtraHeight - 2 - tabBarHeight);
    config.setShellExecutionConfig({
        terminalWidth: Math.floor(terminalWidth * SHELL_WIDTH_FRACTION),
        terminalHeight: Math.max(Math.floor(availableTerminalHeight - SHELL_HEIGHT_PADDING), 1),
        pager: settings.merged.tools?.shell?.pager,
        showColor: settings.merged.tools?.shell?.showColor,
    });
    const isInitialMount = useRef(true);
    useEffect(() => {
        if (activePtyId) {
            ShellExecutionService.resizePty(activePtyId, Math.floor(terminalWidth * SHELL_WIDTH_FRACTION), Math.max(Math.floor(availableTerminalHeight - SHELL_HEIGHT_PADDING), 1));
        }
    }, [terminalWidth, availableTerminalHeight, activePtyId]);
    useEffect(() => {
        if (ideNeedsRestart) {
            // IDE trust changed, force a restart.
            setShowIdeRestartPrompt(true);
        }
    }, [ideNeedsRestart]);
    useEffect(() => {
        if (isInitialMount.current) {
            isInitialMount.current = false;
            return;
        }
        const handler = setTimeout(() => {
            refreshStatic();
        }, 300);
        return () => {
            clearTimeout(handler);
        };
    }, [terminalWidth, refreshStatic]);
    useEffect(() => {
        const unsubscribe = ideContextStore.subscribe(setIdeContextState);
        setIdeContextState(ideContextStore.get());
        return unsubscribe;
    }, []);
    const handleEscapePromptChange = useCallback((showPrompt) => {
        setShowEscapePrompt(showPrompt);
    }, []);
    // --- Rewind selector callbacks ---
    const openRewindSelector = useCallback(() => {
        if (streamingState !== StreamingState.Idle)
            return;
        if (config.getIdeMode())
            return;
        if (dialogsVisibleRef.current)
            return;
        const hasUserTurns = historyManager.history.some((h) => h.type === 'user');
        if (!hasUserTurns)
            return;
        setIsRewindSelectorOpen(true);
    }, [streamingState, config, historyManager.history]);
    openRewindSelectorRef.current = openRewindSelector;
    const closeRewindSelector = useCallback(() => {
        setIsRewindSelectorOpen(false);
    }, []);
    const handleRewindConfirm = useCallback((userItem) => {
        const geminiClient = config.getGeminiClient();
        if (!geminiClient)
            return;
        // 1. Compute values from current history BEFORE truncation
        const originalHistory = historyManager.history;
        const originalLength = originalHistory.length;
        let targetTurnIndex = 0;
        for (const h of originalHistory) {
            if (h.id === userItem.id)
                break;
            if (isRealUserTurn(h))
                targetTurnIndex++;
        }
        // 2. Compute API truncation point
        const apiHistory = geminiClient.getHistory();
        const apiTruncateIndex = computeApiTruncationIndex(originalHistory, userItem.id, apiHistory);
        // Abort if the target turn is unreachable (e.g., absorbed by compression)
        if (apiTruncateIndex < 0) {
            historyManager.addItem({
                type: 'error',
                text: 'Cannot rewind to a turn that was compressed. Try a more recent turn.',
            }, Date.now());
            setIsRewindSelectorOpen(false);
            return;
        }
        // 3. Truncate API history and strip stale thinking blocks
        geminiClient.truncateHistory(apiTruncateIndex);
        geminiClient.stripThoughtsFromHistory();
        // 4. Truncate UI history (keep everything before the target item)
        const truncatedUi = originalHistory.filter((h) => h.id < userItem.id);
        historyManager.loadHistory(truncatedUi);
        // 5. Re-render the terminal
        refreshStatic();
        // 6. Pre-populate input with the original user text
        if (userItem.type === 'user' && userItem.text) {
            buffer.setText(userItem.text);
        }
        // 7. Add info message
        historyManager.addItem({
            type: 'info',
            text: 'Conversation rewound. Edit your prompt and press Enter to continue.',
        }, Date.now());
        // 8. Record the rewind event — re-roots the parentUuid chain so
        //    rewound messages end up on a dead branch during resume.
        config.getChatRecordingService()?.rewindRecording(targetTurnIndex, {
            truncatedCount: originalLength - truncatedUi.length,
        });
        // 9. Close the selector
        setIsRewindSelectorOpen(false);
    }, [config, historyManager, refreshStatic, buffer]);
    const handleDoubleEscRewind = useDoublePress(openRewindSelector, (pending) => setRewindEscPending(pending));
    const handleIdePromptComplete = useCallback((result) => {
        if (result.userSelection === 'yes') {
            // Check whether the extension has been pre-installed
            if (result.isExtensionPreInstalled) {
                handleSlashCommand('/ide enable');
            }
            else {
                handleSlashCommand('/ide install');
            }
            settings.setValue(SettingScope.User, 'ide.hasSeenNudge', true);
        }
        else if (result.userSelection === 'dismiss') {
            settings.setValue(SettingScope.User, 'ide.hasSeenNudge', true);
        }
        setIdePromptAnswered(true);
    }, [handleSlashCommand, settings]);
    const handleCommandMigrationComplete = useCallback(async (result) => {
        setShowCommandMigrationNudge(false);
        if (result.userSelection === 'yes') {
            // Perform migration for both workspace and user levels
            try {
                const results = [];
                // Migrate workspace commands
                const workspaceCommandsDir = config.storage.getProjectCommandsDir();
                const workspaceResult = await migrateTomlCommands({
                    commandDir: workspaceCommandsDir,
                    createBackup: true,
                    deleteOriginal: false,
                });
                if (workspaceResult.convertedFiles.length > 0 ||
                    workspaceResult.failedFiles.length > 0) {
                    results.push({ level: 'workspace', result: workspaceResult });
                }
                // Migrate user commands
                const userCommandsDir = Storage.getUserCommandsDir();
                const userResult = await migrateTomlCommands({
                    commandDir: userCommandsDir,
                    createBackup: true,
                    deleteOriginal: false,
                });
                if (userResult.convertedFiles.length > 0 ||
                    userResult.failedFiles.length > 0) {
                    results.push({ level: 'user', result: userResult });
                }
                // Report results
                for (const { level, result: migrationResult } of results) {
                    if (migrationResult.success &&
                        migrationResult.convertedFiles.length > 0) {
                        historyManager.addItem({
                            type: MessageType.INFO,
                            text: `[${level}] Successfully migrated ${migrationResult.convertedFiles.length} command file${migrationResult.convertedFiles.length > 1 ? 's' : ''} to Markdown format. Original files backed up as .toml.backup`,
                        }, Date.now());
                    }
                    if (migrationResult.failedFiles.length > 0) {
                        historyManager.addItem({
                            type: MessageType.ERROR,
                            text: `[${level}] Failed to migrate ${migrationResult.failedFiles.length} file${migrationResult.failedFiles.length > 1 ? 's' : ''}:\n${migrationResult.failedFiles.map((f) => `  • ${f.file}: ${f.error}`).join('\n')}`,
                        }, Date.now());
                    }
                }
                if (results.length === 0) {
                    historyManager.addItem({
                        type: MessageType.INFO,
                        text: 'No TOML files found to migrate.',
                    }, Date.now());
                }
            }
            catch (error) {
                historyManager.addItem({
                    type: MessageType.ERROR,
                    text: `❌ Migration failed: ${getErrorMessage(error)}`,
                }, Date.now());
            }
        }
    }, [historyManager, setShowCommandMigrationNudge, config.storage]);
    const currentCandidatesTokens = Object.values(sessionStats.metrics?.models ?? {}).reduce((acc, model) => acc + (model.tokens?.candidates ?? 0), 0);
    const { elapsedTime, currentLoadingPhrase, taskStartTokens } = useLoadingIndicator(streamingState, settings.merged.ui?.customWittyPhrases, currentCandidatesTokens);
    useAttentionNotifications({
        isFocused,
        streamingState,
        elapsedTime,
        settings,
        config,
    });
    // Dialog close functionality
    const { closeAnyOpenDialog } = useDialogClose({
        isThemeDialogOpen,
        handleThemeSelect,
        isApprovalModeDialogOpen,
        handleApprovalModeSelect,
        isAuthDialogOpen,
        handleAuthSelect,
        pendingAuthType,
        isEditorDialogOpen,
        exitEditorDialog,
        isSettingsDialogOpen,
        closeSettingsDialog,
        isMemoryDialogOpen,
        closeMemoryDialog,
        activeArenaDialog,
        closeArenaDialog,
        isFolderTrustDialogOpen,
        showWelcomeBackDialog,
        handleWelcomeBackClose,
    });
    const handleExit = useCallback((pressedOnce, setPressedOnce, timerRef) => {
        // Fast double-press: Direct quit (preserve user habit)
        if (pressedOnce) {
            if (timerRef.current) {
                clearTimeout(timerRef.current);
            }
            // Exit directly
            handleSlashCommand('/quit');
            return;
        }
        // First press: Prioritize cleanup tasks
        // 1. Close other dialogs (highest priority)
        /**
         * For AuthDialog it is required to complete the authentication process,
         * otherwise user cannot proceed to the next step.
         * So a quit on AuthDialog should go with normal two press quit.
         */
        if (isAuthDialogOpen) {
            setPressedOnce(true);
            timerRef.current = setTimeout(() => {
                setPressedOnce(false);
            }, 500);
            return;
        }
        // 2. Close other dialogs (highest priority)
        if (closeAnyOpenDialog()) {
            return; // Dialog closed, end processing
        }
        // 3. Cancel in-flight btw side-question
        if (btwItem && btwItem.btw.isPending && !dialogsVisibleRef.current) {
            cancelBtw();
            return; // Btw cancelled, end processing
        }
        // 4. Cancel ongoing requests
        if (streamingState === StreamingState.Responding) {
            cancelOngoingRequest?.();
            return; // Request cancelled, end processing
        }
        // 5. Clear input buffer (if has content)
        if (buffer.text.length > 0) {
            buffer.setText('');
            return; // Input cleared, end processing
        }
        // All cleanup tasks completed, set flag for double-press to quit
        setPressedOnce(true);
        timerRef.current = setTimeout(() => {
            setPressedOnce(false);
        }, CTRL_EXIT_PROMPT_DURATION_MS);
    }, [
        isAuthDialogOpen,
        handleSlashCommand,
        closeAnyOpenDialog,
        btwItem,
        cancelBtw,
        streamingState,
        cancelOngoingRequest,
        buffer,
    ]);
    const handleGlobalKeypress = useCallback((key) => {
        // Debug log keystrokes if enabled
        if (settings.merged.general?.debugKeystrokeLogging) {
            debugLogger.debug('[DEBUG] Keystroke:', JSON.stringify(key));
        }
        if (keyMatchers[Command.QUIT](key)) {
            if (isAuthenticating) {
                return;
            }
            // On first press: set flag, start timer, and call handleExit for cleanup
            // On second press (within timeout): handleExit sees flag and does fast quit
            if (!ctrlCPressedOnce) {
                setCtrlCPressedOnce(true);
                ctrlCTimerRef.current = setTimeout(() => {
                    setCtrlCPressedOnce(false);
                    ctrlCTimerRef.current = null;
                }, CTRL_EXIT_PROMPT_DURATION_MS);
            }
            handleExit(ctrlCPressedOnce, setCtrlCPressedOnce, ctrlCTimerRef);
            return;
        }
        else if (keyMatchers[Command.EXIT](key)) {
            // Cancel in-flight btw even when buffer has text (Ctrl+D)
            if (btwItem && btwItem.btw.isPending && !dialogsVisibleRef.current) {
                cancelBtw();
                return;
            }
            if (buffer.text.length > 0) {
                return;
            }
            handleExit(ctrlDPressedOnce, setCtrlDPressedOnce, ctrlDTimerRef);
            return;
        }
        else if (keyMatchers[Command.ESCAPE](key)) {
            // Dismiss or cancel btw side-question on Escape,
            // but only when btw is actually visible (not hidden behind a dialog).
            if (btwItem && !dialogsVisibleRef.current) {
                cancelBtw();
                return;
            }
            // Skip if shell is focused (to allow shell's own escape handling)
            if (embeddedShellFocused) {
                return;
            }
            // If input has content, use double-press to clear
            if (buffer.text.length > 0) {
                if (escapePressedOnce) {
                    // Second press: clear input, keep the flag to allow immediate cancel
                    buffer.setText('');
                    return;
                }
                // First press: set flag and show prompt
                setEscapePressedOnce(true);
                escapeTimerRef.current = setTimeout(() => {
                    setEscapePressedOnce(false);
                    escapeTimerRef.current = null;
                }, CTRL_EXIT_PROMPT_DURATION_MS);
                return;
            }
            // Input is empty, cancel request immediately (no double-press needed)
            if (streamingState === StreamingState.Responding) {
                if (escapeTimerRef.current) {
                    clearTimeout(escapeTimerRef.current);
                    escapeTimerRef.current = null;
                }
                cancelOngoingRequest?.();
                setEscapePressedOnce(false);
                return;
            }
            // Input is empty and idle — double-ESC opens rewind selector
            if (streamingState === StreamingState.Idle &&
                !dialogsVisibleRef.current) {
                if (escapeTimerRef.current) {
                    clearTimeout(escapeTimerRef.current);
                    escapeTimerRef.current = null;
                }
                setEscapePressedOnce(false);
                handleDoubleEscRewind();
                return;
            }
            // No action available, reset the flag
            if (escapeTimerRef.current) {
                clearTimeout(escapeTimerRef.current);
                escapeTimerRef.current = null;
            }
            setEscapePressedOnce(false);
            return;
        }
        // Dismiss completed btw side-question on Space or Enter,
        // but only when btw is visible and the input buffer is empty.
        if (btwItem &&
            !btwItem.btw.isPending &&
            !dialogsVisibleRef.current &&
            buffer.text.length === 0) {
            if (key.name === 'return' || key.sequence === ' ') {
                setBtwItem(null);
                return;
            }
        }
        // Note: Ctrl+C/D btw cancellation is handled inside handleExit
        // (step 3), not here, because Command.QUIT/EXIT match first.
        let enteringConstrainHeightMode = false;
        if (!constrainHeight) {
            enteringConstrainHeightMode = true;
            setConstrainHeight(true);
        }
        if (keyMatchers[Command.TOGGLE_TOOL_DESCRIPTIONS](key)) {
            const newValue = !showToolDescriptions;
            setShowToolDescriptions(newValue);
            const mcpServers = config.getMcpServers();
            if (Object.keys(mcpServers || {}).length > 0) {
                handleSlashCommand(newValue ? '/mcp desc' : '/mcp nodesc');
            }
        }
        else if (keyMatchers[Command.TOGGLE_IDE_CONTEXT_DETAIL](key) &&
            config.getIdeMode() &&
            ideContextState) {
            handleSlashCommand('/ide status');
        }
        else if (keyMatchers[Command.SHOW_MORE_LINES](key) &&
            !enteringConstrainHeightMode) {
            setConstrainHeight(false);
        }
        else if (keyMatchers[Command.TOGGLE_SHELL_INPUT_FOCUS](key)) {
            if (activePtyId || embeddedShellFocused) {
                setEmbeddedShellFocused((prev) => !prev);
            }
        }
        else if (keyMatchers[Command.TOGGLE_COMPACT_MODE](key)) {
            const newValue = !compactMode;
            setCompactMode(newValue);
            void settings.setValue(SettingScope.User, 'ui.compactMode', newValue);
            refreshStatic();
        }
    }, [
        constrainHeight,
        setConstrainHeight,
        showToolDescriptions,
        setShowToolDescriptions,
        config,
        ideContextState,
        handleExit,
        ctrlCPressedOnce,
        setCtrlCPressedOnce,
        ctrlCTimerRef,
        ctrlDPressedOnce,
        setCtrlDPressedOnce,
        ctrlDTimerRef,
        escapePressedOnce,
        setEscapePressedOnce,
        escapeTimerRef,
        streamingState,
        cancelOngoingRequest,
        buffer,
        handleSlashCommand,
        activePtyId,
        embeddedShellFocused,
        btwItem,
        setBtwItem,
        cancelBtw,
        // `settings` is a stable LoadedSettings instance (not recreated on render).
        // ESLint requires it here because the callback calls settings.setValue().
        // debugKeystrokeLogging is read at call time, so no stale closure risk.
        settings,
        isAuthenticating,
        compactMode,
        setCompactMode,
        refreshStatic,
        handleDoubleEscRewind,
    ]);
    useKeypress(handleGlobalKeypress, { isActive: true });
    // Update terminal title with Qwen Code status and thoughts
    useEffect(() => {
        // Respect both showStatusInTitle and hideWindowTitle settings
        if (!settings.merged.ui?.showStatusInTitle ||
            settings.merged.ui?.hideWindowTitle)
            return;
        let title;
        if (streamingState === StreamingState.Idle) {
            title = originalTitleRef.current;
        }
        else {
            const statusText = thought?.subject
                ?.replace(/[\r\n]+/g, ' ')
                .substring(0, 80);
            title = statusText || originalTitleRef.current;
        }
        // Pad the title to a fixed width to prevent taskbar icon resizing.
        const paddedTitle = title.padEnd(80, ' ');
        // Only update the title if it's different from the last value we set
        if (lastTitleRef.current !== paddedTitle) {
            lastTitleRef.current = paddedTitle;
            stdout.write(`\x1b]2;${paddedTitle}\x07`);
        }
        // Note: We don't need to reset the window title on exit because Qwen Code is already doing that elsewhere
    }, [
        streamingState,
        thought,
        settings.merged.ui?.showStatusInTitle,
        settings.merged.ui?.hideWindowTitle,
        stdout,
    ]);
    // Drain queued messages when idle. `queueDrainNonce` re-fires the effect
    // after each submission settles so multi-step queues drain end-to-end.
    const queueDrainingRef = useRef(false);
    const [queueDrainNonce, setQueueDrainNonce] = useState(0);
    useEffect(() => {
        if (queueDrainingRef.current)
            return;
        if (!isConfigInitialized)
            return;
        if (streamingState !== StreamingState.Idle)
            return;
        if (dialogsVisible)
            return;
        if (messageQueue.length === 0)
            return;
        // Two-phase: batch plain prompts as one turn, else pop next slash command.
        const plainPrompts = drainQueue();
        const submission = plainPrompts.length > 0 ? plainPrompts.join('\n\n') : popNextSegment();
        if (submission === null)
            return;
        queueDrainingRef.current = true;
        Promise.resolve(submitQuery(submission)).finally(() => {
            queueDrainingRef.current = false;
            setQueueDrainNonce((n) => n + 1);
        });
    }, [
        isConfigInitialized,
        streamingState,
        dialogsVisible,
        messageQueue,
        drainQueue,
        popNextSegment,
        submitQuery,
        queueDrainNonce,
    ]);
    const nightly = props.version.includes('nightly');
    const uiState = useMemo(() => ({
        history: historyManager.history,
        historyManager,
        isThemeDialogOpen,
        themeError,
        isAuthenticating,
        isConfigInitialized,
        authError,
        isAuthDialogOpen,
        pendingAuthType,
        externalAuthState,
        // Qwen OAuth state
        qwenAuthState,
        editorError,
        isEditorDialogOpen,
        debugMessage,
        quittingMessages,
        isSettingsDialogOpen,
        isMemoryDialogOpen,
        isModelDialogOpen,
        isFastModelMode,
        isManageModelsDialogOpen,
        isTrustDialogOpen,
        activeArenaDialog,
        isPermissionsDialogOpen,
        isApprovalModeDialogOpen,
        isResumeDialogOpen,
        resumeMatchedSessions,
        isDeleteDialogOpen,
        slashCommands,
        pendingSlashCommandHistoryItems,
        commandContext,
        shellConfirmationRequest,
        confirmationRequest,
        confirmUpdateExtensionRequests,
        codingPlanUpdateRequest,
        settingInputRequests,
        pluginChoiceRequests,
        loopDetectionConfirmationRequest,
        geminiMdFileCount: contextMdFileCount,
        streamingState,
        initError,
        pendingGeminiHistoryItems,
        thought,
        shellModeActive,
        userMessages,
        buffer,
        inputWidth,
        suggestionsWidth,
        isInputActive,
        shouldShowIdePrompt,
        shouldShowCommandMigrationNudge,
        commandMigrationTomlFiles,
        isFolderTrustDialogOpen: isFolderTrustDialogOpen ?? false,
        isTrustedFolder,
        constrainHeight,
        ideContextState,
        showToolDescriptions,
        ctrlCPressedOnce,
        ctrlDPressedOnce,
        showEscapePrompt,
        isFocused,
        elapsedTime,
        currentLoadingPhrase,
        historyRemountKey,
        messageQueue,
        showAutoAcceptIndicator,
        currentModel,
        contextFileNames,
        availableTerminalHeight,
        mainAreaWidth,
        staticAreaMaxItemHeight,
        staticExtraHeight,
        dialogsVisible,
        pendingHistoryItems,
        stickyTodos,
        btwItem,
        setBtwItem,
        cancelBtw,
        nightly,
        branchName,
        sessionStats,
        terminalWidth,
        terminalHeight,
        mainControlsRef,
        currentIDE,
        updateInfo,
        showIdeRestartPrompt,
        ideTrustRestartReason,
        isRestarting,
        extensionsUpdateState,
        activePtyId,
        embeddedShellFocused,
        // Welcome back dialog
        showWelcomeBackDialog,
        welcomeBackInfo,
        welcomeBackChoice,
        // Subagent dialogs
        isSubagentCreateDialogOpen,
        isAgentsManagerDialogOpen,
        // Extensions manager dialog
        isExtensionsManagerDialogOpen,
        // MCP dialog
        isMcpDialogOpen,
        // Hooks dialog
        isHooksDialogOpen,
        // Feedback dialog
        isFeedbackDialogOpen,
        // Per-task token tracking
        taskStartTokens,
        // Real-time token display
        streamingResponseLengthRef,
        isReceivingContent,
        // Session name
        sessionName,
        setSessionName,
        // Prompt suggestion
        promptSuggestion,
        dismissPromptSuggestion,
        // Rewind selector
        isRewindSelectorOpen,
        rewindEscPending,
    }), [
        isThemeDialogOpen,
        themeError,
        isAuthenticating,
        isConfigInitialized,
        authError,
        isAuthDialogOpen,
        pendingAuthType,
        externalAuthState,
        // Qwen OAuth state
        qwenAuthState,
        editorError,
        isEditorDialogOpen,
        debugMessage,
        quittingMessages,
        isSettingsDialogOpen,
        isMemoryDialogOpen,
        isModelDialogOpen,
        isFastModelMode,
        isManageModelsDialogOpen,
        isTrustDialogOpen,
        activeArenaDialog,
        isPermissionsDialogOpen,
        isApprovalModeDialogOpen,
        isResumeDialogOpen,
        resumeMatchedSessions,
        isDeleteDialogOpen,
        slashCommands,
        pendingSlashCommandHistoryItems,
        commandContext,
        shellConfirmationRequest,
        confirmationRequest,
        confirmUpdateExtensionRequests,
        codingPlanUpdateRequest,
        settingInputRequests,
        pluginChoiceRequests,
        loopDetectionConfirmationRequest,
        contextMdFileCount,
        streamingState,
        initError,
        pendingGeminiHistoryItems,
        thought,
        shellModeActive,
        userMessages,
        buffer,
        inputWidth,
        suggestionsWidth,
        isInputActive,
        shouldShowIdePrompt,
        shouldShowCommandMigrationNudge,
        commandMigrationTomlFiles,
        isFolderTrustDialogOpen,
        isTrustedFolder,
        constrainHeight,
        ideContextState,
        showToolDescriptions,
        ctrlCPressedOnce,
        ctrlDPressedOnce,
        showEscapePrompt,
        isFocused,
        elapsedTime,
        currentLoadingPhrase,
        historyRemountKey,
        messageQueue,
        showAutoAcceptIndicator,
        contextFileNames,
        availableTerminalHeight,
        mainAreaWidth,
        staticAreaMaxItemHeight,
        staticExtraHeight,
        dialogsVisible,
        pendingHistoryItems,
        stickyTodos,
        btwItem,
        setBtwItem,
        cancelBtw,
        nightly,
        branchName,
        sessionStats,
        terminalWidth,
        terminalHeight,
        mainControlsRef,
        currentIDE,
        updateInfo,
        showIdeRestartPrompt,
        ideTrustRestartReason,
        isRestarting,
        currentModel,
        extensionsUpdateState,
        activePtyId,
        historyManager,
        embeddedShellFocused,
        // Welcome back dialog
        showWelcomeBackDialog,
        welcomeBackInfo,
        welcomeBackChoice,
        // Subagent dialogs
        isSubagentCreateDialogOpen,
        isAgentsManagerDialogOpen,
        // Extensions manager dialog
        isExtensionsManagerDialogOpen,
        // MCP dialog
        isMcpDialogOpen,
        // Hooks dialog
        isHooksDialogOpen,
        // Feedback dialog
        isFeedbackDialogOpen,
        // Per-task token tracking
        taskStartTokens,
        // Real-time token display
        streamingResponseLengthRef,
        isReceivingContent,
        // Session name
        sessionName,
        setSessionName,
        // Prompt suggestion
        promptSuggestion,
        dismissPromptSuggestion,
        // Rewind selector
        isRewindSelectorOpen,
        rewindEscPending,
    ]);
    const uiActions = useMemo(() => ({
        openThemeDialog,
        openEditorDialog,
        openMemoryDialog,
        handleThemeSelect,
        handleThemeHighlight,
        handleApprovalModeSelect,
        handleAuthSelect,
        setAuthState,
        onAuthError,
        cancelAuthentication,
        handleCodingPlanSubmit,
        handleAlibabaStandardSubmit,
        handleOpenRouterSubmit,
        handleCustomApiKeySubmit,
        handleEditorSelect,
        exitEditorDialog,
        closeSettingsDialog,
        closeMemoryDialog,
        closeModelDialog,
        openModelDialog,
        openManageModelsDialog,
        closeManageModelsDialog,
        openArenaDialog,
        closeArenaDialog,
        handleArenaModelsSelected,
        dismissCodingPlanUpdate,
        closeTrustDialog,
        closePermissionsDialog,
        setShellModeActive,
        vimHandleInput,
        handleIdePromptComplete,
        handleCommandMigrationComplete,
        handleFolderTrustSelect,
        setConstrainHeight,
        onEscapePromptChange: handleEscapePromptChange,
        onSuggestionsVisibilityChange: setHasSuggestionsVisible,
        refreshStatic,
        handleFinalSubmit,
        handleRetryLastPrompt: retryLastPrompt,
        handleClearScreen,
        popAllQueuedMessages: popAllMessages,
        // Welcome back dialog
        handleWelcomeBackSelection,
        handleWelcomeBackClose,
        // Subagent dialogs
        closeSubagentCreateDialog,
        closeAgentsManagerDialog,
        // Extensions manager dialog
        closeExtensionsManagerDialog,
        // MCP dialog
        closeMcpDialog,
        // Hooks dialog
        openHooksDialog,
        // Hooks dialog
        closeHooksDialog,
        // Resume session dialog
        openResumeDialog,
        closeResumeDialog,
        handleResume,
        // Delete session dialog
        openDeleteDialog,
        closeDeleteDialog,
        handleDelete,
        // Feedback dialog
        openFeedbackDialog,
        closeFeedbackDialog,
        temporaryCloseFeedbackDialog,
        submitFeedback,
        // Rewind selector
        openRewindSelector,
        closeRewindSelector,
        handleRewindConfirm,
    }), [
        openThemeDialog,
        openEditorDialog,
        openMemoryDialog,
        handleThemeSelect,
        handleThemeHighlight,
        handleApprovalModeSelect,
        handleAuthSelect,
        setAuthState,
        onAuthError,
        cancelAuthentication,
        handleCodingPlanSubmit,
        handleAlibabaStandardSubmit,
        handleOpenRouterSubmit,
        handleCustomApiKeySubmit,
        handleEditorSelect,
        exitEditorDialog,
        closeSettingsDialog,
        closeMemoryDialog,
        closeModelDialog,
        openModelDialog,
        openManageModelsDialog,
        closeManageModelsDialog,
        openArenaDialog,
        closeArenaDialog,
        handleArenaModelsSelected,
        dismissCodingPlanUpdate,
        closeTrustDialog,
        closePermissionsDialog,
        setShellModeActive,
        vimHandleInput,
        handleIdePromptComplete,
        handleCommandMigrationComplete,
        handleFolderTrustSelect,
        setConstrainHeight,
        handleEscapePromptChange,
        refreshStatic,
        handleFinalSubmit,
        retryLastPrompt,
        handleClearScreen,
        popAllMessages,
        handleWelcomeBackSelection,
        handleWelcomeBackClose,
        // Subagent dialogs
        closeSubagentCreateDialog,
        closeAgentsManagerDialog,
        // Extensions manager dialog
        closeExtensionsManagerDialog,
        // MCP dialog
        closeMcpDialog,
        // Hooks dialog
        openHooksDialog,
        // Hooks dialog
        closeHooksDialog,
        // Resume session dialog
        openResumeDialog,
        closeResumeDialog,
        handleResume,
        // Delete session dialog
        openDeleteDialog,
        closeDeleteDialog,
        handleDelete,
        // Feedback dialog
        openFeedbackDialog,
        closeFeedbackDialog,
        temporaryCloseFeedbackDialog,
        submitFeedback,
        // Rewind selector
        openRewindSelector,
        closeRewindSelector,
        handleRewindConfirm,
    ]);
    const compactModeValue = useMemo(() => ({ compactMode, setCompactMode }), [compactMode, setCompactMode]);
    return (_jsx(UIStateContext.Provider, { value: uiState, children: _jsx(UIActionsContext.Provider, { value: uiActions, children: _jsx(ConfigContext.Provider, { value: config, children: _jsx(AppContext.Provider, { value: {
                        version: props.version,
                        startupWarnings: props.startupWarnings || [],
                    }, children: _jsx(CompactModeProvider, { value: compactModeValue, children: _jsx(ShellFocusContext.Provider, { value: isFocused, children: _jsx(App, {}) }) }) }) }) }) }));
};
//# sourceMappingURL=AppContainer.js.map