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:
Spencer
2026-03-19 02:43:14 +00:00
parent 08b926796c
commit 822e098d32
77 changed files with 541 additions and 746 deletions
@@ -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}
-6
View File
@@ -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
"
`;