Compare commits

...

12 Commits

Author SHA1 Message Date
gemini-cli-robot b1b7231d23 chore(release): v0.5.5 2025-09-20 01:00:20 +00:00
Shruti Padamata b7ece4ee3a CP auto restart fix 2025-09-19 15:30:06 -07:00
shrutip90 7dade1f0e2 fix(cli): Auto restart CLI inner node process on trust change (#8378) 2025-09-19 15:25:35 -07:00
Sandy Tao 46afb7374a fix(security): Pin wrap-ansi to 9.0.2 (#8934) 2025-09-19 15:16:35 -07:00
gemini-cli-robot 9e8778366c chore(release): v0.5.4 2025-09-18 18:47:43 +00:00
gemini-cli[bot] f3cb841e1a fix(patch): cherry-pick ad21c10 to release/v0.5.3 (#8719)
Co-authored-by: Tommaso Sciortino <sciortino@gmail.com>
2025-09-18 17:58:17 +00:00
gemini-cli-robot a47015017b chore(release): v0.5.3 2025-09-17 20:36:33 +00:00
gemini-cli-robot cb341041ed chore(release): v0.5.0-preview-2 2025-09-12 18:46:56 +00:00
Shreya Keshive 3412e842e5 fix(ide): Correct IDE client temp dir and port matching (#8270) 2025-09-12 14:26:03 -04:00
gemini-cli-robot 444724c6db chore(release): v0.5.0-preview.1 2025-09-11 22:37:22 +00:00
Sandy Tao ffe7f5f6d8 fix(core): Default skipNextSpeakerCheck to true when the setting is not manually set (#8295) 2025-09-11 14:30:34 -07:00
gemini-cli-robot 5fc6dce719 chore(release): v0.5.0-preview 2025-09-10 00:41:53 +00:00
19 changed files with 245 additions and 201 deletions
+38 -137
View File
@@ -1,12 +1,12 @@
{
"name": "@google/gemini-cli",
"version": "0.4.0",
"version": "0.5.5",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@google/gemini-cli",
"version": "0.4.0",
"version": "0.5.5",
"workspaces": [
"packages/*"
],
@@ -1507,51 +1507,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@inquirer/core/node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/@inquirer/core/node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true,
"license": "MIT"
},
"node_modules/@inquirer/core/node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@inquirer/core/node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@inquirer/core/node_modules/type-fest": {
"version": "0.21.3",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
@@ -1565,21 +1520,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@inquirer/core/node_modules/wrap-ansi": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
"integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@inquirer/figures": {
"version": "1.0.13",
"resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.13.tgz",
@@ -6189,23 +6129,6 @@
"node": ">=8"
}
},
"node_modules/cliui/node_modules/wrap-ansi": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/cockatiel": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/cockatiel/-/cockatiel-3.2.1.tgz",
@@ -9488,23 +9411,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/ink/node_modules/wrap-ansi": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz",
"integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^6.2.1",
"string-width": "^7.0.0",
"strip-ansi": "^7.1.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/internal-slot": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz",
@@ -14999,18 +14905,6 @@
"url": "https://github.com/yeoman/update-notifier?sponsor=1"
}
},
"node_modules/update-notifier/node_modules/ansi-styles": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/update-notifier/node_modules/boxen": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/boxen/-/boxen-8.0.1.tgz",
@@ -15119,23 +15013,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/update-notifier/node_modules/wrap-ansi": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz",
"integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^6.2.1",
"string-width": "^7.0.0",
"strip-ansi": "^7.1.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/uri-js": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
@@ -15685,17 +15562,17 @@
}
},
"node_modules/wrap-ansi": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz",
"integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^6.1.0",
"string-width": "^5.0.1",
"strip-ansi": "^7.0.1"
"ansi-styles": "^6.2.1",
"string-width": "^7.0.0",
"strip-ansi": "^7.1.0"
},
"engines": {
"node": ">=12"
"node": ">=18"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
@@ -15772,6 +15649,29 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/wrap-ansi/node_modules/emoji-regex": {
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.5.0.tgz",
"integrity": "sha512-lb49vf1Xzfx080OKA0o6l8DQQpV+6Vg95zyCJX9VB/BqKYlhG7N4wgROUUHRA+ZPUefLnteQOad7z1kT2bV7bg==",
"license": "MIT"
},
"node_modules/wrap-ansi/node_modules/string-width": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
"integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
"license": "MIT",
"dependencies": {
"emoji-regex": "^10.3.0",
"get-east-asian-width": "^1.0.0",
"strip-ansi": "^7.1.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
@@ -16021,7 +15921,7 @@
},
"packages/a2a-server": {
"name": "@google/gemini-cli-a2a-server",
"version": "0.4.0",
"version": "0.5.5",
"dependencies": {
"@a2a-js/sdk": "^0.3.2",
"@google-cloud/storage": "^7.16.0",
@@ -16292,7 +16192,7 @@
},
"packages/cli": {
"name": "@google/gemini-cli",
"version": "0.4.0",
"version": "0.5.5",
"dependencies": {
"@google/gemini-cli-core": "file:../core",
"@google/genai": "1.16.0",
@@ -16319,6 +16219,7 @@
"strip-json-comments": "^3.1.1",
"undici": "^7.10.0",
"update-notifier": "^7.3.1",
"wrap-ansi": "9.0.2",
"yargs": "^17.7.2",
"zod": "^3.23.8"
},
@@ -16483,7 +16384,7 @@
},
"packages/core": {
"name": "@google/gemini-cli-core",
"version": "0.4.0",
"version": "0.5.5",
"dependencies": {
"@google/genai": "1.16.0",
"@joshua.litt/get-ripgrep": "^0.0.2",
@@ -16624,7 +16525,7 @@
},
"packages/test-utils": {
"name": "@google/gemini-cli-test-utils",
"version": "0.4.0",
"version": "0.5.5",
"license": "Apache-2.0",
"devDependencies": {
"typescript": "^5.3.3"
@@ -16635,7 +16536,7 @@
},
"packages/vscode-ide-companion": {
"name": "gemini-cli-vscode-ide-companion",
"version": "0.4.0",
"version": "0.5.5",
"license": "LICENSE",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.15.1",
+5 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@google/gemini-cli",
"version": "0.4.0",
"version": "0.5.5",
"engines": {
"node": ">=20.0.0"
},
@@ -14,7 +14,7 @@
"url": "git+https://github.com/google-gemini/gemini-cli.git"
},
"config": {
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.4.0"
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.5.5"
},
"scripts": {
"start": "node scripts/start.js",
@@ -101,5 +101,8 @@
"@lydell/node-pty-win32-arm64": "1.1.0",
"@lydell/node-pty-win32-x64": "1.1.0",
"node-pty": "^1.0.0"
},
"overrides": {
"wrap-ansi": "9.0.2"
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@google/gemini-cli-a2a-server",
"version": "0.4.0",
"version": "0.5.5",
"private": true,
"description": "Gemini CLI A2A Server",
"repository": {
+3 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@google/gemini-cli",
"version": "0.4.0",
"version": "0.5.5",
"description": "Gemini CLI",
"repository": {
"type": "git",
@@ -25,7 +25,7 @@
"dist"
],
"config": {
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.4.0"
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.5.5"
},
"dependencies": {
"@google/gemini-cli-core": "file:../core",
@@ -53,6 +53,7 @@
"strip-json-comments": "^3.1.1",
"undici": "^7.10.0",
"update-notifier": "^7.3.1",
"wrap-ansi": "9.0.2",
"yargs": "^17.7.2",
"zod": "^3.23.8"
},
+14
View File
@@ -163,11 +163,16 @@ describe('gemini.tsx main function', () => {
});
describe('gemini.tsx main function kitty protocol', () => {
let originalEnvNoRelaunch: string | undefined;
let setRawModeSpy: MockInstance<
(mode: boolean) => NodeJS.ReadStream & { fd: 0 }
>;
beforeEach(() => {
// Set no relaunch in tests since process spawning causing issues in tests
originalEnvNoRelaunch = process.env['GEMINI_CLI_NO_RELAUNCH'];
process.env['GEMINI_CLI_NO_RELAUNCH'] = 'true';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
if (!(process.stdin as any).setRawMode) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -176,6 +181,15 @@ describe('gemini.tsx main function kitty protocol', () => {
setRawModeSpy = vi.spyOn(process.stdin, 'setRawMode');
});
afterEach(() => {
// Restore original env variables
if (originalEnvNoRelaunch !== undefined) {
process.env['GEMINI_CLI_NO_RELAUNCH'] = originalEnvNoRelaunch;
} else {
delete process.env['GEMINI_CLI_NO_RELAUNCH'];
}
});
it('should call setRawMode and detectAndEnableKittyProtocol when isInteractive is true', async () => {
const { detectAndEnableKittyProtocol } = await import(
'./ui/utils/kittyProtocolDetector.js'
+49 -16
View File
@@ -16,6 +16,7 @@ import dns from 'node:dns';
import { spawn } from 'node:child_process';
import { start_sandbox } from './utils/sandbox.js';
import type { DnsResolutionOrder, LoadedSettings } from './config/settings.js';
import { RELAUNCH_EXIT_CODE } from './utils/processUtils.js';
import { loadSettings, SettingScope } from './config/settings.js';
import { themeManager } from './ui/themes/theme-manager.js';
import { getStartupWarnings } from './utils/startupWarnings.js';
@@ -103,17 +104,50 @@ function getNodeMemoryArgs(config: Config): string[] {
return [];
}
async function relaunchWithAdditionalArgs(additionalArgs: string[]) {
const nodeArgs = [...additionalArgs, ...process.argv.slice(1)];
const newEnv = { ...process.env, GEMINI_CLI_NO_RELAUNCH: 'true' };
async function relaunchOnExitCode(runner: () => Promise<number>) {
while (true) {
try {
const exitCode = await runner();
const child = spawn(process.execPath, nodeArgs, {
stdio: 'inherit',
env: newEnv,
});
if (exitCode !== RELAUNCH_EXIT_CODE) {
process.exit(exitCode);
}
} catch (error) {
process.stdin.resume();
console.error('Fatal error: Failed to relaunch the CLI process.', error);
process.exit(1);
}
}
}
await new Promise((resolve) => child.on('close', resolve));
process.exit(0);
async function relaunchAppInChildProcess(additionalArgs: string[]) {
if (process.env['GEMINI_CLI_NO_RELAUNCH']) {
return;
}
const runner = () => {
const nodeArgs = [...additionalArgs, ...process.argv.slice(1)];
const newEnv = { ...process.env, GEMINI_CLI_NO_RELAUNCH: 'true' };
// The parent process should not be reading from stdin while the child is running.
process.stdin.pause();
const child = spawn(process.execPath, nodeArgs, {
stdio: 'inherit',
env: newEnv,
});
return new Promise<number>((resolve, reject) => {
child.on('error', reject);
child.on('close', (code) => {
// Resume stdin before the parent process exits.
process.stdin.resume();
resolve(code ?? 1);
});
});
};
await relaunchOnExitCode(runner);
}
import { runZedIntegration } from './zed-integration/zedIntegration.js';
@@ -350,15 +384,14 @@ export async function main() {
const sandboxArgs = injectStdinIntoArgs(process.argv, stdinData);
await start_sandbox(sandboxConfig, memoryArgs, config, sandboxArgs);
await relaunchOnExitCode(() =>
start_sandbox(sandboxConfig, memoryArgs, config, sandboxArgs),
);
process.exit(0);
} else {
// Not in a sandbox and not entering one, so relaunch with additional
// arguments to control memory usage if needed.
if (memoryArgs.length > 0) {
await relaunchWithAdditionalArgs(memoryArgs);
process.exit(0);
}
// Relaunch app so we always have a child process that can be internally
// restarted if needed.
await relaunchAppInChildProcess(memoryArgs);
}
}
+1
View File
@@ -435,6 +435,7 @@ Logging in with Google... Please restart Gemini CLI to continue.
setIsProcessing,
setGeminiMdFileCount,
slashCommandActions,
isConfigInitialized,
);
const performMemoryRefresh = useCallback(async () => {
@@ -8,6 +8,11 @@ import { renderWithProviders } from '../../test-utils/render.js';
import { waitFor } from '@testing-library/react';
import { vi } from 'vitest';
import { FolderTrustDialog, FolderTrustChoice } from './FolderTrustDialog.js';
import * as processUtils from '../../utils/processUtils.js';
vi.mock('../../utils/processUtils.js', () => ({
relaunchApp: vi.fn(),
}));
const mockedExit = vi.hoisted(() => vi.fn());
const mockedCwd = vi.hoisted(() => vi.fn());
@@ -69,21 +74,18 @@ describe('FolderTrustDialog', () => {
<FolderTrustDialog onSelect={vi.fn()} isRestarting={true} />,
);
expect(lastFrame()).toContain(
'To see changes, Gemini CLI must be restarted',
);
expect(lastFrame()).toContain(' Gemini CLI is restarting');
});
it('should call process.exit when "r" is pressed and isRestarting is true', async () => {
const { stdin } = renderWithProviders(
it('should call relaunchApp when isRestarting is true', async () => {
vi.useFakeTimers();
const relaunchApp = vi.spyOn(processUtils, 'relaunchApp');
renderWithProviders(
<FolderTrustDialog onSelect={vi.fn()} isRestarting={true} />,
);
stdin.write('r');
await waitFor(() => {
expect(mockedExit).toHaveBeenCalledWith(0);
});
await vi.advanceTimersByTimeAsync(1000);
expect(relaunchApp).toHaveBeenCalled();
vi.useRealTimers();
});
it('should not call process.exit when "r" is pressed and isRestarting is false', async () => {
@@ -6,12 +6,15 @@
import { Box, Text } from 'ink';
import type React from 'react';
import { useEffect } from 'react';
import { theme } from '../semantic-colors.js';
import { Colors } from '../colors.js';
import type { RadioSelectItem } from './shared/RadioButtonSelect.js';
import { RadioButtonSelect } from './shared/RadioButtonSelect.js';
import { useKeypress } from '../hooks/useKeypress.js';
import * as process from 'node:process';
import * as path from 'node:path';
import { relaunchApp } from '../../utils/processUtils.js';
export enum FolderTrustChoice {
TRUST_FOLDER = 'trust_folder',
@@ -28,6 +31,17 @@ export const FolderTrustDialog: React.FC<FolderTrustDialogProps> = ({
onSelect,
isRestarting,
}) => {
useEffect(() => {
const doRelaunch = async () => {
if (isRestarting) {
setTimeout(async () => {
await relaunchApp();
}, 250);
}
};
doRelaunch();
}, [isRestarting]);
useKeypress(
(key) => {
if (key.name === 'escape') {
@@ -37,20 +51,12 @@ export const FolderTrustDialog: React.FC<FolderTrustDialogProps> = ({
{ isActive: !isRestarting },
);
useKeypress(
(key) => {
if (key.name === 'r') {
process.exit(0);
}
},
{ isActive: !!isRestarting },
);
const dirName = path.basename(process.cwd());
const parentFolder = path.basename(path.dirname(process.cwd()));
const options: Array<RadioSelectItem<FolderTrustChoice>> = [
{
label: 'Trust folder',
label: `Trust folder (${dirName})`,
value: FolderTrustChoice.TRUST_FOLDER,
},
{
@@ -90,9 +96,8 @@ export const FolderTrustDialog: React.FC<FolderTrustDialogProps> = ({
</Box>
{isRestarting && (
<Box marginLeft={1} marginTop={1}>
<Text color={Colors.AccentYellow}>
To see changes, Gemini CLI must be restarted. Press r to exit and
apply changes now.
<Text color={theme.status.warning}>
Gemini CLI is restarting to apply the trust changes...
</Text>
</Box>
)}
@@ -59,6 +59,7 @@ export const useSlashCommandProcessor = (
setIsProcessing: (isProcessing: boolean) => void,
setGeminiMdFileCount: (count: number) => void,
actions: SlashCommandProcessorActions,
isConfigInitialized: boolean,
) => {
const session = useSessionStats();
const [commands, setCommands] = useState<readonly SlashCommand[]>([]);
@@ -255,7 +256,7 @@ export const useSlashCommandProcessor = (
return () => {
controller.abort();
};
}, [config, reloadTrigger]);
}, [config, reloadTrigger, isConfigInitialized]);
const handleSlashCommand = useCallback(
async (
@@ -0,0 +1,22 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { vi } from 'vitest';
import { RELAUNCH_EXIT_CODE, relaunchApp } from './processUtils.js';
import * as cleanup from './cleanup.js';
describe('processUtils', () => {
const processExit = vi
.spyOn(process, 'exit')
.mockReturnValue(undefined as never);
const runExitCleanup = vi.spyOn(cleanup, 'runExitCleanup');
it('should run cleanup and exit with the relaunch code', async () => {
await relaunchApp();
expect(runExitCleanup).toHaveBeenCalledTimes(1);
expect(processExit).toHaveBeenCalledWith(RELAUNCH_EXIT_CODE);
});
});
+20
View File
@@ -0,0 +1,20 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { runExitCleanup } from './cleanup.js';
/**
* Exit code used to signal that the CLI should be relaunched.
*/
export const RELAUNCH_EXIT_CODE = 42;
/**
* Exits the process with a special code to signal that the parent process should relaunch it.
*/
export async function relaunchApp(): Promise<void> {
await runExitCleanup();
process.exit(RELAUNCH_EXIT_CODE);
}
+18 -9
View File
@@ -188,7 +188,7 @@ export async function start_sandbox(
nodeArgs: string[] = [],
cliConfig?: Config,
cliArgs: string[] = [],
) {
): Promise<number> {
const patcher = new ConsolePatcher({
debugMode: cliConfig?.getDebugMode() || !!process.env['DEBUG'],
stderr: true,
@@ -339,11 +339,17 @@ export async function start_sandbox(
);
}
// spawn child and let it inherit stdio
process.stdin.pause();
sandboxProcess = spawn(config.command, args, {
stdio: 'inherit',
});
await new Promise((resolve) => sandboxProcess?.on('close', resolve));
return;
return new Promise((resolve, reject) => {
sandboxProcess?.on('error', reject);
sandboxProcess?.on('close', (code) => {
process.stdin.resume();
resolve(code ?? 1);
});
});
}
console.error(`hopping into sandbox (command: ${config.command}) ...`);
@@ -790,22 +796,25 @@ export async function start_sandbox(
}
// spawn child and let it inherit stdio
process.stdin.pause();
sandboxProcess = spawn(config.command, args, {
stdio: 'inherit',
});
sandboxProcess.on('error', (err) => {
console.error('Sandbox process error:', err);
});
return new Promise<number>((resolve, reject) => {
sandboxProcess.on('error', (err) => {
console.error('Sandbox process error:', err);
reject(err);
});
await new Promise<void>((resolve) => {
sandboxProcess?.on('close', (code, signal) => {
if (code !== 0) {
process.stdin.resume();
if (code !== 0 && code !== null) {
console.log(
`Sandbox process exited with code: ${code}, signal: ${signal}`,
);
}
resolve();
resolve(code ?? 1);
});
});
} finally {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@google/gemini-cli-core",
"version": "0.4.0",
"version": "0.5.5",
"description": "Gemini CLI Core",
"repository": {
"type": "git",
+1 -1
View File
@@ -379,7 +379,7 @@ export class Config {
this.trustedFolder = params.trustedFolder;
this.useRipgrep = params.useRipgrep ?? false;
this.shouldUseNodePtyShell = params.shouldUseNodePtyShell ?? false;
this.skipNextSpeakerCheck = params.skipNextSpeakerCheck ?? false;
this.skipNextSpeakerCheck = params.skipNextSpeakerCheck ?? true;
this.truncateToolOutputThreshold =
params.truncateToolOutputThreshold ??
DEFAULT_TRUNCATE_TOOL_OUTPUT_THRESHOLD;
+35 -3
View File
@@ -330,7 +330,7 @@ describe('IdeClient', () => {
expect(result).toEqual(config);
expect(fs.promises.readFile).toHaveBeenCalledWith(
path.join('/tmp/.gemini/ide', 'gemini-ide-server-12345-123.json'),
path.join('/tmp/gemini/ide', 'gemini-ide-server-12345-123.json'),
'utf8',
);
});
@@ -525,13 +525,45 @@ describe('IdeClient', () => {
expect(result).toEqual(validConfig);
expect(fs.promises.readFile).toHaveBeenCalledWith(
path.join('/tmp/.gemini/ide', 'gemini-ide-server-12345-111.json'),
path.join('/tmp/gemini/ide', 'gemini-ide-server-12345-111.json'),
'utf8',
);
expect(fs.promises.readFile).not.toHaveBeenCalledWith(
path.join('/tmp/.gemini/ide', 'not-a-config-file.txt'),
path.join('/tmp/gemini/ide', 'not-a-config-file.txt'),
'utf8',
);
});
it('should match env port string to a number port in the config', async () => {
process.env['GEMINI_CLI_IDE_SERVER_PORT'] = '3333';
const config1 = { port: 1111, workspacePath: '/test/workspace' };
const config2 = { port: 3333, workspacePath: '/test/workspace2' };
vi.mocked(fs.promises.readFile).mockRejectedValueOnce(
new Error('not found'),
);
(
vi.mocked(fs.promises.readdir) as Mock<
(path: fs.PathLike) => Promise<string[]>
>
).mockResolvedValue([
'gemini-ide-server-12345-111.json',
'gemini-ide-server-12345-222.json',
]);
vi.mocked(fs.promises.readFile)
.mockResolvedValueOnce(JSON.stringify(config1))
.mockResolvedValueOnce(JSON.stringify(config2));
vi.spyOn(IdeClient, 'validateWorkspacePath').mockReturnValue({
isValid: true,
});
const ideClient = await IdeClient.getInstance();
const result = await (
ideClient as unknown as {
getConnectionConfigFromFile: () => Promise<unknown>;
}
).getConnectionConfigFromFile();
expect(result).toEqual(config2);
});
});
});
+2 -2
View File
@@ -414,7 +414,7 @@ export class IdeClient {
// exist.
}
const portFileDir = path.join(os.tmpdir(), '.gemini', 'ide');
const portFileDir = path.join(os.tmpdir(), 'gemini', 'ide');
let portFiles;
try {
portFiles = await fs.promises.readdir(portFileDir);
@@ -476,7 +476,7 @@ export class IdeClient {
const portFromEnv = this.getPortFromEnv();
if (portFromEnv) {
const matchingPort = validWorkspaces.find(
(content) => content.port === portFromEnv,
(content) => String(content.port) === portFromEnv,
);
if (matchingPort) {
return matchingPort;
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@google/gemini-cli-test-utils",
"version": "0.4.0",
"version": "0.5.5",
"private": true,
"main": "src/index.ts",
"license": "Apache-2.0",
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "gemini-cli-vscode-ide-companion",
"displayName": "Gemini CLI Companion",
"description": "Enable Gemini CLI with direct access to your IDE workspace.",
"version": "0.4.0",
"version": "0.5.5",
"publisher": "google",
"icon": "assets/icon.png",
"repository": {