docs(changelog): update for v0.42.0-preview.1

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