diff --git a/packages/cli/src/ui/commands/policiesCommand.test.ts b/packages/cli/src/ui/commands/policiesCommand.test.ts index 268d00b9eb..edd83ed4a6 100644 --- a/packages/cli/src/ui/commands/policiesCommand.test.ts +++ b/packages/cli/src/ui/commands/policiesCommand.test.ts @@ -62,7 +62,7 @@ describe('policiesCommand', () => { ); }); - it('should list active policies in correct format', async () => { + it('should list policies grouped by mode', async () => { const mockRules = [ { decision: PolicyDecision.DENY, @@ -99,13 +99,18 @@ describe('policiesCommand', () => { const call = vi.mocked(mockContext.ui.addItem).mock.calls[0]; const content = (call[0] as { text: string }).text; + expect(content).toContain('### Normal Mode Policies'); expect(content).toContain( - '1. **DENY** tool: `dangerousTool` [Priority: 10]', + '### Auto Edit Mode Policies (combined with normal mode policies)', ); expect(content).toContain( - '2. **ALLOW** all tools (args match: `safe`) [Source: `test.toml`]', + '### Yolo Mode Policies (combined with normal mode policies)', ); - expect(content).toContain('3. **ASK_USER** all tools'); + expect(content).toContain( + '**DENY** tool: `dangerousTool` [Priority: 10]', + ); + expect(content).toContain('**ALLOW** all tools (args match: `safe`)'); + expect(content).toContain('**ASK_USER** all tools'); }); }); }); diff --git a/packages/cli/src/ui/commands/policiesCommand.ts b/packages/cli/src/ui/commands/policiesCommand.ts index f739364c11..198d46be4a 100644 --- a/packages/cli/src/ui/commands/policiesCommand.ts +++ b/packages/cli/src/ui/commands/policiesCommand.ts @@ -4,12 +4,46 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { ApprovalMode, type PolicyRule } from '@google/gemini-cli-core'; import { CommandKind, type SlashCommand } from './types.js'; import { MessageType } from '../types.js'; +interface CategorizedRules { + normal: PolicyRule[]; + autoEdit: PolicyRule[]; + yolo: PolicyRule[]; +} + +const categorizeRulesByMode = ( + rules: readonly PolicyRule[], +): CategorizedRules => { + const result: CategorizedRules = { + normal: [], + autoEdit: [], + yolo: [], + }; + const ALL_MODES = Object.values(ApprovalMode); + rules.forEach((rule) => { + const modes = rule.modes?.length ? rule.modes : ALL_MODES; + const modeSet = new Set(modes); + if (modeSet.has(ApprovalMode.DEFAULT)) result.normal.push(rule); + if (modeSet.has(ApprovalMode.AUTO_EDIT)) result.autoEdit.push(rule); + if (modeSet.has(ApprovalMode.YOLO)) result.yolo.push(rule); + }); + return result; +}; + +const formatRule = (rule: PolicyRule, i: number) => + `${i + 1}. **${rule.decision.toUpperCase()}** ${rule.toolName ? `tool: \`${rule.toolName}\`` : 'all tools'}` + + (rule.argsPattern ? ` (args match: \`${rule.argsPattern.source}\`)` : '') + + (rule.priority !== undefined ? ` [Priority: ${rule.priority}]` : ''); + +const formatSection = (title: string, rules: PolicyRule[]) => + `### ${title}\n${rules.length ? rules.map(formatRule).join('\n') : '_No policies._'}\n\n`; + const listPoliciesCommand: SlashCommand = { name: 'list', - description: 'List all active policies', + description: 'List all active policies grouped by mode', kind: CommandKind.BUILT_IN, autoExecute: true, action: async (context) => { @@ -39,25 +73,25 @@ const listPoliciesCommand: SlashCommand = { return; } + const categorized = categorizeRulesByMode(rules); + const normalRulesSet = new Set(categorized.normal); + const uniqueAutoEdit = categorized.autoEdit.filter( + (rule) => !normalRulesSet.has(rule), + ); + const uniqueYolo = categorized.yolo.filter( + (rule) => !normalRulesSet.has(rule), + ); + let content = '**Active Policies**\n\n'; - rules.forEach((rule, index) => { - content += `${index + 1}. **${rule.decision.toUpperCase()}**`; - if (rule.toolName) { - content += ` tool: \`${rule.toolName}\``; - } else { - content += ` all tools`; - } - if (rule.argsPattern) { - content += ` (args match: \`${rule.argsPattern.source}\`)`; - } - if (rule.priority !== undefined) { - content += ` [Priority: ${rule.priority}]`; - } - if (rule.source) { - content += ` [Source: \`${rule.source}\`]`; - } - content += '\n'; - }); + content += formatSection('Normal Mode Policies', categorized.normal); + content += formatSection( + 'Auto Edit Mode Policies (combined with normal mode policies)', + uniqueAutoEdit, + ); + content += formatSection( + 'Yolo Mode Policies (combined with normal mode policies)', + uniqueYolo, + ); context.ui.addItem( {