mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-21 10:34:35 -07:00
Add setting to disable YOLO mode (#11609)
Co-authored-by: Shreya Keshive <shreyakeshive@google.com>
This commit is contained in:
@@ -1112,6 +1112,23 @@ describe('Approval mode tool exclusion logic', () => {
|
||||
expect(excludedTools).not.toContain(WRITE_FILE_TOOL_NAME); // Should be allowed in auto_edit
|
||||
});
|
||||
|
||||
it('should throw an error if YOLO mode is attempted when disableYoloMode is true', async () => {
|
||||
process.argv = ['node', 'script.js', '--yolo'];
|
||||
const argv = await parseArguments({} as Settings);
|
||||
const settings: Settings = {
|
||||
security: {
|
||||
disableYoloMode: true,
|
||||
},
|
||||
};
|
||||
const extensions: GeminiCLIExtension[] = [];
|
||||
|
||||
await expect(
|
||||
loadCliConfig(settings, extensions, 'test-session', argv),
|
||||
).rejects.toThrow(
|
||||
'Cannot start in YOLO mode when it is disabled by settings',
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw an error for invalid approval mode values in loadCliConfig', async () => {
|
||||
// Create a mock argv with an invalid approval mode that bypasses argument parsing validation
|
||||
const invalidArgv: Partial<CliArgs> & { approvalMode: string } = {
|
||||
|
||||
@@ -442,6 +442,21 @@ export async function loadCliConfig(
|
||||
argv.yolo || false ? ApprovalMode.YOLO : ApprovalMode.DEFAULT;
|
||||
}
|
||||
|
||||
// Override approval mode if disableYoloMode is set.
|
||||
if (settings.security?.disableYoloMode) {
|
||||
if (approvalMode === ApprovalMode.YOLO) {
|
||||
debugLogger.error('YOLO mode is disabled by the "disableYolo" setting.');
|
||||
throw new FatalConfigError(
|
||||
'Cannot start in YOLO mode when it is disabled by settings',
|
||||
);
|
||||
}
|
||||
approvalMode = ApprovalMode.DEFAULT;
|
||||
} else if (approvalMode === ApprovalMode.YOLO) {
|
||||
debugLogger.warn(
|
||||
'YOLO mode is enabled. All tool calls will be automatically approved.',
|
||||
);
|
||||
}
|
||||
|
||||
// Force approval mode to default if the folder is not trusted.
|
||||
if (!trustedFolder && approvalMode !== ApprovalMode.DEFAULT) {
|
||||
debugLogger.warn(
|
||||
@@ -583,6 +598,7 @@ export async function loadCliConfig(
|
||||
geminiMdFileCount: fileCount,
|
||||
geminiMdFilePaths: filePaths,
|
||||
approvalMode,
|
||||
disableYoloMode: settings.security?.disableYoloMode,
|
||||
showMemoryUsage: settings.ui?.showMemoryUsage || false,
|
||||
accessibility: {
|
||||
...settings.ui?.accessibility,
|
||||
|
||||
@@ -609,6 +609,40 @@ describe('Settings Loading and Merging', () => {
|
||||
expect(settings.merged.security?.folderTrust?.enabled).toBe(true); // System setting should be used
|
||||
});
|
||||
|
||||
it('should not allow user or workspace to override system disableYoloMode', () => {
|
||||
(mockFsExistsSync as Mock).mockReturnValue(true);
|
||||
const userSettingsContent = {
|
||||
security: {
|
||||
disableYoloMode: false,
|
||||
},
|
||||
};
|
||||
const workspaceSettingsContent = {
|
||||
security: {
|
||||
disableYoloMode: false, // This should be ignored
|
||||
},
|
||||
};
|
||||
const systemSettingsContent = {
|
||||
security: {
|
||||
disableYoloMode: true,
|
||||
},
|
||||
};
|
||||
|
||||
(fs.readFileSync as Mock).mockImplementation(
|
||||
(p: fs.PathOrFileDescriptor) => {
|
||||
if (p === getSystemSettingsPath())
|
||||
return JSON.stringify(systemSettingsContent);
|
||||
if (p === USER_SETTINGS_PATH)
|
||||
return JSON.stringify(userSettingsContent);
|
||||
if (p === MOCK_WORKSPACE_SETTINGS_PATH)
|
||||
return JSON.stringify(workspaceSettingsContent);
|
||||
return '{}';
|
||||
},
|
||||
);
|
||||
|
||||
const settings = loadSettings(MOCK_WORKSPACE_DIR);
|
||||
expect(settings.merged.security?.disableYoloMode).toBe(true); // System setting should be used
|
||||
});
|
||||
|
||||
it('should handle contextFileName correctly when only in user settings', () => {
|
||||
(mockFsExistsSync as Mock).mockImplementation(
|
||||
(p: fs.PathLike) => p === USER_SETTINGS_PATH,
|
||||
|
||||
@@ -937,6 +937,15 @@ const SETTINGS_SCHEMA = {
|
||||
description: 'Security-related settings.',
|
||||
showInDialog: false,
|
||||
properties: {
|
||||
disableYoloMode: {
|
||||
type: 'boolean',
|
||||
label: 'Disable YOLO Mode',
|
||||
category: 'Security',
|
||||
requiresRestart: true,
|
||||
default: false,
|
||||
description: 'Disable YOLO mode, even if enabled by a flag.',
|
||||
showInDialog: true,
|
||||
},
|
||||
folderTrust: {
|
||||
type: 'object',
|
||||
label: 'Folder Trust',
|
||||
|
||||
@@ -37,6 +37,7 @@ vi.mock('@google/gemini-cli-core', async () => {
|
||||
interface MockConfigInstanceShape {
|
||||
getApprovalMode: Mock<() => ApprovalMode>;
|
||||
setApprovalMode: Mock<(value: ApprovalMode) => void>;
|
||||
isYoloModeDisabled: Mock<() => boolean>;
|
||||
isTrustedFolder: Mock<() => boolean>;
|
||||
getCoreTools: Mock<() => string[]>;
|
||||
getToolDiscoveryCommand: Mock<() => string | undefined>;
|
||||
@@ -76,6 +77,7 @@ describe('useAutoAcceptIndicator', () => {
|
||||
setApprovalMode: instanceSetApprovalModeMock as Mock<
|
||||
(value: ApprovalMode) => void
|
||||
>,
|
||||
isYoloModeDisabled: vi.fn().mockReturnValue(false),
|
||||
isTrustedFolder: vi.fn().mockReturnValue(true) as Mock<() => boolean>,
|
||||
getCoreTools: vi.fn().mockReturnValue([]) as Mock<() => string[]>,
|
||||
getToolDiscoveryCommand: vi.fn().mockReturnValue(undefined) as Mock<
|
||||
@@ -471,6 +473,45 @@ describe('useAutoAcceptIndicator', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('when YOLO mode is disabled by settings', () => {
|
||||
beforeEach(() => {
|
||||
// Ensure isYoloModeDisabled returns true for these tests
|
||||
if (mockConfigInstance && mockConfigInstance.isYoloModeDisabled) {
|
||||
mockConfigInstance.isYoloModeDisabled.mockReturnValue(true);
|
||||
}
|
||||
});
|
||||
|
||||
it('should not enable YOLO mode when Ctrl+Y is pressed and add an info message', () => {
|
||||
mockConfigInstance.getApprovalMode.mockReturnValue(ApprovalMode.DEFAULT);
|
||||
const mockAddItem = vi.fn();
|
||||
const { result } = renderHook(() =>
|
||||
useAutoAcceptIndicator({
|
||||
config: mockConfigInstance as unknown as ActualConfigType,
|
||||
addItem: mockAddItem,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result.current).toBe(ApprovalMode.DEFAULT);
|
||||
|
||||
act(() => {
|
||||
capturedUseKeypressHandler({ name: 'y', ctrl: true } as Key);
|
||||
});
|
||||
|
||||
// setApprovalMode should not be called because the check should return early
|
||||
expect(mockConfigInstance.setApprovalMode).not.toHaveBeenCalled();
|
||||
// An info message should be added
|
||||
expect(mockAddItem).toHaveBeenCalledWith(
|
||||
{
|
||||
type: MessageType.WARNING,
|
||||
text: 'You cannot enter YOLO mode since it is disabled in your settings.',
|
||||
},
|
||||
expect.any(Number),
|
||||
);
|
||||
// The mode should not change
|
||||
expect(result.current).toBe(ApprovalMode.DEFAULT);
|
||||
});
|
||||
});
|
||||
|
||||
it('should call onApprovalModeChange when switching to YOLO mode', () => {
|
||||
mockConfigInstance.getApprovalMode.mockReturnValue(ApprovalMode.DEFAULT);
|
||||
|
||||
|
||||
@@ -34,6 +34,21 @@ export function useAutoAcceptIndicator({
|
||||
let nextApprovalMode: ApprovalMode | undefined;
|
||||
|
||||
if (key.ctrl && key.name === 'y') {
|
||||
if (
|
||||
config.isYoloModeDisabled() &&
|
||||
config.getApprovalMode() !== ApprovalMode.YOLO
|
||||
) {
|
||||
if (addItem) {
|
||||
addItem(
|
||||
{
|
||||
type: MessageType.WARNING,
|
||||
text: 'You cannot enter YOLO mode since it is disabled in your settings.',
|
||||
},
|
||||
Date.now(),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
nextApprovalMode =
|
||||
config.getApprovalMode() === ApprovalMode.YOLO
|
||||
? ApprovalMode.DEFAULT
|
||||
|
||||
@@ -995,6 +995,40 @@ describe('setApprovalMode with folder trust', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('isYoloModeDisabled', () => {
|
||||
const baseParams: ConfigParameters = {
|
||||
sessionId: 'test',
|
||||
targetDir: '.',
|
||||
debugMode: false,
|
||||
model: 'test-model',
|
||||
cwd: '.',
|
||||
};
|
||||
|
||||
it('should return false when yolo mode is not disabled and folder is trusted', () => {
|
||||
const config = new Config(baseParams);
|
||||
vi.spyOn(config, 'isTrustedFolder').mockReturnValue(true);
|
||||
expect(config.isYoloModeDisabled()).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true when yolo mode is disabled by parameter', () => {
|
||||
const config = new Config({ ...baseParams, disableYoloMode: true });
|
||||
vi.spyOn(config, 'isTrustedFolder').mockReturnValue(true);
|
||||
expect(config.isYoloModeDisabled()).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true when folder is untrusted', () => {
|
||||
const config = new Config(baseParams);
|
||||
vi.spyOn(config, 'isTrustedFolder').mockReturnValue(false);
|
||||
expect(config.isYoloModeDisabled()).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true when yolo is disabled and folder is untrusted', () => {
|
||||
const config = new Config({ ...baseParams, disableYoloMode: true });
|
||||
vi.spyOn(config, 'isTrustedFolder').mockReturnValue(false);
|
||||
expect(config.isYoloModeDisabled()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('BaseLlmClient Lifecycle', () => {
|
||||
const MODEL = 'gemini-pro';
|
||||
const SANDBOX: SandboxConfig = {
|
||||
|
||||
@@ -284,6 +284,7 @@ export interface ConfigParameters {
|
||||
retryFetchErrors?: boolean;
|
||||
enableShellOutputEfficiency?: boolean;
|
||||
ptyInfo?: string;
|
||||
disableYoloMode?: boolean;
|
||||
}
|
||||
|
||||
export class Config {
|
||||
@@ -380,6 +381,7 @@ export class Config {
|
||||
private readonly continueOnFailedApiCall: boolean;
|
||||
private readonly retryFetchErrors: boolean;
|
||||
private readonly enableShellOutputEfficiency: boolean;
|
||||
private readonly disableYoloMode: boolean;
|
||||
|
||||
constructor(params: ConfigParameters) {
|
||||
this.sessionId = params.sessionId;
|
||||
@@ -496,6 +498,7 @@ export class Config {
|
||||
format: params.output?.format ?? OutputFormat.TEXT,
|
||||
};
|
||||
this.retryFetchErrors = params.retryFetchErrors ?? false;
|
||||
this.disableYoloMode = params.disableYoloMode ?? false;
|
||||
|
||||
if (params.contextFileName) {
|
||||
setGeminiMdFilename(params.contextFileName);
|
||||
@@ -761,6 +764,10 @@ export class Config {
|
||||
this.approvalMode = mode;
|
||||
}
|
||||
|
||||
isYoloModeDisabled(): boolean {
|
||||
return this.disableYoloMode || !this.isTrustedFolder();
|
||||
}
|
||||
|
||||
getShowMemoryUsage(): boolean {
|
||||
return this.showMemoryUsage;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user