fix: allow ask_user tool in yolo mode (#18541)

This commit is contained in:
Jack Wotherspoon
2026-02-10 13:56:51 -05:00
committed by GitHub
parent b37e67451a
commit 740f0e4c3d
7 changed files with 146 additions and 6 deletions

View File

@@ -483,6 +483,29 @@ describe('Core System Prompt (prompts.ts)', () => {
expect(prompt).toMatchSnapshot();
});
});
it('should include YOLO mode instructions in interactive mode', () => {
vi.mocked(mockConfig.getApprovalMode).mockReturnValue(ApprovalMode.YOLO);
vi.mocked(mockConfig.isInteractive).mockReturnValue(true);
const prompt = getCoreSystemPrompt(mockConfig);
expect(prompt).toContain('# Autonomous Mode (YOLO)');
expect(prompt).toContain('Only use the `ask_user` tool if');
});
it('should NOT include YOLO mode instructions in non-interactive mode', () => {
vi.mocked(mockConfig.getApprovalMode).mockReturnValue(ApprovalMode.YOLO);
vi.mocked(mockConfig.isInteractive).mockReturnValue(false);
const prompt = getCoreSystemPrompt(mockConfig);
expect(prompt).not.toContain('# Autonomous Mode (YOLO)');
});
it('should NOT include YOLO mode instructions for DEFAULT mode', () => {
vi.mocked(mockConfig.getApprovalMode).mockReturnValue(
ApprovalMode.DEFAULT,
);
const prompt = getCoreSystemPrompt(mockConfig);
expect(prompt).not.toContain('# Autonomous Mode (YOLO)');
});
});
describe('Platform-specific and Background Process instructions', () => {

View File

@@ -317,8 +317,8 @@ describe('createPolicyEngineConfig', () => {
(r) => r.decision === PolicyDecision.ALLOW && !r.toolName,
);
expect(rule).toBeDefined();
// Priority 999 in default tier → 1.999
expect(rule?.priority).toBeCloseTo(1.999, 5);
// Priority 998 in default tier → 1.998 (999 reserved for ask_user exception)
expect(rule?.priority).toBeCloseTo(1.998, 5);
});
it('should allow edit tool in AUTO_EDIT mode', async () => {
@@ -582,8 +582,8 @@ describe('createPolicyEngineConfig', () => {
(r) => !r.toolName && r.decision === PolicyDecision.ALLOW,
);
expect(wildcardRule).toBeDefined();
// Priority 999 in default tier → 1.999
expect(wildcardRule?.priority).toBeCloseTo(1.999, 5);
// Priority 998 in default tier → 1.998 (999 reserved for ask_user exception)
expect(wildcardRule?.priority).toBeCloseTo(1.998, 5);
// Write tool ASK_USER rules are present (from write.toml)
const writeToolRules = config.rules?.filter(

View File

@@ -23,10 +23,21 @@
# 10: Write tools default to ASK_USER (becomes 1.010 in default tier)
# 15: Auto-edit tool override (becomes 1.015 in default tier)
# 50: Read-only tools (becomes 1.050 in default tier)
# 999: YOLO mode allow-all (becomes 1.999 in default tier)
# 998: YOLO mode allow-all (becomes 1.998 in default tier)
# 999: Ask-user tool (becomes 1.999 in default tier)
# Ask-user tool always requires user interaction, even in YOLO mode.
# This ensures the model can gather user preferences/decisions when needed.
# Note: In non-interactive mode, this decision is converted to DENY by the policy engine.
[[rule]]
decision = "allow"
toolName = "ask_user"
decision = "ask_user"
priority = 999
modes = ["yolo"]
# Allow everything else in YOLO mode
[[rule]]
decision = "allow"
priority = 998
modes = ["yolo"]
allow_redirection = true

View File

@@ -2030,4 +2030,60 @@ describe('PolicyEngine', () => {
expect(result.decision).toBe(PolicyDecision.DENY);
});
});
describe('YOLO mode with ask_user tool', () => {
it('should return ASK_USER for ask_user tool even in YOLO mode', async () => {
const rules: PolicyRule[] = [
{
toolName: 'ask_user',
decision: PolicyDecision.ASK_USER,
priority: 999,
modes: [ApprovalMode.YOLO],
},
{
decision: PolicyDecision.ALLOW,
priority: 998,
modes: [ApprovalMode.YOLO],
},
];
engine = new PolicyEngine({
rules,
approvalMode: ApprovalMode.YOLO,
});
const result = await engine.check(
{ name: 'ask_user', args: {} },
undefined,
);
expect(result.decision).toBe(PolicyDecision.ASK_USER);
});
it('should return ALLOW for other tools in YOLO mode', async () => {
const rules: PolicyRule[] = [
{
toolName: 'ask_user',
decision: PolicyDecision.ASK_USER,
priority: 999,
modes: [ApprovalMode.YOLO],
},
{
decision: PolicyDecision.ALLOW,
priority: 998,
modes: [ApprovalMode.YOLO],
},
];
engine = new PolicyEngine({
rules,
approvalMode: ApprovalMode.YOLO,
});
const result = await engine.check(
{ name: 'run_shell_command', args: { command: 'ls' } },
undefined,
);
expect(result.decision).toBe(PolicyDecision.ALLOW);
});
});
});

View File

@@ -50,6 +50,7 @@ export class PromptProvider {
const interactiveMode = interactiveOverride ?? config.isInteractive();
const approvalMode = config.getApprovalMode?.() ?? ApprovalMode.DEFAULT;
const isPlanMode = approvalMode === ApprovalMode.PLAN;
const isYoloMode = approvalMode === ApprovalMode.YOLO;
const skills = config.getSkillManager().getSkills();
const toolNames = config.getToolRegistry().getAllToolNames();
const enabledToolNames = new Set(toolNames);
@@ -183,6 +184,11 @@ export class PromptProvider {
}),
),
sandbox: this.withSection('sandbox', () => getSandboxMode()),
interactiveYoloMode: this.withSection(
'interactiveYoloMode',
() => true,
isYoloMode && interactiveMode,
),
gitRepo: this.withSection(
'git',
() => ({ interactive: interactiveMode }),

View File

@@ -32,6 +32,7 @@ export interface SystemPromptOptions {
planningWorkflow?: PlanningWorkflowOptions;
operationalGuidelines?: OperationalGuidelinesOptions;
sandbox?: SandboxMode;
interactiveYoloMode?: boolean;
gitRepo?: GitRepoOptions;
finalReminder?: FinalReminderOptions;
}
@@ -114,6 +115,8 @@ ${
${renderOperationalGuidelines(options.operationalGuidelines)}
${renderInteractiveYoloMode(options.interactiveYoloMode)}
${renderSandbox(options.sandbox)}
${renderGitRepo(options.gitRepo)}
@@ -293,6 +296,25 @@ You are running outside of a sandbox container, directly on the user's system. F
}
}
export function renderInteractiveYoloMode(enabled?: boolean): string {
if (!enabled) return '';
return `
# Autonomous Mode (YOLO)
You are operating in **autonomous mode**. The user has requested minimal interruption.
**Only use the \`${ASK_USER_TOOL_NAME}\` tool if:**
- A wrong decision would cause significant re-work
- The request is fundamentally ambiguous with no reasonable default
- The user explicitly asks you to confirm or ask questions
**Otherwise, work autonomously:**
- Make reasonable decisions based on context and existing code patterns
- Follow established project conventions
- If multiple valid approaches exist, choose the most robust option
`.trim();
}
export function renderGitRepo(options?: GitRepoOptions): string {
if (!options) return '';
return `

View File

@@ -33,6 +33,7 @@ export interface SystemPromptOptions {
planningWorkflow?: PlanningWorkflowOptions;
operationalGuidelines?: OperationalGuidelinesOptions;
sandbox?: SandboxMode;
interactiveYoloMode?: boolean;
gitRepo?: GitRepoOptions;
}
@@ -111,6 +112,8 @@ ${
${renderOperationalGuidelines(options.operationalGuidelines)}
${renderInteractiveYoloMode(options.interactiveYoloMode)}
${renderSandbox(options.sandbox)}
${renderGitRepo(options.gitRepo)}
@@ -312,6 +315,25 @@ export function renderSandbox(mode?: SandboxMode): string {
return '';
}
export function renderInteractiveYoloMode(enabled?: boolean): string {
if (!enabled) return '';
return `
# Autonomous Mode (YOLO)
You are operating in **autonomous mode**. The user has requested minimal interruption.
**Only use the \`${ASK_USER_TOOL_NAME}\` tool if:**
- A wrong decision would cause significant re-work
- The request is fundamentally ambiguous with no reasonable default
- The user explicitly asks you to confirm or ask questions
**Otherwise, work autonomously:**
- Make reasonable decisions based on context and existing code patterns
- Follow established project conventions
- If multiple valid approaches exist, choose the most robust option
`.trim();
}
export function renderGitRepo(options?: GitRepoOptions): string {
if (!options) return '';
return `