mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-27 13:34:15 -07:00
feat(ux): Surface internal errors via unified event system (#11803)
This commit is contained in:
@@ -15,7 +15,29 @@ import {
|
||||
} from 'vitest';
|
||||
import { render, cleanup } from 'ink-testing-library';
|
||||
import { AppContainer } from './AppContainer.js';
|
||||
import { type Config, makeFakeConfig } from '@google/gemini-cli-core';
|
||||
import {
|
||||
type Config,
|
||||
makeFakeConfig,
|
||||
CoreEvent,
|
||||
type UserFeedbackPayload,
|
||||
} from '@google/gemini-cli-core';
|
||||
|
||||
// Mock coreEvents
|
||||
const mockCoreEvents = vi.hoisted(() => ({
|
||||
on: vi.fn(),
|
||||
off: vi.fn(),
|
||||
drainFeedbackBacklog: vi.fn(),
|
||||
emit: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
||||
const actual =
|
||||
await importOriginal<typeof import('@google/gemini-cli-core')>();
|
||||
return {
|
||||
...actual,
|
||||
coreEvents: mockCoreEvents,
|
||||
};
|
||||
});
|
||||
import type { LoadedSettings } from '../config/settings.js';
|
||||
import type { InitializationResult } from '../core/initializer.js';
|
||||
import { useQuotaAndFallback } from './hooks/useQuotaAndFallback.js';
|
||||
@@ -1293,4 +1315,73 @@ describe('AppContainer State Management', () => {
|
||||
expect(mockCloseModelDialog).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('CoreEvents Integration', () => {
|
||||
it('subscribes to UserFeedback and drains backlog on mount', () => {
|
||||
render(
|
||||
<AppContainer
|
||||
config={mockConfig}
|
||||
settings={mockSettings}
|
||||
version="1.0.0"
|
||||
initializationResult={mockInitResult}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(mockCoreEvents.on).toHaveBeenCalledWith(
|
||||
CoreEvent.UserFeedback,
|
||||
expect.any(Function),
|
||||
);
|
||||
expect(mockCoreEvents.drainFeedbackBacklog).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('unsubscribes from UserFeedback on unmount', () => {
|
||||
const { unmount } = render(
|
||||
<AppContainer
|
||||
config={mockConfig}
|
||||
settings={mockSettings}
|
||||
version="1.0.0"
|
||||
initializationResult={mockInitResult}
|
||||
/>,
|
||||
);
|
||||
|
||||
unmount();
|
||||
|
||||
expect(mockCoreEvents.off).toHaveBeenCalledWith(
|
||||
CoreEvent.UserFeedback,
|
||||
expect.any(Function),
|
||||
);
|
||||
});
|
||||
|
||||
it('adds history item when UserFeedback event is received', () => {
|
||||
render(
|
||||
<AppContainer
|
||||
config={mockConfig}
|
||||
settings={mockSettings}
|
||||
version="1.0.0"
|
||||
initializationResult={mockInitResult}
|
||||
/>,
|
||||
);
|
||||
|
||||
// Get the registered handler
|
||||
const handler = mockCoreEvents.on.mock.calls.find(
|
||||
(call: unknown[]) => call[0] === CoreEvent.UserFeedback,
|
||||
)?.[1];
|
||||
expect(handler).toBeDefined();
|
||||
|
||||
// Simulate an event
|
||||
const payload: UserFeedbackPayload = {
|
||||
severity: 'error',
|
||||
message: 'Test error message',
|
||||
};
|
||||
handler(payload);
|
||||
|
||||
expect(mockedUseHistory().addItem).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
type: 'error',
|
||||
text: 'Test error message',
|
||||
}),
|
||||
expect.any(Number),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
type IdeInfo,
|
||||
type IdeContext,
|
||||
type UserTierId,
|
||||
type UserFeedbackPayload,
|
||||
DEFAULT_GEMINI_FLASH_MODEL,
|
||||
IdeClient,
|
||||
ideContextStore,
|
||||
@@ -44,6 +45,8 @@ import {
|
||||
recordExitFail,
|
||||
ShellExecutionService,
|
||||
debugLogger,
|
||||
coreEvents,
|
||||
CoreEvent,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { validateAuthMethod } from '../config/auth.js';
|
||||
import { loadHierarchicalGeminiMemory } from '../config/config.js';
|
||||
@@ -1066,6 +1069,53 @@ Logging in with Google... Please restart Gemini CLI to continue.
|
||||
stdout,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleUserFeedback = (payload: UserFeedbackPayload) => {
|
||||
let type: MessageType;
|
||||
switch (payload.severity) {
|
||||
case 'error':
|
||||
type = MessageType.ERROR;
|
||||
break;
|
||||
case 'warning':
|
||||
type = MessageType.WARNING;
|
||||
break;
|
||||
case 'info':
|
||||
type = MessageType.INFO;
|
||||
break;
|
||||
default:
|
||||
throw new Error(
|
||||
`Unexpected severity for user feedback: ${payload.severity}`,
|
||||
);
|
||||
}
|
||||
|
||||
historyManager.addItem(
|
||||
{
|
||||
type,
|
||||
text: payload.message,
|
||||
},
|
||||
Date.now(),
|
||||
);
|
||||
|
||||
// If there is an attached error object, log it to the debug drawer.
|
||||
if (payload.error) {
|
||||
debugLogger.warn(
|
||||
`[Feedback Details for "${payload.message}"]`,
|
||||
payload.error,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
coreEvents.on(CoreEvent.UserFeedback, handleUserFeedback);
|
||||
|
||||
// Flush any messages that happened during startup before this component
|
||||
// mounted.
|
||||
coreEvents.drainFeedbackBacklog();
|
||||
|
||||
return () => {
|
||||
coreEvents.off(CoreEvent.UserFeedback, handleUserFeedback);
|
||||
};
|
||||
}, [historyManager]);
|
||||
|
||||
const filteredConsoleMessages = useMemo(() => {
|
||||
if (config.getDebugMode()) {
|
||||
return consoleMessages;
|
||||
|
||||
Reference in New Issue
Block a user