chore: merge main and resolve Part 4 conflicts with agnostic task UI and sanitization

This commit is contained in:
mkorwel
2026-03-30 08:14:26 -07:00
963 changed files with 57675 additions and 23520 deletions
+231 -229
View File
@@ -14,7 +14,7 @@ import {
} from 'react';
import {
type DOMElement,
measureElement,
ResizeObserver,
useApp,
useStdout,
useStdin,
@@ -30,8 +30,6 @@ import {
import { ConfigContext } from './contexts/ConfigContext.js';
import {
type HistoryItem,
type HistoryItemWithoutId,
type HistoryItemToolGroup,
AuthState,
type ConfirmationRequest,
type PermissionConfirmationRequest,
@@ -81,7 +79,6 @@ import {
type AgentsDiscoveredPayload,
ChangeAuthRequestedError,
ProjectIdRequiredError,
CoreToolCallStatus,
buildUserSteeringHintPrompt,
formatBackgroundCompletionForModel,
logBillingEvent,
@@ -104,7 +101,7 @@ import {
useOverflowActions,
useOverflowState,
} from './contexts/OverflowContext.js';
import { useConsoleMessages } from './hooks/useConsoleMessages.js';
import { useErrorCount } from './hooks/useConsoleMessages.js';
import { useTerminalSize } from './hooks/useTerminalSize.js';
import { calculatePromptWidths } from './components/InputPrompt.js';
import { calculateMainAreaWidth } from './utils/ui-sizing.js';
@@ -114,7 +111,7 @@ import { computeTerminalTitle } from '../utils/windowTitle.js';
import { useTextBuffer } from './components/shared/text-buffer.js';
import { useLogger } from './hooks/useLogger.js';
import { useGeminiStream } from './hooks/useGeminiStream.js';
import { type BackgroundShell } from './hooks/shellCommandProcessor.js';
import { type BackgroundTask } from './hooks/useExecutionLifecycle.js';
import { useVim } from './hooks/vim.js';
import { type LoadableSettingScope, SettingScope } from '../config/settings.js';
import { type InitializationResult } from '../core/initializer.js';
@@ -155,7 +152,7 @@ import { useInputHistoryStore } from './hooks/useInputHistoryStore.js';
import { useBanner } from './hooks/useBanner.js';
import { useTerminalSetupPrompt } from './utils/terminalSetup.js';
import { useHookDisplayState } from './hooks/useHookDisplayState.js';
import { useBackgroundShellManager } from './hooks/useBackgroundShellManager.js';
import { useBackgroundTaskManager } from './hooks/useBackgroundTaskManager.js';
import {
WARNING_PROMPT_DURATION_MS,
QUEUE_ERROR_DISPLAY_DURATION_MS,
@@ -171,29 +168,11 @@ import { useIsHelpDismissKey } from './utils/shortcutsHelp.js';
import { useSuspend } from './hooks/useSuspend.js';
import { useRunEventNotifications } from './hooks/useRunEventNotifications.js';
import { isNotificationsEnabled } from '../utils/terminalNotifications.js';
function isToolExecuting(pendingHistoryItems: HistoryItemWithoutId[]) {
return pendingHistoryItems.some((item) => {
if (item && item.type === 'tool_group') {
return item.tools.some(
(tool) => CoreToolCallStatus.Executing === tool.status,
);
}
return false;
});
}
function isToolAwaitingConfirmation(
pendingHistoryItems: HistoryItemWithoutId[],
) {
return pendingHistoryItems
.filter((item): item is HistoryItemToolGroup => item.type === 'tool_group')
.some((item) =>
item.tools.some(
(tool) => CoreToolCallStatus.AwaitingApproval === tool.status,
),
);
}
import {
isToolExecuting,
isToolAwaitingConfirmation,
getAllToolCalls,
} from './utils/historyUtils.js';
interface AppContainerProps {
config: Config;
@@ -254,9 +233,9 @@ export const AppContainer = (props: AppContainerProps) => {
);
const [copyModeEnabled, setCopyModeEnabled] = useState(false);
const [pendingRestorePrompt, setPendingRestorePrompt] = useState(false);
const toggleBackgroundShellRef = useRef<() => void>(() => {});
const isBackgroundShellVisibleRef = useRef<boolean>(false);
const backgroundShellsRef = useRef<Map<number, BackgroundShell>>(new Map());
const toggleBackgroundTasksRef = useRef<() => void>(() => {});
const isBackgroundTaskVisibleRef = useRef<boolean>(false);
const backgroundTasksRef = useRef<Map<number, BackgroundTask>>(new Map());
const [adminSettingsChanged, setAdminSettingsChanged] = useState(false);
@@ -419,7 +398,6 @@ export const AppContainer = (props: AppContainerProps) => {
const branchName = useGitBranchName(config.getTargetDir());
// Layout measurements
const mainControlsRef = useRef<DOMElement>(null);
// For performance profiling only
const rootUiRef = useRef<DOMElement>(null);
const lastTitleRef = useRef<string | null>(null);
@@ -477,7 +455,7 @@ export const AppContainer = (props: AppContainerProps) => {
// Kill all background shells
await Promise.all(
Array.from(backgroundShellsRef.current.keys()).map((pid) =>
Array.from(backgroundTasksRef.current.keys()).map((pid) =>
ShellExecutionService.kill(pid),
),
);
@@ -553,8 +531,7 @@ export const AppContainer = (props: AppContainerProps) => {
};
}, [settings]);
const { consoleMessages, clearConsoleMessages: clearConsoleMessagesState } =
useConsoleMessages();
const { errorCount, clearErrorCount } = useErrorCount();
const mainAreaWidth = calculateMainAreaWidth(terminalWidth, config);
// Derive widths for InputPrompt using shared helper
@@ -723,7 +700,10 @@ export const AppContainer = (props: AppContainerProps) => {
// Derive auth state variables for backward compatibility with UIStateContext
const isAuthDialogOpen = authState === AuthState.Updating;
const isAuthenticating = authState === AuthState.Unauthenticated;
// TODO: Consider handling other auth types that should also skip the blocking screen
const isAuthenticating =
authState === AuthState.Unauthenticated &&
settings.merged.security.auth.selectedType !== AuthType.USE_GEMINI;
// Session browser and resume functionality
const isGeminiClientInitialized = config.getGeminiClient()?.isInitialized();
@@ -747,7 +727,7 @@ export const AppContainer = (props: AppContainerProps) => {
// Wrap handleDeleteSession to return a Promise for UIActions interface
const handleDeleteSession = useCallback(
async (session: SessionInfo): Promise<void> => {
handleDeleteSessionSync(session);
await handleDeleteSessionSync(session);
},
[handleDeleteSessionSync],
);
@@ -886,7 +866,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
const { toggleVimEnabled } = useVimMode();
const setIsBackgroundShellListOpenRef = useRef<(open: boolean) => void>(
const setIsBackgroundTaskListOpenRef = useRef<(open: boolean) => void>(
() => {},
);
const [shortcutsHelpVisible, setShortcutsHelpVisible] = useState(false);
@@ -921,14 +901,14 @@ Logging in with Google... Restarting Gemini CLI to continue.
toggleDebugProfiler,
dispatchExtensionStateUpdate,
addConfirmUpdateExtensionRequest,
toggleBackgroundShell: () => {
toggleBackgroundShellRef.current();
if (!isBackgroundShellVisibleRef.current) {
toggleBackgroundTasks: () => {
toggleBackgroundTasksRef.current();
if (!isBackgroundTaskVisibleRef.current) {
setEmbeddedShellFocused(true);
if (backgroundShellsRef.current.size > 1) {
setIsBackgroundShellListOpenRef.current(true);
if (backgroundTasksRef.current.size > 1) {
setIsBackgroundTaskListOpenRef.current(true);
} else {
setIsBackgroundShellListOpenRef.current(false);
setIsBackgroundTaskListOpenRef.current(false);
}
}
},
@@ -1009,10 +989,18 @@ Logging in with Google... Restarting Gemini CLI to continue.
Date.now(),
);
try {
const { memoryContent, fileCount } =
await refreshServerHierarchicalMemory(config);
let flattenedMemory: string;
let fileCount: number;
const flattenedMemory = flattenMemory(memoryContent);
if (config.isJitContextEnabled()) {
await config.getContextManager()?.refresh();
flattenedMemory = flattenMemory(config.getUserMemory());
fileCount = config.getGeminiMdFileCount();
} else {
const result = await refreshServerHierarchicalMemory(config);
flattenedMemory = flattenMemory(result.memoryContent);
fileCount = result.fileCount;
}
historyManager.addItem(
{
@@ -1077,30 +1065,25 @@ Logging in with Google... Restarting Gemini CLI to continue.
}
}, [pendingRestorePrompt, inputHistory, historyManager.history]);
const pendingHintsRef = useRef<string[]>([]);
const [pendingHintCount, setPendingHintCount] = useState(0);
const pendingBgCompletionsRef = useRef<string[]>([]);
const [pendingBgCompletionCount, setPendingBgCompletionCount] = useState(0);
const pendingInjectionsRef = useRef<
Array<{ text: string; source: InjectionSource }>
>([]);
const [pendingInjectionCount, setPendingInjectionCount] = useState(0);
const consumePendingHints = useCallback(() => {
if (pendingHintsRef.current.length === 0) {
return null;
const consumePendingInjections = useCallback(() => {
if (pendingInjectionsRef.current.length === 0) {
return [];
}
const hint = pendingHintsRef.current.join('\n');
pendingHintsRef.current = [];
setPendingHintCount(0);
return hint;
const injections = [...pendingInjectionsRef.current];
pendingInjectionsRef.current = [];
setPendingInjectionCount(0);
return injections;
}, []);
useEffect(() => {
const injectionListener = (text: string, source: InjectionSource) => {
if (source === 'user_steering') {
pendingHintsRef.current.push(text);
setPendingHintCount((prev) => prev + 1);
} else if (source === 'background_completion') {
pendingBgCompletionsRef.current.push(text);
setPendingBgCompletionCount((prev) => prev + 1);
}
pendingInjectionsRef.current.push({ text, source });
setPendingInjectionCount((prev) => prev + 1);
};
config.injectionService.onInjection(injectionListener);
return () => {
@@ -1120,12 +1103,12 @@ Logging in with Google... Restarting Gemini CLI to continue.
activePtyId,
loopDetectionConfirmationRequest,
lastOutputTime,
backgroundShellCount,
isBackgroundShellVisible,
toggleBackgroundShell,
backgroundCurrentShell,
backgroundShells,
dismissBackgroundShell,
backgroundTaskCount,
isBackgroundTaskVisible,
toggleBackgroundTasks,
backgroundCurrentExecution,
backgroundTasks,
dismissBackgroundTask,
retryStatus,
} = useGeminiStream(
config.getGeminiClient(),
@@ -1149,27 +1132,37 @@ Logging in with Google... Restarting Gemini CLI to continue.
consumePendingHints,
);
toggleBackgroundShellRef.current = toggleBackgroundShell;
isBackgroundShellVisibleRef.current = isBackgroundShellVisible;
backgroundShellsRef.current = backgroundShells;
const pendingHistoryItems = useMemo(
() => [...pendingSlashCommandHistoryItems, ...pendingGeminiHistoryItems],
[pendingSlashCommandHistoryItems, pendingGeminiHistoryItems],
);
const hasPendingToolConfirmation = useMemo(
() => isToolAwaitingConfirmation(pendingHistoryItems),
[pendingHistoryItems],
);
toggleBackgroundTasksRef.current = toggleBackgroundTasks;
isBackgroundTaskVisibleRef.current = isBackgroundTaskVisible;
backgroundTasksRef.current = backgroundTasks;
const {
activeBackgroundShellPid,
setIsBackgroundShellListOpen,
isBackgroundShellListOpen,
setActiveBackgroundShellPid,
backgroundShellHeight,
} = useBackgroundShellManager({
backgroundShells,
backgroundShellCount,
isBackgroundShellVisible,
activeBackgroundTaskPid,
setIsBackgroundTaskListOpen,
isBackgroundTaskListOpen,
setActiveBackgroundTaskPid,
backgroundTaskHeight,
} = useBackgroundTaskManager({
backgroundTasks,
backgroundTaskCount,
isBackgroundTaskVisible,
activePtyId,
embeddedShellFocused,
setEmbeddedShellFocused,
terminalHeight,
});
setIsBackgroundShellListOpenRef.current = setIsBackgroundShellListOpen;
setIsBackgroundTaskListOpenRef.current = setIsBackgroundTaskListOpen;
const lastOutputTimeRef = useRef(0);
@@ -1220,10 +1213,6 @@ Logging in with Google... Restarting Gemini CLI to continue.
cancelHandlerRef.current = useCallback(
(shouldRestorePrompt: boolean = true) => {
const pendingHistoryItems = [
...pendingSlashCommandHistoryItems,
...pendingGeminiHistoryItems,
];
if (isToolAwaitingConfirmation(pendingHistoryItems)) {
return; // Don't clear - user may be composing a follow-up message
}
@@ -1257,8 +1246,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
inputHistory,
getQueuedMessagesText,
clearQueue,
pendingSlashCommandHistoryItems,
pendingGeminiHistoryItems,
pendingHistoryItems,
],
);
@@ -1294,10 +1282,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
const isIdle = streamingState === StreamingState.Idle;
const isAgentRunning =
streamingState === StreamingState.Responding ||
isToolExecuting([
...pendingSlashCommandHistoryItems,
...pendingGeminiHistoryItems,
]);
isToolExecuting(pendingHistoryItems);
if (isSlash && isAgentRunning) {
const { commandToExecute } = parseSlashCommand(
@@ -1317,7 +1302,8 @@ Logging in with Google... Restarting Gemini CLI to continue.
return;
}
if (isSlash || (isIdle && isMcpReady)) {
const isMcpOrConfigReady = isConfigInitialized && isMcpReady;
if ((isSlash && isConfigInitialized) || (isIdle && isMcpOrConfigReady)) {
if (!isSlash) {
const permissions = await checkPermissions(submittedValue, config);
if (permissions.length > 0) {
@@ -1340,10 +1326,12 @@ Logging in with Google... Restarting Gemini CLI to continue.
void submitQuery(submittedValue);
} else {
// Check messageQueue.length === 0 to only notify on the first queued item
if (isIdle && !isMcpReady && messageQueue.length === 0) {
if (isIdle && !isMcpOrConfigReady && messageQueue.length === 0) {
coreEvents.emitFeedback(
'info',
'Waiting for MCP servers to initialize... Slash commands are still available and prompts will be queued.',
!isConfigInitialized
? 'Initializing... Prompts will be queued.'
: 'Waiting for MCP servers to initialize... Slash commands are still available and prompts will be queued.',
);
}
addMessage(submittedValue);
@@ -1359,8 +1347,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
isMcpReady,
streamingState,
messageQueue.length,
pendingSlashCommandHistoryItems,
pendingGeminiHistoryItems,
pendingHistoryItems,
config,
constrainHeight,
setConstrainHeight,
@@ -1368,6 +1355,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
refreshStatic,
reset,
handleHintSubmit,
isConfigInitialized,
triggerExpandHint,
],
);
@@ -1377,11 +1365,11 @@ Logging in with Google... Restarting Gemini CLI to continue.
// Explicitly hide the expansion hint and clear its x-second timer when clearing the screen.
triggerExpandHint(null);
historyManager.clearItems();
clearConsoleMessagesState();
clearErrorCount();
refreshStatic();
}, [
historyManager,
clearConsoleMessagesState,
clearErrorCount,
refreshStatic,
reset,
triggerExpandHint,
@@ -1398,31 +1386,55 @@ Logging in with Google... Restarting Gemini CLI to continue.
* - Any future streaming states not explicitly allowed
*/
const isInputActive =
isConfigInitialized &&
!initError &&
!isProcessing &&
!isResuming &&
!!slashCommands &&
(streamingState === StreamingState.Idle ||
streamingState === StreamingState.Responding) &&
!proQuotaRequest;
streamingState === StreamingState.Responding ||
streamingState === StreamingState.WaitingForConfirmation) &&
!proQuotaRequest &&
!copyModeEnabled;
const observerRef = useRef<ResizeObserver | null>(null);
const [controlsHeight, setControlsHeight] = useState(0);
const [lastNonCopyControlsHeight, setLastNonCopyControlsHeight] = useState(0);
useLayoutEffect(() => {
if (mainControlsRef.current) {
const fullFooterMeasurement = measureElement(mainControlsRef.current);
const roundedHeight = Math.round(fullFooterMeasurement.height);
if (roundedHeight > 0 && roundedHeight !== controlsHeight) {
setControlsHeight(roundedHeight);
}
if (!copyModeEnabled && controlsHeight > 0) {
setLastNonCopyControlsHeight(controlsHeight);
}
}, [buffer, terminalWidth, terminalHeight, controlsHeight]);
}, [copyModeEnabled, controlsHeight]);
// Compute available terminal height based on controls measurement
const stableControlsHeight =
copyModeEnabled && lastNonCopyControlsHeight > 0
? lastNonCopyControlsHeight
: controlsHeight;
const mainControlsRef = useCallback((node: DOMElement | null) => {
if (observerRef.current) {
observerRef.current.disconnect();
observerRef.current = null;
}
if (node) {
const observer = new ResizeObserver((entries) => {
const entry = entries[0];
if (entry) {
const roundedHeight = Math.round(entry.contentRect.height);
setControlsHeight((prev) =>
roundedHeight !== prev ? roundedHeight : prev,
);
}
});
observer.observe(node);
observerRef.current = observer;
}
}, []);
// Compute available terminal height based on stable controls measurement
const availableTerminalHeight = Math.max(
0,
terminalHeight - controlsHeight - backgroundShellHeight - 1,
terminalHeight - stableControlsHeight - backgroundTaskHeight - 1,
);
config.setShellExecutionConfig({
@@ -1671,15 +1683,6 @@ Logging in with Google... Restarting Gemini CLI to continue.
[handleSlashCommand, settings],
);
const { elapsedTime, currentLoadingPhrase } = useLoadingIndicator({
streamingState,
shouldShowFocusHint,
retryStatus,
loadingPhrasesMode: settings.merged.ui.loadingPhrases,
customWittyPhrases: settings.merged.ui.customWittyPhrases,
errorVerbosity: settings.merged.ui.errorVerbosity,
});
const handleGlobalKeypress = useCallback(
(key: Key): boolean => {
// Debug log keystrokes if enabled
@@ -1705,6 +1708,10 @@ Logging in with Google... Restarting Gemini CLI to continue.
handleCtrlCPress();
return true;
} else if (keyMatchers[Command.EXIT](key)) {
// If the input field is non-empty, do not exit.
if (bufferRef.current.text.length > 0) {
return false;
}
handleCtrlDPress();
return true;
} else if (keyMatchers[Command.SUSPEND_APP](key)) {
@@ -1783,7 +1790,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
} else if (
(keyMatchers[Command.FOCUS_SHELL_INPUT](key) ||
keyMatchers[Command.UNFOCUS_BACKGROUND_SHELL_LIST](key)) &&
(activePtyId || (isBackgroundShellVisible && backgroundShells.size > 0))
(activePtyId || (isBackgroundTaskVisible && backgroundTasks.size > 0))
) {
if (embeddedShellFocused) {
const capturedTime = lastOutputTimeRef.current;
@@ -1804,12 +1811,12 @@ Logging in with Google... Restarting Gemini CLI to continue.
const isIdle = Date.now() - lastOutputTimeRef.current >= 100;
if (isIdle && !activePtyId && !isBackgroundShellVisible) {
if (isIdle && !activePtyId && !isBackgroundTaskVisible) {
if (tabFocusTimeoutRef.current)
clearTimeout(tabFocusTimeoutRef.current);
toggleBackgroundShell();
toggleBackgroundTasks();
setEmbeddedShellFocused(true);
if (backgroundShells.size > 1) setIsBackgroundShellListOpen(true);
if (backgroundTasks.size > 1) setIsBackgroundTaskListOpen(true);
return true;
}
@@ -1826,15 +1833,15 @@ Logging in with Google... Restarting Gemini CLI to continue.
return false;
} else if (keyMatchers[Command.TOGGLE_BACKGROUND_SHELL](key)) {
if (activePtyId) {
backgroundCurrentShell();
backgroundCurrentExecution();
// After backgrounding, we explicitly do NOT show or focus the background UI.
} else {
toggleBackgroundShell();
toggleBackgroundTasks();
// Toggle focus based on intent: if we were hiding, unfocus; if showing, focus.
if (!isBackgroundShellVisible && backgroundShells.size > 0) {
if (!isBackgroundTaskVisible && backgroundTasks.size > 0) {
setEmbeddedShellFocused(true);
if (backgroundShells.size > 1) {
setIsBackgroundShellListOpen(true);
if (backgroundTasks.size > 1) {
setIsBackgroundTaskListOpen(true);
}
} else {
setEmbeddedShellFocused(false);
@@ -1842,11 +1849,11 @@ Logging in with Google... Restarting Gemini CLI to continue.
}
return true;
} else if (keyMatchers[Command.TOGGLE_BACKGROUND_SHELL_LIST](key)) {
if (backgroundShells.size > 0 && isBackgroundShellVisible) {
if (backgroundTasks.size > 0 && isBackgroundTaskVisible) {
if (!embeddedShellFocused) {
setEmbeddedShellFocused(true);
}
setIsBackgroundShellListOpen(true);
setIsBackgroundTaskListOpen(true);
}
return true;
}
@@ -1871,11 +1878,11 @@ Logging in with Google... Restarting Gemini CLI to continue.
tabFocusTimeoutRef,
isAlternateBuffer,
shortcutsHelpVisible,
backgroundCurrentShell,
toggleBackgroundShell,
backgroundShells,
isBackgroundShellVisible,
setIsBackgroundShellListOpen,
backgroundCurrentExecution,
toggleBackgroundTasks,
backgroundTasks,
isBackgroundTaskVisible,
setIsBackgroundTaskListOpen,
lastOutputTimeRef,
showTransientMessage,
settings.merged.general.devtools,
@@ -1994,22 +2001,6 @@ Logging in with Google... Restarting Gemini CLI to continue.
};
}, [historyManager]);
const filteredConsoleMessages = useMemo(() => {
if (config.getDebugMode()) {
return consoleMessages;
}
return consoleMessages.filter((msg) => msg.type !== 'debug');
}, [consoleMessages, config]);
// Computed values
const errorCount = useMemo(
() =>
filteredConsoleMessages
.filter((msg) => msg.type === 'error')
.reduce((total, msg) => total + msg.count, 0),
[filteredConsoleMessages],
);
const nightly = props.version.includes('nightly');
const dialogsVisible =
@@ -2042,16 +2033,6 @@ Logging in with Google... Restarting Gemini CLI to continue.
authState === AuthState.AwaitingApiKeyInput ||
!!newAgents;
const pendingHistoryItems = useMemo(
() => [...pendingSlashCommandHistoryItems, ...pendingGeminiHistoryItems],
[pendingSlashCommandHistoryItems, pendingGeminiHistoryItems],
);
const hasPendingToolConfirmation = useMemo(
() => isToolAwaitingConfirmation(pendingHistoryItems),
[pendingHistoryItems],
);
const hasConfirmUpdateExtensionRequests =
confirmUpdateExtensionRequests.length > 0;
const hasLoopDetectionConfirmationRequest =
@@ -2069,6 +2050,48 @@ Logging in with Google... Restarting Gemini CLI to continue.
!!emptyWalletRequest ||
!!customDialog;
const loadingPhrases = settings.merged.ui.loadingPhrases;
const showStatusTips = loadingPhrases === 'tips' || loadingPhrases === 'all';
const showStatusWit = loadingPhrases === 'witty' || loadingPhrases === 'all';
const showLoadingIndicator =
(!embeddedShellFocused || isBackgroundTaskVisible) &&
streamingState === StreamingState.Responding &&
!hasPendingActionRequired;
let estimatedStatusLength = 0;
if (activeHooks.length > 0 && settings.merged.hooksConfig.notifications) {
const hookLabel =
activeHooks.length > 1 ? 'Executing Hooks' : 'Executing Hook';
const hookNames = activeHooks
.map(
(h) =>
h.name +
(h.index && h.total && h.total > 1 ? ` (${h.index}/${h.total})` : ''),
)
.join(', ');
estimatedStatusLength = hookLabel.length + hookNames.length + 10;
} else if (showLoadingIndicator) {
const thoughtText = thought?.subject || 'Waiting for model...';
estimatedStatusLength = thoughtText.length + 25;
} else if (hasPendingActionRequired) {
estimatedStatusLength = 35;
}
const maxLength = terminalWidth - estimatedStatusLength - 5;
const { elapsedTime, currentLoadingPhrase, currentTip, currentWittyPhrase } =
useLoadingIndicator({
streamingState,
shouldShowFocusHint,
retryStatus,
showTips: showStatusTips,
showWit: showStatusWit,
customWittyPhrases: settings.merged.ui.customWittyPhrases,
errorVerbosity: settings.merged.ui.errorVerbosity,
maxLength,
});
const allowPlanMode =
config.isPlanEnabled() &&
streamingState === StreamingState.Idle &&
@@ -2114,62 +2137,39 @@ Logging in with Google... Restarting Gemini CLI to continue.
useEffect(() => {
if (
!isConfigInitialized ||
!config.isModelSteeringEnabled() ||
streamingState !== StreamingState.Idle ||
!isMcpReady ||
pendingInjectionCount === 0 ||
isToolAwaitingConfirmation(pendingHistoryItems)
) {
return;
}
const pendingHint = consumePendingHints();
if (!pendingHint) {
const pendingInjections = consumePendingInjections();
if (pendingInjections.length === 0) {
return;
}
void submitQuery([{ text: buildUserSteeringHintPrompt(pendingHint) }]);
const prompts = pendingInjections.map((inj) => {
if (inj.source === 'user_steering') {
return { text: buildUserSteeringHintPrompt(inj.text) };
}
return { text: formatBackgroundCompletionForModel(inj.text) };
});
void submitQuery(prompts);
}, [
config,
historyManager,
isConfigInitialized,
isMcpReady,
streamingState,
submitQuery,
consumePendingHints,
pendingInjectionCount,
pendingHistoryItems,
pendingHintCount,
]);
useEffect(() => {
if (
!isConfigInitialized ||
streamingState !== StreamingState.Idle ||
!isMcpReady ||
pendingBgCompletionsRef.current.length === 0
) {
return;
}
const bgText = pendingBgCompletionsRef.current.join('\n');
pendingBgCompletionsRef.current = [];
setPendingBgCompletionCount(0);
void submitQuery([{ text: formatBackgroundCompletionForModel(bgText) }]);
}, [
isConfigInitialized,
isMcpReady,
streamingState,
submitQuery,
pendingBgCompletionCount,
consumePendingInjections,
]);
const allToolCalls = useMemo(
() =>
pendingHistoryItems
.filter(
(item): item is HistoryItemToolGroup => item.type === 'tool_group',
)
.flatMap((item) => item.tools),
() => getAllToolCalls(pendingHistoryItems),
[pendingHistoryItems],
);
@@ -2267,7 +2267,6 @@ Logging in with Google... Restarting Gemini CLI to continue.
constrainHeight,
showErrorDetails,
showFullTodos,
filteredConsoleMessages,
ideContextState,
renderMarkdown,
ctrlCPressedOnce: ctrlCPressCount >= 1,
@@ -2278,6 +2277,8 @@ Logging in with Google... Restarting Gemini CLI to continue.
isFocused,
elapsedTime,
currentLoadingPhrase,
currentTip,
currentWittyPhrase,
historyRemountKey,
activeHooks,
messageQueue,
@@ -2297,6 +2298,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
contextFileNames,
errorCount,
availableTerminalHeight,
stableControlsHeight,
mainAreaWidth,
staticAreaMaxItemHeight,
staticExtraHeight,
@@ -2316,8 +2318,8 @@ Logging in with Google... Restarting Gemini CLI to continue.
isRestarting,
extensionsUpdateState,
activePtyId,
backgroundShellCount,
isBackgroundShellVisible,
backgroundTaskCount,
isBackgroundTaskVisible,
embeddedShellFocused,
showDebugProfiler,
customDialog,
@@ -2327,19 +2329,15 @@ Logging in with Google... Restarting Gemini CLI to continue.
bannerVisible,
terminalBackgroundColor: config.getTerminalBackground(),
settingsNonce,
backgroundShells,
activeBackgroundShellPid,
backgroundShellHeight,
isBackgroundShellListOpen,
backgroundTasks,
activeBackgroundTaskPid,
backgroundTaskHeight,
isBackgroundTaskListOpen,
adminSettingsChanged,
newAgents,
showIsExpandableHint,
hintMode:
config.isModelSteeringEnabled() &&
isToolExecuting([
...pendingSlashCommandHistoryItems,
...pendingGeminiHistoryItems,
]),
config.isModelSteeringEnabled() && isToolExecuting(pendingHistoryItems),
hintBuffer: '',
}),
[
@@ -2395,7 +2393,6 @@ Logging in with Google... Restarting Gemini CLI to continue.
constrainHeight,
showErrorDetails,
showFullTodos,
filteredConsoleMessages,
ideContextState,
renderMarkdown,
ctrlCPressCount,
@@ -2406,6 +2403,8 @@ Logging in with Google... Restarting Gemini CLI to continue.
isFocused,
elapsedTime,
currentLoadingPhrase,
currentTip,
currentWittyPhrase,
historyRemountKey,
activeHooks,
messageQueue,
@@ -2421,6 +2420,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
contextFileNames,
errorCount,
availableTerminalHeight,
stableControlsHeight,
mainAreaWidth,
staticAreaMaxItemHeight,
staticExtraHeight,
@@ -2441,8 +2441,8 @@ Logging in with Google... Restarting Gemini CLI to continue.
currentModel,
extensionsUpdateState,
activePtyId,
backgroundShellCount,
isBackgroundShellVisible,
backgroundTaskCount,
isBackgroundTaskVisible,
historyManager,
embeddedShellFocused,
showDebugProfiler,
@@ -2455,10 +2455,10 @@ Logging in with Google... Restarting Gemini CLI to continue.
bannerVisible,
config,
settingsNonce,
backgroundShellHeight,
isBackgroundShellListOpen,
activeBackgroundShellPid,
backgroundShells,
backgroundTaskHeight,
isBackgroundTaskListOpen,
activeBackgroundTaskPid,
backgroundTasks,
adminSettingsChanged,
newAgents,
showIsExpandableHint,
@@ -2507,6 +2507,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
handleResumeSession,
handleDeleteSession,
setQueueErrorMessage,
addMessage,
popAllMessages,
handleApiKeySubmit,
handleApiKeyCancel,
@@ -2517,9 +2518,9 @@ Logging in with Google... Restarting Gemini CLI to continue.
revealCleanUiDetailsTemporarily,
handleWarning,
setEmbeddedShellFocused,
dismissBackgroundShell,
setActiveBackgroundShellPid,
setIsBackgroundShellListOpen,
dismissBackgroundTask,
setActiveBackgroundTaskPid,
setIsBackgroundTaskListOpen,
setAuthContext,
onHintInput: () => {},
onHintBackspace: () => {},
@@ -2598,6 +2599,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
handleResumeSession,
handleDeleteSession,
setQueueErrorMessage,
addMessage,
popAllMessages,
handleApiKeySubmit,
handleApiKeyCancel,
@@ -2608,9 +2610,9 @@ Logging in with Google... Restarting Gemini CLI to continue.
revealCleanUiDetailsTemporarily,
handleWarning,
setEmbeddedShellFocused,
dismissBackgroundShell,
setActiveBackgroundShellPid,
setIsBackgroundShellListOpen,
dismissBackgroundTask,
setActiveBackgroundTaskPid,
setIsBackgroundTaskListOpen,
setAuthContext,
setAccountSuspensionInfo,
newAgents,