/** * @license * Copyright 2025 Google LLC * 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[]; plan: PolicyRule[]; } const categorizeRulesByMode = ( rules: readonly PolicyRule[], ): CategorizedRules => { const result: CategorizedRules = { normal: [], autoEdit: [], yolo: [], plan: [], }; 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); if (modeSet.has(ApprovalMode.PLAN)) result.plan.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}]` : '') + (rule.source ? ` [Source: ${rule.source}]` : ''); 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 grouped by mode', kind: CommandKind.BUILT_IN, autoExecute: true, action: async (context) => { const agentContext = context.services.agentContext; const config = agentContext?.config; if (!config) { context.ui.addItem( { type: MessageType.ERROR, text: 'Error: Config not available.', }, Date.now(), ); return; } const policyEngine = config.getPolicyEngine(); const rules = policyEngine.getRules(); if (rules.length === 0) { context.ui.addItem( { type: MessageType.INFO, text: 'No active policies.', }, Date.now(), ); 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), ); const uniquePlan = categorized.plan.filter( (rule) => !normalRulesSet.has(rule), ); let content = '**Active Policies**\n\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, ); content += formatSection( 'Plan Mode Policies (combined with normal mode policies)', uniquePlan, ); context.ui.addItem( { type: MessageType.INFO, text: content, }, Date.now(), ); }, }; export const policiesCommand: SlashCommand = { name: 'policies', description: 'Manage policies', kind: CommandKind.BUILT_IN, autoExecute: false, subCommands: [listPoliciesCommand], };