Compare commits

...

1 Commits

Author SHA1 Message Date
gemini-cli-robot 65893ee03f docs(changelog): update for v0.42.0-preview.1 2026-05-05 22:51:06 +00:00
54 changed files with 215 additions and 292 deletions
+7 -3
View File
@@ -1,6 +1,6 @@
# Preview release: v0.41.0-preview.0
# Preview release: v0.42.0-preview.1
Released: April 28, 2026
Released: May 05, 2026
Our preview release includes the latest, new, and experimental features. This
release may not be as stable as our [latest weekly release](latest.md).
@@ -28,6 +28,10 @@ npm install -g @google/gemini-cli@preview
## What's Changed
- fix(patch): cherry-pick 3627f47 to release/v0.42.0-preview.0-pr-26542 to patch
version v0.42.0-preview.0 and create version 0.42.0-preview.1 by
@gemini-cli-robot in
[#26544](https://github.com/google-gemini/gemini-cli/pull/26544)
- chore(release): bump version to 0.41.0-nightly.20260423.gaa05b4583 by
@gemini-cli-robot in
[#25847](https://github.com/google-gemini/gemini-cli/pull/25847)
@@ -118,4 +122,4 @@ npm install -g @google/gemini-cli@preview
[#26078](https://github.com/google-gemini/gemini-cli/pull/26078)
**Full Changelog**:
https://github.com/google-gemini/gemini-cli/compare/v0.40.0-preview.5...v0.41.0-preview.0
https://github.com/google-gemini/gemini-cli/compare/v0.40.0-preview.5...v0.42.0-preview.1
@@ -56,6 +56,7 @@ creating a "discovery file."
}
}
```
- `port` (number, required): The port of the MCP server.
- `workspacePath` (string, required): A list of all open workspace root paths,
delimited by the OS-specific path separator (`:` for Linux/macOS, `;` for
+4 -6
View File
@@ -329,9 +329,8 @@ async function expectSeedSessionEligible(
fixture: Fixture,
sessionId: string,
): Promise<void> {
const { buildSessionIndex } = await import(
'../packages/core/src/services/memoryService.js'
);
const { buildSessionIndex } =
await import('../packages/core/src/services/memoryService.js');
const { newSessionIds } = await buildSessionIndex(
path.join(fixture.projectTempDir, 'chats'),
{ runs: [] },
@@ -386,9 +385,8 @@ describe('Auto Memory inbox routing', () => {
autoMemoryEval(
'every memory patch lands in .inbox/<kind>/ for review and active files stay untouched',
async () => {
const { startMemoryService } = await import(
'../packages/core/src/services/memoryService.js'
);
const { startMemoryService } =
await import('../packages/core/src/services/memoryService.js');
const fixture = await createFixture();
evalState.sessionFilePath = await seedSession(
fixture,
+2 -3
View File
@@ -141,9 +141,8 @@ async function run() {
// --- Heavy Child Process ---
// Now we can safely import everything.
const { main } = await import('./src/gemini.js');
const { FatalError, writeToStderr } = await import(
'@google/gemini-cli-core'
);
const { FatalError, writeToStderr } =
await import('@google/gemini-cli-core');
const { runExitCleanup } = await import('./src/utils/cleanup.js');
main().catch(async (error: unknown) => {
+2 -3
View File
@@ -566,9 +566,8 @@ describe('Session', () => {
});
it('should send sessionUpdate when approval mode changes', async () => {
const { coreEvents, CoreEvent, ApprovalMode } = await import(
'@google/gemini-cli-core'
);
const { coreEvents, CoreEvent, ApprovalMode } =
await import('@google/gemini-cli-core');
coreEvents.emit(CoreEvent.ApprovalModeChanged, {
sessionId: 'session-1',
@@ -20,9 +20,8 @@ import { ExtensionManager } from '../../config/extension-manager.js';
import { loadSettings, type LoadedSettings } from '../../config/settings.js';
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
const { mockCoreDebugLogger } = await import(
'../../test-utils/mockDebugLogger.js'
);
const { mockCoreDebugLogger } =
await import('../../test-utils/mockDebugLogger.js');
const actual =
await importOriginal<typeof import('@google/gemini-cli-core')>();
const mocked = mockCoreDebugLogger(actual, { stripAnsi: true });
@@ -11,9 +11,8 @@ import { ExtensionManager } from '../../config/extension-manager.js';
import { loadSettings, type LoadedSettings } from '../../config/settings.js';
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
const { mockCoreDebugLogger } = await import(
'../../test-utils/mockDebugLogger.js'
);
const { mockCoreDebugLogger } =
await import('../../test-utils/mockDebugLogger.js');
const actual =
await importOriginal<typeof import('@google/gemini-cli-core')>();
const mocked = mockCoreDebugLogger(actual, { stripAnsi: false });
+2 -3
View File
@@ -16,9 +16,8 @@ import { getLogFilePath } from './constants.js';
import { logsCommand, readLastLines } from './logs.js';
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
const { mockCoreDebugLogger } = await import(
'../../test-utils/mockDebugLogger.js'
);
const { mockCoreDebugLogger } =
await import('../../test-utils/mockDebugLogger.js');
return mockCoreDebugLogger(
await importOriginal<typeof import('@google/gemini-cli-core')>(),
{
+2 -3
View File
@@ -16,9 +16,8 @@ const mockReadServerProcessInfo = vi.hoisted(() => vi.fn());
const mockResolveGemmaConfig = vi.hoisted(() => vi.fn());
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
const { mockCoreDebugLogger } = await import(
'../../test-utils/mockDebugLogger.js'
);
const { mockCoreDebugLogger } =
await import('../../test-utils/mockDebugLogger.js');
return mockCoreDebugLogger(
await importOriginal<typeof import('@google/gemini-cli-core')>(),
{
@@ -14,9 +14,8 @@ import {
} from '../../config/settings.js';
const { emitConsoleLog, debugLogger } = await vi.hoisted(async () => {
const { createMockDebugLogger } = await import(
'../../test-utils/mockDebugLogger.js'
);
const { createMockDebugLogger } =
await import('../../test-utils/mockDebugLogger.js');
return createMockDebugLogger({ stripAnsi: true });
});
@@ -13,9 +13,8 @@ import {
} from '../../config/settings.js';
const { emitConsoleLog, debugLogger } = await vi.hoisted(async () => {
const { createMockDebugLogger } = await import(
'../../test-utils/mockDebugLogger.js'
);
const { createMockDebugLogger } =
await import('../../test-utils/mockDebugLogger.js');
return createMockDebugLogger({ stripAnsi: true });
});
@@ -20,9 +20,8 @@ vi.mock('../../config/extensions/consent.js', () => ({
}));
const { debugLogger, emitConsoleLog } = await vi.hoisted(async () => {
const { createMockDebugLogger } = await import(
'../../test-utils/mockDebugLogger.js'
);
const { createMockDebugLogger } =
await import('../../test-utils/mockDebugLogger.js');
return createMockDebugLogger({ stripAnsi: true });
});
@@ -16,9 +16,8 @@ vi.mock('../../utils/skillUtils.js', () => ({
}));
const { debugLogger } = await vi.hoisted(async () => {
const { createMockDebugLogger } = await import(
'../../test-utils/mockDebugLogger.js'
);
const { createMockDebugLogger } =
await import('../../test-utils/mockDebugLogger.js');
return createMockDebugLogger({ stripAnsi: false });
});
@@ -20,9 +20,8 @@ import { loadCliConfig } from '../../config/config.js';
import chalk from 'chalk';
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
const { mockCoreDebugLogger } = await import(
'../../test-utils/mockDebugLogger.js'
);
const { mockCoreDebugLogger } =
await import('../../test-utils/mockDebugLogger.js');
return mockCoreDebugLogger(
await importOriginal<typeof import('@google/gemini-cli-core')>(),
{
@@ -13,9 +13,8 @@ vi.mock('../../utils/skillUtils.js', () => ({
}));
const { debugLogger, emitConsoleLog } = await vi.hoisted(async () => {
const { createMockDebugLogger } = await import(
'../../test-utils/mockDebugLogger.js'
);
const { createMockDebugLogger } =
await import('../../test-utils/mockDebugLogger.js');
return createMockDebugLogger({ stripAnsi: true });
});
@@ -300,9 +300,8 @@ System using model: \${MODEL_NAME}
});
expect(extension.skills![0].body).toContain('Value is: first');
const { updateSetting, ExtensionSettingScope } = await import(
'./extensions/extensionSettings.js'
);
const { updateSetting, ExtensionSettingScope } =
await import('./extensions/extensionSettings.js');
const extensionConfig =
await extensionManager.loadExtensionConfig(extensionPath);
+2 -2
View File
@@ -334,8 +334,8 @@ Would you like to attempt to install via "git clone" instead?`,
const previousSkills = previous?.skills ?? [];
const isMigrating = Boolean(
previous &&
previous.installMetadata &&
previous.installMetadata.source !== installMetadata.source,
previous.installMetadata &&
previous.installMetadata.source !== installMetadata.source,
);
await maybeRequestConsentOrFail(
+2 -3
View File
@@ -938,9 +938,8 @@ describe('gemini.tsx main function kitty protocol', () => {
});
it.skip('should log error when cleanupExpiredSessions fails', async () => {
const { cleanupExpiredSessions } = await import(
'./utils/sessionCleanup.js'
);
const { cleanupExpiredSessions } =
await import('./utils/sessionCleanup.js');
vi.mocked(cleanupExpiredSessions).mockRejectedValue(
new Error('Cleanup failed'),
);
+2 -3
View File
@@ -552,9 +552,8 @@ export async function main() {
adminControlsListner.setConfig(config);
if (config.isInteractive() && settings.merged.general.devtools) {
const { setupInitialActivityLogger } = await import(
'./utils/devtoolsService.js'
);
const { setupInitialActivityLogger } =
await import('./utils/devtoolsService.js');
setupInitialActivityLogger(config);
}
+8 -12
View File
@@ -201,9 +201,8 @@ describe('gemini.tsx main function cleanup', () => {
});
it.skip('should log error when cleanupExpiredSessions fails', async () => {
const { loadCliConfig, parseArguments } = await import(
'./config/config.js'
);
const { loadCliConfig, parseArguments } =
await import('./config/config.js');
const { loadSettings } = await import('./config/settings.js');
cleanupMockState.shouldThrow = true;
cleanupMockState.called = false;
@@ -272,9 +271,8 @@ describe('gemini.tsx main function cleanup', () => {
});
it('should register SessionEnd hook exactly once in non-interactive mode', async () => {
const { loadCliConfig, parseArguments } = await import(
'./config/config.js'
);
const { loadCliConfig, parseArguments } =
await import('./config/config.js');
const { registerCleanup } = await import('./utils/cleanup.js');
const mockHookSystem = {
@@ -310,9 +308,8 @@ describe('gemini.tsx main function cleanup', () => {
it('should not register ConsolePatcher cleanup in ACP mode', async () => {
const { registerCleanup } = await import('./utils/cleanup.js');
const { ConsolePatcher } = await import('./ui/utils/ConsolePatcher.js');
const { loadCliConfig, parseArguments } = await import(
'./config/config.js'
);
const { loadCliConfig, parseArguments } =
await import('./config/config.js');
const { loadSettings } = await import('./config/settings.js');
vi.mocked(parseArguments).mockResolvedValue({
@@ -364,9 +361,8 @@ describe('gemini.tsx main function cleanup', () => {
it('should register ConsolePatcher cleanup in non-ACP mode', async () => {
const { registerCleanup } = await import('./utils/cleanup.js');
const { ConsolePatcher } = await import('./ui/utils/ConsolePatcher.js');
const { loadCliConfig, parseArguments } = await import(
'./config/config.js'
);
const { loadCliConfig, parseArguments } =
await import('./config/config.js');
const { loadSettings } = await import('./config/settings.js');
vi.mocked(parseArguments).mockResolvedValue({
+10 -15
View File
@@ -215,9 +215,8 @@ describe('runNonInteractive', () => {
computeMergedSettings: vi.fn(),
} as unknown as LoadedSettings;
const { handleAtCommand } = await import(
'./ui/hooks/atCommandProcessor.js'
);
const { handleAtCommand } =
await import('./ui/hooks/atCommandProcessor.js');
vi.mocked(handleAtCommand).mockImplementation(async ({ query }) => ({
processedQuery: [{ text: query }],
}));
@@ -636,9 +635,8 @@ describe('runNonInteractive', () => {
it('should preprocess @include commands before sending to the model', async () => {
// 1. Mock the imported atCommandProcessor
const { handleAtCommand } = await import(
'./ui/hooks/atCommandProcessor.js'
);
const { handleAtCommand } =
await import('./ui/hooks/atCommandProcessor.js');
const mockHandleAtCommand = vi.mocked(handleAtCommand);
// 2. Define the raw input and the expected processed output
@@ -991,9 +989,8 @@ describe('runNonInteractive', () => {
});
it('should handle slash commands', async () => {
const nonInteractiveCliCommands = await import(
'./nonInteractiveCliCommands.js'
);
const nonInteractiveCliCommands =
await import('./nonInteractiveCliCommands.js');
const handleSlashCommandSpy = vi.spyOn(
nonInteractiveCliCommands,
'handleSlashCommand',
@@ -1271,13 +1268,11 @@ describe('runNonInteractive', () => {
it('should instantiate CommandService with correct loaders for slash commands', async () => {
// This test indirectly checks that handleSlashCommand is using the right loaders.
const { FileCommandLoader } = await import(
'./services/FileCommandLoader.js'
);
const { FileCommandLoader } =
await import('./services/FileCommandLoader.js');
const { McpPromptLoader } = await import('./services/McpPromptLoader.js');
const { BuiltinCommandLoader } = await import(
'./services/BuiltinCommandLoader.js'
);
const { BuiltinCommandLoader } =
await import('./services/BuiltinCommandLoader.js');
mockGetCommands.mockReturnValue([]); // No commands found, so it will fall through
const events: ServerGeminiStreamEvent[] = [
{ type: GeminiEventType.Content, value: 'Acknowledged' },
+2 -3
View File
@@ -84,9 +84,8 @@ export async function runNonInteractive(
});
if (process.env['GEMINI_CLI_ACTIVITY_LOG_TARGET']) {
const { setupInitialActivityLogger } = await import(
'./utils/devtoolsService.js'
);
const { setupInitialActivityLogger } =
await import('./utils/devtoolsService.js');
setupInitialActivityLogger(config);
}
@@ -221,9 +221,8 @@ describe('runNonInteractive', () => {
computeMergedSettings: vi.fn(),
} as unknown as LoadedSettings;
const { handleAtCommand } = await import(
'./ui/hooks/atCommandProcessor.js'
);
const { handleAtCommand } =
await import('./ui/hooks/atCommandProcessor.js');
vi.mocked(handleAtCommand).mockImplementation(async ({ query }) => ({
processedQuery: [{ text: query }],
}));
@@ -690,9 +689,8 @@ describe('runNonInteractive', () => {
it('should preprocess @include commands before sending to the model', async () => {
// 1. Mock the imported atCommandProcessor
const { handleAtCommand } = await import(
'./ui/hooks/atCommandProcessor.js'
);
const { handleAtCommand } =
await import('./ui/hooks/atCommandProcessor.js');
const mockHandleAtCommand = vi.mocked(handleAtCommand);
// 2. Define the raw input and the expected processed output
@@ -1118,9 +1116,8 @@ describe('runNonInteractive', () => {
});
it('should handle slash commands', async () => {
const nonInteractiveCliCommands = await import(
'./nonInteractiveCliCommands.js'
);
const nonInteractiveCliCommands =
await import('./nonInteractiveCliCommands.js');
const handleSlashCommandSpy = vi.spyOn(
nonInteractiveCliCommands,
'handleSlashCommand',
@@ -1440,13 +1437,11 @@ describe('runNonInteractive', () => {
it('should instantiate CommandService with correct loaders for slash commands', async () => {
// This test indirectly checks that handleSlashCommand is using the right loaders.
const { FileCommandLoader } = await import(
'./services/FileCommandLoader.js'
);
const { FileCommandLoader } =
await import('./services/FileCommandLoader.js');
const { McpPromptLoader } = await import('./services/McpPromptLoader.js');
const { BuiltinCommandLoader } = await import(
'./services/BuiltinCommandLoader.js'
);
const { BuiltinCommandLoader } =
await import('./services/BuiltinCommandLoader.js');
mockGetCommands.mockReturnValue([]); // No commands found, so it will fall through
const events: ServerGeminiStreamEvent[] = [
{ type: GeminiEventType.Content, value: 'Acknowledged' },
@@ -84,9 +84,8 @@ export async function runNonInteractive({
});
if (process.env['GEMINI_CLI_ACTIVITY_LOG_TARGET']) {
const { setupInitialActivityLogger } = await import(
'./utils/devtoolsService.js'
);
const { setupInitialActivityLogger } =
await import('./utils/devtoolsService.js');
setupInitialActivityLogger(config);
}
+2 -3
View File
@@ -91,9 +91,8 @@ vi.mock('../ui/contexts/StreamingContext.js', async (importOriginal) => {
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
const original =
await importOriginal<typeof import('@google/gemini-cli-core')>();
const { MockShellExecutionService: MockService } = await import(
'./MockShellExecutionService.js'
);
const { MockShellExecutionService: MockService } =
await import('./MockShellExecutionService.js');
// Register the real execution logic so MockShellExecutionService can fall back to it
MockService.setOriginalImplementation(original.ShellExecutionService.execute);
+10 -15
View File
@@ -3134,9 +3134,8 @@ describe('AppContainer State Management', () => {
describe('Submission Handling', () => {
it('resets expansion state on submission when not in alternate buffer', async () => {
const { checkPermissions } = await import(
'./hooks/atCommandProcessor.js'
);
const { checkPermissions } =
await import('./hooks/atCommandProcessor.js');
vi.mocked(checkPermissions).mockResolvedValue([]);
const { unmount } = await act(async () =>
@@ -3164,9 +3163,8 @@ describe('AppContainer State Management', () => {
});
it('resets expansion state on submission when in alternate buffer without clearing terminal', async () => {
const { checkPermissions } = await import(
'./hooks/atCommandProcessor.js'
);
const { checkPermissions } =
await import('./hooks/atCommandProcessor.js');
vi.mocked(checkPermissions).mockResolvedValue([]);
vi.spyOn(mockConfig, 'getUseTerminalBuffer').mockReturnValue(false);
@@ -3448,9 +3446,8 @@ describe('AppContainer State Management', () => {
describe('Permission Handling', () => {
it('shows permission dialog when checkPermissions returns paths', async () => {
const { checkPermissions } = await import(
'./hooks/atCommandProcessor.js'
);
const { checkPermissions } =
await import('./hooks/atCommandProcessor.js');
vi.mocked(checkPermissions).mockResolvedValue(['/test/file.txt']);
const { unmount } = await act(async () => renderAppContainer());
@@ -3471,9 +3468,8 @@ describe('AppContainer State Management', () => {
it.each([true, false])(
'handles permissions when allowed is %s',
async (allowed) => {
const { checkPermissions } = await import(
'./hooks/atCommandProcessor.js'
);
const { checkPermissions } =
await import('./hooks/atCommandProcessor.js');
vi.mocked(checkPermissions).mockResolvedValue(['/test/file.txt']);
const addReadOnlyPathSpy = vi.spyOn(
mockConfig.getWorkspaceContext(),
@@ -3579,9 +3575,8 @@ describe('AppContainer State Management', () => {
describe('Compression Queuing', () => {
beforeEach(async () => {
const { checkPermissions } = await import(
'./hooks/atCommandProcessor.js'
);
const { checkPermissions } =
await import('./hooks/atCommandProcessor.js');
vi.mocked(checkPermissions).mockResolvedValue([]);
vi.spyOn(mockConfig, 'isModelSteeringEnabled').mockReturnValue(true);
+5 -6
View File
@@ -1644,9 +1644,9 @@ Logging in with Google... Restarting Gemini CLI to continue.
}, []);
const shouldShowIdePrompt = Boolean(
currentIDE &&
!config.getIdeMode() &&
!settings.merged.ide.hasSeenNudge &&
!idePromptAnswered,
!config.getIdeMode() &&
!settings.merged.ide.hasSeenNudge &&
!idePromptAnswered,
);
const [showErrorDetails, setShowErrorDetails] = useState<boolean>(false);
@@ -1927,9 +1927,8 @@ Logging in with Google... Restarting Gemini CLI to continue.
if (keyMatchers[Command.SHOW_ERROR_DETAILS](key)) {
if (settings.merged.general.devtools) {
void (async () => {
const { toggleDevToolsPanel } = await import(
'../utils/devtoolsService.js'
);
const { toggleDevToolsPanel } =
await import('../utils/devtoolsService.js');
await toggleDevToolsPanel(
config,
showErrorDetails,
@@ -78,9 +78,8 @@ describe('authCommand', () => {
const logoutCommand = authCommand.subCommands?.[1];
expect(logoutCommand?.name).toBe('signout');
const { clearCachedCredentialFile } = await import(
'@google/gemini-cli-core'
);
const { clearCachedCredentialFile } =
await import('@google/gemini-cli-core');
await logoutCommand!.action!(mockContext, '');
@@ -1139,9 +1139,8 @@ describe('extensionsCommand', () => {
const prompts = (await import('prompts')).default;
vi.mocked(prompts).mockResolvedValue({ overwrite: true });
const { getScopedEnvContents } = await import(
'../../config/extensions/extensionSettings.js'
);
const { getScopedEnvContents } =
await import('../../config/extensions/extensionSettings.js');
vi.mocked(getScopedEnvContents).mockResolvedValue({});
});
@@ -635,9 +635,8 @@ describe('MainContent', () => {
});
it('renders a ToolConfirmationQueue without an extra line when preceded by hidden tools', async () => {
const { ApprovalMode, WRITE_FILE_DISPLAY_NAME } = await import(
'@google/gemini-cli-core'
);
const { ApprovalMode, WRITE_FILE_DISPLAY_NAME } =
await import('@google/gemini-cli-core');
const hiddenToolCalls = [
{
callId: 'tool-hidden',
@@ -713,9 +712,8 @@ describe('MainContent', () => {
});
it('renders a spurious line when a tool group has only hidden tools and borderBottom true', async () => {
const { ApprovalMode, WRITE_FILE_DISPLAY_NAME } = await import(
'@google/gemini-cli-core'
);
const { ApprovalMode, WRITE_FILE_DISPLAY_NAME } =
await import('@google/gemini-cli-core');
const uiState = {
...defaultMockUiState,
history: [{ id: 1, type: 'user', text: 'Apply plan' }],
@@ -11,8 +11,10 @@ import { MaxSizedBox, type MaxSizedBoxProps } from './MaxSizedBox.js';
// outputs that will get truncated further MaxSizedBox anyway.
const MAXIMUM_RESULT_DISPLAY_CHARACTERS = 20000;
export interface SlicingMaxSizedBoxProps<T>
extends Omit<MaxSizedBoxProps, 'children'> {
export interface SlicingMaxSizedBoxProps<T> extends Omit<
MaxSizedBoxProps,
'children'
> {
data: T;
maxLines?: number;
isAlternateBuffer?: boolean;
+4 -4
View File
@@ -153,15 +153,15 @@ const inScreen = (): boolean =>
const isSSH = (): boolean =>
Boolean(
process.env['SSH_TTY'] ||
process.env['SSH_CONNECTION'] ||
process.env['SSH_CLIENT'],
process.env['SSH_CONNECTION'] ||
process.env['SSH_CLIENT'],
);
const isWSL = (): boolean =>
Boolean(
process.env['WSL_DISTRO_NAME'] ||
process.env['WSLENV'] ||
process.env['WSL_INTEROP'],
process.env['WSLENV'] ||
process.env['WSL_INTEROP'],
);
const isWindowsTerminal = (): boolean =>
+10 -15
View File
@@ -220,9 +220,8 @@ describe('rewindFileOps', () => {
});
it('reverts exact match', async () => {
const { getFileDiffFromResultDisplay } = await import(
'@google/gemini-cli-core'
);
const { getFileDiffFromResultDisplay } =
await import('@google/gemini-cli-core');
vi.mocked(getFileDiffFromResultDisplay).mockReturnValue({
filePath: '/abs/path/test.ts',
fileName: 'test.ts',
@@ -270,9 +269,8 @@ describe('rewindFileOps', () => {
});
it('deletes new file on revert', async () => {
const { getFileDiffFromResultDisplay } = await import(
'@google/gemini-cli-core'
);
const { getFileDiffFromResultDisplay } =
await import('@google/gemini-cli-core');
vi.mocked(getFileDiffFromResultDisplay).mockReturnValue({
filePath: '/abs/path/new.ts',
fileName: 'new.ts',
@@ -317,9 +315,8 @@ describe('rewindFileOps', () => {
});
it('handles smart revert (patching) successfully', async () => {
const { getFileDiffFromResultDisplay } = await import(
'@google/gemini-cli-core'
);
const { getFileDiffFromResultDisplay } =
await import('@google/gemini-cli-core');
vi.mocked(getFileDiffFromResultDisplay).mockReturnValue({
filePath: '/abs/path/test.ts',
fileName: 'test.ts',
@@ -369,9 +366,8 @@ describe('rewindFileOps', () => {
});
it('emits warning on smart revert failure', async () => {
const { getFileDiffFromResultDisplay } = await import(
'@google/gemini-cli-core'
);
const { getFileDiffFromResultDisplay } =
await import('@google/gemini-cli-core');
vi.mocked(getFileDiffFromResultDisplay).mockReturnValue({
filePath: '/abs/path/test.ts',
fileName: 'test.ts',
@@ -421,9 +417,8 @@ describe('rewindFileOps', () => {
});
it('emits error if fs.readFile fails with a generic error', async () => {
const { getFileDiffFromResultDisplay } = await import(
'@google/gemini-cli-core'
);
const { getFileDiffFromResultDisplay } =
await import('@google/gemini-cli-core');
vi.mocked(getFileDiffFromResultDisplay).mockReturnValue({
filePath: '/abs/path/test.ts',
fileName: 'test.ts',
+4 -2
View File
@@ -17,8 +17,10 @@ export type AgentActionStatus = 'success' | 'no-op' | 'error';
/**
* Metadata representing the result of an agent settings operation.
*/
export interface AgentActionResult
extends Omit<FeatureActionResult, 'featureName'> {
export interface AgentActionResult extends Omit<
FeatureActionResult,
'featureName'
> {
agentName: string;
}
+2 -3
View File
@@ -207,9 +207,8 @@ export async function toggleDevToolsPanel(
}
try {
const { openBrowserSecurely, shouldLaunchBrowser } = await import(
'@google/gemini-cli-core'
);
const { openBrowserSecurely, shouldLaunchBrowser } =
await import('@google/gemini-cli-core');
const url = await startDevToolsServer(config);
if (shouldLaunchBrowser()) {
try {
+4 -2
View File
@@ -20,8 +20,10 @@ export type SkillActionStatus = 'success' | 'no-op' | 'error';
/**
* Metadata representing the result of a skill settings operation.
*/
export interface SkillActionResult
extends Omit<FeatureActionResult, 'featureName'> {
export interface SkillActionResult extends Omit<
FeatureActionResult,
'featureName'
> {
skillName: string;
}
@@ -194,9 +194,8 @@ describe('getUserStartupWarnings', () => {
describe('folder trust check', () => {
it('should throw FatalUntrustedWorkspaceError when untrusted in headless mode', async () => {
const { isHeadlessMode, FatalUntrustedWorkspaceError } = await import(
'@google/gemini-cli-core'
);
const { isHeadlessMode, FatalUntrustedWorkspaceError } =
await import('@google/gemini-cli-core');
vi.mocked(isFolderTrustEnabled).mockReturnValue(true);
vi.mocked(isWorkspaceTrusted).mockImplementation(() => {
throw new FatalUntrustedWorkspaceError(
+1 -1
View File
@@ -281,7 +281,7 @@ export type ElicitationResponse = {
export interface ErrorData {
// One of https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto
status: // 400
| 'INVALID_ARGUMENT'
| 'INVALID_ARGUMENT'
| 'FAILED_PRECONDITION'
| 'OUT_OF_RANGE'
// 401
@@ -84,9 +84,8 @@ vi.mock('../../utils/debugLogger.js', () => ({
}));
// Re-import mocked modules for assertions.
const { MCPOAuthTokenStorage } = await import(
'../../mcp/oauth-token-storage.js'
);
const { MCPOAuthTokenStorage } =
await import('../../mcp/oauth-token-storage.js');
const {
refreshAccessToken,
exchangeCodeForToken,
@@ -379,9 +379,8 @@ describe('browserAgentFactory', () => {
describe('resetBrowserSession', () => {
it('should delegate to BrowserManager.resetAll', async () => {
const { BrowserManager: MockBrowserManager } = await import(
'./browserManager.js'
);
const { BrowserManager: MockBrowserManager } =
await import('./browserManager.js');
await resetBrowserSession();
expect(
(
+6 -9
View File
@@ -1627,9 +1627,8 @@ describe('oauth2', () => {
});
it('should save credentials using OAuthCredentialStorage during web login', async () => {
const { OAuthCredentialStorage } = await import(
'./oauth-credential-storage.js'
);
const { OAuthCredentialStorage } =
await import('./oauth-credential-storage.js');
const mockAuthUrl = 'https://example.com/auth';
const mockCode = 'test-code';
const mockState = 'test-state';
@@ -1729,9 +1728,8 @@ describe('oauth2', () => {
});
it('should load credentials using OAuthCredentialStorage and not from file', async () => {
const { OAuthCredentialStorage } = await import(
'./oauth-credential-storage.js'
);
const { OAuthCredentialStorage } =
await import('./oauth-credential-storage.js');
const cachedCreds = { refresh_token: 'cached-encrypted-token' };
vi.mocked(OAuthCredentialStorage.loadCredentials).mockResolvedValue(
cachedCreds,
@@ -1767,9 +1765,8 @@ describe('oauth2', () => {
});
it('should clear credentials using OAuthCredentialStorage', async () => {
const { OAuthCredentialStorage } = await import(
'./oauth-credential-storage.js'
);
const { OAuthCredentialStorage } =
await import('./oauth-credential-storage.js');
// Create a dummy unencrypted credential file. It should not be deleted.
const credsPath = path.join(tempHomeDir, GEMINI_DIR, 'oauth_creds.json');
+4 -6
View File
@@ -436,9 +436,8 @@ describe('Server Config (config.ts)', () => {
// interactive defaults to false
});
const { McpClientManager } = await import(
'../tools/mcp-client-manager.js'
);
const { McpClientManager } =
await import('../tools/mcp-client-manager.js');
let mcpStarted = false;
vi.mocked(McpClientManager).mockImplementation(
@@ -466,9 +465,8 @@ describe('Server Config (config.ts)', () => {
interactive: true,
});
const { McpClientManager } = await import(
'../tools/mcp-client-manager.js'
);
const { McpClientManager } =
await import('../tools/mcp-client-manager.js');
let mcpStarted = false;
let resolveMcp: (value: unknown) => void;
const mcpPromise = new Promise((resolve) => {
+2 -3
View File
@@ -2453,9 +2453,8 @@ export class Config implements McpContext, AgentLoopContext {
if (this.experimentalJitContext && this.memoryContextManager) {
await this.memoryContextManager.refresh();
} else {
const { refreshServerHierarchicalMemory } = await import(
'../utils/memoryDiscovery.js'
);
const { refreshServerHierarchicalMemory } =
await import('../utils/memoryDiscovery.js');
await refreshServerHierarchicalMemory(this);
}
if (this._geminiClient?.isInitialized()) {
+12 -18
View File
@@ -1305,9 +1305,8 @@ ${JSON.stringify(
it('should stop infinite loop after MAX_TURNS when nextSpeaker always returns model', async () => {
// Get the mocked checkNextSpeaker function and configure it to trigger infinite loop
const { checkNextSpeaker } = await import(
'../utils/nextSpeakerChecker.js'
);
const { checkNextSpeaker } =
await import('../utils/nextSpeakerChecker.js');
const mockCheckNextSpeaker = vi.mocked(checkNextSpeaker);
mockCheckNextSpeaker.mockResolvedValue({
next_speaker: 'model',
@@ -1429,9 +1428,8 @@ ${JSON.stringify(
// someone tries to bypass it by calling with a very large turns value
// Get the mocked checkNextSpeaker function and configure it to trigger infinite loop
const { checkNextSpeaker } = await import(
'../utils/nextSpeakerChecker.js'
);
const { checkNextSpeaker } =
await import('../utils/nextSpeakerChecker.js');
const mockCheckNextSpeaker = vi.mocked(checkNextSpeaker);
mockCheckNextSpeaker.mockResolvedValue({
next_speaker: 'model',
@@ -2837,9 +2835,8 @@ ${JSON.stringify(
it('should not call checkNextSpeaker when turn.run() yields an error', async () => {
// Arrange
const { checkNextSpeaker } = await import(
'../utils/nextSpeakerChecker.js'
);
const { checkNextSpeaker } =
await import('../utils/nextSpeakerChecker.js');
const mockCheckNextSpeaker = vi.mocked(checkNextSpeaker);
const mockStream = (async function* () {
@@ -2874,9 +2871,8 @@ ${JSON.stringify(
it('should not call checkNextSpeaker when turn.run() yields a value then an error', async () => {
// Arrange
const { checkNextSpeaker } = await import(
'../utils/nextSpeakerChecker.js'
);
const { checkNextSpeaker } =
await import('../utils/nextSpeakerChecker.js');
const mockCheckNextSpeaker = vi.mocked(checkNextSpeaker);
const mockStream = (async function* () {
@@ -3254,9 +3250,8 @@ ${JSON.stringify(
});
it('should fire BeforeAgent once and AfterAgent once even with recursion', async () => {
const { checkNextSpeaker } = await import(
'../utils/nextSpeakerChecker.js'
);
const { checkNextSpeaker } =
await import('../utils/nextSpeakerChecker.js');
vi.mocked(checkNextSpeaker)
.mockResolvedValueOnce({ next_speaker: 'model', reasoning: 'more' })
.mockResolvedValueOnce(null);
@@ -3295,9 +3290,8 @@ ${JSON.stringify(
});
it('should use original request in AfterAgent hook even when continuation happened', async () => {
const { checkNextSpeaker } = await import(
'../utils/nextSpeakerChecker.js'
);
const { checkNextSpeaker } =
await import('../utils/nextSpeakerChecker.js');
vi.mocked(checkNextSpeaker)
.mockResolvedValueOnce({ next_speaker: 'model', reasoning: 'more' })
.mockResolvedValueOnce(null);
@@ -699,9 +699,8 @@ describe('ide-connection-utils', () => {
describe('createProxyAwareFetch', () => {
it('should return a proxy-aware fetcher function', async () => {
const { createProxyAwareFetch } = await import(
'./ide-connection-utils.js'
);
const { createProxyAwareFetch } =
await import('./ide-connection-utils.js');
const fetcher = await createProxyAwareFetch('127.0.0.1');
expect(typeof fetcher).toBe('function');
});
@@ -334,9 +334,8 @@ describe('memoryService', () => {
});
it('writes state atomically via temp file + rename', async () => {
const { writeExtractionState, readExtractionState } = await import(
'./memoryService.js'
);
const { writeExtractionState, readExtractionState } =
await import('./memoryService.js');
const statePath = path.join(tmpDir, '.extraction-state.json');
const state: ExtractionState = {
@@ -364,9 +363,8 @@ describe('memoryService', () => {
describe('startMemoryService', () => {
it('skips when lock is held by another instance', async () => {
const { startMemoryService } = await import('./memoryService.js');
const { LocalAgentExecutor } = await import(
'../agents/local-executor.js'
);
const { LocalAgentExecutor } =
await import('../agents/local-executor.js');
const memoryDir = path.join(tmpDir, 'memory');
const skillsDir = path.join(tmpDir, 'skills');
@@ -404,9 +402,8 @@ describe('memoryService', () => {
it('skips when no unprocessed sessions exist', async () => {
const { startMemoryService } = await import('./memoryService.js');
const { LocalAgentExecutor } = await import(
'../agents/local-executor.js'
);
const { LocalAgentExecutor } =
await import('../agents/local-executor.js');
const memoryDir = path.join(tmpDir, 'memory2');
const skillsDir = path.join(tmpDir, 'skills2');
@@ -439,12 +436,10 @@ describe('memoryService', () => {
it('releases lock on error', async () => {
const { startMemoryService } = await import('./memoryService.js');
const { LocalAgentExecutor } = await import(
'../agents/local-executor.js'
);
const { ExecutionLifecycleService } = await import(
'./executionLifecycleService.js'
);
const { LocalAgentExecutor } =
await import('../agents/local-executor.js');
const { ExecutionLifecycleService } =
await import('./executionLifecycleService.js');
const memoryDir = path.join(tmpDir, 'memory3');
const skillsDir = path.join(tmpDir, 'skills3');
@@ -498,9 +493,8 @@ describe('memoryService', () => {
it('emits feedback when new skills are created during extraction', async () => {
const { startMemoryService } = await import('./memoryService.js');
const { LocalAgentExecutor } = await import(
'../agents/local-executor.js'
);
const { LocalAgentExecutor } =
await import('../agents/local-executor.js');
// Reset mocks that may carry state from prior tests
vi.mocked(coreEvents.emitFeedback).mockClear();
@@ -568,12 +562,10 @@ describe('memoryService', () => {
});
it('records inbox patches as memoryCandidatesCreated without applying them', async () => {
const { startMemoryService, readExtractionState } = await import(
'./memoryService.js'
);
const { LocalAgentExecutor } = await import(
'../agents/local-executor.js'
);
const { startMemoryService, readExtractionState } =
await import('./memoryService.js');
const { LocalAgentExecutor } =
await import('../agents/local-executor.js');
vi.mocked(coreEvents.emitFeedback).mockClear();
vi.mocked(LocalAgentExecutor.create).mockReset();
@@ -671,12 +663,10 @@ describe('memoryService', () => {
});
it('records only sessions whose read_file completed successfully as processed', async () => {
const { startMemoryService, readExtractionState } = await import(
'./memoryService.js'
);
const { LocalAgentExecutor } = await import(
'../agents/local-executor.js'
);
const { startMemoryService, readExtractionState } =
await import('./memoryService.js');
const { LocalAgentExecutor } =
await import('../agents/local-executor.js');
vi.mocked(LocalAgentExecutor.create).mockReset();
@@ -1633,9 +1623,8 @@ describe('memoryService', () => {
});
it('writeExtractionState + readExtractionState roundtrips runs correctly', async () => {
const { writeExtractionState, readExtractionState } = await import(
'./memoryService.js'
);
const { writeExtractionState, readExtractionState } =
await import('./memoryService.js');
const statePath = path.join(tmpDir, 'roundtrip-state.json');
const runs: ExtractionRun[] = [
@@ -1981,9 +1970,8 @@ describe('memoryService', () => {
describe('startMemoryService feedback for patch-only runs', () => {
it('emits feedback when extraction produces only patch suggestions', async () => {
const { startMemoryService } = await import('./memoryService.js');
const { LocalAgentExecutor } = await import(
'../agents/local-executor.js'
);
const { LocalAgentExecutor } =
await import('../agents/local-executor.js');
vi.mocked(coreEvents.emitFeedback).mockClear();
vi.mocked(LocalAgentExecutor.create).mockReset();
@@ -2065,9 +2053,8 @@ describe('memoryService', () => {
it('does not emit feedback for old inbox patches when this run creates none', async () => {
const { startMemoryService } = await import('./memoryService.js');
const { LocalAgentExecutor } = await import(
'../agents/local-executor.js'
);
const { LocalAgentExecutor } =
await import('../agents/local-executor.js');
vi.mocked(coreEvents.emitFeedback).mockClear();
vi.mocked(LocalAgentExecutor.create).mockReset();
@@ -143,9 +143,8 @@ describe('sessionSummaryUtils', () => {
mockGenerateSummary = vi.fn().mockResolvedValue('Add dark mode to the app');
const { SessionSummaryService } = await import(
'./sessionSummaryService.js'
);
const { SessionSummaryService } =
await import('./sessionSummaryService.js');
(
SessionSummaryService as unknown as ReturnType<typeof vi.fn>
).mockImplementation(() => ({
@@ -1885,9 +1885,8 @@ describe('ShellExecutionService environment variables', () => {
vi.stubEnv('GEMINI_CLI_TEST_VAR', 'test-value'); // A test var that should be kept
vi.resetModules();
const { ShellExecutionService } = await import(
'./shellExecutionService.js'
);
const { ShellExecutionService } =
await import('./shellExecutionService.js');
// Test pty path
await ShellExecutionService.execute(
@@ -1945,9 +1944,8 @@ describe('ShellExecutionService environment variables', () => {
vi.stubEnv('GEMINI_CLI_TEST_VAR', 'test-value'); // A test var that should be kept
vi.resetModules();
const { ShellExecutionService } = await import(
'./shellExecutionService.js'
);
const { ShellExecutionService } =
await import('./shellExecutionService.js');
// Test pty path
await ShellExecutionService.execute(
@@ -2002,9 +2000,8 @@ describe('ShellExecutionService environment variables', () => {
vi.stubEnv('GITHUB_SHA', '');
vi.stubEnv('SURFACE', '');
vi.resetModules();
const { ShellExecutionService } = await import(
'./shellExecutionService.js'
);
const { ShellExecutionService } =
await import('./shellExecutionService.js');
// Test pty path
await ShellExecutionService.execute(
@@ -2110,9 +2107,8 @@ describe('ShellExecutionService environment variables', () => {
vi.stubEnv('GIT_CONFIG_KEY_1', 'pull.rebase');
vi.stubEnv('GIT_CONFIG_VALUE_1', 'true');
const { ShellExecutionService } = await import(
'./shellExecutionService.js'
);
const { ShellExecutionService } =
await import('./shellExecutionService.js');
mockGetPty.mockResolvedValue(null); // Force child_process fallback
await ShellExecutionService.execute(
@@ -2162,9 +2158,8 @@ describe('ShellExecutionService environment variables', () => {
vi.stubEnv('GCM_INTERACTIVE', undefined);
vi.stubEnv('GIT_CONFIG_COUNT', undefined);
const { ShellExecutionService } = await import(
'./shellExecutionService.js'
);
const { ShellExecutionService } =
await import('./shellExecutionService.js');
mockGetPty.mockResolvedValue(null); // Force child_process fallback
await ShellExecutionService.execute(
+4 -6
View File
@@ -1261,9 +1261,8 @@ function doIt() {
describe('JIT context discovery', () => {
it('should append JIT context to output when enabled and context is found', async () => {
const { discoverJitContext, appendJitContext } = await import(
'./jit-context.js'
);
const { discoverJitContext, appendJitContext } =
await import('./jit-context.js');
vi.mocked(discoverJitContext).mockResolvedValue('Use the useAuth hook.');
vi.mocked(appendJitContext).mockImplementation((content, context) => {
if (!context) return content;
@@ -1292,9 +1291,8 @@ function doIt() {
});
it('should not append JIT context when disabled', async () => {
const { discoverJitContext, appendJitContext } = await import(
'./jit-context.js'
);
const { discoverJitContext, appendJitContext } =
await import('./jit-context.js');
vi.mocked(discoverJitContext).mockResolvedValue('');
vi.mocked(appendJitContext).mockImplementation((content, context) => {
if (!context) return content;
+3 -2
View File
@@ -21,8 +21,9 @@ import { debugLogger } from '../utils/debugLogger.js';
/**
* A declarative tool that supports a modify operation.
*/
export interface ModifiableDeclarativeTool<TParams extends object>
extends DeclarativeTool<TParams, ToolResult> {
export interface ModifiableDeclarativeTool<
TParams extends object,
> extends DeclarativeTool<TParams, ToolResult> {
getModifyContext(abortSignal: AbortSignal): ModifyContext<TParams>;
}
+2 -4
View File
@@ -157,8 +157,7 @@ export interface PolicyUpdateOptions {
export abstract class BaseToolInvocation<
TParams extends object,
TResult extends ToolResult,
> implements ToolInvocation<TParams, TResult>
{
> implements ToolInvocation<TParams, TResult> {
constructor(
readonly params: TParams,
protected readonly messageBus: MessageBus,
@@ -462,8 +461,7 @@ export interface ToolParameterSchema {
export abstract class DeclarativeTool<
TParams extends object,
TResult extends ToolResult,
> implements ToolBuilder<TParams, TResult>
{
> implements ToolBuilder<TParams, TResult> {
constructor(
readonly name: string,
readonly displayName: string,
+2 -1
View File
@@ -978,7 +978,8 @@ describe('WriteFileTool', () => {
const content = 'test content';
let existsSyncSpy: // eslint-disable-next-line @typescript-eslint/no-explicit-any
ReturnType<typeof vi.spyOn<any, 'existsSync'>> | undefined = undefined;
ReturnType<typeof vi.spyOn<any, 'existsSync'>> | undefined =
undefined;
try {
if (mockFsExistsSync) {
@@ -20,8 +20,7 @@ export interface TranscriptionEvents {
/**
* Common interface for all transcription backends (Cloud or Local).
*/
export interface TranscriptionProvider
extends EventEmitter<TranscriptionEvents> {
export interface TranscriptionProvider extends EventEmitter<TranscriptionEvents> {
/** Establish connection to the transcription service. */
connect(): Promise<void>;
/** Send a chunk of raw audio data to the service. */
+3 -3
View File
@@ -56,11 +56,11 @@ SOFTWARE.
============================================================
ajv@6.14.0
(https://github.com/ajv-validator/ajv.git)
(No repository found)
The MIT License (MIT)
Copyright (c) 2015-2017 Evgeny Poberezkin
Copyright (c) 2015-2021 Evgeny Poberezkin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -492,7 +492,7 @@ eventsource-parser@3.0.3
MIT License
Copyright (c) 2025 Espen Hovlandsdal <espen@hovlandsdal.com>
Copyright (c) 2026 Espen Hovlandsdal <espen@hovlandsdal.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal