/** * @license * Copyright 2025 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import type { CliUiRuntime } from '../cliUiRuntime.js'; import { Box, Text } from 'ink'; import { useCallback, useMemo } from 'react'; import { IdeIntegrationNudge } from '../IdeIntegrationNudge.js'; import { useRuntimeApi } from '../contexts/RuntimeContext.js'; import type { ContinueTarget, HydratedModel, } from '@vybestack/llxprt-code-core'; import type { Profile } from '@vybestack/llxprt-code-settings'; import { getProjectHash } from '@vybestack/llxprt-code-core'; import { DebugLogger } from '@vybestack/llxprt-code-telemetry'; import { join } from 'node:path'; import { performResume, type PerformResumeResult, type ResumeContext, } from '../../services/performResume.js'; import { iContentToHistoryItems } from '../utils/iContentToHistoryItems.js'; // import { LoopDetectionConfirmation } from './LoopDetectionConfirmation.js'; // NOTE: Not yet ported from upstream import { FolderTrustDialog } from './FolderTrustDialog.js'; import { WelcomeDialog } from './WelcomeOnboarding/WelcomeDialog.js'; import { ConsentPrompt } from './ConsentPrompt.js'; import { ThemeDialog } from './ThemeDialog.js'; import { SettingsDialog } from './SettingsDialog.js'; import { AuthDialog } from './AuthDialog.js'; import { OAuthCodeDialog } from './OAuthCodeDialog.js'; import { getPendingOAuthProvider } from '../oauthGlobalState.js'; import { EditorSettingsDialog } from './EditorSettingsDialog.js'; import { ProviderDialog } from './ProviderDialog.js'; import { LoadProfileDialog } from './LoadProfileDialog.js'; import { ProfileCreateWizard } from './ProfileCreateWizard/index.js'; import { ProfileListDialog } from './ProfileListDialog.js'; import { ProfileDetailDialog } from './ProfileDetailDialog.js'; import { ProfileInlineEditor } from './ProfileInlineEditor.js'; import { ToolsDialog } from './ToolsDialog.js'; import { PrivacyNotice } from '../privacy/PrivacyNotice.js'; import { WorkspaceMigrationDialog } from './WorkspaceMigrationDialog.js'; import { PermissionsModifyTrustDialog } from './PermissionsModifyTrustDialog.js'; import { LoggingDialog } from './LoggingDialog.js'; import { SubagentManagerDialog } from './SubagentManagement/index.js'; import { SubagentView } from './SubagentManagement/types.js'; import { ModelsDialog } from './ModelDialog.js'; import { ModelConfigDialog } from './ModelConfigDialog.js'; import { PoliciesDialog } from './PoliciesDialog.js'; import { useModelDialogHandler } from './modelDialogHandler.js'; /** * @plan PLAN-20260214-SESSIONBROWSER.P21 */ import { SessionBrowserDialog } from './SessionBrowserDialog.js'; import { theme } from '../semantic-colors.js'; import { useUIState } from '../contexts/UIStateContext.js'; import { useUIActions } from '../contexts/UIActionsContext.js'; import type { LoadedSettings, SettingScope } from '../../config/settings.js'; import { type UseHistoryManagerReturn } from '../hooks/useHistoryManager.js'; import { firstNonEmptyString } from '../../utils/coalesce.js'; // import { IdeTrustChangeDialog } from './IdeTrustChangeDialog.js'; // NOTE: Not yet ported from upstream interface DialogManagerProps { addItem: UseHistoryManagerReturn['addItem']; terminalWidth: number; config: CliUiRuntime; settings: LoadedSettings; } const dialogManagerLogger = new DebugLogger('llxprt:ui:dialogmanager'); /** * Handler for SessionBrowserDialog selection - performs real session resume. * @plan PLAN-20260214-SESSIONBROWSER.P23 * @requirement REQ-PR-001, REQ-PR-002 */ function useSessionBrowserHandler( config: CliUiRuntime, commandContext: { ui: { clear: () => void; addItem: UseHistoryManagerReturn['addItem']; pendingItem: unknown; }; recordingSwapCallbacks?: unknown; }, addItem: UseHistoryManagerReturn['addItem'], uiActions: ReturnType, ) { return useCallback( async (target: ContinueTarget): Promise => { const recordingSwapCallbacks = commandContext.recordingSwapCallbacks; if (recordingSwapCallbacks == null) { dialogManagerLogger.warn( 'Cannot resume session: recording infrastructure not available.', ); return { ok: false, error: 'Recording infrastructure not available.', }; } const chatsDir = join(config.getProjectTempDir(), 'chats'); const projectHash = getProjectHash(config.getProjectRoot()); const currentSessionId = config.getSessionId(); const currentProvider = config.getProvider() ?? 'unknown'; const currentModel = config.getModel(); const workspaceDirs = [...config.getWorkspaceContext().getDirectories()]; const resumeContext: ResumeContext = { chatsDir, projectHash, currentSessionId, currentProvider, currentModel, workspaceDirs, recordingCallbacks: recordingSwapCallbacks as NonNullable< ResumeContext['recordingCallbacks'] >, historyService: config.getAgentClient().getHistoryService(), adoptSessionId: (sessionId) => config.adoptSessionId(sessionId), logger: dialogManagerLogger, }; const ref = target.kind === 'session' ? target.session.sessionId : target.checkpointId; const resumeResult = await performResume(ref, resumeContext); if (!resumeResult.ok) { addItem({ type: 'error', text: resumeResult.error }); return resumeResult; } for (const warning of resumeResult.warnings) { addItem({ type: 'info', text: `Warning: ${warning}` }); } const uiHistory = iContentToHistoryItems(resumeResult.history); commandContext.ui.clear(); uiHistory.forEach((item, index) => { commandContext.ui.addItem(item, index); }); uiActions.closeSessionBrowserDialog(); return resumeResult; }, [config, commandContext, addItem, uiActions], ); } function renderEarlyDialogs( uiState: ReturnType, uiActions: ReturnType, terminalWidth: number, config: CliUiRuntime, ) { if (uiState.showWorkspaceMigrationDialog) { return ( ); } if (uiState.shouldShowIdePrompt) { return ( ); } if (uiState.isFolderTrustDialogOpen) { return ( ); } if (uiState.isWelcomeDialogOpen) { return ( ); } if (uiState.confirmationRequest) { return ( ); } if (uiState.confirmUpdateLlxprtExtensionRequests.length > 0) { const request = uiState.confirmUpdateLlxprtExtensionRequests[0]; return ( ); } return null; } function renderThemeDialog( uiState: ReturnType, uiActions: ReturnType, settings: LoadedSettings, constrainHeight: boolean, terminalHeight: number, staticExtraHeight: number, mainAreaWidth: number, ) { return ( {uiState.themeError && ( {uiState.themeError} )} ); } function renderAuthDialog( uiState: ReturnType, settings: LoadedSettings, handleAuthSelect: (method: string | undefined, scope: SettingScope) => void, ) { return ( ); } function renderOAuthCodeDialog( uiState: ReturnType, uiActions: ReturnType, handleOAuthCodeSubmit: (code: string) => void, ) { const provider = firstNonEmptyString(getPendingOAuthProvider(), 'unknown'); return ( ); } function renderEditorDialog( uiState: ReturnType, uiActions: ReturnType, settings: LoadedSettings, ) { return ( {uiState.editorError && ( {uiState.editorError} )} ); } function renderProviderDialog( uiState: ReturnType, uiActions: ReturnType, handleProviderSelect: (provider: string) => void, ) { return ( ); } function renderLoadProfileDialog( uiState: ReturnType, uiActions: ReturnType, ) { return ( ); } function renderCreateProfileDialog( uiState: ReturnType, uiActions: ReturnType, ) { return ( ); } function renderProfileListDialogView( uiState: ReturnType, uiActions: ReturnType, ) { return ( ); } function renderProfileDetailDialogView( uiState: ReturnType, uiActions: ReturnType, ) { return ( ); } function renderProfileEditorDialogView( uiState: ReturnType, uiActions: ReturnType, ) { return ( void } onCancel={uiActions.closeProfileEditor} error={uiState.profileDialogError ?? undefined} /> ); } function renderProfileDialogs( uiState: ReturnType, uiActions: ReturnType, ) { if (uiState.isLoadProfileDialogOpen) { return renderLoadProfileDialog(uiState, uiActions); } if (uiState.isCreateProfileDialogOpen) { return renderCreateProfileDialog(uiState, uiActions); } if (uiState.isProfileListDialogOpen) { return renderProfileListDialogView(uiState, uiActions); } if (uiState.isProfileDetailDialogOpen) { return renderProfileDetailDialogView(uiState, uiActions); } if ( uiState.isProfileEditorDialogOpen && uiState.selectedProfileData != null ) { return renderProfileEditorDialogView(uiState, uiActions); } return null; } function renderToolsDialog( uiState: ReturnType, uiActions: ReturnType, ) { return ( ); } function renderLoggingDialog( uiState: ReturnType, uiActions: ReturnType, ) { return ( ; response?: string; tokens?: { input?: number; output?: number }; error?: string; tool?: string; duration?: number; success?: boolean; gitStats?: { linesAdded: number; linesRemoved: number; filesChanged: number; }; }> } onClose={uiActions.closeLoggingDialog} /> ); } function renderModelsDialog( uiState: ReturnType, uiActions: ReturnType, handleModelsDialogSelect: (model: HydratedModel) => void, currentProvider: string | null, ) { return ( ); } /** * @plan PLAN-20260214-SESSIONBROWSER.P21 * @plan PLAN-20260214-SESSIONBROWSER.P23 */ function renderSessionBrowserDialog( uiState: ReturnType, uiActions: ReturnType, config: CliUiRuntime, commandContext: { ui: { pendingItem: unknown }; recordingSwapCallbacks?: ResumeContext['recordingCallbacks']; }, handleSessionBrowserSelect: ( target: ContinueTarget, ) => Promise, ) { const chatsDir = join(config.getProjectTempDir(), 'chats'); const projectHash = getProjectHash(config.getProjectRoot()); const currentSessionId = config.getSessionId(); const hasActiveConversation = commandContext.ui.pendingItem !== null; return ( ); } function useDialogManagerState( addItem: UseHistoryManagerReturn['addItem'], config: CliUiRuntime, settings: LoadedSettings, uiState: ReturnType, uiActions: ReturnType, runtime: ReturnType, _terminalWidth: number, ) { const { constrainHeight, terminalHeight, mainAreaWidth, commandContext } = uiState; const staticExtraHeight = 0; const currentProvider = useMemo(() => { try { return runtime.getActiveProviderName() || null; } catch { return null; } }, [runtime]); const handlePrivacyNoticeExit = useCallback(() => { uiActions.handlePrivacyNoticeExit(); }, [uiActions]); const handleAuthSelect = useCallback( (method: string | undefined, scope: SettingScope) => { void uiActions.handleAuthSelect(method, scope); }, [uiActions], ); const handleOAuthCodeSubmit = useCallback( (code: string) => { void uiActions.handleOAuthCodeSubmit(code); }, [uiActions], ); const handleProviderSelect = useCallback( (provider: string) => { void uiActions.handleProviderSelect(provider); }, [uiActions], ); const handleModelsDialogSelect = useModelDialogHandler( runtime, addItem, uiActions, currentProvider, commandContext, ); const handleSessionBrowserSelect = useSessionBrowserHandler( config, commandContext, addItem, uiActions, ); return { constrainHeight, terminalHeight, mainAreaWidth, commandContext, staticExtraHeight, currentProvider, handlePrivacyNoticeExit, handleAuthSelect, handleOAuthCodeSubmit, handleProviderSelect, handleModelsDialogSelect, handleSessionBrowserSelect, }; } function renderDialogBodyFirstHalf( uiState: ReturnType, uiActions: ReturnType, settings: LoadedSettings, config: CliUiRuntime, state: ReturnType, ) { if (uiState.isThemeDialogOpen) { return renderThemeDialog( uiState, uiActions, settings, state.constrainHeight, state.terminalHeight, state.staticExtraHeight, state.mainAreaWidth, ); } if (uiState.isSettingsDialogOpen) { return ( ); } if (uiState.isAuthDialogOpen) { return renderAuthDialog(uiState, settings, state.handleAuthSelect); } if (uiState.isOAuthCodeDialogOpen) { return renderOAuthCodeDialog( uiState, uiActions, state.handleOAuthCodeSubmit, ); } if (uiState.isEditorDialogOpen) { return renderEditorDialog(uiState, uiActions, settings); } if (uiState.isProviderDialogOpen) { return renderProviderDialog(uiState, uiActions, state.handleProviderSelect); } return undefined; } function renderDialogBodySecondHalf( uiState: ReturnType, uiActions: ReturnType, config: CliUiRuntime, addItem: UseHistoryManagerReturn['addItem'], state: ReturnType, ) { if (uiState.isToolsDialogOpen) { return renderToolsDialog(uiState, uiActions); } if (uiState.showPrivacyNotice) { return ( ); } if (uiState.isPermissionsDialogOpen) { return ( ); } if (uiState.isLoggingDialogOpen) { return renderLoggingDialog(uiState, uiActions); } if (uiState.isSubagentDialogOpen) { return ( ); } if (uiState.isModelsDialogOpen) { return renderModelsDialog( uiState, uiActions, state.handleModelsDialogSelect, state.currentProvider, ); } if (uiState.isSessionBrowserDialogOpen) { return renderSessionBrowserDialog( uiState, uiActions, config, state.commandContext, state.handleSessionBrowserSelect, ); } if (uiState.isModelConfigDialogOpen) { return ( ); } if (uiState.isPoliciesDialogOpen) { return ( ); } return null; } function renderDialogBody( uiState: ReturnType, uiActions: ReturnType, settings: LoadedSettings, config: CliUiRuntime, addItem: UseHistoryManagerReturn['addItem'], state: ReturnType, ) { const firstHalf = renderDialogBodyFirstHalf( uiState, uiActions, settings, config, state, ); if (firstHalf !== undefined) return firstHalf; const profileDialog = renderProfileDialogs(uiState, uiActions); if (profileDialog) return profileDialog; return renderDialogBodySecondHalf(uiState, uiActions, config, addItem, state); } // Props for DialogManager export const DialogManager = ({ addItem, terminalWidth, config, settings, }: DialogManagerProps) => { const uiState = useUIState(); const uiActions = useUIActions(); const runtime = useRuntimeApi(); const state = useDialogManagerState( addItem, config, settings, uiState, uiActions, runtime, terminalWidth, ); // NOTE: IdeTrustChangeDialog not yet ported from upstream const earlyDialog = renderEarlyDialogs( uiState, uiActions, terminalWidth, config, ); if (earlyDialog) return earlyDialog; return renderDialogBody(uiState, uiActions, settings, config, addItem, state); };