diff --git a/packages/cli/src/test-utils/render.tsx b/packages/cli/src/test-utils/render.tsx
index d07f6663cb..3eba2ff964 100644
--- a/packages/cli/src/test-utils/render.tsx
+++ b/packages/cli/src/test-utils/render.tsx
@@ -64,6 +64,7 @@ const baseMockUiState = {
streamingState: StreamingState.Idle,
mainAreaWidth: 100,
terminalWidth: 120,
+ currentModel: 'gemini-pro',
};
export const renderWithProviders = (
diff --git a/packages/cli/src/ui/AppContainer.tsx b/packages/cli/src/ui/AppContainer.tsx
index ae0f43b418..a6ff6c0eeb 100644
--- a/packages/cli/src/ui/AppContainer.tsx
+++ b/packages/cli/src/ui/AppContainer.tsx
@@ -261,20 +261,18 @@ export const AppContainer = (props: AppContainerProps) => {
[historyManager.addItem],
);
- // Watch for model changes (e.g., from Flash fallback)
+ // Subscribe to fallback mode changes from core
useEffect(() => {
- const checkModelChange = () => {
+ const handleFallbackModeChanged = () => {
const effectiveModel = getEffectiveModel();
- if (effectiveModel !== currentModel) {
- setCurrentModel(effectiveModel);
- }
+ setCurrentModel(effectiveModel);
};
- checkModelChange();
- const interval = setInterval(checkModelChange, 1000); // Check every second
-
- return () => clearInterval(interval);
- }, [config, currentModel, getEffectiveModel]);
+ coreEvents.on(CoreEvent.FallbackModeChanged, handleFallbackModeChanged);
+ return () => {
+ coreEvents.off(CoreEvent.FallbackModeChanged, handleFallbackModeChanged);
+ };
+ }, [getEffectiveModel]);
const {
consoleMessages,
diff --git a/packages/cli/src/ui/components/Footer.test.tsx b/packages/cli/src/ui/components/Footer.test.tsx
index a27f6b26d1..f5ef617e0d 100644
--- a/packages/cli/src/ui/components/Footer.test.tsx
+++ b/packages/cli/src/ui/components/Footer.test.tsx
@@ -256,3 +256,31 @@ describe('', () => {
});
});
});
+
+describe('fallback mode display', () => {
+ it('should display Flash model when in fallback mode, not the configured Pro model', () => {
+ const { lastFrame } = renderWithProviders(, {
+ width: 120,
+ uiState: {
+ sessionStats: mockSessionStats,
+ currentModel: 'gemini-2.5-flash', // Fallback active, showing Flash
+ },
+ });
+
+ // Footer should show the effective model (Flash), not the config model (Pro)
+ expect(lastFrame()).toContain('gemini-2.5-flash');
+ expect(lastFrame()).not.toContain('gemini-2.5-pro');
+ });
+
+ it('should display Pro model when NOT in fallback mode', () => {
+ const { lastFrame } = renderWithProviders(, {
+ width: 120,
+ uiState: {
+ sessionStats: mockSessionStats,
+ currentModel: 'gemini-2.5-pro', // Normal mode, showing Pro
+ },
+ });
+
+ expect(lastFrame()).toContain('gemini-2.5-pro');
+ });
+});
diff --git a/packages/cli/src/ui/components/Footer.tsx b/packages/cli/src/ui/components/Footer.tsx
index 377bc55667..7c4aa4347b 100644
--- a/packages/cli/src/ui/components/Footer.tsx
+++ b/packages/cli/src/ui/components/Footer.tsx
@@ -15,7 +15,6 @@ import { MemoryUsageDisplay } from './MemoryUsageDisplay.js';
import { ContextUsageDisplay } from './ContextUsageDisplay.js';
import { DebugProfiler } from './DebugProfiler.js';
import { isDevelopment } from '../../utils/installationInfo.js';
-
import { useUIState } from '../contexts/UIStateContext.js';
import { useConfig } from '../contexts/ConfigContext.js';
import { useSettings } from '../contexts/SettingsContext.js';
@@ -41,7 +40,7 @@ export const Footer: React.FC = () => {
isTrustedFolder,
mainAreaWidth,
} = {
- model: config.getModel(),
+ model: uiState.currentModel,
targetDir: config.getTargetDir(),
debugMode: config.getDebugMode(),
branchName: uiState.branchName,
diff --git a/packages/core/src/fallback/handler.ts b/packages/core/src/fallback/handler.ts
index 762552cd2d..69eaf38d22 100644
--- a/packages/core/src/fallback/handler.ts
+++ b/packages/core/src/fallback/handler.ts
@@ -8,6 +8,7 @@ import type { Config } from '../config/config.js';
import { AuthType } from '../core/contentGenerator.js';
import { DEFAULT_GEMINI_FLASH_MODEL } from '../config/models.js';
import { logFlashFallback, FlashFallbackEvent } from '../telemetry/index.js';
+import { coreEvents } from '../utils/events.js';
export async function handleFallback(
config: Config,
@@ -62,6 +63,7 @@ export async function handleFallback(
function activateFallbackMode(config: Config, authType: string | undefined) {
if (!config.isInFallbackMode()) {
config.setFallbackMode(true);
+ coreEvents.emitFallbackModeChanged(true);
if (authType) {
logFlashFallback(config, new FlashFallbackEvent(authType));
}
diff --git a/packages/core/src/utils/events.ts b/packages/core/src/utils/events.ts
index 76038560d8..9b34d27883 100644
--- a/packages/core/src/utils/events.ts
+++ b/packages/core/src/utils/events.ts
@@ -33,8 +33,19 @@ export interface UserFeedbackPayload {
error?: unknown;
}
+/**
+ * Payload for the 'fallback-mode-changed' event.
+ */
+export interface FallbackModeChangedPayload {
+ /**
+ * Whether fallback mode is now active.
+ */
+ isInFallbackMode: boolean;
+}
+
export enum CoreEvent {
UserFeedback = 'user-feedback',
+ FallbackModeChanged = 'fallback-mode-changed',
}
export class CoreEventEmitter extends EventEmitter {
@@ -66,6 +77,15 @@ export class CoreEventEmitter extends EventEmitter {
}
}
+ /**
+ * Notifies subscribers that fallback mode has changed.
+ * This is synchronous and doesn't use backlog (UI should already be initialized).
+ */
+ emitFallbackModeChanged(isInFallbackMode: boolean): void {
+ const payload: FallbackModeChangedPayload = { isInFallbackMode };
+ this.emit(CoreEvent.FallbackModeChanged, payload);
+ }
+
/**
* Flushes buffered messages. Call this immediately after primary UI listener
* subscribes.
@@ -82,6 +102,10 @@ export class CoreEventEmitter extends EventEmitter {
event: CoreEvent.UserFeedback,
listener: (payload: UserFeedbackPayload) => void,
): this;
+ override on(
+ event: CoreEvent.FallbackModeChanged,
+ listener: (payload: FallbackModeChangedPayload) => void,
+ ): this;
override on(
event: string | symbol,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -94,6 +118,10 @@ export class CoreEventEmitter extends EventEmitter {
event: CoreEvent.UserFeedback,
listener: (payload: UserFeedbackPayload) => void,
): this;
+ override off(
+ event: CoreEvent.FallbackModeChanged,
+ listener: (payload: FallbackModeChangedPayload) => void,
+ ): this;
override off(
event: string | symbol,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -106,6 +134,10 @@ export class CoreEventEmitter extends EventEmitter {
event: CoreEvent.UserFeedback,
payload: UserFeedbackPayload,
): boolean;
+ override emit(
+ event: CoreEvent.FallbackModeChanged,
+ payload: FallbackModeChangedPayload,
+ ): boolean;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
override emit(event: string | symbol, ...args: any[]): boolean {
return super.emit(event, ...args);