mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-07-09 17:40:44 -07:00
feat(policy): map --yolo to allowedTools wildcard policy
This PR maps the `--yolo` flag natively into a wildcard policy array (`allowedTools: ["*"]`) and removes the concept of `ApprovalMode.YOLO` as a distinct state in the application, fulfilling issue #11303. This removes the hardcoded `ApprovalMode.YOLO` state and its associated UI/bypasses. The `PolicyEngine` now evaluates YOLO purely via data-driven rules. - Removes `ApprovalMode.YOLO` - Removes UI toggle (`Ctrl+Y`) and indicators for YOLO - Removes `yolo.toml` - Updates A2A server and CLI config logic to translate YOLO into a wildcard tool - Rewrites policy engine tests to evaluate the wildcard - Enforces enterprise `disableYoloMode` and `secureModeEnabled` controls by actively preventing manual `--allowed-tools=*` bypasses. Fixes #11303
This commit is contained in:
@@ -35,8 +35,11 @@ describe('ApprovalModeIndicator', () => {
|
||||
});
|
||||
|
||||
it('renders correctly for YOLO mode', async () => {
|
||||
const { lastFrame } = await render(
|
||||
<ApprovalModeIndicator approvalMode={ApprovalMode.YOLO} />,
|
||||
const { lastFrame, waitUntilReady } = await render(
|
||||
<ApprovalModeIndicator
|
||||
approvalMode={ApprovalMode.DEFAULT}
|
||||
isYoloMode={true}
|
||||
/>,
|
||||
);
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
@@ -14,43 +14,45 @@ import { Command } from '../key/keyBindings.js';
|
||||
interface ApprovalModeIndicatorProps {
|
||||
approvalMode: ApprovalMode;
|
||||
allowPlanMode?: boolean;
|
||||
isYoloMode?: boolean;
|
||||
}
|
||||
|
||||
export const ApprovalModeIndicator: React.FC<ApprovalModeIndicatorProps> = ({
|
||||
approvalMode,
|
||||
allowPlanMode,
|
||||
isYoloMode,
|
||||
}) => {
|
||||
let textColor = '';
|
||||
let textContent = '';
|
||||
let subText = '';
|
||||
|
||||
const cycleHint = formatCommand(Command.CYCLE_APPROVAL_MODE);
|
||||
const yoloHint = formatCommand(Command.TOGGLE_YOLO);
|
||||
|
||||
switch (approvalMode) {
|
||||
case ApprovalMode.AUTO_EDIT:
|
||||
textColor = theme.status.warning;
|
||||
textContent = 'auto-accept edits';
|
||||
subText = allowPlanMode
|
||||
? `${cycleHint} to plan`
|
||||
: `${cycleHint} to manual`;
|
||||
break;
|
||||
case ApprovalMode.PLAN:
|
||||
textColor = theme.status.success;
|
||||
textContent = 'plan';
|
||||
subText = `${cycleHint} to manual`;
|
||||
break;
|
||||
case ApprovalMode.YOLO:
|
||||
textColor = theme.status.error;
|
||||
textContent = 'YOLO';
|
||||
subText = yoloHint;
|
||||
break;
|
||||
case ApprovalMode.DEFAULT:
|
||||
default:
|
||||
textColor = theme.text.accent;
|
||||
textContent = '';
|
||||
subText = `${cycleHint} to accept edits`;
|
||||
break;
|
||||
if (isYoloMode) {
|
||||
textColor = theme.status.error;
|
||||
textContent = 'YOLO';
|
||||
subText = '';
|
||||
} else {
|
||||
switch (approvalMode) {
|
||||
case ApprovalMode.AUTO_EDIT:
|
||||
textColor = theme.status.warning;
|
||||
textContent = 'auto-accept edits';
|
||||
subText = allowPlanMode
|
||||
? `${cycleHint} to plan`
|
||||
: `${cycleHint} to manual`;
|
||||
break;
|
||||
case ApprovalMode.PLAN:
|
||||
textColor = theme.status.success;
|
||||
textContent = 'plan';
|
||||
subText = `${cycleHint} to manual`;
|
||||
break;
|
||||
case ApprovalMode.DEFAULT:
|
||||
default:
|
||||
textColor = theme.text.accent;
|
||||
textContent = '';
|
||||
subText = `${cycleHint} to accept edits`;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -231,6 +231,7 @@ const createMockConfig = (overrides = {}): Config =>
|
||||
getAccessibility: vi.fn(() => ({})),
|
||||
getMcpServers: vi.fn(() => ({})),
|
||||
isPlanEnabled: vi.fn(() => true),
|
||||
getAllowedTools: vi.fn(() => []),
|
||||
getToolRegistry: () => ({
|
||||
getTool: vi.fn(),
|
||||
}),
|
||||
@@ -625,7 +626,6 @@ describe('Composer', () => {
|
||||
[ApprovalMode.DEFAULT],
|
||||
[ApprovalMode.AUTO_EDIT],
|
||||
[ApprovalMode.PLAN],
|
||||
[ApprovalMode.YOLO],
|
||||
])(
|
||||
'shows ApprovalModeIndicator when approval mode is %s and shell mode is inactive',
|
||||
async (mode) => {
|
||||
@@ -640,6 +640,20 @@ describe('Composer', () => {
|
||||
},
|
||||
);
|
||||
|
||||
it('shows ApprovalModeIndicator when YOLO mode is active and shell mode is inactive', async () => {
|
||||
const config = createMockConfig({
|
||||
getAllowedTools: vi.fn(() => ['*']),
|
||||
});
|
||||
const uiState = createMockUIState({
|
||||
showApprovalModeIndicator: ApprovalMode.DEFAULT,
|
||||
shellModeActive: false,
|
||||
});
|
||||
|
||||
const { lastFrame } = await renderComposer(uiState, undefined, config);
|
||||
|
||||
expect(lastFrame()).toMatch(/ApprovalModeIndic[\s\S]*ator/);
|
||||
});
|
||||
|
||||
it('shows ShellModeIndicator when shell mode is active', async () => {
|
||||
const uiState = createMockUIState({
|
||||
shellModeActive: true,
|
||||
@@ -671,7 +685,6 @@ describe('Composer', () => {
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ mode: ApprovalMode.YOLO, label: '● YOLO' },
|
||||
{ mode: ApprovalMode.PLAN, label: '● plan' },
|
||||
{
|
||||
mode: ApprovalMode.AUTO_EDIT,
|
||||
@@ -690,6 +703,19 @@ describe('Composer', () => {
|
||||
},
|
||||
);
|
||||
|
||||
it('shows minimal mode badge "YOLO" when clean UI details are hidden and YOLO mode is active', async () => {
|
||||
const config = createMockConfig({
|
||||
getAllowedTools: vi.fn(() => ['*']),
|
||||
});
|
||||
const uiState = createMockUIState({
|
||||
cleanUiDetailsVisible: false,
|
||||
showApprovalModeIndicator: ApprovalMode.DEFAULT,
|
||||
});
|
||||
|
||||
const { lastFrame } = await renderComposer(uiState, undefined, config);
|
||||
expect(lastFrame()).toContain('YOLO');
|
||||
});
|
||||
|
||||
it('hides minimal mode badge while loading in clean mode', async () => {
|
||||
const uiState = createMockUIState({
|
||||
cleanUiDetailsVisible: false,
|
||||
@@ -983,7 +1009,7 @@ describe('Composer', () => {
|
||||
|
||||
const uiState = createMockUIState({
|
||||
cleanUiDetailsVisible: true,
|
||||
showApprovalModeIndicator: ApprovalMode.YOLO,
|
||||
showApprovalModeIndicator: ApprovalMode.AUTO_EDIT,
|
||||
});
|
||||
|
||||
const { lastFrame } = await renderComposer(uiState);
|
||||
|
||||
@@ -77,6 +77,7 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
|
||||
const hasToast = shouldShowToast(uiState);
|
||||
const hideUiDetailsForSuggestions =
|
||||
suggestionsVisible && suggestionsPosition === 'above';
|
||||
const isYoloMode = config.getAllowedTools()?.includes('*');
|
||||
|
||||
// Mini Mode VIP Flags (Pure Content Triggers)
|
||||
const showMinimalToast = hasToast;
|
||||
@@ -147,6 +148,7 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
|
||||
shellModeActive={uiState.shellModeActive}
|
||||
setShellModeActive={uiActions.setShellModeActive}
|
||||
approvalMode={uiState.showApprovalModeIndicator}
|
||||
isYoloMode={isYoloMode}
|
||||
onEscapePromptChange={uiActions.onEscapePromptChange}
|
||||
focus={isFocused}
|
||||
vimHandleInput={uiActions.vimHandleInput}
|
||||
|
||||
@@ -153,12 +153,6 @@ export const Help: React.FC<Help> = ({ commands }) => (
|
||||
</Text>{' '}
|
||||
- Open input in external editor
|
||||
</Text>
|
||||
<Text color={theme.text.primary}>
|
||||
<Text bold color={theme.text.accent}>
|
||||
{formatCommand(Command.TOGGLE_YOLO)}
|
||||
</Text>{' '}
|
||||
- Toggle YOLO mode
|
||||
</Text>
|
||||
<Text color={theme.text.primary}>
|
||||
<Text bold color={theme.text.accent}>
|
||||
{formatCommand(Command.SUBMIT)}
|
||||
|
||||
@@ -4033,15 +4033,7 @@ describe('InputPrompt', () => {
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('should render correctly in yolo mode', async () => {
|
||||
props.approvalMode = ApprovalMode.YOLO;
|
||||
const { stdout, unmount } = await renderWithProviders(
|
||||
<InputPrompt {...props} />,
|
||||
);
|
||||
await waitFor(() => expect(stdout.lastFrame()).toContain('*'));
|
||||
expect(stdout.lastFrame()).toMatchSnapshot();
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('should not show inverted cursor when shell is focused', async () => {
|
||||
props.isEmbeddedShellFocused = true;
|
||||
props.focus = false;
|
||||
|
||||
@@ -110,6 +110,7 @@ export interface InputPromptProps {
|
||||
shellModeActive: boolean;
|
||||
setShellModeActive: (value: boolean) => void;
|
||||
approvalMode: ApprovalMode;
|
||||
isYoloMode?: boolean;
|
||||
onEscapePromptChange?: (showPrompt: boolean) => void;
|
||||
onSuggestionsVisibilityChange?: (visible: boolean) => void;
|
||||
vimHandleInput?: (key: Key) => boolean;
|
||||
@@ -205,6 +206,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
shellModeActive,
|
||||
setShellModeActive,
|
||||
approvalMode,
|
||||
isYoloMode,
|
||||
onEscapePromptChange,
|
||||
onSuggestionsVisibilityChange,
|
||||
vimHandleInput,
|
||||
@@ -1489,8 +1491,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
|
||||
const showAutoAcceptStyling =
|
||||
!shellModeActive && approvalMode === ApprovalMode.AUTO_EDIT;
|
||||
const showYoloStyling =
|
||||
!shellModeActive && approvalMode === ApprovalMode.YOLO;
|
||||
const showYoloStyling = !shellModeActive && isYoloMode;
|
||||
const showPlanStyling =
|
||||
!shellModeActive && approvalMode === ApprovalMode.PLAN;
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ const buildShortcutItems = (): ShortcutItem[] => [
|
||||
{ key: '@', description: 'select file or folder' },
|
||||
{ key: 'Double Esc', description: 'clear & rewind' },
|
||||
{ key: formatCommand(Command.FOCUS_SHELL_INPUT), description: 'focus UI' },
|
||||
{ key: formatCommand(Command.TOGGLE_YOLO), description: 'YOLO mode' },
|
||||
{
|
||||
key: formatCommand(Command.CYCLE_APPROVAL_MODE),
|
||||
description: 'cycle mode',
|
||||
@@ -64,16 +63,14 @@ export const ShortcutsHelp: React.FC = () => {
|
||||
const itemsForDisplay = isNarrow
|
||||
? items
|
||||
: [
|
||||
// Keep first column stable: !, @, Esc Esc, Tab Tab.
|
||||
items[0],
|
||||
items[5],
|
||||
items[6],
|
||||
items[1],
|
||||
items[4],
|
||||
items[7],
|
||||
items[5],
|
||||
items[1],
|
||||
items[6],
|
||||
items[2],
|
||||
items[7],
|
||||
items[8],
|
||||
items[9],
|
||||
items[3],
|
||||
];
|
||||
|
||||
|
||||
@@ -26,6 +26,6 @@ exports[`ApprovalModeIndicator > renders correctly for PLAN mode 1`] = `
|
||||
`;
|
||||
|
||||
exports[`ApprovalModeIndicator > renders correctly for YOLO mode 1`] = `
|
||||
"YOLO Ctrl+Y
|
||||
"YOLO
|
||||
"
|
||||
`;
|
||||
|
||||
@@ -189,13 +189,6 @@ exports[`InputPrompt > snapshots > should render correctly in shell mode 1`] = `
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`InputPrompt > snapshots > should render correctly in yolo mode 1`] = `
|
||||
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||
* Type your message or @path/to/file
|
||||
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`InputPrompt > snapshots > should render correctly when accepting edits 1`] = `
|
||||
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||
> Type your message or @path/to/file
|
||||
|
||||
@@ -7,7 +7,6 @@ exports[`ShortcutsHelp > renders correctly in 'narrow' mode on 'linux' 1`] = `
|
||||
@ select file or folder
|
||||
Double Esc clear & rewind
|
||||
Tab focus UI
|
||||
Ctrl+Y YOLO mode
|
||||
Shift+Tab cycle mode
|
||||
Ctrl+V paste images
|
||||
Alt+M raw markdown mode
|
||||
@@ -23,7 +22,6 @@ exports[`ShortcutsHelp > renders correctly in 'narrow' mode on 'mac' 1`] = `
|
||||
@ select file or folder
|
||||
Double Esc clear & rewind
|
||||
Tab focus UI
|
||||
Ctrl+Y YOLO mode
|
||||
Shift+Tab cycle mode
|
||||
Ctrl+V paste images
|
||||
Option+M raw markdown mode
|
||||
@@ -36,9 +34,8 @@ exports[`ShortcutsHelp > renders correctly in 'wide' mode on 'linux' 1`] = `
|
||||
"────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
Shortcuts See /help for more
|
||||
! shell mode Shift+Tab cycle mode Ctrl+V paste images
|
||||
@ select file or folder Ctrl+Y YOLO mode Alt+M raw markdown mode
|
||||
Double Esc clear & rewind Ctrl+R reverse-search history Ctrl+X open external editor
|
||||
Tab focus UI
|
||||
@ select file or folder Alt+M raw markdown mode Double Esc clear & rewind
|
||||
Ctrl+R reverse-search history Ctrl+X open external editor Tab focus UI
|
||||
"
|
||||
`;
|
||||
|
||||
@@ -46,8 +43,7 @@ exports[`ShortcutsHelp > renders correctly in 'wide' mode on 'mac' 1`] = `
|
||||
"────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
Shortcuts See /help for more
|
||||
! shell mode Shift+Tab cycle mode Ctrl+V paste images
|
||||
@ select file or folder Ctrl+Y YOLO mode Option+M raw markdown mode
|
||||
Double Esc clear & rewind Ctrl+R reverse-search history Ctrl+X open external editor
|
||||
Tab focus UI
|
||||
@ select file or folder Option+M raw markdown mode Double Esc clear & rewind
|
||||
Ctrl+R reverse-search history Ctrl+X open external editor Tab focus UI
|
||||
"
|
||||
`;
|
||||
|
||||
Reference in New Issue
Block a user