mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-12 23:21:27 -07:00
fix(core): improve headless mode detection for flags and query args (#18855)
This commit is contained in:
@@ -445,7 +445,11 @@ export async function loadCliConfig(
|
|||||||
process.env['VITEST'] === 'true'
|
process.env['VITEST'] === 'true'
|
||||||
? false
|
? false
|
||||||
: (settings.security?.folderTrust?.enabled ?? false);
|
: (settings.security?.folderTrust?.enabled ?? false);
|
||||||
const trustedFolder = isWorkspaceTrusted(settings, cwd)?.isTrusted ?? false;
|
const trustedFolder =
|
||||||
|
isWorkspaceTrusted(settings, cwd, undefined, {
|
||||||
|
prompt: argv.prompt,
|
||||||
|
query: argv.query,
|
||||||
|
})?.isTrusted ?? false;
|
||||||
|
|
||||||
// Set the context filename in the server's memoryTool module BEFORE loading memory
|
// Set the context filename in the server's memoryTool module BEFORE loading memory
|
||||||
// TODO(b/343434939): This is a bit of a hack. The contextFileName should ideally be passed
|
// TODO(b/343434939): This is a bit of a hack. The contextFileName should ideally be passed
|
||||||
@@ -602,8 +606,7 @@ export async function loadCliConfig(
|
|||||||
const interactive =
|
const interactive =
|
||||||
!!argv.promptInteractive ||
|
!!argv.promptInteractive ||
|
||||||
!!argv.experimentalAcp ||
|
!!argv.experimentalAcp ||
|
||||||
(!isHeadlessMode({ prompt: argv.prompt }) &&
|
(!isHeadlessMode({ prompt: argv.prompt, query: argv.query }) &&
|
||||||
!argv.query &&
|
|
||||||
!argv.isCommand);
|
!argv.isCommand);
|
||||||
|
|
||||||
const allowedTools = argv.allowedTools || settings.tools?.allowed || [];
|
const allowedTools = argv.allowedTools || settings.tools?.allowed || [];
|
||||||
|
|||||||
@@ -449,6 +449,14 @@ describe('Trusted Folders', () => {
|
|||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should return true for isPathTrusted when isHeadlessMode is true', async () => {
|
||||||
|
const geminiCore = await import('@google/gemini-cli-core');
|
||||||
|
vi.spyOn(geminiCore, 'isHeadlessMode').mockReturnValue(true);
|
||||||
|
|
||||||
|
const folders = loadTrustedFolders();
|
||||||
|
expect(folders.isPathTrusted('/any-untrusted-path')).toBe(true);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Trusted Folders Caching', () => {
|
describe('Trusted Folders Caching', () => {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
homedir,
|
homedir,
|
||||||
isHeadlessMode,
|
isHeadlessMode,
|
||||||
coreEvents,
|
coreEvents,
|
||||||
|
type HeadlessModeOptions,
|
||||||
} from '@google/gemini-cli-core';
|
} from '@google/gemini-cli-core';
|
||||||
import type { Settings } from './settings.js';
|
import type { Settings } from './settings.js';
|
||||||
import stripJsonComments from 'strip-json-comments';
|
import stripJsonComments from 'strip-json-comments';
|
||||||
@@ -128,7 +129,11 @@ export class LoadedTrustedFolders {
|
|||||||
isPathTrusted(
|
isPathTrusted(
|
||||||
location: string,
|
location: string,
|
||||||
config?: Record<string, TrustLevel>,
|
config?: Record<string, TrustLevel>,
|
||||||
|
headlessOptions?: HeadlessModeOptions,
|
||||||
): boolean | undefined {
|
): boolean | undefined {
|
||||||
|
if (isHeadlessMode(headlessOptions)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
const configToUse = config ?? this.user.config;
|
const configToUse = config ?? this.user.config;
|
||||||
|
|
||||||
// Resolve location to its realpath for canonical comparison
|
// Resolve location to its realpath for canonical comparison
|
||||||
@@ -333,6 +338,7 @@ export function isFolderTrustEnabled(settings: Settings): boolean {
|
|||||||
function getWorkspaceTrustFromLocalConfig(
|
function getWorkspaceTrustFromLocalConfig(
|
||||||
workspaceDir: string,
|
workspaceDir: string,
|
||||||
trustConfig?: Record<string, TrustLevel>,
|
trustConfig?: Record<string, TrustLevel>,
|
||||||
|
headlessOptions?: HeadlessModeOptions,
|
||||||
): TrustResult {
|
): TrustResult {
|
||||||
const folders = loadTrustedFolders();
|
const folders = loadTrustedFolders();
|
||||||
const configToUse = trustConfig ?? folders.user.config;
|
const configToUse = trustConfig ?? folders.user.config;
|
||||||
@@ -346,7 +352,11 @@ function getWorkspaceTrustFromLocalConfig(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const isTrusted = folders.isPathTrusted(workspaceDir, configToUse);
|
const isTrusted = folders.isPathTrusted(
|
||||||
|
workspaceDir,
|
||||||
|
configToUse,
|
||||||
|
headlessOptions,
|
||||||
|
);
|
||||||
return {
|
return {
|
||||||
isTrusted,
|
isTrusted,
|
||||||
source: isTrusted !== undefined ? 'file' : undefined,
|
source: isTrusted !== undefined ? 'file' : undefined,
|
||||||
@@ -357,8 +367,9 @@ export function isWorkspaceTrusted(
|
|||||||
settings: Settings,
|
settings: Settings,
|
||||||
workspaceDir: string = process.cwd(),
|
workspaceDir: string = process.cwd(),
|
||||||
trustConfig?: Record<string, TrustLevel>,
|
trustConfig?: Record<string, TrustLevel>,
|
||||||
|
headlessOptions?: HeadlessModeOptions,
|
||||||
): TrustResult {
|
): TrustResult {
|
||||||
if (isHeadlessMode()) {
|
if (isHeadlessMode(headlessOptions)) {
|
||||||
return { isTrusted: true, source: undefined };
|
return { isTrusted: true, source: undefined };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -372,5 +383,9 @@ export function isWorkspaceTrusted(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fall back to the local user configuration
|
// Fall back to the local user configuration
|
||||||
return getWorkspaceTrustFromLocalConfig(workspaceDir, trustConfig);
|
return getWorkspaceTrustFromLocalConfig(
|
||||||
|
workspaceDir,
|
||||||
|
trustConfig,
|
||||||
|
headlessOptions,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,16 +99,50 @@ describe('isHeadlessMode', () => {
|
|||||||
expect(isHeadlessMode({ prompt: true })).toBe(true);
|
expect(isHeadlessMode({ prompt: true })).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return false if query is provided but it is still a TTY', () => {
|
it('should return true if query is provided', () => {
|
||||||
// Note: per current logic, query alone doesn't force headless if TTY
|
expect(isHeadlessMode({ query: 'test query' })).toBe(true);
|
||||||
// This matches the existing behavior in packages/cli/src/config/config.ts
|
});
|
||||||
expect(isHeadlessMode({ query: 'test query' })).toBe(false);
|
|
||||||
|
it('should return true if -p or --prompt is in process.argv as a fallback', () => {
|
||||||
|
const originalArgv = process.argv;
|
||||||
|
process.argv = ['node', 'index.js', '-p', 'hello'];
|
||||||
|
try {
|
||||||
|
expect(isHeadlessMode()).toBe(true);
|
||||||
|
} finally {
|
||||||
|
process.argv = originalArgv;
|
||||||
|
}
|
||||||
|
|
||||||
|
process.argv = ['node', 'index.js', '--prompt', 'hello'];
|
||||||
|
try {
|
||||||
|
expect(isHeadlessMode()).toBe(true);
|
||||||
|
} finally {
|
||||||
|
process.argv = originalArgv;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true if -y or --yolo is in process.argv as a fallback', () => {
|
||||||
|
const originalArgv = process.argv;
|
||||||
|
process.argv = ['node', 'index.js', '-y'];
|
||||||
|
try {
|
||||||
|
expect(isHeadlessMode()).toBe(true);
|
||||||
|
} finally {
|
||||||
|
process.argv = originalArgv;
|
||||||
|
}
|
||||||
|
|
||||||
|
process.argv = ['node', 'index.js', '--yolo'];
|
||||||
|
try {
|
||||||
|
expect(isHeadlessMode()).toBe(true);
|
||||||
|
} finally {
|
||||||
|
process.argv = originalArgv;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle undefined process.stdout gracefully', () => {
|
it('should handle undefined process.stdout gracefully', () => {
|
||||||
const originalStdout = process.stdout;
|
const originalStdout = process.stdout;
|
||||||
// @ts-expect-error - testing edge case
|
Object.defineProperty(process, 'stdout', {
|
||||||
delete process.stdout;
|
value: undefined,
|
||||||
|
configurable: true,
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
expect(isHeadlessMode()).toBe(false);
|
expect(isHeadlessMode()).toBe(false);
|
||||||
@@ -122,8 +156,10 @@ describe('isHeadlessMode', () => {
|
|||||||
|
|
||||||
it('should handle undefined process.stdin gracefully', () => {
|
it('should handle undefined process.stdin gracefully', () => {
|
||||||
const originalStdin = process.stdin;
|
const originalStdin = process.stdin;
|
||||||
// @ts-expect-error - testing edge case
|
Object.defineProperty(process, 'stdin', {
|
||||||
delete process.stdin;
|
value: undefined,
|
||||||
|
configurable: true,
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
expect(isHeadlessMode()).toBe(false);
|
expect(isHeadlessMode()).toBe(false);
|
||||||
|
|||||||
@@ -28,18 +28,25 @@ export interface HeadlessModeOptions {
|
|||||||
* @returns true if the environment is considered headless.
|
* @returns true if the environment is considered headless.
|
||||||
*/
|
*/
|
||||||
export function isHeadlessMode(options?: HeadlessModeOptions): boolean {
|
export function isHeadlessMode(options?: HeadlessModeOptions): boolean {
|
||||||
if (process.env['GEMINI_CLI_INTEGRATION_TEST'] === 'true') {
|
if (process.env['GEMINI_CLI_INTEGRATION_TEST'] !== 'true') {
|
||||||
return (
|
const isCI =
|
||||||
!!options?.prompt ||
|
process.env['CI'] === 'true' || process.env['GITHUB_ACTIONS'] === 'true';
|
||||||
(!!process.stdin && !process.stdin.isTTY) ||
|
if (isCI) {
|
||||||
(!!process.stdout && !process.stdout.isTTY)
|
return true;
|
||||||
);
|
}
|
||||||
}
|
}
|
||||||
return (
|
|
||||||
process.env['CI'] === 'true' ||
|
const isNotTTY =
|
||||||
process.env['GITHUB_ACTIONS'] === 'true' ||
|
|
||||||
!!options?.prompt ||
|
|
||||||
(!!process.stdin && !process.stdin.isTTY) ||
|
(!!process.stdin && !process.stdin.isTTY) ||
|
||||||
(!!process.stdout && !process.stdout.isTTY)
|
(!!process.stdout && !process.stdout.isTTY);
|
||||||
|
|
||||||
|
if (isNotTTY || !!options?.prompt || !!options?.query) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: check process.argv for flags that imply headless or auto-approve mode.
|
||||||
|
return process.argv.some(
|
||||||
|
(arg) =>
|
||||||
|
arg === '-p' || arg === '--prompt' || arg === '-y' || arg === '--yolo',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user