diff --git a/packages/cli/src/gemini.tsx b/packages/cli/src/gemini.tsx index e7da7582a..1add91580 100644 --- a/packages/cli/src/gemini.tsx +++ b/packages/cli/src/gemini.tsx @@ -44,6 +44,7 @@ import { SessionStatsProvider } from './ui/contexts/SessionContext.js'; import { SettingsContext } from './ui/contexts/SettingsContext.js'; import { VimModeProvider } from './ui/contexts/VimModeContext.js'; import { AgentViewProvider } from './ui/contexts/AgentViewContext.js'; +import { BackgroundTaskViewProvider } from './ui/contexts/BackgroundTaskViewContext.js'; import { useKittyKeyboardProtocol } from './ui/hooks/useKittyKeyboardProtocol.js'; import { themeManager, AUTO_THEME_NAME } from './ui/themes/theme-manager.js'; import { @@ -251,13 +252,15 @@ export async function startInteractiveUI( - + + + diff --git a/packages/cli/src/nonInteractiveCli.test.ts b/packages/cli/src/nonInteractiveCli.test.ts index a6c9761e0..b2757d845 100644 --- a/packages/cli/src/nonInteractiveCli.test.ts +++ b/packages/cli/src/nonInteractiveCli.test.ts @@ -166,7 +166,7 @@ describe('runNonInteractive', () => { getBackgroundTaskRegistry: vi.fn().mockReturnValue({ setNotificationCallback: vi.fn(), setRegisterCallback: vi.fn(), - getRunning: vi.fn().mockReturnValue([]), + getAll: vi.fn().mockReturnValue([]), hasUnfinalizedTasks: vi.fn().mockReturnValue(false), abortAll: vi.fn(), }), diff --git a/packages/cli/src/nonInteractiveCli.ts b/packages/cli/src/nonInteractiveCli.ts index f13c79f0c..5232f6096 100644 --- a/packages/cli/src/nonInteractiveCli.ts +++ b/packages/cli/src/nonInteractiveCli.ts @@ -5,7 +5,7 @@ */ import type { - BackgroundAgentStatus, + BackgroundTaskStatus, Config, ToolCallRequestInfo, } from '@qwen-code/qwen-code-core'; @@ -302,7 +302,7 @@ export async function runNonInteractive( sdkNotification?: { task_id: string; tool_use_id?: string; - status: BackgroundAgentStatus; + status: BackgroundTaskStatus; usage?: { total_tokens: number; tool_uses: number; diff --git a/packages/cli/src/ui/AppContainer.tsx b/packages/cli/src/ui/AppContainer.tsx index 8d66b3a14..e33beca8a 100644 --- a/packages/cli/src/ui/AppContainer.tsx +++ b/packages/cli/src/ui/AppContainer.tsx @@ -127,6 +127,10 @@ import { import { useCodingPlanUpdates } from './hooks/useCodingPlanUpdates.js'; import { ShellFocusContext } from './contexts/ShellFocusContext.js'; import { useAgentViewState } from './contexts/AgentViewContext.js'; +import { + useBackgroundTaskViewState, + useBackgroundTaskViewActions, +} from './contexts/BackgroundTaskViewContext.js'; import { t } from '../i18n/index.js'; import { useWelcomeBack } from './hooks/useWelcomeBack.js'; import { useDialogClose } from './hooks/useDialogClose.js'; @@ -900,6 +904,8 @@ export const AppContainer = (props: AppContainerProps) => { const [hasSuggestionsVisible, setHasSuggestionsVisible] = useState(false); const agentViewState = useAgentViewState(); + const { dialogOpen: bgTasksDialogOpen } = useBackgroundTaskViewState(); + const { closeDialog: closeBgTasksDialog } = useBackgroundTaskViewActions(); // Prompt suggestion state const [promptSuggestion, setPromptSuggestion] = useState(null); @@ -1593,7 +1599,8 @@ export const AppContainer = (props: AppContainerProps) => { isResumeDialogOpen || isDeleteDialogOpen || isExtensionsManagerDialogOpen || - isRewindSelectorOpen; + isRewindSelectorOpen || + bgTasksDialogOpen; dialogsVisibleRef.current = dialogsVisible; const shouldShowStickyTodos = stickyTodos !== null && @@ -1918,6 +1925,8 @@ export const AppContainer = (props: AppContainerProps) => { isFolderTrustDialogOpen, showWelcomeBackDialog, handleWelcomeBackClose, + isBackgroundTasksDialogOpen: bgTasksDialogOpen, + closeBackgroundTasksDialog: closeBgTasksDialog, }); const handleExit = useCallback( diff --git a/packages/cli/src/ui/components/DialogManager.tsx b/packages/cli/src/ui/components/DialogManager.tsx index f884d089a..e15ad77b4 100644 --- a/packages/cli/src/ui/components/DialogManager.tsx +++ b/packages/cli/src/ui/components/DialogManager.tsx @@ -47,6 +47,8 @@ import { HooksManagementDialog } from './hooks/HooksManagementDialog.js'; import { SessionPicker } from './SessionPicker.js'; import { RewindSelector } from './RewindSelector.js'; import { MemoryDialog } from './MemoryDialog.js'; +import { BackgroundTasksDialog } from './background-view/BackgroundTasksDialog.js'; +import { useBackgroundTaskViewState } from '../contexts/BackgroundTaskViewContext.js'; import { t } from '../../i18n/index.js'; interface DialogManagerProps { @@ -64,6 +66,7 @@ export const DialogManager = ({ const uiState = useUIState(); const uiActions = useUIActions(); + const { dialogOpen: bgTasksDialogOpen } = useBackgroundTaskViewState(); const { constrainHeight, terminalHeight, staticExtraHeight, mainAreaWidth } = uiState; @@ -436,5 +439,19 @@ export const DialogManager = ({ ); } + // Background tasks dialog — lowest priority so other dialogs + // (permissions, trust prompts, auth, etc.) always take precedence. The + // dialog is part of the shared dialogsVisible machinery (see + // AppContainer) so its visibility mutes the composer and the global + // Ctrl+C / Esc handlers route through `closeAnyOpenDialog`. + if (bgTasksDialogOpen) { + return ( + + ); + } + return null; }; diff --git a/packages/cli/src/ui/components/Footer.test.tsx b/packages/cli/src/ui/components/Footer.test.tsx index c405dbe3e..064206a92 100644 --- a/packages/cli/src/ui/components/Footer.test.tsx +++ b/packages/cli/src/ui/components/Footer.test.tsx @@ -13,6 +13,7 @@ import { type UIState, UIStateContext } from '../contexts/UIStateContext.js'; import { ConfigContext } from '../contexts/ConfigContext.js'; import { VimModeProvider } from '../contexts/VimModeContext.js'; import { SettingsContext } from '../contexts/SettingsContext.js'; +import { KeypressProvider } from '../contexts/KeypressContext.js'; import type { LoadedSettings } from '../../config/settings.js'; vi.mock('../hooks/useTerminalSize.js'); @@ -97,11 +98,13 @@ const renderWithWidth = (width: number, uiState: UIState) => { return render( - - -