fix(patch): cherry-pick bce1cae to release/v0.28.0-preview.6-pr-18407 [CONFLICTS] (#18739)

Co-authored-by: Gal Zahavi <38544478+galz10@users.noreply.github.com>
Co-authored-by: galz10 <galzahavi@google.com>
This commit is contained in:
gemini-cli-robot
2026-02-10 13:50:49 -05:00
committed by GitHub
parent b27fd49c16
commit b90be47308
14 changed files with 593 additions and 52 deletions
@@ -23,11 +23,22 @@ import { FolderTrustChoice } from '../components/FolderTrustDialog.js';
import type { LoadedTrustedFolders } from '../../config/trustedFolders.js';
import { TrustLevel } from '../../config/trustedFolders.js';
import * as trustedFolders from '../../config/trustedFolders.js';
import { coreEvents, ExitCodes } from '@google/gemini-cli-core';
import { coreEvents, ExitCodes, isHeadlessMode } from '@google/gemini-cli-core';
import { MessageType } from '../types.js';
const mockedCwd = vi.hoisted(() => vi.fn());
const mockedExit = vi.hoisted(() => vi.fn());
vi.mock('@google/gemini-cli-core', async () => {
const actual = await vi.importActual<
typeof import('@google/gemini-cli-core')
>('@google/gemini-cli-core');
return {
...actual,
isHeadlessMode: vi.fn().mockReturnValue(false),
};
});
vi.mock('node:process', async () => {
const actual =
await vi.importActual<typeof import('node:process')>('node:process');
@@ -46,8 +57,24 @@ describe('useFolderTrust', () => {
let onTrustChange: (isTrusted: boolean | undefined) => void;
let addItem: Mock;
const originalStdoutIsTTY = process.stdout.isTTY;
const originalStdinIsTTY = process.stdin.isTTY;
beforeEach(() => {
vi.useFakeTimers();
// Default to interactive mode for tests
Object.defineProperty(process.stdout, 'isTTY', {
value: true,
configurable: true,
writable: true,
});
Object.defineProperty(process.stdin, 'isTTY', {
value: true,
configurable: true,
writable: true,
});
mockSettings = {
merged: {
security: {
@@ -75,6 +102,16 @@ describe('useFolderTrust', () => {
afterEach(() => {
vi.useRealTimers();
vi.clearAllMocks();
Object.defineProperty(process.stdout, 'isTTY', {
value: originalStdoutIsTTY,
configurable: true,
writable: true,
});
Object.defineProperty(process.stdin, 'isTTY', {
value: originalStdinIsTTY,
configurable: true,
writable: true,
});
});
it('should not open dialog when folder is already trusted', () => {
@@ -114,7 +151,7 @@ describe('useFolderTrust', () => {
renderHook(() => useFolderTrust(mockSettings, onTrustChange, addItem));
expect(addItem).toHaveBeenCalledWith(
{
text: 'This folder is not trusted. Some features may be disabled. Use the `/permissions` command to change the trust level.',
text: 'This folder is untrusted, project settings, hooks, MCPs, and GEMINI.md files will not be applied for this folder.\nUse the `/permissions` command to change the trust level.',
type: 'info',
},
expect.any(Number),
@@ -318,4 +355,28 @@ describe('useFolderTrust', () => {
);
expect(mockedExit).toHaveBeenCalledWith(ExitCodes.FATAL_CONFIG_ERROR);
});
describe('headless mode', () => {
it('should force trust and hide dialog in headless mode', () => {
vi.mocked(isHeadlessMode).mockReturnValue(true);
isWorkspaceTrustedSpy.mockReturnValue({
isTrusted: false,
source: 'file',
});
const { result } = renderHook(() =>
useFolderTrust(mockSettings, onTrustChange, addItem),
);
expect(result.current.isFolderTrustDialogOpen).toBe(false);
expect(onTrustChange).toHaveBeenCalledWith(true);
expect(addItem).toHaveBeenCalledWith(
expect.objectContaining({
type: MessageType.INFO,
text: expect.stringContaining('This folder is untrusted'),
}),
expect.any(Number),
);
});
});
});
+31 -13
View File
@@ -14,7 +14,7 @@ import {
} from '../../config/trustedFolders.js';
import * as process from 'node:process';
import { type HistoryItemWithoutId, MessageType } from '../types.js';
import { coreEvents, ExitCodes } from '@google/gemini-cli-core';
import { coreEvents, ExitCodes, isHeadlessMode } from '@google/gemini-cli-core';
import { runExitCleanup } from '../../utils/cleanup.js';
export const useFolderTrust = (
@@ -30,21 +30,39 @@ export const useFolderTrust = (
const folderTrust = settings.merged.security.folderTrust.enabled ?? true;
useEffect(() => {
let isMounted = true;
const { isTrusted: trusted } = isWorkspaceTrusted(settings.merged);
setIsTrusted(trusted);
setIsFolderTrustDialogOpen(trusted === undefined);
onTrustChange(trusted);
if (trusted === false && !startupMessageSent.current) {
addItem(
{
type: MessageType.INFO,
text: 'This folder is not trusted. Some features may be disabled. Use the `/permissions` command to change the trust level.',
},
Date.now(),
);
startupMessageSent.current = true;
const showUntrustedMessage = () => {
if (trusted === false && !startupMessageSent.current) {
addItem(
{
type: MessageType.INFO,
text: 'This folder is untrusted, project settings, hooks, MCPs, and GEMINI.md files will not be applied for this folder.\nUse the `/permissions` command to change the trust level.',
},
Date.now(),
);
startupMessageSent.current = true;
}
};
if (isHeadlessMode()) {
if (isMounted) {
setIsTrusted(trusted);
setIsFolderTrustDialogOpen(false);
onTrustChange(true);
showUntrustedMessage();
}
} else if (isMounted) {
setIsTrusted(trusted);
setIsFolderTrustDialogOpen(trusted === undefined);
onTrustChange(trusted);
showUntrustedMessage();
}
return () => {
isMounted = false;
};
}, [folderTrust, onTrustChange, settings.merged, addItem]);
const handleFolderTrustSelect = useCallback(